All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m56s
Test / ShareFS (push) Successful in 4m11s
Test / Hpkg (push) Successful in 4m43s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m6s
Test / Hakurei (push) Successful in 2m45s
Test / Flake checks (push) Successful in 1m39s
This enables building on arm64. Signed-off-by: Ophestra <cat@gensokyo.uk>
325 lines
8.1 KiB
Go
325 lines
8.1 KiB
Go
// 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"
|
|
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 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,
|
|
exclusive bool,
|
|
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"
|
|
var version, checksum string
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
version = "20260111T160052Z"
|
|
checksum = "c5_FwMnRN8RZpTdBLGYkL4RR8ampdaZN2JbkgrFLe8-QHQAVQy08APVvIL6eT7KW"
|
|
case "arm64":
|
|
version = "20260125T234618Z"
|
|
checksum = "79uRbRI44PyknQQ9RlFUQrwqplup7vImiIk6klefL8TN-fT42TXMS_v4XszwexCb"
|
|
|
|
default:
|
|
panic("unsupported target " + runtime.GOARCH)
|
|
}
|
|
path = fhs.AbsRoot.Append("bin", "bash")
|
|
args[0] = "bash"
|
|
support = slices.Concat([]pkg.Artifact{
|
|
cureEtc{},
|
|
toolchainBusybox.New("stage3-"+version, false, 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, exclusive,
|
|
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, 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, false, stage3Concat(t, []pkg.Artifact{},
|
|
t.Load(Patch),
|
|
), nil, nil, script, paths...)
|
|
}
|