// Package rosa provides Rosa OS toolchain artifacts and miscellaneous software. package rosa import ( "log" "runtime" "slices" "strconv" "strings" "hakurei.app/container/fhs" "hakurei.app/internal/pkg" ) const ( // kindEtc is the kind of [pkg.Artifact] of cureEtc. kindEtc = iota + pkg.KindCustomOffset // kindBusyboxBin is the kind of [pkg.Artifact] of busyboxBin. kindBusyboxBin ) // mustDecode is like [pkg.MustDecode], but replaces the zero value and prints // a warning. func mustDecode(s string) pkg.Checksum { var fallback = pkg.Checksum{} if s == "" { log.Println( "falling back to", pkg.Encode(fallback), "for unpopulated checksum", ) return fallback } return pkg.MustDecode(s) } var ( // AbsUsrSrc is the conventional directory to place source code under. AbsUsrSrc = fhs.AbsUsr.Append("src") // AbsSystem is the Rosa OS installation prefix. AbsSystem = fhs.AbsRoot.Append("system") ) // linuxArch returns the architecture name used by linux corresponding to // [runtime.GOARCH]. func linuxArch() string { switch runtime.GOARCH { case "amd64": return "x86_64" default: panic("unsupported target " + runtime.GOARCH) } } // triplet returns the Rosa OS host triple corresponding to [runtime.GOARCH]. func triplet() string { return linuxArch() + "-rosa-linux-musl" } const ( // EnvTriplet holds the return value of triplet. EnvTriplet = "ROSA_TRIPLE" ) // earlyLDFLAGS returns LDFLAGS corresponding to triplet. func earlyLDFLAGS(static bool) string { s := "-fuse-ld=lld " + "-L/system/lib -Wl,-rpath=/system/lib " + "-L/system/lib/" + triplet() + " " + "-Wl,-rpath=/system/lib/" + triplet() + " " + "-rtlib=compiler-rt " + "-unwindlib=libunwind " + "-Wl,--as-needed" if !static { s += " -Wl,--dynamic-linker=/system/bin/linker" } return s } // earlyCFLAGS is reference CFLAGS for the stage3 toolchain. const earlyCFLAGS = "-Qunused-arguments " + "-isystem/system/include" // earlyCXXFLAGS returns reference CXXFLAGS for the stage3 toolchain // corresponding to [runtime.GOARCH]. func earlyCXXFLAGS() string { return "--start-no-unused-arguments " + "-stdlib=libc++ " + "--end-no-unused-arguments " + "-isystem/system/include/c++/v1 " + "-isystem/system/include/" + triplet() + "/c++/v1 " + "-isystem/system/include " } // Toolchain denotes the infrastructure to compile a [pkg.Artifact] on. type Toolchain uintptr const ( // toolchainBusybox denotes a busybox installation from the busyboxBin // binary distribution. This is for decompressing unsupported formats. toolchainBusybox Toolchain = iota // toolchainStage3 denotes the Gentoo stage3 toolchain. Special care must be // taken to compile correctly against this toolchain. toolchainStage3 // toolchainIntermediate denotes the intermediate toolchain compiled against // toolchainStage3. This toolchain should be functionally identical to [Std] // and is used to bootstrap [Std]. toolchainIntermediate // Std denotes the standard Rosa OS toolchain. Std // _toolchainEnd is the total number of toolchains available and does not // denote a valid toolchain. _toolchainEnd ) // stage3Concat concatenates s and values. If the current toolchain is // toolchainStage3, stage3Concat returns s as is. func stage3Concat[S ~[]E, E any](t Toolchain, s S, values ...E) S { if t == toolchainStage3 { return s } return slices.Concat(s, values) } // stage3ExclConcat concatenates s and values. If the current toolchain is not // toolchainStage3, stage3ExclConcat returns s as is. func stage3ExclConcat[S ~[]E, E any](t Toolchain, s S, values ...E) S { if t == toolchainStage3 { return slices.Concat(s, values) } return s } // lastIndexFunc is like [strings.LastIndexFunc] but for [slices]. func lastIndexFunc[S ~[]E, E any](s S, f func(E) bool) (i int) { if i = slices.IndexFunc(s, f); i < 0 { return } if i0 := lastIndexFunc[S](s[i+1:], f); i0 >= 0 { i = i0 } return } // fixupEnviron fixes up PATH, prepends extras and returns the resulting slice. func fixupEnviron(env, extras []string, paths ...string) []string { const pathPrefix = "PATH=" pathVal := strings.Join(paths, ":") if i := lastIndexFunc(env, func(s string) bool { return strings.HasPrefix(s, pathPrefix) }); i < 0 { env = append(env, pathPrefix+pathVal) } else { if len(env[i]) == len(pathPrefix) { env[i] = pathPrefix + pathVal } else { env[i] += ":" + pathVal } } return append(extras, env...) } // absCureScript is the absolute pathname [Toolchain.New] places the fixed-up // build script under. var absCureScript = fhs.AbsUsrBin.Append(".cure-script") // New returns a [pkg.Artifact] compiled on this toolchain. func (t Toolchain) New( name string, extra []pkg.Artifact, checksum *pkg.Checksum, env []string, script string, paths ...pkg.ExecPath, ) pkg.Artifact { const lcMessages = "LC_MESSAGES=C.UTF-8" var ( path = AbsSystem.Append("bin", "busybox") args = []string{"hush", absCureScript.String()} support []pkg.Artifact ) switch t { case toolchainBusybox: name += "-early" support = slices.Concat([]pkg.Artifact{newBusyboxBin()}, extra) env = fixupEnviron(env, nil, "/system/bin") case toolchainStage3: name += "-boot" const ( version = "20260111T160052Z" checksum = "c5_FwMnRN8RZpTdBLGYkL4RR8ampdaZN2JbkgrFLe8-QHQAVQy08APVvIL6eT7KW" ) path = fhs.AbsRoot.Append("bin", "bash") args[0] = "bash" support = slices.Concat([]pkg.Artifact{ cureEtc{}, toolchainBusybox.New("stage3-"+version, nil, nil, nil, ` tar -C /work -xf /usr/src/stage3.tar.xz rm -rf /work/dev/ /work/proc/ ln -vs ../usr/bin /work/bin `, pkg.Path(AbsUsrSrc.Append("stage3.tar.xz"), false, pkg.NewHTTPGet( nil, "https://distfiles.gentoo.org/releases/"+ runtime.GOARCH+"/autobuilds/"+version+ "/stage3-"+runtime.GOARCH+"-musl-llvm-"+version+".tar.xz", mustDecode(checksum), ), )), }, extra) env = fixupEnviron(env, []string{ EnvTriplet + "=" + triplet(), lcMessages, "LDFLAGS=" + earlyLDFLAGS(true), }, "/system/bin", "/usr/bin", "/usr/lib/llvm/21/bin", ) case toolchainIntermediate, Std: if t < Std { name += "-std" } boot := t - 1 musl, compilerRT, runtimes, clang := boot.NewLLVM() support = slices.Concat(extra, []pkg.Artifact{ cureEtc{newIANAEtc()}, musl, compilerRT, runtimes, clang, boot.Load(Busybox), }) env = fixupEnviron(env, []string{ EnvTriplet + "=" + triplet(), lcMessages, "AR=ar", "RANLIB=ranlib", "LIBCC=/system/lib/clang/21/lib/" + triplet() + "/libclang_rt.builtins.a", }, "/system/bin", "/bin") default: panic("unsupported toolchain " + strconv.Itoa(int(t))) } return pkg.NewExec( name, checksum, pkg.ExecTimeoutMax, fhs.AbsRoot, env, path, args, slices.Concat([]pkg.ExecPath{pkg.Path( fhs.AbsRoot, true, support..., ), pkg.Path( absCureScript, false, pkg.NewFile(".cure-script", []byte("set -e\n"+script)), )}, paths)..., ) } // NewPatchedSource returns [pkg.Artifact] of source with patches applied. If // passthrough is true, source is returned as is for zero length patches. func (t Toolchain) NewPatchedSource( name string, source pkg.Artifact, passthrough bool, patches ...[2]string, ) pkg.Artifact { if passthrough && len(patches) == 0 { return source } paths := make([]pkg.ExecPath, len(patches)+1) for i, p := range patches { paths[i+1] = pkg.Path( AbsUsrSrc.Append(name+"-patches", p[0]+".patch"), false, pkg.NewFile(p[0]+".patch", []byte(p[1])), ) } paths[0] = pkg.Path(AbsUsrSrc.Append(name), false, source) aname := name + "-src" script := ` cp -r /usr/src/` + name + `/. /work/. chmod -R +w /work && cd /work ` if len(paths) > 1 { script += `cat /usr/src/` + name + `-patches/* | patch -p 1` aname += "-patched" } return t.New(aname, stage3Concat(t, []pkg.Artifact{}, t.Load(Patch), ), nil, nil, script, paths...) }