From 9e9a0b59fcafd64c523dbccaeb34c0a4e3862935 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Sun, 5 Apr 2026 11:56:06 +0200 Subject: [PATCH] system: validate development environment overlay --- docs/GUIX_DIFFERENCES.md | 20 +- docs/PROGRESS.md | 53 ++--- ...phase20-development-environment-freebsd.md | 160 +++++++++++++ docs/system-deployment-workflow.md | 28 ++- modules/fruix/system/freebsd.scm | 1 + modules/fruix/system/freebsd/media.scm | 33 ++- modules/fruix/system/freebsd/model.scm | 22 +- modules/fruix/system/freebsd/render.scm | 38 +++ ...hase20-development-operating-system.scm.in | 73 ++++++ ...n-phase20-development-environment-xcpng.sh | 220 ++++++++++++++++++ 10 files changed, 612 insertions(+), 36 deletions(-) create mode 100644 docs/reports/phase20-development-environment-freebsd.md create mode 100644 tests/system/phase20-development-operating-system.scm.in create mode 100755 tests/system/run-phase20-development-environment-xcpng.sh diff --git a/docs/GUIX_DIFFERENCES.md b/docs/GUIX_DIFFERENCES.md index cdfb19d..d2bb7b8 100644 --- a/docs/GUIX_DIFFERENCES.md +++ b/docs/GUIX_DIFFERENCES.md @@ -1,6 +1,6 @@ # Fruix differences for Guix sysadmins -Date: 2026-04-04 +Date: 2026-04-05 This document is aimed at operators who already know Guix well and want a quick map of where Fruix behaves similarly and where it intentionally differs. @@ -242,6 +242,24 @@ For Guix-familiar operators, the practical takeaway is: - do **not** assume Fruix store prefixes are byte-for-byte comparable to Guix/Nix ones - expect Fruix to prefer a simpler, centralized naming policy unless exact Guix/Nix behavior becomes necessary later +## 8. Fruix can expose a separate in-system development profile overlay + +For the validated Phase 20.1 path, Fruix can now expose development tooling separately from the main runtime profile. + +On those systems, Fruix exposes: + +- `/run/current-system/development-profile` +- `/run/current-development` +- `/usr/local/bin/fruix-development-environment` + +The intent is: + +- keep the main runtime profile lean +- expose headers, `usr/share/mk`, and selected toolchain commands explicitly +- avoid treating a development-heavy system image as the default runtime shape + +Compared with Guix, this is conceptually similar to keeping development-oriented state separate from the main runtime identity, but Fruix currently expresses it as a system-attached development overlay rather than through Guix's broader profile/tooling model. + ## Where Fruix is intentionally trying to improve on Guix's representation Fruix is not trying to improve on Guix's core semantics. Guix already got those right. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 3ab51e3..44c6c9b 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -34,6 +34,10 @@ Fruix currently has: - `fruix system status` - `fruix system switch` - `fruix system rollback` +- a validated separate in-system development environment overlay via: + - `/run/current-system/development-profile` + - `/run/current-development` + - `/usr/local/bin/fruix-development-environment` Validated boot modes still are: @@ -46,44 +50,37 @@ The validated Phase 18 installation work currently uses: ## Latest completed achievement -### 2026-04-04 — Phase 19.3 completed +### 2026-04-05 — Phase 20.1 completed -Fruix now has a validated installed-system operator workflow for switching to a staged candidate generation and rolling back to the recorded previous generation. +Fruix now has a validated real-VM path where a booted Fruix-managed FreeBSD system exposes a separate development environment for native base work without collapsing the runtime/development split. Highlights: -- installed systems now ship an in-guest Fruix deployment helper at: - - `/usr/local/bin/fruix` -- validated in-guest command surface: - - `fruix system status` - - `fruix system switch /frx/store/...-fruix-system-...` - - `fruix system rollback` -- switching now records explicit rollback state under: - - `/var/lib/fruix/system/rollback` - - `/var/lib/fruix/system/rollback-generation` -- switching now records explicit rollback GC roots under: - - `/frx/var/fruix/gcroots/rollback-system` -- the validated installed-system workflow now supports: - - stage candidate closure in `/frx/store` - - switch to generation 2 - - reboot into the candidate - - rollback to generation 1 - - reboot into the restored current system +- operating-system declarations now support: + - `#:development-packages` +- system closures can now carry a separate development profile at: + - `/run/current-system/development-profile` + - `/run/current-development` +- opt-in systems now ship an in-guest helper at: + - `/usr/local/bin/fruix-development-environment` +- the validated Phase 20.1 guest path exposes: + - native headers + - `usr/share/mk` for `bsd.*.mk` + - Clang toolchain commands such as `cc`, `c++`, `ar`, `ranlib`, and `nm` +- the validated guest workflow now supports: + - `eval "$(/usr/local/bin/fruix-development-environment)"` + - direct compilation with the Fruix-provided toolchain + - a simple `bsd.prog.mk` build on the running Fruix guest Validation: -- `PASS phase19-installed-system-rollback-qemu` -- regression re-checks: - - `PASS phase19-generation-layout-qemu` - - `PASS phase18-installer-iso` +- `PASS phase20-development-environment-xcpng` Reports: - `docs/system-deployment-workflow.md` - `docs/GUIX_DIFFERENCES.md` -- `docs/reports/phase19-deployment-workflow-freebsd.md` -- `docs/reports/phase19-generation-layout-freebsd.md` -- `docs/reports/phase19-installed-system-rollback-freebsd.md` +- `docs/reports/phase20-development-environment-freebsd.md` ## Recent major milestones @@ -109,6 +106,6 @@ Reports: Per `docs/PLAN_4.md`, the next planned step is: -- **Phase 20.1** — validate a Fruix-managed development environment for native FreeBSD base work +- **Phase 20.2** — run host-initiated native base builds inside a Fruix-managed environment -Phase 19.3 is now complete: Fruix validates installed-system generation switching and rollback through the intended operator-facing workflow. +Phase 20.1 is now complete: Fruix validates a separate in-system development environment for native FreeBSD base work on the approved real XCP-ng path. diff --git a/docs/reports/phase20-development-environment-freebsd.md b/docs/reports/phase20-development-environment-freebsd.md new file mode 100644 index 0000000..c282450 --- /dev/null +++ b/docs/reports/phase20-development-environment-freebsd.md @@ -0,0 +1,160 @@ +# Phase 20.1: Fruix-managed development environment for native FreeBSD base work + +Date: 2026-04-05 + +## Goal + +Validate that a booted Fruix-managed FreeBSD system can expose a usable development environment for deeper native base work without collapsing the runtime/development boundary back into one broad profile. + +This step explicitly builds on the Phase 14 split between: + +- native runtime/boot artifacts for the running system +- separate development-facing artifacts such as headers and toolchain pieces + +The goal is **not** full self-hosting yet. + +It is to prove that a running Fruix system can expose the tools and paths needed for native FreeBSD build work in a controlled way. + +## Implementation + +### New operating-system field + +`modules/fruix/system/freebsd/model.scm` now supports: + +- `#:development-packages` +- `operating-system-development-packages` + +The default remains empty, so existing systems do not change unless they opt in. + +### Separate development profile inside the system closure + +`modules/fruix/system/freebsd/media.scm` now materializes an additional profile tree when `development-packages` is non-empty: + +- `/frx/store/...-fruix-system-.../development-profile` + +The main runtime tree remains: + +- `/frx/store/...-fruix-system-.../profile` + +This preserves the runtime/development split: + +- runtime stays under `profile` +- development tooling stays under `development-profile` + +The development stores are also now part of the closure references and recorded in `metadata/store-layout.scm`. + +### In-guest development environment helper + +Opt-in systems with development packages now ship: + +- `/usr/local/bin/fruix-development-environment` + +and expose a stable runtime link: + +- `/run/current-development -> /run/current-system/development-profile` + +The helper emits shell exports for the active development profile, including at least: + +- `FRUIX_DEVELOPMENT_PROFILE` +- `FRUIX_DEVELOPMENT_INCLUDE` +- `FRUIX_DEVELOPMENT_SHARE_MK` +- `FRUIX_CC` +- `FRUIX_CXX` +- `FRUIX_AR` +- `FRUIX_RANLIB` +- `FRUIX_NM` +- `FRUIX_BMAKE` +- `CPPFLAGS` +- `MAKEFLAGS` +- `PATH` + +Intended use: + +```sh +eval "$(/usr/local/bin/fruix-development-environment)" +``` + +### Chosen development overlay for this phase + +For the validated Phase 20.1 guest path, the development overlay is intentionally narrow: + +- `freebsd-native-headers` +- `freebsd-clang-toolchain` + +This was chosen deliberately. + +The earlier standalone Phase 14 development-profile package set was designed for broad profile composition, but for a running Fruix system it unnecessarily duplicated runtime pieces already present in the system profile. + +For native base work, the important additions here are: + +- `usr/include` +- `usr/share/mk` +- Clang/binutils-style frontend tools + +while the running system already supplies: + +- base runtime +- `/usr/bin/make` +- system libraries and boot/runtime state + +That keeps the Phase 20.1 environment focused on native base development rather than reintroducing a broad mixed profile. + +## New files + +Added: + +- `tests/system/phase20-development-operating-system.scm.in` +- `tests/system/run-phase20-development-environment-xcpng.sh` + +## Validation + +Passing run: + +- `PASS phase20-development-environment-xcpng` +- workdir: `/tmp/fruix-phase20-development-xcpng` + +Validated on the approved real XCP-ng path: + +- VM `90490f2e-e8fc-4b7a-388e-5c26f0157289` +- VDI `0f1f90d3-48ca-4fa2-91d8-fc6339b95743` + +Representative result: + +```text +closure_path=/frx/store/c0ad43d7ef72323d4270a4f1e96ca1f5cc99566c-fruix-system-fruix-freebsd +development_profile_path=/frx/store/c0ad43d7ef72323d4270a4f1e96ca1f5cc99566c-fruix-system-fruix-freebsd/development-profile +development_profile_guest=/run/current-system/development-profile +cc_version=FreeBSD clang version 19.1.7 (https://github.com/llvm/llvm-project.git llvmorg-19.1.7-0-gcd708029e0b2) +hello_direct=hello-from-direct-dev-env +hello_make=hello-from-make-dev-env +development_environment=ok +``` + +The harness verified all of the following on the real booted Fruix guest: + +- runtime boot/regression still succeeds on XCP-ng +- the closure contains a separate `development-profile` +- runtime `profile` does **not** regain headers or `usr/share/mk` +- `/usr/local/bin/fruix-development-environment` exists and emits the expected exports +- `/run/current-development` points at `/run/current-system/development-profile` +- direct compilation works with the exported Clang toolchain and headers +- a simple native FreeBSD `make` build using `.include ` also succeeds + +## Result + +Phase 20.1 is complete. + +Fruix now validates a real booted FreeBSD system path where: + +- the running system remains lean and runtime-focused +- native development artifacts are exposed separately and explicitly +- the guest can compile code directly with the Fruix-provided toolchain +- the guest can also drive a simple `bsd.prog.mk` build using the exported development environment + +This is enough to say that Fruix can host a controlled native FreeBSD base-development environment, without yet claiming full self-hosting. + +## Next step + +Per `docs/PLAN_4.md`, the next planned step is: + +- **Phase 20.2** — run host-initiated native base builds inside a Fruix-managed environment diff --git a/docs/system-deployment-workflow.md b/docs/system-deployment-workflow.md index cd2eb5c..5bf3ca9 100644 --- a/docs/system-deployment-workflow.md +++ b/docs/system-deployment-workflow.md @@ -1,6 +1,6 @@ # Fruix system deployment workflow -Date: 2026-04-04 +Date: 2026-04-05 ## Purpose @@ -197,6 +197,32 @@ Important current limitation: - `fruix system switch` does **not** yet fetch or copy the candidate closure onto the target for you - it assumes the selected closure is already present in the installed system's `/frx/store` +### In-guest development environment + +Opt-in systems can also expose a separate development overlay under: + +- `/run/current-system/development-profile` +- `/run/current-development` + +Those systems now ship a helper at: + +- `/usr/local/bin/fruix-development-environment` + +Intended use: + +```sh +eval "$(/usr/local/bin/fruix-development-environment)" +``` + +That helper exports a development-oriented environment while keeping the main runtime profile separate. The validated Phase 20.1 path currently uses this to expose at least: + +- native headers under `usr/include` +- FreeBSD `share/mk` files for `bsd.*.mk` +- Clang toolchain commands such as `cc`, `c++`, `ar`, `ranlib`, and `nm` +- `MAKEFLAGS` pointing at the development profile's `usr/share/mk` + +This is the current Fruix-native way to make a running system suitable for controlled native base-development work without merging development content back into the main runtime profile. + ## Deployment patterns ### 1. Build-first workflow diff --git a/modules/fruix/system/freebsd.scm b/modules/fruix/system/freebsd.scm index f30743b..35c9806 100644 --- a/modules/fruix/system/freebsd.scm +++ b/modules/fruix/system/freebsd.scm @@ -31,6 +31,7 @@ operating-system-kernel operating-system-bootloader operating-system-base-packages + operating-system-development-packages operating-system-users operating-system-groups operating-system-file-systems diff --git a/modules/fruix/system/freebsd/media.scm b/modules/fruix/system/freebsd/media.scm index de7115f..271a06a 100644 --- a/modules/fruix/system/freebsd/media.scm +++ b/modules/fruix/system/freebsd/media.scm @@ -79,11 +79,16 @@ (kernel-package (operating-system-kernel os)) (bootloader-package (operating-system-bootloader os)) (base-packages (operating-system-base-packages os)) + (development-packages (operating-system-development-packages os)) (kernel-store (materialize-freebsd-package kernel-package store-dir cache source-cache)) (bootloader-store (materialize-freebsd-package bootloader-package store-dir cache source-cache)) (base-package-stores (map (lambda (package) (materialize-freebsd-package package store-dir cache source-cache)) base-packages)) + (development-package-stores + (map (lambda (package) + (materialize-freebsd-package package store-dir cache source-cache)) + development-packages)) (base-package-pairs (map cons base-packages base-package-stores)) (store-classification (append (list (cons kernel-package kernel-store) @@ -148,6 +153,8 @@ (host-base-stores . ,host-base-stores) (native-base-store-count . ,(length native-base-stores)) (native-base-stores . ,native-base-stores) + (development-package-store-count . ,(length development-package-stores)) + (development-package-stores . ,development-package-stores) (fruix-runtime-store-count . ,(length fruix-runtime-stores)) (fruix-runtime-stores . ,fruix-runtime-stores) (host-base-replacement-order . ,%freebsd-host-staged-replacement-order) @@ -161,7 +168,12 @@ . ,(render-activation-rc-script)) ("usr/local/etc/rc.d/fruix-shepherd" . ,(render-rc-script shepherd-store guile-store guile-extra-store))))) - (references (delete-duplicates (append materialized-source-stores host-base-stores native-base-stores fruix-runtime-stores))) + (references (delete-duplicates + (append materialized-source-stores + host-base-stores + native-base-stores + development-package-stores + fruix-runtime-stores))) (manifest (string-append "closure-spec=\n" (object->string (operating-system-closure-spec os)) @@ -175,7 +187,9 @@ (display-name (string-append "fruix-system-" (operating-system-host-name os))) (closure-path (make-store-path store-dir display-name manifest - #:kind 'operating-system))) + #:kind 'operating-system)) + (development-profile-path (and (not (null? development-package-stores)) + (string-append closure-path "/development-profile")))) (unless (file-exists? closure-path) (mkdir-p closure-path) (mkdir-p (string-append closure-path "/boot/kernel")) @@ -193,6 +207,11 @@ (for-each (lambda (output) (merge-output-into-tree output (string-append closure-path "/profile"))) base-package-stores) + (when development-profile-path + (mkdir-p development-profile-path) + (for-each (lambda (output) + (merge-output-into-tree output development-profile-path)) + development-package-stores)) (for-each (lambda (entry) (write-file (string-append closure-path "/" (car entry)) (cdr entry))) @@ -204,6 +223,8 @@ (chmod (string-append closure-path "/usr/local/etc/rc.d/fruix-shepherd") #o555) (when (file-exists? (string-append closure-path "/usr/local/bin/fruix")) (chmod (string-append closure-path "/usr/local/bin/fruix") #o555)) + (when (file-exists? (string-append closure-path "/usr/local/bin/fruix-development-environment")) + (chmod (string-append closure-path "/usr/local/bin/fruix-development-environment") #o555)) (when (file-exists? (string-append closure-path "/boot/fruix-pid1")) (chmod (string-append closure-path "/boot/fruix-pid1") #o555)) (write-file (string-append closure-path "/parameters.scm") @@ -218,6 +239,8 @@ (guile-extra-store . ,guile-extra-store) (shepherd-store . ,shepherd-store) (base-package-stores . ,base-package-stores) + (development-package-stores . ,development-package-stores) + (development-profile-path . ,development-profile-path) (host-base-stores . ,host-base-stores) (native-base-stores . ,native-base-stores) (fruix-runtime-stores . ,fruix-runtime-stores) @@ -351,6 +374,12 @@ '("kernel" "loader" "loader.efi" "device.hints" "defaults" "lua" "loader.conf")) (symlink-force "/run/current-system/usr/local/bin/fruix" (string-append rootfs "/usr/local/bin/fruix")) + (when (file-exists? (string-append closure-path "/development-profile")) + (symlink-force "/run/current-system/development-profile" + (string-append rootfs "/run/current-development"))) + (when (file-exists? (string-append closure-path "/usr/local/bin/fruix-development-environment")) + (symlink-force "/run/current-system/usr/local/bin/fruix-development-environment" + (string-append rootfs "/usr/local/bin/fruix-development-environment"))) (symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-activate" (string-append rootfs "/usr/local/etc/rc.d/fruix-activate")) (symlink-force "/run/current-system/usr/local/etc/rc.d/fruix-shepherd" diff --git a/modules/fruix/system/freebsd/model.scm b/modules/fruix/system/freebsd/model.scm index 87aaf53..331d041 100644 --- a/modules/fruix/system/freebsd/model.scm +++ b/modules/fruix/system/freebsd/model.scm @@ -33,6 +33,7 @@ operating-system-kernel operating-system-bootloader operating-system-base-packages + operating-system-development-packages operating-system-users operating-system-groups operating-system-file-systems @@ -95,8 +96,8 @@ (make-file-system device mount-point type options needed-for-boot?)) (define-record-type - (make-operating-system host-name freebsd-base kernel bootloader base-packages users groups - file-systems services loader-entries rc-conf-entries + (make-operating-system host-name freebsd-base kernel bootloader base-packages development-packages + users groups file-systems services loader-entries rc-conf-entries init-mode ready-marker root-authorized-keys) operating-system? (host-name operating-system-host-name) @@ -104,6 +105,7 @@ (kernel operating-system-kernel) (bootloader operating-system-bootloader) (base-packages operating-system-base-packages) + (development-packages operating-system-development-packages) (users operating-system-users) (groups operating-system-groups) (file-systems operating-system-file-systems) @@ -120,6 +122,7 @@ (kernel freebsd-kernel) (bootloader freebsd-bootloader) (base-packages %freebsd-system-packages) + (development-packages '()) (users (list (user-account #:name "root" #:uid 0 #:group "wheel" @@ -161,8 +164,8 @@ (init-mode 'freebsd-init+rc.d-shepherd) (ready-marker "/var/lib/fruix/ready") (root-authorized-keys '())) - (make-operating-system host-name freebsd-base kernel bootloader base-packages users groups - file-systems services loader-entries rc-conf-entries + (make-operating-system host-name freebsd-base kernel bootloader base-packages development-packages + users groups file-systems services loader-entries rc-conf-entries init-mode ready-marker root-authorized-keys)) (define default-minimal-operating-system (operating-system)) @@ -231,6 +234,8 @@ (define (validate-operating-system os) (let* ((host-name (operating-system-host-name os)) (base (operating-system-freebsd-base os)) + (base-packages (operating-system-base-packages os)) + (development-packages (operating-system-development-packages os)) (users (operating-system-users os)) (groups (operating-system-groups os)) (file-systems (operating-system-file-systems os)) @@ -242,6 +247,10 @@ (error "operating-system host-name must not be empty")) (unless (freebsd-base? base) (error "operating-system freebsd-base must be a record")) + (unless (every freebsd-package? base-packages) + (error "operating-system base-packages must be a list of records")) + (unless (every freebsd-package? development-packages) + (error "operating-system development-packages must be a list of records")) (validate-freebsd-source (freebsd-base-source base)) (let ((dups (duplicate-elements user-names))) (unless (null? dups) @@ -297,6 +306,9 @@ "activate" "shepherd/init.scm" "usr/local/bin/fruix") + (if (null? (operating-system-development-packages os)) + '() + '("usr/local/bin/fruix-development-environment")) (if (pid1-init-mode? os) '("boot/fruix-pid1") '()) @@ -316,6 +328,8 @@ (bootloader-package . ,(freebsd-package-name (operating-system-bootloader os))) (base-package-count . ,(length (operating-system-base-packages os))) (base-packages . ,(package-names (operating-system-base-packages os))) + (development-package-count . ,(length (operating-system-development-packages os))) + (development-packages . ,(package-names (operating-system-development-packages os))) (user-count . ,(length (operating-system-users os))) (users . ,(map user-account-name (operating-system-users os))) (group-count . ,(length (operating-system-groups os))) diff --git a/modules/fruix/system/freebsd/render.scm b/modules/fruix/system/freebsd/render.scm index 332ad58..7a443d9 100644 --- a/modules/fruix/system/freebsd/render.scm +++ b/modules/fruix/system/freebsd/render.scm @@ -751,6 +751,40 @@ " exit 1\n" " ;;\n" "esac\n")) +(define (render-development-environment-script os) + (string-append + "#!/bin/sh\n" + "set -eu\n" + "profile=/run/current-system/development-profile\n" + "[ -d \"$profile\" ] || {\n" + " echo \"fruix-development-environment: development profile is not available\" >&2\n" + " exit 1\n" + "}\n" + "cat <&2; exit 1; } +[ "$sshd_status" = running ] || { echo "sshd is not running" >&2; exit 1; } +[ "$compat_prefix_shims" = absent ] || { echo "compatibility prefix shims reappeared" >&2; exit 1; } +[ "$guile_module_smoke" = ok ] || { echo "guest Guile module smoke failed" >&2; exit 1; } +case "$activate_log" in + *fruix-activate:done*) : ;; + *) echo "activation log does not show success" >&2; exit 1 ;; +esac + +for path in \ + "$development_profile_path/bin/cc" \ + "$development_profile_path/bin/c++" \ + "$development_profile_path/bin/ar" \ + "$development_profile_path/usr/include/sys/param.h" \ + "$development_profile_path/usr/share/mk/bsd.prog.mk" \ + "$development_env_script" +do + [ -e "$path" ] || { + echo "required development environment path missing: $path" >&2 + exit 1 + } +done +[ ! -e "$runtime_profile_path/include" ] || { echo "runtime profile unexpectedly contains headers" >&2; exit 1; } +[ ! -e "$runtime_profile_path/usr/share/mk" ] || { echo "runtime profile unexpectedly contains /usr/share/mk" >&2; exit 1; } +[ ! -e "$runtime_profile_path/bin/cc" ] || { echo "runtime profile unexpectedly contains cc" >&2; exit 1; } + +ssh_guest() { + ssh -i "$root_ssh_private_key_file" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=5 \ + root@"$guest_ip" "$@" +} + +guest_dev_metadata=$(ssh -i "$root_ssh_private_key_file" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ConnectTimeout=5 \ + root@"$guest_ip" 'sh -s' <<'EOF' +set -eu +[ -x /usr/local/bin/fruix-development-environment ] +[ -L /run/current-development ] +[ "$(readlink /run/current-development)" = "/run/current-system/development-profile" ] +exports=$(/usr/local/bin/fruix-development-environment) +printf '%s\n' "$exports" | grep '^export FRUIX_DEVELOPMENT_PROFILE="/run/current-system/development-profile"$' >/dev/null +printf '%s\n' "$exports" | grep '^export MAKEFLAGS="-m /run/current-system/development-profile/usr/share/mk"$' >/dev/null +eval "$exports" +[ -d "$FRUIX_DEVELOPMENT_PROFILE" ] +[ -x "$FRUIX_CC" ] +[ -x "$FRUIX_CXX" ] +[ -x "$FRUIX_AR" ] +[ -f "$FRUIX_DEVELOPMENT_INCLUDE/sys/param.h" ] +[ -f "$FRUIX_DEVELOPMENT_SHARE_MK/bsd.prog.mk" ] +cc_version=$($FRUIX_CC --version | awk 'NR==1 { print; exit }') +tmp=/tmp/fruix-phase20-development-env +rm -rf "$tmp" +mkdir -p "$tmp/direct" "$tmp/mk" +cat > "$tmp/direct/hello.c" <<'EOS' +#include +int main(void){puts("hello-from-direct-dev-env");return 0;} +EOS +$FRUIX_CC $CPPFLAGS $LDFLAGS "$tmp/direct/hello.c" -o "$tmp/direct/hello" +hello_direct=$($tmp/direct/hello) +cat > "$tmp/mk/hello.c" <<'EOS' +#include +int main(void){puts("hello-from-make-dev-env");return 0;} +EOS +cat > "$tmp/mk/Makefile" <<'EOS' +PROG=hello +SRCS=hello.c +NO_MAN=yes +.include +EOS +cat > "$tmp/mk/hello.1" <<'EOS' +.Dd April 5, 2026 +.Dt HELLO 1 +.Sh NAME +.Nm hello +.Nd phase20 development environment smoke binary +EOS +cd "$tmp/mk" +make clean >/dev/null 2>&1 || true +make > "$tmp/mk/make.log" 2>&1 +hello_make=$(./hello) +make_log_tail=$(tail -n 20 "$tmp/mk/make.log" | tr '\n' ' ') +exports_flat=$(printf '%s' "$exports" | tr '\n' ' ') +printf 'development_profile=%s\n' "$FRUIX_DEVELOPMENT_PROFILE" +printf 'cc_version=%s\n' "$cc_version" +printf 'hello_direct=%s\n' "$hello_direct" +printf 'hello_make=%s\n' "$hello_make" +printf 'exports=%s\n' "$exports_flat" +printf 'make_log_tail=%s\n' "$make_log_tail" +EOF +) + +development_profile=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^development_profile=//p') +cc_version=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^cc_version=//p') +hello_direct=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^hello_direct=//p') +hello_make=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^hello_make=//p') +development_exports=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^exports=//p') +make_log_tail=$(printf '%s\n' "$guest_dev_metadata" | sed -n 's/^make_log_tail=//p') + +[ "$development_profile" = "/run/current-system/development-profile" ] || { + echo "unexpected guest development profile path: $development_profile" >&2 + exit 1 +} +case "$cc_version" in + *"FreeBSD clang version"*) : ;; + *) echo "unexpected cc version output: $cc_version" >&2; exit 1 ;; +esac +[ "$hello_direct" = hello-from-direct-dev-env ] || { + echo "unexpected direct compile output: $hello_direct" >&2 + exit 1 +} +[ "$hello_make" = hello-from-make-dev-env ] || { + echo "unexpected make-based compile output: $hello_make" >&2 + exit 1 +} +case "$development_exports" in + *'export FRUIX_CC="/run/current-system/development-profile/bin/cc"'*) : ;; + *) echo "development environment exports do not include FRUIX_CC" >&2; exit 1 ;; +esac + +cat >"$metadata_file" <