Skip to content
Merged
Show file tree
Hide file tree
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
14 changes: 14 additions & 0 deletions docs/ci-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<include>` 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' `<include>`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/<abi>/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:
Expand Down
25 changes: 24 additions & 1 deletion scripts/pack-sysroot.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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]}`);
Expand All @@ -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'))) {
Expand All @@ -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),
};
Expand Down
21 changes: 21 additions & 0 deletions scripts/pack-sysroot.test.mjs
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down Expand Up @@ -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'), '<repository><include name="cairo" version="1.0"/></repository>');
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 });
}
});
35 changes: 35 additions & 0 deletions tools/host-typelibs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/sh
# usage: host-typelibs <build-dir> <typelib-dir> <scratch-dir>
#
# Compiles the namespaces the built GIRs <include> 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 <typelib-dir>, 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 '<include name="[^"]*" version="[^"]*"' "$@" |
sed 's/<include name="\([^"]*\)" version="\([^"]*\)"/\1-\2/' | sort -u
}

built=$(cd "$dest" && ls | sed -n 's/\.typelib$//p')
needed=$(find "$build" -name '*.gir' -exec grep -ho '<include name="[^"]*" version="[^"]*"' {} + |
sed 's/<include name="\([^"]*\)" version="\([^"]*\)"/\1-\2/' | sort -u)
done_list=
while [ -n "$needed" ]; do
next=
for ns in $needed; do
echo "$built $done_list" | tr ' ' '\n' | grep -qx "$ns" && continue
sed -E 's/(\.so)\.[0-9.]+/\1/g' "$gir/$ns.gir" > "$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
16 changes: 3 additions & 13 deletions tools/stage-typelibs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 '<include name="[^"]*" version="[^"]*"' {} + |
sed 's/<include name="\([^"]*\)" version="\([^"]*\)"/\1-\2/' | sort -u)
for ns in $needed; do
echo "$built" | grep -qx "$ns" && continue
sed -E 's/(\.so)\.[0-9.]+/\1/g' "/usr/share/gir-1.0/$ns.gir" > "$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"
Loading