fixes not incl runtime deps of build deps

Refactor build a bit. This was causing build failure for GHC on linux as the system perl didn't have something autoconf needed.

TODO: infuser shouldn't have perl installed!
This commit is contained in:
Max Howell 2022-09-19 09:32:27 -04:00
parent d680562710
commit 54781e96e1
6 changed files with 254 additions and 52 deletions

View file

@ -3,3 +3,12 @@
```sh
scripts/ls.ts | xargs scripts/sort.ts | xargs scripts/build.ts
```
# test all
`each.ts` reduces output for each input to a concise ✅ or ❌ based on exit
status.
```sh
scripts/ls.ts | xargs scripts/each.ts scripts/test.ts
```

View file

@ -12,31 +12,30 @@ args:
- --import-map={{ srcroot }}/import-map.json
---*/
import useSourceUnarchiver from "hooks/useSourceUnarchiver.ts"
import useCellar from "hooks/useCellar.ts"
import usePantry from "hooks/usePantry.ts"
import useCache from "hooks/useCache.ts"
import { lvl1 as link } from "prefab/link.ts"
import build from "./build.prefab.ts"
import build from "./build/build.ts"
import { Package, parsePackageRequirement, semver } from "types"
import useFlags from "hooks/useFlags.ts"
import usePlatform from "hooks/usePlatform.ts"
import hydrate from "prefab/hydrate.ts"
import useCellar from "hooks/useCellar.ts"
useFlags()
const pantry = usePantry()
const cellar = useCellar()
const dry = Deno.args.map(parsePackageRequirement)
const gha = !!Deno.env.get("GITHUB_ACTIONS")
const group_it = gha && dry.length > 1
const rv: Package[] = []
if (cellar.prefix.string != "/opt") {
console.error({ TEA_PREFIX: cellar.prefix.string })
throw new Error("builds must be performed in /opt (try TEA_PREFIX=/opt)")
}
for (const pkgrq of dry) {
const versions = await pantry.getVersions(pkgrq)
const version = semver.maxSatisfying(versions, pkgrq.constraint)
if (!version) throw "no-version-found"
if (!version) throw new Error(`no-version-found: ${pkgrq.project}`)
const pkg = { project: pkgrq.project, version }
if (group_it) {
@ -45,48 +44,7 @@ for (const pkgrq of dry) {
console.log({ building: pkgrq.project })
}
// Get the source
const prebuild = async () => {
const dstdir = cellar.mkpath(pkg).join("src")
const { url, stripComponents } = await pantry.getDistributable(pkg)
const { download } = useCache()
const zip = await download({ pkg, url, type: 'src' })
await useSourceUnarchiver().unarchive({
dstdir,
zipfile: zip,
stripComponents
})
}
//TODO this was already calculated in `sort` and should not be recalculated
const deps = await pantry.getDeps(pkg)
const wet = await hydrate(deps.runtime, pkg => pantry.getDeps(pkg).then(x => x.runtime))
deps.runtime.push(...wet.pkgs)
// only required as we aren't passing everything into hydrate
const depends_on_self = () => deps.build.some(x => x.project === pkg.project)
// provided this package doesn't transitively depend on itself (yes this happens)
// clean out the destination prefix first
if (!wet.bootstrap_required.has(pkg.project) && !depends_on_self()) {
const installation = await cellar.isInstalled(pkg)
if (installation) {
console.log({ cleaning: installation.path })
for await (const [path, {name}] of installation.path.ls()) {
if (name == 'src') continue
path.rm({ recursive: true })
}
}
}
const env = usePlatform().platform == 'darwin'
? {MACOSX_DEPLOYMENT_TARGET: ['11.0']}
: undefined
// Build and link
const path = await build({ pkg, deps, prebuild, env })
await link({ path, pkg })
await build(pkg)
rv.push(pkg)
if (group_it) {

135
scripts/build/build.ts Normal file
View file

@ -0,0 +1,135 @@
import useSourceUnarchiver from "hooks/useSourceUnarchiver.ts"
import useCellar from "hooks/useCellar.ts"
import usePantry from "hooks/usePantry.ts"
import useCache from "hooks/useCache.ts"
import { lvl1 as link } from "prefab/link.ts"
import { Installation, Package, Path, semver } from "types"
import usePlatform from "hooks/usePlatform.ts"
import hydrate from "prefab/hydrate.ts"
import useShellEnv, { expand } from "hooks/useShellEnv.ts"
import { run, undent } from "utils"
import fix_pkg_config_files from "./fix-pkg-config-files.ts"
import fix_linux_rpaths from "./fix-linux-rpaths.ts"
const cellar = useCellar()
const pantry = usePantry()
const { platform } = usePlatform()
export default async function _build(pkg: Package) {
const [deps, wet] = await calc_deps()
await clean()
const env = await mkenv()
const dst = cellar.mkpath(pkg)
const src = await fetch_src(pkg)
const installation = await build()
await link(installation)
await fix_binaries(installation)
await fix_pkg_config_files(installation)
return installation
//////// utils
async function calc_deps() {
const deps = await pantry.getDeps(pkg)
const wet = await hydrate([...deps.runtime, ...deps.build], pkg => pantry.getDeps(pkg).then(x => x.runtime))
deps.runtime.push(...wet.pkgs)
function tuplize<T extends any[]>(...elements: T) { return elements }
return tuplize(deps, wet)
}
async function clean() {
const installation = await should_clean()
if (!installation) return
console.log({ cleaning: installation.path })
for await (const [path, {name}] of installation.path.ls()) {
if (name == 'src') continue
path.rm({ recursive: true })
}
async function should_clean() {
// only required as we aren't passing everything into hydrate
const depends_on_self = () => deps.build.some(x => x.project === pkg.project)
// provided this package doesn't transitively depend on itself (yes this happens)
// clean out the destination prefix first
if (!wet.bootstrap_required.has(pkg.project) && !depends_on_self()) {
return await cellar.isInstalled(pkg)
}
}
}
async function mkenv() {
const env = await useShellEnv([...deps.runtime, ...deps.build])
if (env.pending.length) {
console.error({uninstalled: env.pending})
throw new Error("uninstalled")
}
if (platform == 'darwin') {
env.vars['MACOSX_DEPLOYMENT_TARGET'] = ['11.0']
}
return env
}
async function build() {
const sh = await pantry.getScript(pkg, 'build')
const cmd = dst.join("build.sh").write({ force: true, text: undent`
#!/bin/bash
set -e
set -o pipefail
set -x
cd "${src}"
${expand(env.vars)}
${/*FIXME hardcoded paths*/ ''}
export PATH=/opt/tea.xyz/var/pantry/scripts/brewkit:"$PATH"
${sh}
`
}).chmod(0o500)
await run({ cmd }) // THE BUILD
return { path: dst, pkg }
}
async function fix_binaries(installation: Installation) {
switch (usePlatform().platform) {
case 'darwin':
await fix_macho(installation)
break
case 'linux': {
const self = {project: pkg.project, constraint: new semver.Range(`=${pkg.version}`)}
await fix_linux_rpaths(installation, [...deps.runtime, self])
}}
}
}
async function fetch_src(pkg: Package): Promise<Path> {
const dstdir = cellar.mkpath(pkg).join("src")
const { url, stripComponents } = await pantry.getDistributable(pkg)
const { download } = useCache()
const zip = await download({ pkg, url, type: 'src' })
await useSourceUnarchiver().unarchive({
dstdir,
zipfile: zip,
stripComponents
})
return dstdir
}
async function fix_macho(installation: Installation) {
const d = new Path(new URL(import.meta.url).pathname).parent()
const walk = ['bin', 'lib', 'libexec'].map(x => installation.path.join(x)).filter(x => x.isDirectory())
await run({
cmd: [d.join('fix-machos.rb'), installation.path, ...walk],
env: {
TEA_PREFIX: cellar.prefix.string,
}
})
}

View file

@ -0,0 +1,28 @@
import { Installation } from "types"
import { Path } from "types"
export default async function fix_pkg_config_files(installation: Installation) {
for await (const pcfile of find_pkg_config_files(installation)) {
const orig = await pcfile.read()
const relative_path = installation.path.relative({ to: pcfile.parent() })
const text = orig.replace(installation.path.string, `\${pcfiledir}/${relative_path}`)
if (orig !== text) {
console.verbose({ fixed: pcfile })
pcfile.write({text, force: true})
}
}
}
//NOTE currently we only support pc files in lib/pkgconfig
// we aim to standardize on this but will relent if a package is found
// that uses share and other tools that build against it only accept that
async function *find_pkg_config_files(installation: Installation): AsyncIterable<Path> {
const pcdir = installation.path.join("lib/pkgconfig")
if (!pcdir.isDirectory()) return
for await (const [path, { isFile }] of pcdir.ls()) {
if (isFile && path.extname() == ".pc") {
yield path
}
}
}

27
scripts/each.ts Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env -S tea -E
/*---
args:
- deno
- run
- --allow-run
- --allow-read
- --allow-env
- --import-map={{ srcroot }}/import-map.json
---*/
const args = [...Deno.args]
const via = args.shift()
for (const arg of args) {
const proc = Deno.run({
stdout: "null", stderr: "null",
cmd: [via!, arg]
})
const status = await proc.status()
if (status.code !== 0) {
console.error(`${arg}`)
} else {
console.info(`${arg}`)
}
}

45
scripts/fetch.ts Executable file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env -S tea -E
/*---
args:
- deno
- run
- --allow-net
- --allow-run
- --allow-read
- --allow-write={{ tea.prefix }}
- --allow-env
- --import-map={{ srcroot }}/import-map.json
---*/
import usePantry from "hooks/usePantry.ts"
import useCache from "hooks/useCache.ts"
import useCellar from "hooks/useCellar.ts"
import useSourceUnarchiver from "hooks/useSourceUnarchiver.ts"
import { parsePackageRequirement, semver } from "types"
import { Command } from "cliffy/command/mod.ts"
import { print } from "utils"
const { args } = await new Command()
.name("tea-fetch-src")
.arguments("<pkgspec:string>")
.parse(Deno.args)
const pantry = usePantry()
const req = parsePackageRequirement(args[0])
const versions = await pantry.getVersions(req)
const version = semver.maxSatisfying(versions, req.constraint)
if (!version) throw "no-version-found"
const pkg = { project: req.project, version }; console.debug(pkg)
const dstdir = useCellar().mkpath(pkg).join("src")
const { url, stripComponents } = await pantry.getDistributable(pkg)
const { download } = useCache()
const zip = await download({ pkg, url, type: 'src' })
await useSourceUnarchiver().unarchive({
dstdir,
zipfile: zip,
stripComponents
})
await print(`${dstdir}\n`)