Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions lib/porcelain/build-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export default async function(config: Config, PATH?: Path): Promise<string> {
}
const user_script = await usePantry().getScript(config.pkg, 'build', config.deps.gas, config)

// Sysroot routing — opt-in via `build.sysroot:` in package.yml.
// When set, redirects the compiler at a specific glibc / kernel-headers
// bottle (instead of the build host's libc). Lines emitted into the
// generated script export CC / CXX / CPP / SYSROOT before the user
// script runs, so recipes don't need to know the paths themselves.
// Empty string when the yaml has no `build.sysroot` block — no
// behaviour change for existing recipes.
const sysroot_env = await sysroot_block(config)

const pkgx = find_in_PATH('pkgx')
const bash = find_in_PATH('bash')
const gum = find_in_PATH('gum')
Expand Down Expand Up @@ -71,6 +80,7 @@ export default async function(config: Config, PATH?: Path): Promise<string> {
fi
mkdir -p $HOME
${FLAGS.join('\n ')}
${sysroot_env}

env -u GH_TOKEN -u GITHUB_TOKEN

Expand All @@ -82,6 +92,71 @@ export default async function(config: Config, PATH?: Path): Promise<string> {
`
}

/// Emit `export CC=… CXX=… CPP=… SYSROOT=…` if the package.yml has a
/// `build.sysroot` block. Resolves the libc bottle from the package's
/// installed deps so the recipe doesn't need to know the absolute path.
///
/// YAML schema:
/// build:
/// sysroot:
/// libc: gnu.org/glibc=~2.28 # required
/// kernel-headers: kernel.org/linux-headers=^7 # optional
///
/// The libc package must already be a build dependency of the recipe
/// (so it's installed and available in config.deps); this directive
/// doesn't add a dep, just routes the compiler at it. Same for the
/// kernel headers when set.
async function sysroot_block(config: Config): Promise<string> {
const yml = await usePantry().project(config.pkg).yaml() as Record<string, unknown>
const build = yml.build as Record<string, unknown> | undefined
const sysroot = build?.sysroot as Record<string, unknown> | undefined
if (!sysroot) return ''

const want_libc = sysroot.libc as string | undefined
if (!want_libc) {
console.warn("build.sysroot present but no libc — skipping")
return ''
}
const libc_project = want_libc.split(/[=<>~^]/)[0]
const libc_install = config.deps.gas.find(i => i.pkg.project === libc_project)
if (!libc_install) {
throw new Error(`build.sysroot.libc='${want_libc}' but ${libc_project} is not in the resolved deps — declare it as a build.dependencies entry`)
}

const want_khdr = sysroot['kernel-headers'] as string | undefined
const khdr_path: string | undefined = want_khdr
? (() => {
const proj = want_khdr.split(/[=<>~^]/)[0]
const inst = config.deps.gas.find(i => i.pkg.project === proj)
if (!inst) throw new Error(`build.sysroot.kernel-headers='${want_khdr}' not in resolved deps`)
return inst.path.string
})()
: undefined

const ldso = (() => {
switch (host().arch) {
case 'x86-64': return 'ld-linux-x86-64.so.2'
case 'aarch64': return 'ld-linux-aarch64.so.1'
default: throw new Error(`sysroot directive unsupported on ${host().arch}`)
}
})()

const libc = libc_install.path.string
const isystems = [
`-isystem ${libc}/include`,
khdr_path ? `-isystem ${khdr_path}/include` : null,
].filter(Boolean).join(' ')
const wrap = `-nostdinc ${isystems} -B ${libc}/lib -Wl,--enable-new-dtags,--dynamic-linker=${libc}/lib/${ldso},--rpath=${libc}/lib`

return undent`
# sysroot routing (build.sysroot in package.yml)
export SYSROOT=${libc}
export CC="\${CC:-gcc} ${wrap}"
export CXX="\${CXX:-g++} ${wrap} -nostdinc++"
export CPP="\${CPP:-gcc} ${wrap} -E"
`
}

function flags(): string[] {
const {platform, arch} = host()
const is_linux_x86_64 = platform == 'linux' && arch == 'x86-64'
Expand Down