FDS/OS 1.0 fixes

This commit is contained in:
2026-09-23 03:12:45 +08:00
parent 8a4788fca8
commit 6bfcce8070
23 changed files with 403 additions and 40 deletions
+2 -1
View File
@@ -23,7 +23,8 @@ claim Pi boot or physical power-cycle recovery is verified.
commands and options with typed parsers; do not hand-parse argument vectors.
- No systemd, runit, BusyBox, shell PID1, boot-time cache compilation, or sleeps
to hide races in the target system.
- Keep upstream Void files unchanged; pin its submodule and use `packages/` overlays.
- Keep `vendor/void-packages` clean and pinned. Use `.host/void-packages` for
generated build state; keep authoritative FDS overlays in `packages/`.
- Explain every new dependency and keep documentation accurate.
- Run `make bootstrap`, `make smoke-test`, and `make check` for M0 build changes.
Report skips and missing hardware honestly. Do not invent boot benchmarks.
+2 -1
View File
@@ -44,7 +44,8 @@ Unknown names are left alone, including ad hoc diagnostic experiments.
- User-created cartridge images, installed software trees and personal emulator sessions
such as `out/my-emulator/`, including their persistent DATA overlays.
- `out/logs/`, `out/manifests/`, packages, downloads, `out/cache/`, `.host/`, the
Void checkout/build container, and Cargo/Rustup caches outside this checkout.
writable Void checkout/build container under `.host/void-packages/`, and
Cargo/Rustup caches outside this checkout.
- Git-tracked files and any recognized workspace containing a `.fds-keep` entry.
To retain an older generated workspace for investigation, put a marker in it
+2
View File
@@ -10,6 +10,8 @@ the inputs named in those reports; a changed source tree needs fresh checks.
## Design and build references
- [Clean upstream and writable Void workspace](void-workspace.md)
- [Master plan](master-plan.md)
- [Build host and dependencies](build-host.md)
- [Development workflow](development.md)
+16 -6
View File
@@ -112,7 +112,7 @@ equivalent of:
```sh
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd vendor/void-packages
cd .host/void-packages
./xbps-src -A x86_64 binary-bootstrap
```
@@ -131,7 +131,7 @@ To reproduce the two builds individually from the repository root:
```
The latter command sets the local XBPS PATH, checks the Void pin, and executes
this command within the submodule:
this command within the writable build checkout:
```sh
./xbps-src -a aarch64 pkg hello
@@ -186,9 +186,11 @@ tests accumulate. [Cleanup](cleanup.md) explains the retained current images,
release/input archives and caches. Full image builds and VM suites need tens of
GiB per working tree, beyond the initial M0 smoke test's requirements.
- `vendor/void-packages/`: clean, pinned upstream submodule; no generated overlays.
- `.host/void-packages/`: independent local build checkout at the same pin.
- `.host/xbps/`: project-local host tools, not installed into `/usr`.
- `vendor/void-packages/masterdir-x86_64/`: glibc build container.
- `vendor/void-packages/hostdir/`: download caches, cross packages, and build output.
- `.host/void-packages/masterdir-x86_64/`: glibc build container.
- `.host/void-packages/hostdir/`: download caches, cross packages, and build output.
- `out/packages/`: exported aarch64 test package and its repository index.
- `out/manifests/void-build-packages.txt`: packages remaining installed after the
build. Cross toolchains and temporary dependencies removed by xbps-src remain
@@ -197,12 +199,20 @@ GiB per working tree, beyond the initial M0 smoke test's requirements.
- `out/manifests/artifacts.sha256`: validation artifact digests.
- `out/logs/`: actual command logs, including logs from failed attempts.
Place FDS package overlays at `packages/fds-*/template`. `tools/prepare-void`
copies only directories containing a template and refuses to replace tracked
Place FDS package overlays at `packages/fds-*/template`. `tools/prepare-void-workspace`
clones the pinned upstream locally without shared Git objects or network access.
`tools/prepare-void` copies directories containing a template into that writable checkout and refuses to replace tracked
upstream packages. These copies are visible inside the build container. Base,
base-files, init, CLI, kernel and Dasung overlays are active; future directories
without templates remain inactive.
Bootstrap migrates matching old generated copies, configuration, `hostdir` and
`masterdir-*` out of the submodule using renames, preserving cached downloads
and container files. Unknown files, staged edits, changed copies or destination
conflicts stop migration for explicit reconciliation. Python 3 is used for
checked migration and locking; it is already a documented FDS host prerequisite.
The workstation builder also rejects submodules as writable build targets.
If an overlay changes and preparation reports a stale copy, inspect and preserve
any unique edits before moving the specific generated copy out of `srcpkgs/` and
retrying. Modified upstream tracked files cause immediate failure. Conflicting
+4 -1
View File
@@ -453,7 +453,10 @@ Record:
VOID_PACKAGES_COMMIT
```
Inject FDS packages through an overlay.
Keep this submodule clean. Prepare a writable checkout at `.host/void-packages`
from the same pinned commit. Inject FDS packages from `packages/` into that
build checkout; keep generated sources, caches and build containers outside
the submodule.
Required relationship:
+7
View File
@@ -16,6 +16,13 @@ Normal builds still use Void's signed rolling repositories. A frozen build uses
the selected package files and build environment from one explicit snapshot.
It does not silently consult a newer repository if an input is missing.
Current builds keep the upstream reference in `vendor/void-packages/` and use
`.host/void-packages/` for overlays and cached build state. New snapshots and
restores use this separate layout. Restoring an older
snapshot retains its historical layout and saved build scripts; its files and
acceptance are not rewritten. See [the workspace change](void-workspace.md) for
fresh validation of this migration.
## What is frozen
`tools/frozen-inputs create NEW_DIRECTORY` records:
+3 -3
View File
@@ -94,14 +94,14 @@ normal. Never use a broad reset or clean command without checking what it remove
For `etc/conf differs from config/xbps-src.conf`, compare the two:
```sh
diff -u vendor/void-packages/etc/conf config/xbps-src.conf
diff -u .host/void-packages/etc/conf config/xbps-src.conf
```
Put the desired settings in `config/xbps-src.conf`, then follow
[the explicit synchronization procedure](development.md#change-build-configuration-deliberately).
A `diff` exit status of 1 means differences were found, not that the comparison failed.
For `Stale overlay`, compare the named `vendor/void-packages/srcpkgs/fds-*`
For `Stale overlay`, compare the named `.host/void-packages/srcpkgs/fds-*`
directory with its source under `packages/`. Preserve any unique edits in the
source first. Move that specific generated copy into a backup directory under
`out/`, outside `srcpkgs/`, then run `./tools/prepare-void` again. Do not move or
@@ -121,7 +121,7 @@ space before assuming that a quiet download is a deadlock:
```sh
tail -n 40 out/logs/bootstrap.log
tail -n 40 out/logs/xbps-hello.log
du -sh out/downloads vendor/void-packages/hostdir
du -sh out/downloads .host/void-packages/hostdir
df -h .
```
+77
View File
@@ -0,0 +1,77 @@
# Clean upstream and writable Void workspace
FDS keeps upstream source separate from its build state:
| Path | Ownership and purpose |
| --- | --- |
| `vendor/void-packages/` | Clean upstream Git submodule at `VOID_PACKAGES_COMMIT` |
| `packages/` | Authoritative FDS base-package sources, including Dasung |
| `examples/software/*/void/` | Authoritative example PROGRAM package sources |
| `.host/void-packages/` | Generated writable checkout, package copies, local configuration, caches and build container |
`make bootstrap` creates the build checkout locally at the pinned commit. It
copies Git objects independently; there is no network fetch for this step, no
Git alternates dependency and no hardlinks into the upstream source. Normal
package bootstrap still uses its configured repositories.
`tools/prepare-void` copies FDS package overlays into the writable checkout and
retains the existing checks against replacing upstream packages or overwriting
changed generated copies. Workstation builds default to the same checkout.
Explicitly selecting a Git submodule with `--void-packages` is rejected before
source files or build state can be written there.
The workspace helper migrates old, matching generated files and build caches by
renaming them. It compares every file, mode and symlink in each known source
copy first. Unknown upstream files, staged changes, independent edits and
conflicting destinations stop migration. Run builds and migration sequentially.
The workspace lock prevents two preparations from changing the layout together.
Python is already a build-host prerequisite; this introduces no new dependency.
For this checkout, the 58 staged additions matched the main-repository sources.
Their local commit `47580322e801ef195c9bf279fd1fe4d8a1f723e9` and both staged
patches were saved under `.host/backups/void-overlays-20260922-134611/` before
unstaging those copies. The parent gitlink was restored to upstream commit
`02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1`, matching the declared pin. All eleven
package directories, `hostdir`, `masterdir-x86_64` and `etc/conf` were moved into
the build checkout. No upstream tracked source was changed.
An explicit future pin update advances the writable checkout without replacing
its caches. Git refuses updates that would overwrite local files. Continue to
record the parent gitlink and `VOID_PACKAGES_COMMIT` together.
Frozen-input creation uses the separate build checkout. New restores recreate
that layout. Older saved projects retain their
original layout and scripts; the frozen 0.1.0 release is untouched. Restore
compatibility tests do not imply a new reproducibility or physical-hardware claim.
## Validation
Fresh checks for this change passed:
- `make bootstrap`, `make smoke-test`, and `make check`: migrated container use,
ARM/glibc package compilation, static-musl compilation, clean upstream state,
exact overlay/cache preservation, shallow pin updates, rejection of independent
edits and source conflicts, and cleanup protections.
- `make workstation-test`: `out/workstation-images.ckkodex6`; real Void source
builds from `.host/void-packages`, image validation and confirmed file write/readback.
Selecting the upstream submodule as a build target is rejected without writes.
- `make emulator-test`: `out/emu-test.op5azhlm`; full twelve-bay PROGRAM/DATA,
foreground terminal, eject/unplug, legacy reading and service-restart checks.
- `make release-contract-test`: `out/m12-release-contracts.wmsfrcl0`; both separate
and legacy frozen-layout restores, retained source caches, local offline
repositories and existing tamper/release-comparison checks.
- Documentation links, formatting, Python syntax and a final empty upstream Git
status, including ignored files, were checked.
Logs are in `out/logs/void-workspace/`; source and log hashes, pins, native binary
hashes and fixture results are recorded in `out/manifests/void-workspace-acceptance.json`.
The optional host-native ARM execution shortcut was skipped in smoke-test. ARM
execution was exercised by the full emulator suite. Physical hardware was not used.
The first emulator attempt (`out/emu-test.463emq8u`) failed on an empty cartridge
IPC response during initial discovery in its admin VM. The complete rerun passed
without target-system changes. SYSTEM, kernel and initramfs hashes matched the
previous accepted inputs. The cause of that first response failure was not
established; its command/serial logs are retained. Future failures now collect
live service diagnostics before VM shutdown, without retrying failed commands
or relaxing their acceptance conditions.
+3
View File
@@ -34,6 +34,9 @@ make check
```
Bootstrap prepares pinned Rust and XBPS tools and the Void build container.
The upstream reference stays clean in `vendor/void-packages/`. Generated package
copies, build configuration and caches live in `.host/void-packages/`; edit
FDS package sources in `packages/`, not in either generated checkout.
The smoke test checks the AArch64 build toolchains; `make check` validates the
build guardrails. Stop and resolve any failed command before continuing. Logs
are in `out/logs/`. Repeated preparation reuses verified downloads and caches.
+4 -1
View File
@@ -26,7 +26,10 @@ normal Void `template` and any `files/` or `patches/` directories it uses. Paths
are relative to this recipe. The [included hello package](../examples/software/hello/void/template)
is a complete example.
Custom sources are copied into an untracked `srcpkgs/PACKAGE` directory. Existing
Custom sources are copied into `srcpkgs/PACKAGE` inside the selected writable
build checkout. FDS uses `.host/void-packages/`; its `vendor/void-packages/`
submodule remains unchanged. The builder rejects a Git submodule as its build
checkout. Existing
tracked Void sources are never overwritten. If a generated source copy differs,
inspect it, preserve any independent edits, then remove that generated copy and
retry. An unchanged copy can be reused.
+5 -3
View File
@@ -18,7 +18,7 @@ export PATH="$PWD/out/workstation:$PATH"
| Prerequisite | Used for |
| --- | --- |
| Prepared Void `void-packages` checkout and native XBPS utilities | Build source packages and install their AArch64 runtime dependencies |
| Prepared writable Void `void-packages` checkout and native XBPS utilities | Build source packages and install their AArch64 runtime dependencies |
| `mkfs.erofs`, `fsck.erofs` | Create and inspect read-only payload filesystems |
| `bwrap` | Inspect filesystems and install package trees in a user namespace |
| `xz` | Read older software cartridges and package/source archives |
@@ -31,12 +31,14 @@ share the same Void checkout.
Prepare a Void source checkout using its [upstream instructions](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/README.md).
Complete `xbps-src binary-bootstrap` for the workstation architecture before
using it. The workstation itself need not run Void. Select that checkout with
using it. Keep this writable checkout outside Git submodules. The workstation itself need not run Void. Select that checkout with
`--void-packages /path/to/void-packages`; native `xbps-*` tools must be in PATH,
or selected with `--xbps-bin /path/to/bin`. Builds target AArch64/glibc.
For the complete FDS build workstation, [bootstrap](getting-started.md) already
prepares `vendor/void-packages` and `.host/xbps/usr/bin`. Reuse its local tools:
keeps `vendor/void-packages` as a clean upstream reference and prepares the
writable `.host/void-packages` checkout plus `.host/xbps/usr/bin`. The native
builder defaults to `.host/void-packages`. Reuse its local tools:
```sh
./tools/prepare-image-tools
+12 -2
View File
@@ -13,8 +13,8 @@ use std::{
#[derive(clap::Args, Debug, Clone)]
pub struct BuildOptions {
/// Prepared Void source checkout used by xbps-src (not the workstation OS).
#[arg(long, global = true, default_value = "vendor/void-packages")]
/// Writable Void build checkout; keep it outside Git submodules.
#[arg(long, global = true, default_value = ".host/void-packages")]
pub void_packages: PathBuf,
/// Optional rootless XBPS wrapper, for example this checkout's tools/in-void.
#[arg(long, global = true)]
@@ -81,6 +81,16 @@ pub fn build(recipe: &Path, output: &Path, options: &BuildOptions) -> Result<Sof
"--void-packages must name a prepared Void source checkout".into(),
));
}
let superproject = Command::new("git")
.arg("-C")
.arg(&checkout)
.args(["rev-parse", "--show-superproject-working-tree"])
.output()?;
if !superproject.status.success() || !superproject.stdout.is_empty() {
return Err(Error(
"Use a writable Void build checkout outside Git submodules; FDS prepares .host/void-packages with make bootstrap".into(),
));
}
let destination = checkout.join("srcpkgs").join(&input.source.package);
if let Some(template) = input.source.template {
let source = parent(recipe)?.join(template).canonicalize()?;
+2 -1
View File
@@ -61,7 +61,8 @@ class CleanupTests(unittest.TestCase):
self.file('out/manifests/acceptance.json'),
self.file('out/.gitkeep'),
self.file('.host/xbps/tool'),
self.file('vendor/void-packages/hostdir/download'),
self.file('.host/void-packages/hostdir/download'),
self.file('vendor/void-packages/xbps-src'),
]
(self.root / 'out/rootfs-cli.tar').symlink_to('rootfs-build.NEW123/rootfs.tar')
preview = self.run_cli('--dry-run')
+41 -7
View File
@@ -30,31 +30,65 @@ expect_failure 'unsupported bootstrap option' tools/bootstrap-host --unknown
# Use a disposable minimal checkout to exercise pin and dirty-tree detection.
mkdir -p "$scratch/repo/tools" "$scratch/repo/vendor/void-packages"
cp tools/lib.sh tools/prepare-void "$scratch/repo/tools/"
cp tools/lib.sh tools/prepare-void tools/prepare-void-workspace "$scratch/repo/tools/"
fake="$scratch/repo/vendor/void-packages"
git -C "$fake" init -q
printf '# fixture\n' >"$fake/xbps-src"
mkdir -p "$fake/srcpkgs/fds-upstream" "$fake/etc" "$scratch/repo/config" "$scratch/repo/packages/fds-fixture"
printf '# upstream\n' >"$fake/srcpkgs/fds-upstream/template"
printf 'etc/conf\n' >"$fake/.gitignore"
printf 'etc/conf\nhostdir/\nmasterdir-*/\n' >"$fake/.gitignore"
cp config/xbps-src.conf "$scratch/repo/config/"
printf '# inert overlay fixture\n' >"$scratch/repo/packages/fds-fixture/template"
git -C "$fake" add .
git -C "$fake" -c user.name=FDS -c user.email=test@example.invalid commit -qm fixture
git -C "$fake" rev-parse HEAD >"$scratch/repo/VOID_PACKAGES_COMMIT"
bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
bash -c 'source "$1"; check_void_source' _ "$scratch/repo/tools/lib.sh"
printf 'PASS: accepts exact clean Void commit\n'
# Existing generated files and caches move without copying their payloads.
cp -a "$scratch/repo/packages/fds-fixture" "$fake/srcpkgs/"
cp config/xbps-src.conf "$fake/etc/conf"
mkdir -p "$fake/hostdir" "$fake/masterdir-x86_64"
printf 'cached input\n' >"$fake/hostdir/cache"
cache_inode=$(stat -c %i "$fake/hostdir/cache")
"$scratch/repo/tools/prepare-void"
cmp "$scratch/repo/packages/fds-fixture/template" "$fake/srcpkgs/fds-fixture/template"
build="$scratch/repo/.host/void-packages"
[[ -z $(git -C "$fake" status --porcelain --untracked-files=all) ]] || die 'Migration dirtied upstream'
[[ ! -e $fake/hostdir && ! -e $fake/etc/conf && ! -e $fake/srcpkgs/fds-fixture ]] || die 'Migration left build state in upstream'
[[ $(stat -c %i "$build/hostdir/cache") == "$cache_inode" ]] || die 'Migration copied cache data'
[[ ! -f $build/.git/objects/info/alternates ]] || die 'Build checkout depends on upstream object storage'
bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
cmp "$scratch/repo/packages/fds-fixture/template" "$build/srcpkgs/fds-fixture/template"
"$scratch/repo/tools/prepare-void"
printf 'PASS: overlay copied and repeat preparation is idempotent\n'
printf 'PASS: overlays and caches moved outside clean upstream; preparation is idempotent\n'
printf '# changed\n' >>"$scratch/repo/packages/fds-fixture/template"
expect_failure 'stale generated overlay' "$scratch/repo/tools/prepare-void"
cp "$fake/srcpkgs/fds-fixture/template" "$scratch/repo/packages/fds-fixture/template"
cp "$build/srcpkgs/fds-fixture/template" "$scratch/repo/packages/fds-fixture/template"
# Preserve unexpected upstream additions and independently edited legacy copies.
printf 'personal source\n' >"$fake/personal-file"
expect_failure 'unknown upstream addition' "$scratch/repo/tools/prepare-void"
[[ -f $fake/personal-file ]] || die 'Unknown file was removed'
rm "$fake/personal-file"
cp -a "$scratch/repo/packages/fds-fixture" "$fake/srcpkgs/"
printf '# independent edit\n' >>"$fake/srcpkgs/fds-fixture/template"
expect_failure 'edited legacy overlay' "$scratch/repo/tools/prepare-void"
grep -q 'independent edit' "$fake/srcpkgs/fds-fixture/template"
rm -r "$fake/srcpkgs/fds-fixture"
# An explicit upstream pin update advances the build checkout and keeps caches.
printf 'new upstream revision\n' >"$fake/README"
git -C "$fake" add README
git -C "$fake" -c user.name=FDS -c user.email=test@example.invalid commit -qm update
git -C "$fake" rev-parse HEAD >"$scratch/repo/VOID_PACKAGES_COMMIT"
# Production initializes upstream with depth 1; updating that boundary must work.
git -C "$fake" rev-parse HEAD >"$fake/.git/shallow"
expect_failure 'outdated build checkout' bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
"$scratch/repo/tools/prepare-void"
[[ $(stat -c %i "$build/hostdir/cache") == "$cache_inode" ]] || die 'Pin update replaced cache'
[[ $(git -C "$build" rev-parse HEAD) == "$(cat "$scratch/repo/VOID_PACKAGES_COMMIT")" ]] || die 'Build pin not advanced'
mkdir -p "$scratch/repo/packages/fds-upstream"
printf '# collision\n' >"$scratch/repo/packages/fds-upstream/template"
expect_failure 'overlay replacing upstream package' "$scratch/repo/tools/prepare-void"
printf '# changed config\n' >>"$fake/etc/conf"
rm -r "$scratch/repo/packages/fds-upstream"
printf '# changed config\n' >>"$build/etc/conf"
expect_failure 'overwriting local xbps configuration' "$scratch/repo/tools/prepare-void"
printf '\n# dirty\n' >>"$fake/xbps-src"
expect_failure 'modified Void source' bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
+42
View File
@@ -131,4 +131,46 @@ command[-1] = linked
result = invoke(*command, ok=False)
assert 'not a symlink' in result.stderr
print(f'PASS: release output preservation and private-key symlink rejection: {work}')
# Restore small real Git bundles through both workspace layouts. These fixtures
# exercise path selection and cache preservation, not an operating-system build.
upstream = work / 'upstream'
upstream.mkdir()
invoke('git', 'init', '-q', upstream)
(upstream / 'xbps-src').write_text('# inert source fixture\n')
(upstream / '.gitignore').write_text('hostdir/\nmasterdir-*/\netc/conf\netc/xbps.d/custom/\n')
invoke('git', '-C', upstream, 'add', '.')
invoke('git', '-C', upstream, '-c', 'user.name=FDS', '-c', 'user.email=test@example.invalid', 'commit', '-qm', 'fixture')
pin = invoke('git', '-C', upstream, 'rev-parse', 'HEAD').stdout.strip()
epoch = int(invoke('git', '-C', upstream, 'show', '-s', '--format=%ct', 'HEAD').stdout)
for layout in ('separate', 'legacy'):
fixture = work / ('restore-input-' + layout)
(fixture / 'project/tools').mkdir(parents=True)
(fixture / 'project/config').mkdir()
(fixture / 'project/VOID_PACKAGES_COMMIT').write_text(pin + '\n')
(fixture / 'project/config/xbps-src.conf').write_text('# fixture\n')
if layout == 'separate':
shutil.copy2(project / 'tools/prepare-void-workspace', fixture / 'project/tools/prepare-void-workspace')
invoke('git', '-C', upstream, 'bundle', 'create', fixture / 'void.bundle', 'HEAD')
for name in ('masterdir/etc/xbps.d', 'sources', 'repositories/build',
'repositories/cli', 'repositories/development', 'repositories/recovery',
'xbps', 'image-tools', 'eeprom', 'cache', 'downloads', 'cargo-vendor', 'rust-toolchain'):
(fixture / name).mkdir(parents=True, exist_ok=True)
(fixture / 'sources/retained.tar.gz').write_bytes(b'cached source fixture')
restore_lock = dict(lock, void_commit=pin, source_epoch=epoch)
restore_lock['files'] = frozen.inventory(fixture)
restore_lock['source_sha256'] = frozen.source_digest(restore_lock['files'])
(fixture / 'lock.json').write_text(json.dumps(restore_lock))
destination = work / ('restored-' + layout)
invoke(verify, 'restore', fixture, destination)
build = destination / ('.host/void-packages' if layout == 'separate' else 'vendor/void-packages')
assert (build / 'hostdir/sources/retained.tar.gz').read_bytes() == b'cached source fixture'
assert (build / 'masterdir-x86_64/etc/xbps.d/05-fds-frozen-local.conf').read_text() == 'repository=/host/frozen-repository\n'
assert invoke('git', '-C', build, 'rev-parse', 'HEAD').stdout.strip() == pin
if layout == 'separate':
reference = destination / 'vendor/void-packages'
assert not (reference / 'hostdir').exists()
assert not invoke('git', '-C', reference, 'status', '--porcelain', '--untracked-files=all').stdout.strip()
invoke(verify, 'verify', fixture)
print('PASS: separate-workspace and legacy frozen restores retain source caches and offline repositories', flush=True)
print('NOTE: these are small contract fixtures; actual source-to-image offline reproduction remains a separate gate')
+10
View File
@@ -39,6 +39,16 @@ def invoke(*command, ok=True):
result = subprocess.run([cli, '--session', str(session), *map(str, command)], capture_output=True, text=True, timeout=240)
log.write(repr(command) + '\n' + result.stdout + result.stderr)
log.flush()
if (result.returncode == 0) != ok and command and command[0] == 'guest':
# Preserve live service evidence before the finally block stops the VM.
# Do not retry or relax the failed command's acceptance condition.
diagnostics = subprocess.run([
cli, '--session', str(session), 'guest', '--', 'sh', '-c',
'cat /run/log/cartridged/current; s6-svstat /run/service/cartridged; '
'dmesg | tail -40',
], capture_output=True, text=True, timeout=60)
log.write('Failure diagnostics:\n' + diagnostics.stdout + diagnostics.stderr)
log.flush()
assert (result.returncode == 0) == ok, (command, result.returncode, result.stdout, result.stderr)
return result.stdout
+12 -2
View File
@@ -14,7 +14,7 @@ from image_formats import gpt, LINUX_FILESYSTEM
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--cli', type=Path, required=True)
parser.add_argument('--image-tool-runner', type=Path)
parser.add_argument('--void-packages', type=Path, default=project / 'vendor/void-packages')
parser.add_argument('--void-packages', type=Path, default=project / '.host/void-packages')
parser.add_argument('--xbps-tool-runner', type=Path)
parser.add_argument('--xbps-bin', type=Path)
args = parser.parse_args()
@@ -43,6 +43,15 @@ def digest(path):
return hashlib.file_digest(stream, 'sha256').hexdigest()
# A user-provided submodule path must fail before copying any package sources.
upstream = project / 'vendor/void-packages'
assert not subprocess.check_output(['git', '-C', str(upstream), 'status', '--porcelain', '--untracked-files=all'])
rejected = subprocess.run([cli[0], '--void-packages', str(upstream), 'software', 'build',
str(project / 'examples/software/hello/software.toml'), str(work / 'forbidden-build')],
capture_output=True, text=True)
assert rejected.returncode != 0 and 'outside Git submodules' in rejected.stderr, rejected.stderr
assert not (work / 'forbidden-build').exists()
for name in ['hello', 'report']:
recipe = project / f'examples/software/{name}/software.toml'
if name == 'report':
@@ -149,7 +158,8 @@ for case in ['program-corrupt', 'catalogue-mapping']:
malformed = work / (case + '.img')
gpt(malformed, [(part['name'], LINUX_FILESYSTEM, filesystem) for part, filesystem in zip(parts, selected)])
invoke(['inspect', malformed], False)
record = dict(status='passed', work=str(work), cli_sha256=digest(args.cli.resolve()), compiled_aarch64_software=True,
assert not subprocess.check_output(['git', '-C', str(upstream), 'status', '--porcelain', '--untracked-files=all'])
record = dict(source_checkout_clean=True, submodule_build_rejected=True, status='passed', work=str(work), cli_sha256=digest(args.cli.resolve()), compiled_aarch64_software=True,
void_source_packages=True, installed_runtime_dependencies=True, wrong_elf_and_escaping_symlink_rejected=True, shared_and_separate_payload_partitions=True,
repeat_image_identical=True, direct_installed_erofs_programs=True,
exact_and_larger_target_readback=True, wrong_confirmation_unchanged=True,
+3 -3
View File
@@ -8,10 +8,10 @@ source /etc/os-release
[[ ! $FDS_ROOT =~ [[:space:]] ]] || die 'xbps-src requires a checkout path without whitespace'
if [[ ${1:-} == --install-deps ]]; then
sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux python
fi
for cmd in bash git curl make file readelf ldd tar xz gzip zstd sha256sum bwrap rustup \
find diff grep sed awk flock install; do
find diff grep sed awk flock install python3; do
need "$cmd"
done
# Fail before downloading if unprivileged build containers cannot run.
@@ -21,7 +21,7 @@ mkdir -p out/downloads out/logs .host
if [[ ${FDS_OFFLINE:-0} != 1 ]]; then
git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=60 submodule update --init --depth 1 vendor/void-packages
fi
check_void_pin
check_void_source
source config/host-tools.conf
archive="out/downloads/${XBPS_STATIC_URL##*/}"
if [[ ! -f $archive ]]; then
+13 -1
View File
@@ -79,7 +79,13 @@ def check_pin():
if pin != actual:
raise ValueError('Void checkout differs from its pin')
run('git', '-C', void, 'diff', '--exit-code', 'HEAD', '--')
return void, pin
build = project / '.host/void-packages'
if subprocess.check_output(['git', '-C', str(build), 'rev-parse', 'HEAD'], text=True).strip() != pin:
raise ValueError('Void build checkout differs from its pin')
run('git', '-C', build, 'diff', '--exit-code', 'HEAD', '--')
if subprocess.check_output(['git', '-C', str(void), 'status', '--porcelain', '--untracked-files=all'], text=True).strip():
raise ValueError('Void upstream submodule is dirty')
return build, pin
def add_package(source, destination):
@@ -245,6 +251,12 @@ def restore(source, destination):
epoch = int(subprocess.check_output(['git', '-C', str(void), 'show', '-s', '--format=%ct', 'HEAD']))
if epoch != lock['source_epoch']:
raise ValueError('Restored Void commit timestamp differs from the snapshot')
# Older frozen projects expect caches under vendor; their saved sources and
# release inputs remain unchanged. New projects use the separate workspace.
prepare = destination / 'tools/prepare-void-workspace'
if prepare.is_file():
run(sys.executable, prepare)
void = destination / '.host/void-packages'
copy(source / 'masterdir', void / 'masterdir-x86_64')
copy(source / 'sources', void / 'hostdir/sources')
copy(source / 'repositories/build', void / 'hostdir/frozen-repository')
+15 -6
View File
@@ -3,22 +3,31 @@
set -euo pipefail
export LC_ALL=C
FDS_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
FDS_VOID="$FDS_ROOT/vendor/void-packages"
FDS_VOID_SOURCE="$FDS_ROOT/vendor/void-packages"
FDS_VOID="$FDS_ROOT/.host/void-packages"
FDS_XBPS="$FDS_ROOT/.host/xbps/usr/bin"
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
need() { command -v "$1" >/dev/null || die "Missing host command: $1 (see docs/developer/build-host.md)"; }
check_void_pin() {
check_void_source() {
local pin actual
pin=$(cat "$FDS_ROOT/VOID_PACKAGES_COMMIT")
[[ $pin =~ ^[0-9a-f]{40}$ ]] || die 'Invalid VOID_PACKAGES_COMMIT'
[[ -f $FDS_VOID/xbps-src ]] || die 'Void submodule is missing; run make bootstrap'
actual=$(git -C "$FDS_VOID" rev-parse HEAD)
[[ -f $FDS_VOID_SOURCE/xbps-src ]] || die 'Void submodule is missing; run make bootstrap'
actual=$(git -C "$FDS_VOID_SOURCE" rev-parse HEAD)
[[ $actual == "$pin" ]] || die "Void checkout mismatch: expected $pin, found $actual"
git -C "$FDS_VOID" diff --quiet HEAD -- || die 'Tracked Void files were changed; keep FDS changes in packages/'
git -C "$FDS_VOID_SOURCE" diff --quiet HEAD -- || die 'Tracked Void files were changed; keep FDS changes in packages/'
export SOURCE_DATE_EPOCH
SOURCE_DATE_EPOCH=$(git -C "$FDS_VOID" show -s --format=%ct HEAD)
SOURCE_DATE_EPOCH=$(git -C "$FDS_VOID_SOURCE" show -s --format=%ct HEAD)
}
check_void_pin() {
check_void_source
[[ -z $(git -C "$FDS_VOID_SOURCE" status --porcelain --untracked-files=all) ]] || die 'Upstream Void submodule is dirty; run make bootstrap to migrate known build copies'
[[ -f $FDS_VOID/xbps-src ]] || die 'Void build checkout is missing; run make bootstrap'
[[ $(git -C "$FDS_VOID" rev-parse HEAD) == "$(cat "$FDS_ROOT/VOID_PACKAGES_COMMIT")" ]] || die 'Void build checkout pin changed; run make bootstrap'
git -C "$FDS_VOID" diff --quiet HEAD -- || die 'Tracked build-checkout files changed; keep FDS sources in packages/'
}
# Keep the upstream entry point unchanged. Offline builds retain normal
+2 -1
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env bash
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
[[ $# == 0 ]] || die 'Usage: tools/prepare-void'
"$FDS_ROOT/tools/prepare-void-workspace"
check_void_pin
# xbps-src reads this ignored local configuration instead of user-global settings.
if [[ -f $FDS_VOID/etc/conf ]] && ! cmp -s "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"; then
die 'vendor/void-packages/etc/conf differs from config/xbps-src.conf; reconcile it explicitly'
die '.host/void-packages/etc/conf differs from config/xbps-src.conf; reconcile it explicitly'
fi
cp -- "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"
# Only directories containing a template are active overlays.
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Prepare a writable Void build checkout outside the upstream submodule."""
import fcntl
import hashlib
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
def git(directory, *arguments):
return subprocess.check_output(['git', '-C', str(directory), *arguments], text=True).strip()
def inventory(root):
result = {}
for path in sorted(root.rglob('*')):
name = path.relative_to(root).as_posix()
if path.is_symlink():
result[name] = ('link', os.readlink(path))
elif path.is_file():
with path.open('rb') as stream:
result[name] = ('file', path.stat().st_mode & 0o777, hashlib.file_digest(stream, 'sha256').hexdigest())
elif path.is_dir():
result[name] = ('directory',)
else:
raise ValueError(f'Unexpected special source file: {path}')
return result
def prepare(project):
source = project / 'vendor/void-packages'
workspace = project / '.host/void-packages'
pin = (project / 'VOID_PACKAGES_COMMIT').read_text().strip()
if not re.fullmatch('[0-9a-f]{40}', pin) or git(source, 'rev-parse', 'HEAD') != pin:
raise ValueError('Upstream Void checkout differs from VOID_PACKAGES_COMMIT')
if git(source, 'diff', 'HEAD', '--'):
raise ValueError('Upstream Void has staged or tracked changes; preserve them outside the submodule first')
if source.is_symlink() or workspace.is_symlink():
raise ValueError('Void source and build checkout must be real directories')
# Migrate only known generated copies after comparing every entry. Unknown
# or independently edited source files are never deleted or overwritten.
overlays = {path.parent.name: path.parent for path in (project / 'packages').glob('*/template')}
for name in ('hello', 'report'):
example = project / f'examples/software/{name}/void'
if (example / 'template').is_file():
overlays[f'fds-demo-{name}'] = example
moves = []
duplicates = []
known = set()
for package, original in overlays.items():
old = source / 'srcpkgs' / package
if not old.exists() and not old.is_symlink():
continue
if git(source, 'ls-tree', 'HEAD', f'srcpkgs/{package}'):
raise ValueError(f'FDS overlay collides with upstream package: {package}')
if old.is_symlink() or not old.is_dir() or inventory(old) != inventory(original):
raise ValueError(f'Preserve independent edits in {old} before migrating it')
known.add(f'srcpkgs/{package}/')
new = workspace / 'srcpkgs' / package
if new.exists() or new.is_symlink():
if new.is_symlink() or not new.is_dir() or inventory(new) != inventory(original):
raise ValueError(f'Conflicting generated overlay: {new}')
duplicates.append(old)
else:
moves.append((old, new))
for name in git(source, 'ls-files', '--others', '--exclude-standard').splitlines():
if not any(name.startswith(prefix) for prefix in known):
raise ValueError(f'Untracked upstream file must be moved outside the submodule: {name}')
for old in [source / 'hostdir', *sorted(source.glob('masterdir-*')), source / 'etc/conf']:
if not old.exists() and not old.is_symlink():
continue
new = workspace / old.relative_to(source)
if old.is_symlink() or new.exists() or new.is_symlink():
raise ValueError(f'Cannot migrate {old}: symlink or existing destination {new}; reconcile explicitly')
if old == source / 'etc/conf' and old.read_bytes() != (project / 'config/xbps-src.conf').read_bytes():
raise ValueError('Old Void configuration differs from config/xbps-src.conf; reconcile explicitly')
moves.append((old, new))
if not workspace.exists():
# Independent objects keep this cache usable without Git alternates or
# hardlinks back into the submodule. No network access is needed.
with tempfile.TemporaryDirectory(prefix='void-checkout.', dir=workspace.parent) as temporary:
checkout = Path(temporary) / 'checkout'
subprocess.run(['git', 'clone', '--quiet', '--no-hardlinks', '--no-checkout', str(source), str(checkout)], check=True)
subprocess.run(['git', '-C', str(checkout), 'checkout', '--quiet', '--detach', pin], check=True)
subprocess.run(['git', '-C', str(checkout), 'remote', 'remove', 'origin'], check=True)
checkout.rename(workspace)
else:
if not (workspace / '.git').is_dir() or git(workspace, 'diff', 'HEAD', '--'):
raise ValueError('Build checkout has tracked changes; preserve them before preparing it')
if git(workspace, 'rev-parse', 'HEAD') != pin:
subprocess.run(['git', '-C', str(workspace), 'fetch', '--quiet', '--update-shallow', str(source), pin], check=True)
# Git refuses an update that would overwrite local files.
subprocess.run(['git', '-C', str(workspace), 'checkout', '--quiet', '--detach', pin], check=True)
for old, new in moves:
new.parent.mkdir(parents=True, exist_ok=True)
old.rename(new)
print(f'Moved {old.relative_to(project)} to {new.relative_to(project)}', flush=True)
for old in duplicates:
shutil.rmtree(old)
if git(source, 'status', '--porcelain', '--untracked-files=all'):
raise ValueError('Upstream submodule is not clean after preparation')
print('PASS: clean upstream reference; writable build checkout in .host/void-packages', flush=True)
def main():
project = Path(__file__).resolve().parents[1]
if len(sys.argv) != 1:
sys.exit('Usage: tools/prepare-void-workspace')
try:
(project / '.host').mkdir(exist_ok=True)
with (project / '.host/void-workspace.lock').open('a') as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
prepare(project)
except (OSError, ValueError, subprocess.CalledProcessError) as error:
sys.exit(f'ERROR: {error}')
if __name__ == '__main__':
main()
+1 -1
View File
@@ -42,7 +42,7 @@ else
printf 'SKIP: ARM execution (optional qemu-aarch64 not installed); ELF verification passed\n'
fi
XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" -l >out/manifests/void-build-packages.txt
find vendor/void-packages/hostdir -type f -name '*.xbps' -print0 \
find "$FDS_VOID/hostdir" -type f -name '*.xbps' -print0 \
| sort -z | xargs -0 -r sha256sum >out/manifests/void-package-inputs.sha256
sha256sum "$package" out/fds-smoketest >out/manifests/artifacts.sha256
printf 'PASS: M0 smoke test complete\n'