// Package rosa provides Rosa OS toolchain artifacts and miscellaneous software. package rosa import ( "errors" "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" case "arm64": return "aarch64" 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 stage0 toolchain. const earlyCFLAGS = "-Qunused-arguments " + "-isystem/system/include" // earlyCXXFLAGS returns reference CXXFLAGS for the stage0 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 defined as a toolchain to make use of the // toolchain abstractions to preprocess toolchainGentoo and is not a real, // functioning toolchain. It does not contain any compilers. _toolchainBusybox Toolchain = iota // toolchainGentoo denotes the toolchain in a Gentoo stage3 tarball. Special // care must be taken to compile correctly against this toolchain. toolchainGentoo // toolchainIntermediateGentoo is like to toolchainIntermediate, but // compiled against toolchainGentoo. toolchainIntermediateGentoo // toolchainStdGentoo is like Std, but bootstrapped from toolchainGentoo. // This toolchain creates the first [Stage0] distribution. toolchainStdGentoo // toolchainStage0 denotes the stage0 toolchain. Special care must be taken // to compile correctly against this toolchain. toolchainStage0 // toolchainIntermediate denotes the intermediate toolchain compiled against // toolchainStage0. 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 ) // isStage0 returns whether t is a stage0 toolchain. func (t Toolchain) isStage0() bool { switch t { case toolchainGentoo, toolchainStage0: return true default: return false } } // isIntermediate returns whether t is an intermediate toolchain. func (t Toolchain) isIntermediate() bool { switch t { case toolchainIntermediateGentoo, toolchainIntermediate: return true default: return false } } // isStd returns whether t is considered functionally equivalent to [Std]. func (t Toolchain) isStd() bool { switch t { case toolchainStdGentoo, Std: return true default: return false } } // stage0Concat concatenates s and values. If the current toolchain is // toolchainStage0, stage0Concat returns s as is. func stage0Concat[S ~[]E, E any](t Toolchain, s S, values ...E) S { if t.isStage0() { return s } return slices.Concat(s, values) } // stage0ExclConcat concatenates s and values. If the current toolchain is not // toolchainStage0, stage0ExclConcat returns s as is. func stage0ExclConcat[S ~[]E, E any](t Toolchain, s S, values ...E) S { if t.isStage0() { 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 = AbsSystem.Append("bin", ".cure-script") const ( // TExclusive denotes an exclusive [pkg.Artifact]. TExclusive = 1 << iota // TEarly hints for an early variant of [Toybox] to be used when available. TEarly ) var ( // gentooStage3 is the url of a Gentoo stage3 tarball. gentooStage3 string // gentooStage3Checksum is the expected checksum of gentooStage3. gentooStage3Checksum pkg.Checksum ) // SetGentooStage3 sets the Gentoo stage3 tarball url and checksum. It panics // if given zero values or if these values have already been set. func SetGentooStage3(url string, checksum pkg.Checksum) { if gentooStage3 != "" { 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")) } gentooStage3, gentooStage3Checksum = url, checksum } // New returns a [pkg.Artifact] compiled on this toolchain. func (t Toolchain) New( name string, flag int, extra []pkg.Artifact, knownChecksum *pkg.Checksum, env []string, script string, paths ...pkg.ExecPath, ) pkg.Artifact { const lcMessages = "LC_MESSAGES=C.UTF-8" var support []pkg.Artifact switch t { case _toolchainBusybox: name += "-early" support = slices.Concat([]pkg.Artifact{newBusyboxBin()}, extra) env = fixupEnviron(env, nil, "/system/bin") case toolchainGentoo, toolchainStage0: name += "-boot" support = append(support, cureEtc{}) if t == toolchainStage0 { support = append(support, NewStage0()) } else { support = append(support, _toolchainBusybox.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, gentooStage3, gentooStage3Checksum, ), ))) } support = slices.Concat(support, extra) env = fixupEnviron(env, []string{ EnvTriplet + "=" + triplet(), lcMessages, "LDFLAGS=" + earlyLDFLAGS(true), }, "/system/bin", "/usr/bin", ) case toolchainIntermediateGentoo, toolchainStdGentoo, toolchainIntermediate, Std: if t.isIntermediate() { name += "-std" } boot := t - 1 musl, compilerRT, runtimes, clang := boot.NewLLVM() toybox := Toybox if flag&TEarly != 0 { toybox = toyboxEarly } support = slices.Concat(extra, []pkg.Artifact{ cureEtc{newIANAEtc()}, musl, compilerRT, runtimes, clang, boot.Load(Mksh), boot.Load(toybox), }) 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, knownChecksum, pkg.ExecTimeoutMax, flag&TExclusive != 0, fhs.AbsRoot, env, AbsSystem.Append("bin", "sh"), []string{"sh", absCureScript.String()}, 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, version 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 + "-" + version + "-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 \ --ignore-whitespace ` aname += "-patched" } return t.New(aname, 0, stage0Concat(t, []pkg.Artifact{}, t.Load(Patch), ), nil, nil, script, paths...) }