// 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" // EnvRefCFLAGS holds toolchain-specific reference CFLAGS. EnvRefCFLAGS = "ROSA_CFLAGS" // EnvRefCXXFLAGS holds toolchain-specific reference CXXFLAGS. EnvRefCXXFLAGS = "ROSA_CXXFLAGS" ) // ldflags returns LDFLAGS corresponding to triplet. func ldflags(static bool) string { s := "LDFLAGS=" + "-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/lib/ld-musl-x86_64.so.1" } return s } // cflags is reference CFLAGS for the Rosa OS toolchain. const cflags = "-Qunused-arguments " + "-isystem/system/include" // cxxflags returns reference CXXFLAGS for the Rosa OS toolchain corresponding // to [runtime.GOARCH]. func cxxflags() 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 ) // 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: support = slices.Concat([]pkg.Artifact{newBusyboxBin()}, extra) env = fixupEnviron(env, nil, "/system/bin") case toolchainStage3: 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, EnvRefCFLAGS + "=" + cflags, EnvRefCXXFLAGS + "=" + cxxflags(), ldflags(true), }, "/system/bin", "/usr/bin", "/usr/lib/llvm/21/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)..., ) }