diff --git a/docs/ci-build.md b/docs/ci-build.md index 5f2b623..0bed995 100644 --- a/docs/ci-build.md +++ b/docs/ci-build.md @@ -58,6 +58,20 @@ resolved from `/usr/share/gir-1.0`. A hand build on a GTK workstation never noti Java is not installed; `pixiewood build --skip-gradle` stops before the APK, which this build does not want. +## Typelibs the stack does not build + +Built GIRs `` cairo, freetype2 and fontconfig, but their GIRs ship with +gobject-introspection and no subproject builds them. `tools/host-typelibs` derives the +list from the built GIRs' ``s (transitively), minus what the build produced, and +compiles those from the host's `/usr/share/gir-1.0` with the soname versions stripped. +`pack-sysroot.mjs` runs it into `lib//girepository-1.0` and `tools/stage-typelibs` +runs it for the probe APK. The manifest lists them in `typelibs` and records, under +`hostTypelibs`, the namespaces, the host rpm package(s) the GIRs came from and the +`g-ir-compiler` version. Their content therefore depends on the `fedora:44` container's +gobject-introspection, like the stack's own typelibs. The GIRs themselves are not packed: the prefix keeps none. No cairo or fontconfig +library is in the prefix either (only their typelibs, built from the GIRs), so no licence +file is added for them; freetype's is already copied. + ## What byte identity depends on Reproducing a release needs all of these to match, and the workflow has a knob for each: diff --git a/scripts/pack-sysroot.mjs b/scripts/pack-sysroot.mjs index 0a6e7bf..6402e53 100644 --- a/scripts/pack-sysroot.mjs +++ b/scripts/pack-sysroot.mjs @@ -37,6 +37,7 @@ import { } from 'node:fs'; import { tmpdir } from 'node:os'; import { basename, dirname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; /** ABI -> pixiewood's build directory suffix. */ const ABIS = { @@ -192,6 +193,24 @@ function ndkRevision(ndk) { return /Pkg\.Revision\s*=\s*(\S+)/.exec(props)?.[1] ?? 'unknown'; } +/** + * Typelibs for namespaces the built GIRs include but the stack does not build + * (cairo, freetype2, fontconfig), compiled from the host's GIRs by tools/host-typelibs. + * Their content depends on the host's gobject-introspection, so its package is recorded. + */ +function compileHostTypelibs(buildDir, typelibDir, scratch) { + const helper = join(dirname(fileURLToPath(import.meta.url)), '..', 'tools', 'host-typelibs'); + const namespaces = run(helper, [buildDir, typelibDir, scratch]).split('\n').filter(Boolean).sort(); + const girs = namespaces.map((ns) => `/usr/share/gir-1.0/${ns}.gir`); + let packages; + try { + packages = [...new Set(run('rpm', ['-qf', ...girs]).split('\n').filter(Boolean))].sort(); + } catch { + packages = ['unknown']; + } + return { namespaces, packages, compiler: run('g-ir-compiler', ['--version']).trim() }; +} + function packAbi({ abi, source, ndk, tag, out, scratch }) { const pixiewood = join(source, '.pixiewood'); const buildDir = join(pixiewood, `bin-${ABIS[abi]}`); @@ -217,6 +236,9 @@ function packAbi({ abi, source, ndk, tag, out, scratch }) { const strip = join(ndk, 'toolchains', 'llvm', 'prebuilt', 'linux-x86_64', 'bin', 'llvm-strip'); run(strip, ['--strip-debug', ...libs.map((lib) => join(stage, 'lib', abi, lib))]); + const typelibDir = join(stage, 'lib', abi, 'girepository-1.0'); + const hostTypelibs = compileHostTypelibs(buildDir, typelibDir, join(scratch, `host-gir-${abi}`)); + const pcDir = join(stage, 'lib', abi, 'pkgconfig'); const problems = []; for (const name of readdirSync(pcDir).filter((f) => f.endsWith('.pc'))) { @@ -239,7 +261,8 @@ function packAbi({ abi, source, ndk, tag, out, scratch }) { ndkRoot: resolve(ndk), ndk: ndkRevision(ndk), libraries: libs.sort(), - typelibs: readdirSync(join(stage, 'lib', abi, 'girepository-1.0')).sort(), + typelibs: readdirSync(typelibDir).sort(), + hostTypelibs, licensedProjects: licensed, sources: sourceRevisions(subprojects), }; diff --git a/scripts/pack-sysroot.test.mjs b/scripts/pack-sysroot.test.mjs index 1196a84..82c6d08 100644 --- a/scripts/pack-sysroot.test.mjs +++ b/scripts/pack-sysroot.test.mjs @@ -1,6 +1,11 @@ // SPDX-License-Identifier: MIT import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; import { absolutePaths, rewritePc } from './pack-sysroot.mjs'; const NDK_LIBZ = @@ -30,3 +35,19 @@ test('an absolute path left in Cflags or Libs is reported, variables are not', ( const text = 'sysconfdir=/etc\nCflags: -I${includedir}\nLibs: -L/usr/lib -lfoo'; assert.deepEqual(absolutePaths('foo.pc', text), ['foo.pc:3: Libs: -L/usr/lib -lfoo']); }); + +test('host-typelibs compiles included namespaces the build lacks', { skip: !existsSync('/usr/share/gir-1.0/cairo-1.0.gir') }, () => { + const dir = mkdtempSync(join(tmpdir(), 'host-typelibs-')); + try { + mkdirSync(join(dir, 'build')); + mkdirSync(join(dir, 'out')); + writeFileSync(join(dir, 'build', 'X-1.0.gir'), ''); + for (const ns of ['GLib-2.0', 'GObject-2.0']) writeFileSync(join(dir, 'out', `${ns}.typelib`), ''); + const helper = join(dirname(fileURLToPath(import.meta.url)), '..', 'tools', 'host-typelibs'); + const out = execFileSync(helper, [join(dir, 'build'), join(dir, 'out'), join(dir, 'tmp')], { encoding: 'utf8' }); + assert.equal(out, 'cairo-1.0\n'); + assert.ok(existsSync(join(dir, 'out', 'cairo-1.0.typelib'))); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/tools/host-typelibs b/tools/host-typelibs new file mode 100755 index 0000000..ae5c903 --- /dev/null +++ b/tools/host-typelibs @@ -0,0 +1,35 @@ +#!/bin/sh +# usage: host-typelibs +# +# Compiles the namespaces the built GIRs but the stack does not build +# (harfbuzz introspection is off, cairo/freetype2/fontconfig GIRs ship with +# gobject-introspection) from the build host's GIRs into , with the +# soname versions stripped the way the Android build names its libraries. +# Prints one namespace per line. The typelibs therefore carry the host's +# gobject-introspection version; pack-sysroot records its package in the manifest. +set -eu +build=$1 dest=$2 scratch=$3 +gir=/usr/share/gir-1.0 +mkdir -p "$dest" "$scratch" + +includes() { + grep -ho ' "$scratch/$ns.gir" + g-ir-compiler --includedir="$gir" -o "$dest/$ns.typelib" "$scratch/$ns.gir" + echo "$ns" + done_list="$done_list $ns" + next="$next $(includes "$scratch/$ns.gir")" + done + needed=$(echo $next | tr ' ' '\n' | sort -u) +done diff --git a/tools/stage-typelibs b/tools/stage-typelibs index d10d8cd..514c140 100755 --- a/tools/stage-typelibs +++ b/tools/stage-typelibs @@ -4,10 +4,8 @@ # reach the APK. Puts them under assets/share/girepository-1.0, which the GDK # Android runtime extracts to the first XDG data dir. # -# Namespaces the stack includes but does not build (harfbuzz introspection is -# off, cairo/freetype2/fontconfig GIRs ship with gobject-introspection) are -# compiled from the build host's GIRs, with the soname versions stripped the -# way the Android build names its libraries. +# Namespaces the stack includes but does not build are compiled from the +# build host's GIRs by tools/host-typelibs. set -eu arch=${1:-x86_64} pw=.pixiewood @@ -19,13 +17,5 @@ meson install -C "$pw/bin-$arch" --destdir "$(realpath "$pw")/typelib-root" --ta mkdir -p "$assets" find "$stage" -name '*.typelib' -exec cp {} "$assets" \; -built=$(cd "$assets" && ls | sed 's/\.typelib$//') -needed=$(find "$pw/bin-$arch" -name '*.gir' -exec grep -ho ' "$stage/$ns.gir" - g-ir-compiler --includedir=/usr/share/gir-1.0 -o "$assets/$ns.typelib" "$stage/$ns.gir" - echo "host-derived: $ns" -done +"$(dirname "$0")/host-typelibs" "$pw/bin-$arch" "$assets" "$stage" | sed 's/^/host-derived: /' ls "$assets"