Phase 20: package Linux builds and document release and maintenance workflows
This commit is contained in:
parent
6b85f28add
commit
830b6e1914
22 changed files with 490 additions and 17 deletions
11
Makefile
11
Makefile
|
|
@ -22,6 +22,7 @@ JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack)
|
||||||
JACK_LIBS := $(shell $(PKG_CONFIG) --libs jack)
|
JACK_LIBS := $(shell $(PKG_CONFIG) --libs jack)
|
||||||
BACKEND_TEST := $(BUILD_DIR)/test_backend
|
BACKEND_TEST := $(BUILD_DIR)/test_backend
|
||||||
$(BUILD_DIR)/tests/unit/test_backend.o: CPPFLAGS += $(JACK_CFLAGS)
|
$(BUILD_DIR)/tests/unit/test_backend.o: CPPFLAGS += $(JACK_CFLAGS)
|
||||||
|
$(BUILD_DIR)/src/core/xtc.o: CPPFLAGS += -DXTC_HAVE_JACK
|
||||||
$(BUILD_DIR)/src/backend/jack_backend.o: CPPFLAGS += -DXTC_HAVE_JACK $(JACK_CFLAGS)
|
$(BUILD_DIR)/src/backend/jack_backend.o: CPPFLAGS += -DXTC_HAVE_JACK $(JACK_CFLAGS)
|
||||||
endif
|
endif
|
||||||
CORE_OBJECTS := $(CORE_SOURCES:%.c=$(BUILD_DIR)/%.o)
|
CORE_OBJECTS := $(CORE_SOURCES:%.c=$(BUILD_DIR)/%.o)
|
||||||
|
|
@ -181,7 +182,7 @@ install: all $(BUILD_DIR)/xtc.pc
|
||||||
install -m 0644 "$(BUILD_DIR)/libxtc.a" "$(DESTDIR)$(PREFIX)/lib/"
|
install -m 0644 "$(BUILD_DIR)/libxtc.a" "$(DESTDIR)$(PREFIX)/lib/"
|
||||||
install -m 0644 "$(BUILD_DIR)/xtc.pc" "$(DESTDIR)$(PREFIX)/lib/pkgconfig/"
|
install -m 0644 "$(BUILD_DIR)/xtc.pc" "$(DESTDIR)$(PREFIX)/lib/pkgconfig/"
|
||||||
install -m 0755 "$(BUILD_DIR)/xtc-check" "$(DESTDIR)$(PREFIX)/bin/"
|
install -m 0755 "$(BUILD_DIR)/xtc-check" "$(DESTDIR)$(PREFIX)/bin/"
|
||||||
install -m 0644 README.md docs/*.md "$(DESTDIR)$(PREFIX)/share/doc/xtc/"
|
install -m 0644 README.md docs/*.md docs/performance-baseline.json "$(DESTDIR)$(PREFIX)/share/doc/xtc/"
|
||||||
install -m 0644 docs/decisions/*.md "$(DESTDIR)$(PREFIX)/share/doc/xtc/decisions/"
|
install -m 0644 docs/decisions/*.md "$(DESTDIR)$(PREFIX)/share/doc/xtc/decisions/"
|
||||||
install -m 0644 LICENSE "$(DESTDIR)$(PREFIX)/share/licenses/xtc/"
|
install -m 0644 LICENSE "$(DESTDIR)$(PREFIX)/share/licenses/xtc/"
|
||||||
|
|
||||||
|
|
@ -366,3 +367,11 @@ BENCHMARK_MAX_REGRESSION ?= 1.5
|
||||||
.PHONY: benchmark-compare
|
.PHONY: benchmark-compare
|
||||||
benchmark-compare: $(BUILD_DIR)/benchmark_graph
|
benchmark-compare: $(BUILD_DIR)/benchmark_graph
|
||||||
$(PYTHON) tools/benchmark.py --binary "$(BUILD_DIR)/benchmark_graph" --output "$(BUILD_DIR)/performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --baseline "$(BENCHMARK_BASELINE)" --max-regression "$(BENCHMARK_MAX_REGRESSION)"
|
$(PYTHON) tools/benchmark.py --binary "$(BUILD_DIR)/benchmark_graph" --output "$(BUILD_DIR)/performance.json" --compiler "$(CC)" --flags "$(CFLAGS)" --baseline "$(BENCHMARK_BASELINE)" --max-regression "$(BENCHMARK_MAX_REGRESSION)"
|
||||||
|
|
||||||
|
PACKAGE_ARCH := $(shell uname -m)
|
||||||
|
PACKAGE_FILE := $(BUILD_DIR)/xtc-$(VERSION)-linux-$(PACKAGE_ARCH).tar.gz
|
||||||
|
.PHONY: package test-package
|
||||||
|
package: release
|
||||||
|
$(PYTHON) tools/package.py --build-dir "$(BUILD_DIR)/release" --output "$(PACKAGE_FILE)" $(if $(GUI_AVAILABLE),--gui)
|
||||||
|
test-package: package
|
||||||
|
$(PYTHON) tests/integration/test_package.py "$(PACKAGE_FILE)" --build-dir "$(BUILD_DIR)/release"
|
||||||
|
|
|
||||||
|
|
@ -121,3 +121,10 @@ Phases 13–15 add no-clobber recording/recovery, parameterized native C process
|
||||||
and trusted in-process LADSPA hosting with persisted identity/control values.
|
and trusted in-process LADSPA hosting with persisted identity/control values.
|
||||||
See [RECOVERY.md](docs/RECOVERY.md), [NATIVE_API.md](docs/NATIVE_API.md) and
|
See [RECOVERY.md](docs/RECOVERY.md), [NATIVE_API.md](docs/NATIVE_API.md) and
|
||||||
[PLUGIN_HOSTING.md](docs/PLUGIN_HOSTING.md) for commands and limits.
|
[PLUGIN_HOSTING.md](docs/PLUGIN_HOSTING.md) for commands and limits.
|
||||||
|
|
||||||
|
For everyday use see [USER_MANUAL.md](docs/USER_MANUAL.md),
|
||||||
|
[KNOWN_LIMITATIONS.md](docs/KNOWN_LIMITATIONS.md) and
|
||||||
|
[MAINTENANCE.md](docs/MAINTENANCE.md). `xtc-check --diagnose` reports build features;
|
||||||
|
`make test-package` checks a local system-linked Linux binary archive.
|
||||||
|
Phases 16–20 add effect automation/controllers, editing commands, v7 project-folder
|
||||||
|
media paths, performance baselines and release/installation checks.
|
||||||
|
|
|
||||||
35
docs/KNOWN_LIMITATIONS.md
Normal file
35
docs/KNOWN_LIMITATIONS.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Known limitations and qualification boundaries
|
||||||
|
|
||||||
|
- Development version 0.1.0, Linux C99/C++17/FLTK, optional JACK. Physical-device
|
||||||
|
playback/capture, human transition listening, independent user acceptance and
|
||||||
|
long-duration device qualification are pending.
|
||||||
|
- Fixed 4/4 and constant tempo snapshots; no tempo map, loop transport, plugin
|
||||||
|
latency compensation, limiter, background streaming or multichannel mixer buses.
|
||||||
|
- Mono capture/import downmix; stereo track output. Graph ports are mono with
|
||||||
|
independent paired processing. Maximum 64 project graph nodes (256 standalone),
|
||||||
|
64 tracks, 1024 clips/automation points, 4096 scheduled notes, 256 live MIDI
|
||||||
|
events/block, 32 voices, 65536 frames/block. See API headers for exact bounds.
|
||||||
|
- WAV samples preload with the memory/format limits in AUDIO_FILES.md. Callback
|
||||||
|
overflow reports/silences; there is no CPU preemption or expensive-plugin rescue.
|
||||||
|
- Live MIDI fixed volume/pan/sustain mapping; no controller learn, capture, SMF
|
||||||
|
import, pitch bend, custom-instrument chooser or instrument timbre controls.
|
||||||
|
- Native ABI 1 graph nodes are session-only. ABI 2 state is parameter-only. LADSPA
|
||||||
|
supports the documented mono amp/inverter/sine set; no opaque state, native
|
||||||
|
editor, MIDI, true multichannel layout, latency compensation or crash isolation.
|
||||||
|
Automated LADSPA controls use run(1) and can be expensive.
|
||||||
|
- GUI loading/compilation is synchronous on the control thread. Timeline grid/
|
||||||
|
zoom/selection and graph positions are session UI state. Undo retains 32 edits
|
||||||
|
plus baseline; allocation failures are reported, not silently hidden. Media is
|
||||||
|
never removed by undo/delete. Broad screen-reader/accessibility testing is pending.
|
||||||
|
- Format v7 reads v1–v6 and uses relative local media references. External absolute
|
||||||
|
media/module paths need manual repair after relocation. Saving does not bundle
|
||||||
|
media; missing media stays referenced, missing plugins reject GUI installation.
|
||||||
|
Unknown data is rejected rather than stripped. Post-rename directory-sync errors
|
||||||
|
can report failure even though the complete new document was published.
|
||||||
|
- No autosave/session crash restoration, RF64, system service, automatic dependency
|
||||||
|
installation or public signed release. Source/binary archives are local artifacts;
|
||||||
|
binary packages require compatible system libraries. No third-party plugin or
|
||||||
|
dependency binary is bundled. Power-cut durability depends on the filesystem.
|
||||||
|
|
||||||
|
See ROADMAP.md, AUDIO_QUALITY.md, CONTROL.md, WORKFLOW.md, PROJECT_FORMAT.md,
|
||||||
|
PERFORMANCE.md, PLUGIN_HOSTING.md and RECOVERY.md for tested contracts/evidence.
|
||||||
|
|
@ -56,3 +56,29 @@ Static backend consumers need JACK if that optional backend was built; the pc
|
||||||
file records its private dependency. Public headers contain no JACK/FLTK types.
|
file records its private dependency. Public headers contain no JACK/FLTK types.
|
||||||
To build without optional libraries, set PKG_CONFIG=false FLTK_CONFIG=false.
|
To build without optional libraries, set PKG_CONFIG=false FLTK_CONFIG=false.
|
||||||
Python is not needed to run the installed executables or library.
|
Python is not needed to run the installed executables or library.
|
||||||
|
|
||||||
|
## Phase 20 binary/development archive
|
||||||
|
|
||||||
|
`make package` creates a system-linked Linux archive plus SHA-256 sidecar below
|
||||||
|
BUILD_DIR; `make test-package` verifies its contents. It includes usr/bin,
|
||||||
|
headers/static library/pkg-config metadata, desktop file (when FLTK is present),
|
||||||
|
manuals, baseline and MIT license. No shared library, third-party plugin, media,
|
||||||
|
service or dependency installer is bundled. `BINARY_MANIFEST.json` lists file
|
||||||
|
hashes/sizes, architecture/libc and ELF NEEDED libraries. The entire archive's hash
|
||||||
|
covers the manifest too. Do not assume compatibility with another distribution.
|
||||||
|
|
||||||
|
Inspect/test under a temporary directory first. For a writable user prefix,
|
||||||
|
extract the archive with two leading path components stripped (`xtc-VERSION-linux-
|
||||||
|
ARCH/usr`), placing bin/include/lib/share directly under that prefix. Put its bin
|
||||||
|
on PATH and lib/pkgconfig on PKG_CONFIG_PATH. The archive's pkg-config prefix is
|
||||||
|
relative to its installed location; normal `make install` retains explicit PREFIX.
|
||||||
|
Use the matching source's uninstall with that PREFIX, or review the manifest
|
||||||
|
before removing its files. Unknown/unrelated files are retained by source uninstall.
|
||||||
|
This local tar package is not a pacman/RPM/deb registration or public distribution.
|
||||||
|
|
||||||
|
`xtc-check --diagnose` reports build capabilities and format/engine limits without
|
||||||
|
opening a server or executing a plugin. `xtc --diagnose` works headlessly as well.
|
||||||
|
Missing runtime GUI libraries are loader errors: install the distribution's
|
||||||
|
compatible dependencies or rebuild the core-only configuration. Build without
|
||||||
|
optional libraries using a fresh directory, `PKG_CONFIG=false FLTK_CONFIG=false
|
||||||
|
CPPFLAGS=-DXTC_DISABLE_LADSPA`. No system setting or package is changed by tests.
|
||||||
|
|
|
||||||
55
docs/MAINTENANCE.md
Normal file
55
docs/MAINTENANCE.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Release and maintenance policy
|
||||||
|
|
||||||
|
## Supported development target
|
||||||
|
|
||||||
|
Initial target: x86_64 Artix/Arch-family Linux with glibc, GNU Make, GCC/Clang,
|
||||||
|
FLTK 1.4 and a JACK-compatible client/server. The inspected host uses GCC 16.2.1,
|
||||||
|
FLTK 1.4.5 and the dependencies in LICENSES.md. Core-only/no-JACK/no-LADSPA builds
|
||||||
|
are checked too. Build on the destination distribution for different library ABIs;
|
||||||
|
there is no cross-distribution static-binary promise. Python 3.12+ and binutils
|
||||||
|
are build/package/test tools only. No dependency is fetched automatically.
|
||||||
|
|
||||||
|
## Release checklist
|
||||||
|
|
||||||
|
1. Work from a clean reviewed commit. Record `git rev-parse HEAD`, compiler,
|
||||||
|
libc/FLTK/JACK versions and `xtc-check --diagnose`. No automatic version bump,
|
||||||
|
Git tag, public upload or external release is performed by local targets.
|
||||||
|
2. Run warning-enabled `make all test gui test-plugins test-plugins-real
|
||||||
|
test-gui-plugins test-workflow test-gui-jack test-jack-midi test-recording
|
||||||
|
test-recovery test-reliability test-scale analyze` where dependencies exist.
|
||||||
|
3. Use a separate BUILD_DIR for ASan/UBSan/leak runs and another for optional-
|
||||||
|
dependency builds. GUI leak checks use only the documented fontconfig
|
||||||
|
suppression; core tests use none. Run publication/recording race tests as
|
||||||
|
described in RELIABILITY.md. Sanitizers are not latency qualification.
|
||||||
|
4. Run `make benchmark test-performance test-stress`; compare on matching
|
||||||
|
host/compiler/flags against a reviewed baseline. Investigate regressions;
|
||||||
|
do not weaken thresholds or replace evidence without explanation.
|
||||||
|
5. Run `make test-install test-release test-package`. Inspect archive manifests,
|
||||||
|
hashes, dynamic dependency names and licenses. Source exports rebuild twice
|
||||||
|
with identical artifacts on this host. Repeat binary packaging compares bytes.
|
||||||
|
6. Complete physical listening, recording/device/endurance, and independent human
|
||||||
|
install/workflow checks before claiming production qualification. They remain
|
||||||
|
pending for this prototype, even when all automated checks pass.
|
||||||
|
7. Save checksums/test logs/conditions with the proposed release. Review known
|
||||||
|
limitations and format compatibility and update user/developer docs. Publishing
|
||||||
|
or distro repository submission is a separate action.
|
||||||
|
|
||||||
|
## Regressions and compatibility
|
||||||
|
|
||||||
|
Report the exact version/commit, OS/toolchain, compiled features, reproduction,
|
||||||
|
expected/actual output, GUI errors/xruns and whether plugins are involved. Keep a
|
||||||
|
minimal project/media fixture when redistribution is permitted; do not attach
|
||||||
|
private recordings or full memory dumps inadvertently. No telemetry/upload occurs.
|
||||||
|
|
||||||
|
A confirmed defect gets a focused deterministic regression or repeatable runtime
|
||||||
|
check before its fix. Memory/thread/file-loss defects take priority; disable the
|
||||||
|
unsafe path rather than silently dropping media or unknown project records. Keep
|
||||||
|
candidate preparation/plan publication transactional and callbacks bounded.
|
||||||
|
|
||||||
|
Format changes increment XTC_PROJECT_FORMAT_VERSION, retain explicit bounded
|
||||||
|
migrations/fixtures for all supported older versions, and add malformed/roundtrip
|
||||||
|
checks. Unknown versions/types/records fail with actionable errors. Never rewrite
|
||||||
|
an unsupported source file during failed Open. Keep the original when diagnosing
|
||||||
|
post-publication sync errors. Native descriptor ABI compatibility is distinct from
|
||||||
|
project format and is documented in NATIVE_API.md; opaque DSP state is not promised.
|
||||||
|
No public stability guarantee or support SLA is implied by development version 0.1.0.
|
||||||
|
|
@ -154,7 +154,8 @@ discrete-mode compatibility are checked on control-thread compilation. Versions
|
||||||
Current writer emits v7; readers migrate v1–v6. Record ordering and field bounds
|
Current writer emits v7; readers migrate v1–v6. Record ordering and field bounds
|
||||||
are otherwise unchanged from v6. v7 ASSET may contain a relative path, interpreted
|
are otherwise unchanged from v6. v7 ASSET may contain a relative path, interpreted
|
||||||
against the actual containing directory of the named project file (resolved on
|
against the actual containing directory of the named project file (resolved on
|
||||||
the control thread), never the launch working directory. After loading, all core
|
the control thread), never the launch working directory. Opening a symlink to a project uses the
|
||||||
|
resolved target project directory; Save replaces the named entry as before. After loading, all core
|
||||||
asset references are absolute, including missing media. Media inside the save
|
asset references are absolute, including missing media. Media inside the save
|
||||||
folder are written relative; references outside remain absolute. Save As resolves
|
folder are written relative; references outside remain absolute. Save As resolves
|
||||||
against its new folder and preserves media identity rather than rebasing it by
|
against its new folder and preserves media identity rather than rebasing it by
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,21 @@ semantics. Physical devices, power loss, sustained recording/performance, user
|
||||||
acceptance on other distributions and binary redistribution are still separate
|
acceptance on other distributions and binary redistribution are still separate
|
||||||
qualification work. There is no DEB/RPM/AppImage, bundled runtime, autosave,
|
qualification work. There is no DEB/RPM/AppImage, bundled runtime, autosave,
|
||||||
external plugin host or stable shared-library extension ABI in this release.
|
external plugin host or stable shared-library extension ABI in this release.
|
||||||
|
|
||||||
|
## Phase 20 local binary packaging and installed workflow
|
||||||
|
|
||||||
|
`make test-package` builds the release, stages installation only in a temporary
|
||||||
|
DESTDIR, writes a normalized Linux binary/development archive, checks its SHA-256
|
||||||
|
sidecar and per-file manifest, relocatable pkg-config paths and headless commands.
|
||||||
|
Same-host repeated packaging is byte-identical; dependency versions and toolchain
|
||||||
|
still determine binary compatibility. `make test-install` now repeats installation
|
||||||
|
to verify repair/upgrade of owned files while preserving an unrelated sentinel,
|
||||||
|
then compiles both C99 and C++17 installed consumers that create MIDI, save/reopen
|
||||||
|
and compare nonzero exact rendering. Removal remains exact and idempotent.
|
||||||
|
|
||||||
|
USER_MANUAL.md consolidates operation; KNOWN_LIMITATIONS.md lists qualification
|
||||||
|
gaps; MAINTENANCE.md defines the release checklist, regression and compatibility
|
||||||
|
policy. Build diagnostics make no server/plugin calls. There is no asynchronous
|
||||||
|
crash uploader or signal-handler cleanup: inspect OS core/debugger evidence on
|
||||||
|
the control side, preserving project/media recovery paths. No release is publicly
|
||||||
|
published by these commands, and human/device/listening checks remain pending.
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,16 @@ policy. Arbitrary plugin costs and physical realtime/endurance are unqualified.
|
||||||
- Dependencies, licensing, limitations, and troubleshooting steps are documented.
|
- Dependencies, licensing, limitations, and troubleshooting steps are documented.
|
||||||
- A release can be reproduced from a clean checkout with recorded build instructions.
|
- A release can be reproduced from a clean checkout with recorded build instructions.
|
||||||
|
|
||||||
|
Implemented: normalized Linux binary/development archives with SHA-256/file/
|
||||||
|
ELF-dependency manifests, headless compiled-feature diagnostics, repeat-install
|
||||||
|
upgrade/repair and installed C/C++ save/reopen/render checks. Existing deterministic
|
||||||
|
source/release, no-dependency, sanitizer, staged uninstall and GUI/live tests are
|
||||||
|
retained. USER_MANUAL.md, KNOWN_LIMITATIONS.md and MAINTENANCE.md consolidate setup,
|
||||||
|
operation, licensing/diagnostics, release and regression policy. No public release
|
||||||
|
or distro registration is performed. Independent human installation/usability,
|
||||||
|
physical-device/endurance and listening acceptance remain pending and must be
|
||||||
|
completed before claiming production qualification.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Recommended Execution Order
|
## Recommended Execution Order
|
||||||
|
|
|
||||||
98
docs/USER_MANUAL.md
Normal file
98
docs/USER_MANUAL.md
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# xtc user manual (0.1.0 development build)
|
||||||
|
|
||||||
|
xtc is a bounded Linux music-making prototype. Start with a copy of an important
|
||||||
|
project and keep backups of its WAV media. Supported limits and unfinished areas
|
||||||
|
are listed in KNOWN_LIMITATIONS.md; no production/device qualification is implied.
|
||||||
|
|
||||||
|
## Build or install
|
||||||
|
|
||||||
|
Follow BUILD.md and LINUX.md. Core: `make all test`; GUI: `make gui`, then
|
||||||
|
`./build/xtc`. Use an existing JACK-compatible server and reconnect with F6.
|
||||||
|
Connect `midi_in` to a controller, `input` to mono capture and `output` /
|
||||||
|
`output_right` to left/right playback using your JACK routing application.
|
||||||
|
No server, physical routing or system configuration is changed automatically.
|
||||||
|
`xtc --version`, `xtc --help` and `xtc --diagnose` need no display/server.
|
||||||
|
|
||||||
|
For a source install stage with `make install-gui DESTDIR="$PWD/build/stage"
|
||||||
|
PREFIX=/usr`, inspect the stage, and use your normal deployment process. A user
|
||||||
|
prefix is supported (for example an absolute path to your `.local` folder).
|
||||||
|
`make uninstall` with the same DESTDIR/PREFIX removes only that version's known
|
||||||
|
files. See LINUX.md for package extraction, dependencies and removal. Never use
|
||||||
|
a release built for a different system ABI without rebuilding/testing it.
|
||||||
|
|
||||||
|
## Make a short project
|
||||||
|
|
||||||
|
1. New/Open starts in Sequencer. Add track; use F2 to name it.
|
||||||
|
2. Add MIDI clip, select it and open Piano roll (Ctrl+4). Left-click to draw
|
||||||
|
notes, drag to move, drag a right edge to resize, Delete/right-click to remove.
|
||||||
|
3. Alternatively Import WAV (Ctrl+I). It creates/routes an Audio clips source;
|
||||||
|
supported WAV formats and preload limits are in AUDIO_FILES.md.
|
||||||
|
4. Drag clips to arrange them and edges to trim. View/Snap sets the timeline
|
||||||
|
grid; +/- zoom buttons and scrollbars navigate. Ctrl+D duplicates a clip after
|
||||||
|
its end, retaining its complete source. F2 names clips; right-click offers
|
||||||
|
duplicate/rename/delete. Alt+Up/Down with timeline focus reorders tracks.
|
||||||
|
5. Play/F5 starts; Pause/F7 or Stop/F8 leaves the position for resuming. Home
|
||||||
|
returns to start while stopped (or stops first); click the ruler to seek.
|
||||||
|
Recording uses Stop, not Pause. F9 Panic releases the instrument.
|
||||||
|
6. Mixer (Ctrl+2) offers track gain/pan/mute/solo and Gain/Pan automation lanes.
|
||||||
|
Click to add points, right-click to remove. CONTROL.md explains timing and
|
||||||
|
live CC7 volume, CC10 pan and CC64 sustain.
|
||||||
|
7. Save (Ctrl+S), close, reopen and continue editing. Ctrl+Z undoes and
|
||||||
|
Ctrl+Shift+Z/Ctrl+Y redoes. New/Open resets history after unsaved-change handling.
|
||||||
|
|
||||||
|
Modular Graph (Ctrl+3) exposes routing. Drag output to input to connect, select a
|
||||||
|
wire/node and Delete to remove; cycles and invalid ports are rejected. Keep an
|
||||||
|
Output route for playback. GUI changes show queued/active graph status. Graph
|
||||||
|
positions are session-only; music/routing/automation are saved. Edits that prepare
|
||||||
|
new plans stop playback; disk/compilation may pause the GUI, never run in audio.
|
||||||
|
|
||||||
|
## Record and retain media
|
||||||
|
|
||||||
|
Choose a project folder and save the project there first. Record/Shift+F5 asks for
|
||||||
|
an unused WAV path. Stop finalizes the take and inserts it. Existing destinations
|
||||||
|
are protected, including a file created while recording. Undo/removing a take
|
||||||
|
removes its reference, never the recorded file. Imported external files are also
|
||||||
|
never deleted. WAVs inside the save folder become relative references; move the
|
||||||
|
folder and media together. Save As preserves the same source media, not a copy.
|
||||||
|
|
||||||
|
On write failure the GUI reports a retained path. Keep it and recover complete
|
||||||
|
frames to a different, unused destination:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xtc-check --recover-recording take.wav.tmp.XXXXXX recovered.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
Only xtc's canonical float32 mono temporary format is salvaged. Incomplete trailing
|
||||||
|
frames are omitted. The source is retained. Import/relink the recovered WAV.
|
||||||
|
Missing/unreadable media is silent and reported; select the clip and use File/
|
||||||
|
Relink selected audio. See RECOVERY.md for interruption, disk-full and power-loss
|
||||||
|
limits. Close/project operations are refused while recording; finish the take first.
|
||||||
|
|
||||||
|
## Trusted effects and sources
|
||||||
|
|
||||||
|
File/Load LADSPA plugin lists compatible descriptor indices from the chosen `.so`.
|
||||||
|
Loading executes native code. Select a documented compatible mono plugin, connect
|
||||||
|
its graph ports, then use Edit/Selected plugin control. Edit/Selected plugin
|
||||||
|
automation adds/removes tick-based linear or step points. Control names/ranges
|
||||||
|
appear in the dialogs. Module identity/control values/lanes save with the project;
|
||||||
|
module binaries and DSP history do not. The initial amp/inverter/sine set and
|
||||||
|
unsupported layouts are listed in PLUGIN_HOSTING.md. Missing plugins reject GUI
|
||||||
|
Open with identity/path information and preserve the saved/current documents.
|
||||||
|
Restore the compatible module at that path and retry. Plugins are not sandboxed.
|
||||||
|
|
||||||
|
## Diagnose a problem
|
||||||
|
|
||||||
|
Run `xtc-check --diagnose` and capture the version, architecture, compiled features,
|
||||||
|
GUI error text and steps to reproduce. `xtc-check --validate song.xtc` checks
|
||||||
|
metadata without opening plugins/media; it does not certify playback resources.
|
||||||
|
Check JACK routing, sample rate/format and xrun/process status in the GUI. After
|
||||||
|
server loss/rate change stop/reconnect and retry. Keep the original project,
|
||||||
|
media and retained temporary files before recovery attempts.
|
||||||
|
|
||||||
|
A native plugin can crash/hang the process. There is no crash-isolation guarantee.
|
||||||
|
Use the operating system's core-dump/debugger tools if available; xtc does not
|
||||||
|
install signal handlers, change core limits or upload crash reports. Debugger/
|
||||||
|
core files may contain media paths and audio memory; share only what you intend.
|
||||||
|
Report bugs following MAINTENANCE.md, including whether the issue reproduces
|
||||||
|
without plugins. Physical listening and independent human install/usability
|
||||||
|
acceptance remain pending.
|
||||||
|
|
@ -9,6 +9,10 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct xtc_app xtc_app;
|
typedef struct xtc_app xtc_app;
|
||||||
|
#define XTC_FEATURE_JACK 1U
|
||||||
|
#define XTC_FEATURE_LADSPA 2U
|
||||||
|
/* Compile-time capabilities, no loading/server connection or I/O. */
|
||||||
|
unsigned int xtc_build_features(void);
|
||||||
|
|
||||||
/* Creates an application context. On success, *out_app is caller-owned. */
|
/* Creates an application context. On success, *out_app is caller-owned. */
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define XTC_PROJECT_FORMAT_VERSION 7U
|
||||||
#define XTC_PROJECT_MAX_TRACKS 64U
|
#define XTC_PROJECT_MAX_TRACKS 64U
|
||||||
#define XTC_PROJECT_MAX_CLIPS 1024U
|
#define XTC_PROJECT_MAX_CLIPS 1024U
|
||||||
#define XTC_PROJECT_NAME_BYTES 64U
|
#define XTC_PROJECT_NAME_BYTES 64U
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "xtc/xtc.h"
|
#include "xtc/xtc.h"
|
||||||
|
#include "../plugin/config.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
@ -73,3 +74,15 @@ void xtc_app_destroy(xtc_app *app)
|
||||||
{
|
{
|
||||||
free(app);
|
free(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int xtc_build_features(void)
|
||||||
|
{
|
||||||
|
unsigned int features = 0;
|
||||||
|
#ifdef XTC_HAVE_JACK
|
||||||
|
features |= XTC_FEATURE_JACK;
|
||||||
|
#endif
|
||||||
|
#ifdef XTC_HAVE_LADSPA
|
||||||
|
features |= XTC_FEATURE_LADSPA;
|
||||||
|
#endif
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include "main_window.hpp"
|
#include "main_window.hpp"
|
||||||
#include "xtc/xtc_version.h"
|
#include "xtc/xtc_version.h"
|
||||||
|
#include "xtc/xtc.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,9 +20,16 @@ int main(int argc, char **argv)
|
||||||
std::printf("%s %s\n", XTC_PROJECT_NAME, XTC_VERSION_STRING);
|
std::printf("%s %s\n", XTC_PROJECT_NAME, XTC_VERSION_STRING);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
if (argc == 2 && !std::strcmp(argv[1], "--diagnose")) {
|
||||||
|
unsigned int features = xtc_build_features();
|
||||||
|
std::printf("xtc %s; project formats 1..%u; JACK %s; LADSPA %s\nFor limits and recovery commands run xtc-check --diagnose/--help.\n",
|
||||||
|
XTC_VERSION_STRING,XTC_PROJECT_FORMAT_VERSION,features & XTC_FEATURE_JACK ? "compiled" : "disabled",
|
||||||
|
features & XTC_FEATURE_LADSPA ? "compiled" : "disabled");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
if (argc == 2 && !std::strcmp(argv[1], "--help")) {
|
if (argc == 2 && !std::strcmp(argv[1], "--help")) {
|
||||||
std::puts(
|
std::puts(
|
||||||
"Usage: xtc [PROJECT] or xtc [FLTK options]\n--version and --help work without a display or audio server.");
|
"Usage: xtc [PROJECT] or xtc [FLTK options]\n--version, --diagnose and --help work without a display or audio server.");
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
const char *project_path = nullptr;
|
const char *project_path = nullptr;
|
||||||
|
|
|
||||||
16
src/main.c
16
src/main.c
|
|
@ -6,6 +6,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "xtc/xtc_project.h"
|
#include "xtc/xtc_project.h"
|
||||||
#include "xtc/xtc_audio.h"
|
#include "xtc/xtc_audio.h"
|
||||||
|
#include "xtc/xtc_midi.h"
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Runs the xtc-check command-line entry point.
|
* @brief Runs the xtc-check command-line entry point.
|
||||||
|
|
@ -25,8 +27,18 @@ int main(int argc, char **argv)
|
||||||
printf("%s %s\n", XTC_PROJECT_NAME, XTC_VERSION_STRING);
|
printf("%s %s\n", XTC_PROJECT_NAME, XTC_VERSION_STRING);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
if (argc == 2 && !strcmp(argv[1], "--diagnose")) {
|
||||||
|
unsigned int features = xtc_build_features();
|
||||||
|
printf("xtc %s; %zu-bit process\nProject formats: 1..%u\nJACK compiled: %s\nLADSPA compiled: %s\n"
|
||||||
|
"Limits: 256 graph nodes (64/project), %u tracks, %u clips, %u notes, 65536 frames/block\n"
|
||||||
|
"No server, device or plugin probed. Runtime errors appear in GUI status/stderr.\n",
|
||||||
|
XTC_VERSION_STRING,sizeof(void *)*CHAR_BIT,XTC_PROJECT_FORMAT_VERSION,
|
||||||
|
features & XTC_FEATURE_JACK ? "yes" : "no",features & XTC_FEATURE_LADSPA ? "yes" : "no",
|
||||||
|
XTC_PROJECT_MAX_TRACKS,XTC_PROJECT_MAX_CLIPS,XTC_PROJECT_MAX_NOTES);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
if (argc == 2 && !strcmp(argv[1], "--help")) {
|
if (argc == 2 && !strcmp(argv[1], "--help")) {
|
||||||
puts("Usage: xtc-check [--version | --help | --validate PROJECT | --recover-recording TEMP NEW.wav]\nNo arguments checks core startup/shutdown. "
|
puts("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --recover-recording TEMP NEW.wav]\nNo arguments checks core startup/shutdown. "
|
||||||
"Validation checks metadata only.");
|
"Validation checks metadata only.");
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +66,7 @@ int main(int argc, char **argv)
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
if (argc != 1) {
|
if (argc != 1) {
|
||||||
fputs("Usage: xtc-check [--version | --help | --validate PROJECT | --recover-recording TEMP NEW.wav]\n", stderr);
|
fputs("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --recover-recording TEMP NEW.wav]\n", stderr);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
||||||
xtc_graph *graph = project->graph;
|
xtc_graph *graph = project->graph;
|
||||||
READ("XTC_PROJECT", 2U);
|
READ("XTC_PROJECT", 2U);
|
||||||
NUM(1U, version);
|
NUM(1U, version);
|
||||||
if (version < 1U || version > 7U)
|
if (version < 1U || version > XTC_PROJECT_FORMAT_VERSION)
|
||||||
return XTC_ERR_UNSUPPORTED;
|
return XTC_ERR_UNSUPPORTED;
|
||||||
format_version = version;
|
format_version = version;
|
||||||
READ("APP", 4U);
|
READ("APP", 4U);
|
||||||
|
|
@ -448,8 +448,8 @@ static xtc_result write_project(FILE *file, const xtc_project *project, const ch
|
||||||
return XTC_ERR_UNSUPPORTED;
|
return XTC_ERR_UNSUPPORTED;
|
||||||
if (sizeof(float) != sizeof(uint32_t) || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128)
|
if (sizeof(float) != sizeof(uint32_t) || FLT_RADIX != 2 || FLT_MANT_DIG != 24 || FLT_MAX_EXP != 128)
|
||||||
return XTC_ERR_UNSUPPORTED;
|
return XTC_ERR_UNSUPPORTED;
|
||||||
if (fprintf(file, "XTC_PROJECT 7\nAPP %d %d %d\nTEMPO %u\nIDS %" PRIu64 " %" PRIu64 "\nGRAPH %d %zu %zu %zu\n",
|
if (fprintf(file, "XTC_PROJECT %u\nAPP %d %d %d\nTEMPO %u\nIDS %" PRIu64 " %" PRIu64 "\nGRAPH %d %zu %zu %zu\n",
|
||||||
XTC_VERSION_MAJOR, XTC_VERSION_MINOR, XTC_VERSION_PATCH, project->tempo, project->next_track_id,
|
XTC_PROJECT_FORMAT_VERSION, XTC_VERSION_MAJOR, XTC_VERSION_MINOR, XTC_VERSION_PATCH, project->tempo, project->next_track_id,
|
||||||
project->next_clip_id, graph->require_output_nodes, graph->next_id, graph->node_count,
|
project->next_clip_id, graph->require_output_nodes, graph->next_id, graph->node_count,
|
||||||
graph->edge_count) < 0)
|
graph->edge_count) < 0)
|
||||||
return XTC_ERR_IO;
|
return XTC_ERR_IO;
|
||||||
|
|
@ -582,7 +582,11 @@ xtc_result xtc_project_load(xtc_project *project, const char *path)
|
||||||
input.file = fopen(path, "rb");
|
input.file = fopen(path, "rb");
|
||||||
if (input.file == NULL)
|
if (input.file == NULL)
|
||||||
return XTC_ERR_IO;
|
return XTC_ERR_IO;
|
||||||
directory = project_folder(path);
|
{
|
||||||
|
char *canonical = realpath(path,NULL);
|
||||||
|
directory = canonical ? project_folder(canonical) : NULL;
|
||||||
|
free(canonical);
|
||||||
|
}
|
||||||
if (!directory) { fclose(input.file); return XTC_ERR_IO; }
|
if (!directory) { fclose(input.file); return XTC_ERR_IO; }
|
||||||
result = xtc_project_create(&candidate);
|
result = xtc_project_create(&candidate);
|
||||||
if (result == XTC_OK)
|
if (result == XTC_OK)
|
||||||
|
|
|
||||||
8
src/plugin/config.h
Normal file
8
src/plugin/config.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef XTC_PLUGIN_CONFIG_H
|
||||||
|
#define XTC_PLUGIN_CONFIG_H
|
||||||
|
#ifdef __has_include
|
||||||
|
#if __has_include(<ladspa.h>) && !defined(XTC_DISABLE_LADSPA)
|
||||||
|
#define XTC_HAVE_LADSPA 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
@ -8,11 +8,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
/* Header-only optional dependency; absent development header gives explicit
|
/* Header-only optional dependency; absent development header gives explicit
|
||||||
* unsupported stubs, preserving metadata-only load/save of plugin documents. */
|
* unsupported stubs, preserving metadata-only load/save of plugin documents. */
|
||||||
#ifdef __has_include
|
#include "config.h"
|
||||||
#if __has_include(<ladspa.h>) && !defined(XTC_DISABLE_LADSPA)
|
|
||||||
#define XTC_HAVE_LADSPA 1
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
static int terminated(const char *s, size_t n) { return memchr(s, 0, n) != NULL; }
|
static int terminated(const char *s, size_t n) { return memchr(s, 0, n) != NULL; }
|
||||||
xtc_result xtc_plugin_spec_validate(const xtc_plugin_spec *s)
|
xtc_result xtc_plugin_spec_validate(const xtc_plugin_spec *s)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@ def main():
|
||||||
assert (root / 'share/doc/xtc/PROJECT_FORMAT.md').is_file()
|
assert (root / 'share/doc/xtc/PROJECT_FORMAT.md').is_file()
|
||||||
assert os.access(root / 'bin/xtc-check', os.X_OK)
|
assert os.access(root / 'bin/xtc-check', os.X_OK)
|
||||||
run([str(root / 'bin/xtc-check')])
|
run([str(root / 'bin/xtc-check')])
|
||||||
|
diagnose = subprocess.check_output([str(root / 'bin/xtc-check'), '--diagnose'], text=True)
|
||||||
|
assert 'Project formats: 1..7' in diagnose and 'JACK compiled:' in diagnose
|
||||||
|
# Upgrade/repair repeats the same owned-file install and keeps unrelated data.
|
||||||
|
(root / 'include/xtc/xtc.h').write_text('old installation marker\n')
|
||||||
|
run(make + ['install'] + variables, env=env)
|
||||||
|
assert (root / 'include/xtc/xtc.h').read_bytes() == (source / 'include/xtc/xtc.h').read_bytes()
|
||||||
|
assert sentinel.read_text() == 'keep\n'
|
||||||
version = subprocess.check_output([str(root / 'bin/xtc-check'), '--version'], text=True)
|
version = subprocess.check_output([str(root / 'bin/xtc-check'), '--version'], text=True)
|
||||||
assert version.startswith('xtc ')
|
assert version.startswith('xtc ')
|
||||||
valid = stage / 'valid.xtc'
|
valid = stage / 'valid.xtc'
|
||||||
|
|
@ -54,12 +61,29 @@ def main():
|
||||||
flags = shlex.split(subprocess.check_output(pkg + ['--cflags', '--libs', '--static', 'xtc'], env=pkg_env, text=True))
|
flags = shlex.split(subprocess.check_output(pkg + ['--cflags', '--libs', '--static', 'xtc'], env=pkg_env, text=True))
|
||||||
includes = ''.join(f'#include "xtc/{p.name}"\n' for p in sorted((source / 'include/xtc').glob('*.h')))
|
includes = ''.join(f'#include "xtc/{p.name}"\n' for p in sorted((source / 'include/xtc').glob('*.h')))
|
||||||
consumer = stage / 'consumer.c'
|
consumer = stage / 'consumer.c'
|
||||||
consumer.write_text(includes + '\nint main(void) { xtc_project *p = 0; xtc_result r = xtc_project_create(&p); xtc_project_destroy(p); return r != XTC_OK; }\n')
|
consumer.write_text(includes + r'''
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
xtc_project *p = 0, *q = 0; xtc_midi_plan *a = 0, *b = 0;
|
||||||
|
uint64_t track, clip, note; float before[127], after[127]; int i, nonzero = 0;
|
||||||
|
if (argc != 2 || xtc_project_create(&p) != XTC_OK || xtc_project_create(&q) != XTC_OK) return 1;
|
||||||
|
if (xtc_project_add_track(p,"Installed",&track) != XTC_OK ||
|
||||||
|
xtc_project_add_midi_clip(p,track,0,960,&clip) != XTC_OK ||
|
||||||
|
xtc_project_add_note(p,clip,0,960,69,127,0,¬e) != XTC_OK ||
|
||||||
|
xtc_project_save(p,argv[1]) != XTC_OK || xtc_project_load(q,argv[1]) != XTC_OK ||
|
||||||
|
xtc_midi_plan_create(p,48000,&a) != XTC_OK || xtc_midi_plan_create(q,48000,&b) != XTC_OK ||
|
||||||
|
xtc_midi_plan_render(a,0,0,0,before,127) != XTC_OK || xtc_midi_plan_render(b,0,0,0,after,127) != XTC_OK) return 2;
|
||||||
|
for (i = 0; i < 127; ++i) { if (before[i] != after[i]) return 3; if (before[i] != 0) nonzero = 1; }
|
||||||
|
xtc_midi_plan_destroy(a); xtc_midi_plan_destroy(b); xtc_project_destroy(p); xtc_project_destroy(q);
|
||||||
|
remove(argv[1]); return !nonzero;
|
||||||
|
}
|
||||||
|
''')
|
||||||
for compiler, standard in [('CC', 'c99'), ('CXX', 'c++17')]:
|
for compiler, standard in [('CC', 'c99'), ('CXX', 'c++17')]:
|
||||||
output = stage / ('consumer-' + compiler)
|
output = stage / ('consumer-' + compiler)
|
||||||
command = shlex.split(env.get(compiler, 'cc' if compiler == 'CC' else 'c++'))
|
command = shlex.split(env.get(compiler, 'cc' if compiler == 'CC' else 'c++'))
|
||||||
run(command + [f'-std={standard}', '-Wall', '-Wextra', '-Werror', str(consumer)] + flags + ['-o', str(output)])
|
run(command + [f'-std={standard}', '-Wall', '-Wextra', '-Werror', str(consumer)] + flags + ['-o', str(output)])
|
||||||
run([str(output)])
|
run([str(output), str(stage / ("installed-" + compiler + ".xtc"))])
|
||||||
if args.gui:
|
if args.gui:
|
||||||
run(make + ['install-gui'] + variables, env=env)
|
run(make + ['install-gui'] + variables, env=env)
|
||||||
gui_env = dict(env, DISPLAY='', WAYLAND_DISPLAY='')
|
gui_env = dict(env, DISPLAY='', WAYLAND_DISPLAY='')
|
||||||
|
|
|
||||||
59
tests/integration/test_package.py
Normal file
59
tests/integration/test_package.py
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify every packaged file, relocatable metadata and headless executables."""
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = argparse.ArgumentParser(description=__doc__)
|
||||||
|
p.add_argument('archive', type=Path)
|
||||||
|
p.add_argument('--build-dir', type=Path)
|
||||||
|
a = p.parse_args()
|
||||||
|
expected = a.archive.with_name(a.archive.name + '.sha256').read_text().split()[0]
|
||||||
|
assert hashlib.sha256(a.archive.read_bytes()).hexdigest() == expected
|
||||||
|
with tempfile.TemporaryDirectory(prefix='xtc-package-test-') as temporary:
|
||||||
|
root = Path(temporary)
|
||||||
|
with tarfile.open(a.archive) as tar:
|
||||||
|
members = tar.getmembers()
|
||||||
|
assert members and all(m.isfile() for m in members)
|
||||||
|
assert len({m.name for m in members}) == len(members)
|
||||||
|
assert all(not Path(m.name).is_absolute() and '..' not in Path(m.name).parts for m in members)
|
||||||
|
tar.extractall(root, filter='data')
|
||||||
|
prefix = root / members[0].name.split('/')[0] / 'usr'
|
||||||
|
manifest_path = prefix / 'share/doc/xtc/BINARY_MANIFEST.json'
|
||||||
|
manifest = json.loads(manifest_path.read_text())
|
||||||
|
assert manifest['schema'] == 1 and manifest['needed']
|
||||||
|
expected_files = {f['path'] for f in manifest['files']} | {'usr/share/doc/xtc/BINARY_MANIFEST.json'}
|
||||||
|
assert expected_files == {str(p.relative_to(prefix.parent)) for p in prefix.rglob('*') if p.is_file()}
|
||||||
|
for f in manifest['files']:
|
||||||
|
content = (prefix.parent / f['path']).read_bytes()
|
||||||
|
assert len(content) == f['size'] and hashlib.sha256(content).hexdigest() == f['sha256']
|
||||||
|
env = dict(os.environ, DISPLAY='', WAYLAND_DISPLAY='', PKG_CONFIG_PATH=str(prefix / 'lib/pkgconfig'))
|
||||||
|
for name in ('xtc-check', 'xtc'):
|
||||||
|
binary = prefix / 'bin' / name
|
||||||
|
if binary.exists():
|
||||||
|
for option in ('--version', '--help', '--diagnose'):
|
||||||
|
subprocess.run([str(binary), option], env=env, check=True)
|
||||||
|
flags = subprocess.check_output(['pkg-config', '--cflags', '--libs', '--static', 'xtc'], env=env, text=True)
|
||||||
|
assert str(prefix / 'include') in flags or str(prefix / 'lib/pkgconfig/../../include') in flags
|
||||||
|
assert (prefix / 'share/doc/xtc/USER_MANUAL.md').exists()
|
||||||
|
if a.build_dir:
|
||||||
|
again = root / 'repacked.tar.gz'
|
||||||
|
command = [sys.executable, 'tools/package.py', '--build-dir', str(a.build_dir), '--output', str(again)]
|
||||||
|
if (prefix / 'bin/xtc').exists():
|
||||||
|
command.append('--gui')
|
||||||
|
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||||
|
assert result.returncode == 0, result.stdout
|
||||||
|
assert hashlib.sha256(again.read_bytes()).hexdigest() == expected, 'Binary repack differs'
|
||||||
|
print('Binary archive hashes, manifest, relocation, headless diagnostics and documentation passed.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -58,6 +58,14 @@ int main(void)
|
||||||
assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && !strcmp(c.asset_path,media));
|
assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && !strcmp(c.asset_path,media));
|
||||||
unlink(copy); rmdir(sub);
|
unlink(copy); rmdir(sub);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
char alias_dir[512], alias[512]; xtc_clip_info c;
|
||||||
|
snprintf(alias_dir,sizeof(alias_dir),"%s/aliases",root); assert(mkdir(alias_dir,0700) == 0);
|
||||||
|
snprintf(alias,sizeof(alias),"%s/aliases/link.xtc",root); assert(symlink(file,alias) == 0);
|
||||||
|
assert(xtc_project_load(p,alias) == XTC_OK);
|
||||||
|
assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && !strcmp(c.asset_path,media));
|
||||||
|
unlink(alias); rmdir(alias_dir);
|
||||||
|
}
|
||||||
snprintf(moved,sizeof(moved),"%s-moved",root); assert(rename(root,moved) == 0);
|
snprintf(moved,sizeof(moved),"%s-moved",root); assert(rename(root,moved) == 0);
|
||||||
snprintf(file,sizeof(file),"%s-moved/migration.xtc",root);
|
snprintf(file,sizeof(file),"%s-moved/migration.xtc",root);
|
||||||
assert(xtc_project_load(p,file) == XTC_OK);
|
assert(xtc_project_load(p,file) == XTC_OK);
|
||||||
|
|
|
||||||
76
tools/package.py
Normal file
76
tools/package.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Stage a Linux binary/development archive without modifying a live prefix."""
|
||||||
|
import argparse
|
||||||
|
import gzip
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
from dist import version
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = argparse.ArgumentParser(description=__doc__)
|
||||||
|
p.add_argument('--build-dir', type=Path, required=True)
|
||||||
|
p.add_argument('--output', type=Path, required=True)
|
||||||
|
p.add_argument('--gui', action='store_true')
|
||||||
|
a = p.parse_args()
|
||||||
|
epoch = int(os.environ.get('SOURCE_DATE_EPOCH', '0'))
|
||||||
|
if not 0 <= epoch <= 4294967295:
|
||||||
|
p.error('SOURCE_DATE_EPOCH must fit uint32')
|
||||||
|
name = f'xtc-{version(Path.cwd())}-linux-{platform.machine()}'
|
||||||
|
a.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with tempfile.TemporaryDirectory(prefix='xtc-binary-stage-') as temporary:
|
||||||
|
stage = Path(temporary)
|
||||||
|
env = dict(os.environ, MAKEFLAGS='', MFLAGS='')
|
||||||
|
subprocess.run(shlex.split(env.get('MAKE', 'make')) + ['install-gui' if a.gui else 'install',
|
||||||
|
f'BUILD_DIR={a.build_dir.resolve()}', f'DESTDIR={stage}', 'PREFIX=/usr'], env=env, check=True)
|
||||||
|
pc = stage / 'usr/lib/pkgconfig/xtc.pc'
|
||||||
|
pc.write_text(pc.read_text().replace('prefix=/usr\n', 'prefix=${pcfiledir}/../..\n'))
|
||||||
|
entries = []
|
||||||
|
needed = {}
|
||||||
|
for path in sorted(stage.rglob('*')):
|
||||||
|
if not path.is_file():
|
||||||
|
continue
|
||||||
|
assert not path.is_symlink()
|
||||||
|
relative = path.relative_to(stage).as_posix()
|
||||||
|
entries.append(dict(path=relative, size=path.stat().st_size,
|
||||||
|
sha256=hashlib.sha256(path.read_bytes()).hexdigest()))
|
||||||
|
if relative in ('usr/bin/xtc', 'usr/bin/xtc-check'):
|
||||||
|
dynamic = subprocess.check_output(['readelf', '-d', str(path)], text=True)
|
||||||
|
needed[relative] = re.findall(r'\(NEEDED\).*\[(.*?)\]', dynamic)
|
||||||
|
manifest = dict(schema=1, version=version(Path.cwd()), architecture=platform.machine(),
|
||||||
|
libc=list(platform.libc_ver()), needed=needed, files=entries,
|
||||||
|
scope='System-linked Linux build; no libraries, plugins or media bundled.')
|
||||||
|
(stage / 'usr/share/doc/xtc/BINARY_MANIFEST.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
||||||
|
fd, pending = tempfile.mkstemp(prefix='.xtc-package-', dir=a.output.parent)
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, 'wb') as raw, gzip.GzipFile(fileobj=raw, filename='', mode='wb', mtime=epoch) as gz:
|
||||||
|
with tarfile.open(fileobj=gz, mode='w', format=tarfile.PAX_FORMAT) as tar:
|
||||||
|
for path in sorted(stage.rglob('*')):
|
||||||
|
if not path.is_file():
|
||||||
|
continue
|
||||||
|
data = path.read_bytes()
|
||||||
|
info = tarfile.TarInfo(name + '/' + path.relative_to(stage).as_posix())
|
||||||
|
info.size = len(data)
|
||||||
|
info.mode = 0o755 if os.access(path, os.X_OK) else 0o644
|
||||||
|
info.mtime = epoch
|
||||||
|
tar.addfile(info, io.BytesIO(data))
|
||||||
|
os.replace(pending, a.output)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(pending):
|
||||||
|
os.unlink(pending)
|
||||||
|
sha = hashlib.sha256(a.output.read_bytes()).hexdigest()
|
||||||
|
a.output.with_name(a.output.name + '.sha256').write_text(f'{sha} {a.output.name}\n')
|
||||||
|
print(f'{a.output}: {sha}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -8,7 +8,8 @@ import stat
|
||||||
def owned_files(source):
|
def owned_files(source):
|
||||||
files = ['lib/libxtc.a', 'lib/pkgconfig/xtc.pc', 'bin/xtc-check', 'bin/xtc',
|
files = ['lib/libxtc.a', 'lib/pkgconfig/xtc.pc', 'bin/xtc-check', 'bin/xtc',
|
||||||
'share/applications/xtc.desktop', 'share/licenses/xtc/LICENSE',
|
'share/applications/xtc.desktop', 'share/licenses/xtc/LICENSE',
|
||||||
'share/doc/xtc/README.md']
|
'share/doc/xtc/README.md', 'share/doc/xtc/performance-baseline.json',
|
||||||
|
'share/doc/xtc/BINARY_MANIFEST.json']
|
||||||
files += ['include/xtc/' + p.name for p in sorted((source / 'include/xtc').glob('*.h'))]
|
files += ['include/xtc/' + p.name for p in sorted((source / 'include/xtc').glob('*.h'))]
|
||||||
files += ['share/doc/xtc/' + p.name for p in sorted((source / 'docs').glob('*.md'))]
|
files += ['share/doc/xtc/' + p.name for p in sorted((source / 'docs').glob('*.md'))]
|
||||||
files += ['share/doc/xtc/decisions/' + p.name for p in sorted((source / 'docs/decisions').glob('*.md'))]
|
files += ['share/doc/xtc/decisions/' + p.name for p in sorted((source / 'docs/decisions').glob('*.md'))]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue