From 0d3efeb456117b87f256e3bfaabd5abbace67ab4 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 25 Jul 2026 14:14:34 +0900 Subject: [PATCH] internal/rosa: remove xz utils hack This replaces all xz invocations with the native decompressor. This also significantly cleans up the Gentoo stage3 bootstrap path. Signed-off-by: Ophestra --- internal/rosa/busybox.go | 124 ----------------------------------- internal/rosa/gentoo.go | 104 +++++++++++++++++++++++++++++ internal/rosa/llvm_test.go | 2 +- internal/rosa/package/gnu.az | 6 +- internal/rosa/rosa.go | 56 ++++------------ internal/rosa/state.go | 15 +++-- 6 files changed, 131 insertions(+), 176 deletions(-) delete mode 100644 internal/rosa/busybox.go create mode 100644 internal/rosa/gentoo.go diff --git a/internal/rosa/busybox.go b/internal/rosa/busybox.go deleted file mode 100644 index ae900a04..00000000 --- a/internal/rosa/busybox.go +++ /dev/null @@ -1,124 +0,0 @@ -package rosa - -import ( - "fmt" - "io" - "net/http" - "os" - "time" - - "hakurei.app/fhs" - "hakurei.app/internal/pkg" -) - -// busyboxBin is a busybox binary distribution installed under bin/busybox. -type busyboxBin struct { - // Underlying busybox binary. - bin pkg.FileArtifact -} - -// Kind returns the hardcoded [pkg.Kind] value. -func (a busyboxBin) Kind() pkg.Kind { return kindBusyboxBin } - -// Params is a noop. -func (a busyboxBin) Params(*pkg.IContext) {} - -// IsExclusive returns false: Cure performs a trivial filesystem write. -func (busyboxBin) IsExclusive() bool { return false } - -// Inputs returns the underlying busybox [pkg.FileArtifact]. -func (a busyboxBin) Inputs() []pkg.Artifact { - return []pkg.Artifact{a.bin} -} - -func init() { - pkg.Register(kindBusyboxBin, func(r *pkg.IRReader) pkg.Artifact { - a := busyboxBin{r.Next().(pkg.FileArtifact)} - if _, ok := r.Finalise(); ok { - panic(pkg.ErrUnexpectedChecksum) - } - return a - }) -} - -// String returns the reporting name of the underlying file prefixed with expand. -func (a busyboxBin) String() string { - return "expand-" + a.bin.(fmt.Stringer).String() -} - -// Cure installs the underlying busybox [pkg.File] to bin/busybox. -func (a busyboxBin) Cure(t *pkg.TContext) (err error) { - var r io.ReadCloser - if r, err = t.Open(a.bin); err != nil { - return - } - defer func() { - closeErr := r.Close() - if err == nil { - err = closeErr - } - }() - - binDir := t.GetWorkDir().Append("bin") - if err = os.MkdirAll(binDir.String(), 0700); err != nil { - return - } - - var w *os.File - if w, err = os.OpenFile( - binDir.Append("busybox").String(), - os.O_WRONLY|os.O_CREATE|os.O_EXCL, - 0500, - ); err != nil { - return - } - defer func() { - closeErr := w.Close() - if err == nil { - err = closeErr - } - }() - - _, err = io.Copy(w, r) - return -} - -// newBusyboxBin returns a [pkg.Artifact] containing a busybox installation from -// the https://busybox.net/downloads/binaries/ binary release. -func (s *S) newBusyboxBin() pkg.Artifact { - var version, url, checksum string - switch s.arch { - case "amd64": - version = "1.35.0" - url = "https://busybox.net/downloads/binaries/" + - version + "-" + s.linuxArch() + "-linux-musl/busybox" - checksum = "L7OBIsPu9enNHn7FqpBT1kOg_mCLNmetSeNMA3i4Y60Z5jTgnlX3qX3zcQtLx5AB" - case "arm64": - version = "1.31.0" - url = "https://busybox.net/downloads/binaries/" + - version + "-defconfig-multiarch-musl/busybox-armv8l" - checksum = "npJjBO7iwhjW6Kx2aXeSxf8kXhVgTCDChOZTTsI8ZfFfa3tbsklxRiidZQdrVERg" - - default: - panic("unsupported target " + s.arch) - } - - return pkg.NewExec( - "busybox-bin-"+version, s.arch, nil, pkg.ExecTimeoutMax, false, false, - fhs.AbsRoot, []string{ - "PATH=/system/bin", - }, - AbsSystem.Append("bin", "busybox"), - []string{"hush", "-c", "" + - "busybox mkdir -p /work/system/bin/ && " + - "busybox cp /system/bin/busybox /work/system/bin/ && " + - "busybox --install -s /work/system/bin/"}, - pkg.Path(AbsSystem, true, busyboxBin{pkg.NewHTTPGet( - &http.Client{Transport: &http.Transport{ - // busybox website is really slow to respond - TLSHandshakeTimeout: 2 * time.Minute, - }}, url, - mustDecode(checksum), - )}), - ) -} diff --git a/internal/rosa/gentoo.go b/internal/rosa/gentoo.go new file mode 100644 index 00000000..a0a8e525 --- /dev/null +++ b/internal/rosa/gentoo.go @@ -0,0 +1,104 @@ +package rosa + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + + "hakurei.app/internal/pkg" +) + +// gentooOverlay are symlinks on top of a Gentoo LLVM stage3 tarball for +// compatibility with the Rosa OS stage0 distribution. +type gentooOverlay struct{ stage3 pkg.Artifact } + +// Kind returns the hardcoded [pkg.Kind] value. +func (a gentooOverlay) Kind() pkg.Kind { return kindGentooOverlay } + +// Params is a noop. +func (a gentooOverlay) Params(*pkg.IContext) {} + +// IsExclusive returns false: Cure performs a trivial filesystem write. +func (gentooOverlay) IsExclusive() bool { return false } + +// Inputs returns the underlying Gentoo stage3. +func (a gentooOverlay) Inputs() []pkg.Artifact { + return []pkg.Artifact{a.stage3} +} + +func init() { + pkg.Register(kindGentooOverlay, func(r *pkg.IRReader) pkg.Artifact { + a := gentooOverlay{r.Next()} + if _, ok := r.Finalise(); ok { + panic(pkg.ErrUnexpectedChecksum) + } + return a + }) +} + +// String returns a hardcoded name. +func (a gentooOverlay) String() string { return "gentoo-overlay" } + +// Revision returns a hardcoded value incremented on layout changes. +func (a gentooOverlay) Revision() uint64 { return 0 } + +// Cure installs the overlay. +func (a gentooOverlay) Cure(f *pkg.FContext) (err error) { + pathname, _ := f.GetArtifact(a.stage3) + var stage3 *os.Root + if stage3, err = os.OpenRoot(pathname.String()); err != nil { + return err + } + defer func() { + closeErr := stage3.Close() + if err == nil { + err = closeErr + } + }() + fsys := stage3.FS() + + system := f.GetWorkDir().Append("system") + bin := system.Append("bin") + if err = os.MkdirAll(bin.String(), 0700); err != nil { + return + } + + linknames := []string{ + "../../bin/sh", + } + + var dents []os.DirEntry + + const llvm = "usr/lib/llvm" + dents, err = fs.ReadDir(fsys, llvm) + if err != nil { + return + } + if len(dents) != 1 { + return errors.New("stage3 /" + llvm + " contains more than one entry") + } + + llvmBin := filepath.Join(llvm, dents[0].Name(), "bin") + dents, err = fs.ReadDir(fsys, llvmBin) + if err != nil { + return + } + for _, dent := range dents { + linknames = append(linknames, filepath.Join( + "../..", + llvmBin, + dent.Name(), + )) + } + + for _, linkname := range linknames { + if err = os.Symlink( + linkname, + bin.Append(filepath.Base(linkname)).String(), + ); err != nil { + return + } + } + return +} diff --git a/internal/rosa/llvm_test.go b/internal/rosa/llvm_test.go index af5f6530..2b1d2e7d 100644 --- a/internal/rosa/llvm_test.go +++ b/internal/rosa/llvm_test.go @@ -8,7 +8,7 @@ import ( ) func TestLLVMInputs(t *testing.T) { - const wantInputCount = 473 + const wantInputCount = 470 _, llvm := rosa.Native().Std().MustLoad(rosa.H("llvm")) var n int diff --git a/internal/rosa/package/gnu.az b/internal/rosa/package/gnu.az index d1da6b8a..7cfcc726 100644 --- a/internal/rosa/package/gnu.az +++ b/internal/rosa/package/gnu.az @@ -292,11 +292,14 @@ package findutils { anitya = 812; version# = "4.11.0"; - source = remoteFile { + source = remoteTar { url = "https://ftpmirror.gnu.org/gnu/findutils/findutils-"+version+".tar.xz"; + compress = xz; checksum = "X-qsv9aUoBL583q-4iZcBI3_7Ufd3fSIqFmNLXWdEpUihlRfMpGGyAUt8SAvDKae"; }; + writable = true; + chmod = true; early = ` echo '#!/bin/sh' > gnulib-tests/test-c32ispunct.sh echo 'int main(){return 0;}' > gnulib-tests/test-fstatat.c @@ -307,7 +310,6 @@ echo 'int main(){return 0;}' > tests/xargs/test-sigusr.c inputs = [ diffutils, - xz, sed, ]; } diff --git a/internal/rosa/rosa.go b/internal/rosa/rosa.go index de4229ea..456f82e7 100644 --- a/internal/rosa/rosa.go +++ b/internal/rosa/rosa.go @@ -26,8 +26,8 @@ const ( // kindEtc is the kind of [pkg.Artifact] of cureEtc. kindEtc = iota + pkg.KindCustomOffset - // kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin. - kindBusyboxBin + // kindGentooOverlay is the kind of [pkg.Artifact] of gentooOverlay. + kindGentooOverlay ) // mustDecode is like [pkg.MustDecode], but replaces the zero value and prints @@ -97,15 +97,9 @@ func (s *S) earlyLDFLAGS(static bool) string { } const ( - // _stageBusybox denotes a busybox installation from the busyboxBin - // binary distribution. This is defined as a [Stage] to make use of the - // toolchain abstractions to preprocess stageGentoo and is not a real, - // functioning toolchain. It does not contain any compilers. - _stageBusybox Stage = iota - // stageGentoo denotes the toolchain in a Gentoo stage3 tarball. Special // care must be taken to compile correctly against this stage. - stageGentoo + stageGentoo Stage = iota // stageIntermediateGentoo is like stageIntermediate, but compiled against // stageGentoo. @@ -239,11 +233,6 @@ func (t Toolchain) New( var support []pkg.Artifact switch t.stage { - case _stageBusybox: - name += "-early" - support = slices.Concat([]pkg.Artifact{t.newBusyboxBin()}, extra) - env = fixupEnviron(env, nil, "/system/bin") - case stageGentoo, stageEarly: name += "-boot" support = append(support, extra...) @@ -251,22 +240,17 @@ func (t Toolchain) New( if t.stage == stageEarly { _, a := t.MustLoad(_stage0Dist) support = append(support, a) + } else if t.gentooStage3 == nil { + panic(os.ErrInvalid) } else { - support = append(support, t.S.New(_stageBusybox).New("gentoo", 0, nil, nil, nil, ` -tar -C /work -xf /usr/src/stage3.tar.xz -rm -rf /work/dev/ /work/proc/ -ln -vs ../usr/bin /work/bin -mkdir -vp /work/system/bin -(cd /work/system/bin && ln -vs \ - ../../bin/sh \ - ../../usr/lib/llvm/*/bin/* \ - .) -`, pkg.Path(AbsUsrSrc.Append("stage3.tar.xz"), false, - pkg.NewHTTPGet( - nil, t.gentooStage3, - t.gentooStage3Checksum, - ), - ))) + support = append(support, + t.gentooStage3, + gentooOverlay{t.gentooStage3}, + ) + env = append(env, + "CC=clang", + "CXX=clang++", + ) } env = fixupEnviron(env, []string{ EnvTriple + "=" + t.triple(), @@ -516,20 +500,6 @@ func (t Toolchain) NewPackage( sourceSuffix string ) - if _, ok := source.(pkg.FileArtifact); ok { - if attr.Writable || attr.Chmod || - wantsChmod || wantsWrite || - len(attr.Patches) > 0 { - panic("source processing requested on a xz-compressed tarball") - } - - sourceSuffix = ".tar.xz" - scriptEarly += ` -tar -C /usr/src/ -xf '/usr/src/` + name + `.tar.xz' -mv '/usr/src/` + rn + `' '/usr/src/` + name + `' -` - } - dir, create := helper.wantsDir() helperScriptEarly := helper.scriptEarly() if attr.EnterSource || diff --git a/internal/rosa/state.go b/internal/rosa/state.go index 36fe9451..516efd81 100644 --- a/internal/rosa/state.go +++ b/internal/rosa/state.go @@ -213,10 +213,8 @@ type S struct { // Cached [pkg.Artifact]. c [_stageEnd]sync.Map - // URL of a Gentoo stage3 tarball. - gentooStage3 string - // Expected checksum of gentooStage3. - gentooStage3Checksum pkg.Checksum + // Unpacked Gentoo LLVM stage3. + gentooStage3 pkg.Artifact } // Clone returns a copy of s. @@ -485,6 +483,7 @@ func (s *S) getFrame() azalea.Frame { k("gzip"): pkg.Gzip + compressOffset, k("bzip2"): pkg.Bzip2 + compressOffset, k("zstd"): pkg.Zstd + compressOffset, + k("xz"): pkg.XZ + compressOffset, } s.frame.Val = map[unique.Handle[azalea.Ident]]any{ @@ -803,6 +802,7 @@ func (s *S) getFrame() azalea.Frame { k("gzip"): uint32(pkg.Gzip), k("bzip2"): uint32(pkg.Bzip2), k("zstd"): uint32(pkg.Zstd), + k("xz"): uint32(pkg.XZ), }}, // high-level helpers @@ -1372,12 +1372,15 @@ func (s *S) SetSource(fsys fs.FS) error { // SetGentooStage3 sets the Gentoo stage3 tarball url and checksum. It panics // if given zero values or if these values have already been set. func (s *S) SetGentooStage3(url string, checksum pkg.Checksum) { - if s.gentooStage3 != "" { + if s.gentooStage3 != nil { panic(errors.New("attempting to set Gentoo stage3 url twice")) } if url == "" { panic(errors.New("attempting to set Gentoo stage3 url to the zero value")) } - s.gentooStage3, s.gentooStage3Checksum = url, checksum + s.gentooStage3 = pkg.NewTar(pkg.NewDecompress( + pkg.NewHTTPGet(nil, url, checksum), + pkg.XZ, + )) s.DropCaches(s.Arch(), s.Flags()) }