Skip to content
Open
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
185 changes: 122 additions & 63 deletions .github/workflows/build-native.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Build DexKit Native Libraries

env:
LINUX_CODENAME: xenial
LINUX_SYSROOT_PATH: .dexkit-sysroot/ubuntu-16.04-x86_64
LINUX_GCC10_HEADERS_PATH: .dexkit-sysroot/gcc10-headers
LINUX_SYSROOT_CACHE_KEY_PREFIX: dexkit-linux-amd64-sysroot-v1

on:
push:
branches: [ master ]
Expand All @@ -12,9 +18,9 @@ jobs:
strategy:
matrix:
sys:
- { name: linux-musl, os: ubuntu-latest, shell: 'alpine.sh --root {0}'}
- { name: windows, os: windows-latest, shell: bash}
- { name: macos, os: macos-latest, shell: bash}
- { name: linux, os: ubuntu-latest, shell: bash}
- { name: windows, os: windows-latest, shell: bash}
- { name: macos, os: macos-latest, shell: bash}
defaults:
run:
shell: ${{ matrix.sys.shell }}
Expand All @@ -24,50 +30,6 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v4

- name: Setup latest Alpine Linux
uses: jirutka/setup-alpine@v1
if: ${{ matrix.sys.name == 'linux-musl' }}
with:
arch: x86_64
packages: >
build-base
cmake
ninja
git
musl-dev
libstdc++
g++
openjdk17-jdk
wget
unzip
zlib-dev

- name: Install Android SDK on Alpine
if: ${{ matrix.sys.name == 'linux-musl' }}
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk
run: |
export ANDROID_SDK_ROOT=$HOME/android-sdk

mkdir -p $ANDROID_SDK_ROOT/cmdline-tools
wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O /tmp/tools.zip
unzip -q /tmp/tools.zip -d $ANDROID_SDK_ROOT/cmdline-tools
rm /tmp/tools.zip

mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest

export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$PATH

yes | sdkmanager --licenses || true
sdkmanager --sdk_root=$ANDROID_SDK_ROOT \
"platform-tools" \
"build-tools;33.0.2" \
"cmake;3.22.1" \
"ndk;26.1.10909125" \
"platforms;android-34"

echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties

- name: Setup Java
uses: actions/setup-java@v4
with:
Expand All @@ -79,25 +41,122 @@ jobs:
with:
version: 3.25.0

- name: Build musl PIC static libc (musl)
if: ${{ matrix.sys.name == 'linux-musl' }}
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk
- name: Install Linux build tools
if: ${{ matrix.sys.name == 'linux' }}
run: |
git clone https://git.musl-libc.org/git/musl
cd musl
CFLAGS="-O2 -fPIC" ./configure --prefix=/opt/musl-pic
make
make install

- name: Run Gradle CMake build (musl)
if: ${{ matrix.sys.name == 'linux-musl' }}
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk
MUSL_PATH: /opt/musl-pic
sudo apt-get update
sudo apt-get install -y clang lld ninja-build binutils xz-utils

- name: Restore target Linux sysroot cache
if: ${{ matrix.sys.name == 'linux' }}
id: cache_linux_sysroot
uses: actions/cache@v4
with:
path: ${{ env.LINUX_SYSROOT_PATH }}.tar.xz
key: ${{ env.LINUX_SYSROOT_CACHE_KEY_PREFIX }}-${{ env.LINUX_CODENAME }}-${{ hashFiles('build-support/linux-sysroot/**') }}

- name: Restore GCC 10 headers cache
if: ${{ matrix.sys.name == 'linux' }}
id: cache_linux_gcc10_headers
uses: actions/cache@v4
with:
path: ${{ env.LINUX_GCC10_HEADERS_PATH }}.tar.xz
key: ${{ env.LINUX_SYSROOT_CACHE_KEY_PREFIX }}-gcc10-headers-${{ hashFiles('build-support/linux-sysroot/**') }}

- name: Build missing Linux sysroot and GCC 10 headers
if: ${{ matrix.sys.name == 'linux' }}
run: |
build_sysroot() {
local codename="$1"
local path="$2"

if [ -f "$path.tar.xz" ]; then
return
fi

rm -rf "$path"
sudo env \
UBUNTU_CODENAME="$codename" \
bash build-support/linux-sysroot/build-linux-sysroot.sh "$path"
sudo tar -C "$path" \
--exclude=./dev \
--exclude=./proc \
--exclude=./sys \
--exclude=./run \
--exclude=./tmp \
--exclude=./var/tmp \
-cJf "$path.tar.xz" .
sudo chown "$USER:$USER" "$path.tar.xz"
sudo rm -rf "$path"
sudo chown -R "$USER:$USER" "$(dirname "$path")"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github ci 不应该假设主组与用户一致, 考虑 "$(id -u):$(id -g)"

}

build_gcc10_headers() {
local path="$1"

if [ -f "$path.tar.xz" ]; then
return
fi

rm -rf "$path"
bash build-support/linux-sysroot/fetch-libstdcxx-headers.sh "$path"
tar -C "$path" -cJf "$path.tar.xz" .
rm -rf "$path"
}

build_sysroot "$LINUX_CODENAME" "$LINUX_SYSROOT_PATH"
build_gcc10_headers "$LINUX_GCC10_HEADERS_PATH"

- name: Unpack Linux sysroot and GCC 10 headers
if: ${{ matrix.sys.name == 'linux' }}
run: |
unpack_archive() {
local path="$1"
rm -rf "$path"
mkdir -p "$path"
tar -C "$path" -xJf "$path.tar.xz"
}

unpack_archive "$LINUX_SYSROOT_PATH"
unpack_archive "$LINUX_GCC10_HEADERS_PATH"

- name: Prepare Linux compatibility headers
if: ${{ matrix.sys.name == 'linux' }}
run: |
compat_dir="$GITHUB_WORKSPACE/.dexkit-sysroot/compat"
target_sysroot="$GITHUB_WORKSPACE/$LINUX_SYSROOT_PATH"
template_dir="$GITHUB_WORKSPACE/build-support/linux-sysroot"
mkdir -p "$compat_dir/gcc5-thread-wrapper"
cp "$template_dir/glibc-libstdcxx10-compat.h" "$compat_dir/glibc-libstdcxx10-compat.h"

sed "s|@TARGET_SYSROOT@|$target_sysroot|g" \
"$template_dir/gcc5-thread-wrapper/thread.in" \
> "$compat_dir/gcc5-thread-wrapper/thread"
sed "s|@TARGET_SYSROOT@|$target_sysroot|g" \
"$template_dir/gcc5-thread-wrapper/future.in" \
> "$compat_dir/gcc5-thread-wrapper/future"

- name: Run Gradle CMake build (linux)
if: ${{ matrix.sys.name == 'linux' }}
run: |
chmod +x gradlew
./gradlew :dexkit:cmakeBuild -PlinuxMuslBuild --console=plain

sysroot="$GITHUB_WORKSPACE/$LINUX_SYSROOT_PATH"
gcc10_headers="$GITHUB_WORKSPACE/$LINUX_GCC10_HEADERS_PATH/usr/include"
compat_dir="$GITHUB_WORKSPACE/.dexkit-sysroot/compat"
compat_header="$compat_dir/glibc-libstdcxx10-compat.h"
cxxflags="-fno-exceptions -nostdinc++ -isystem $compat_dir/gcc5-thread-wrapper -isystem $gcc10_headers/c++/10 -isystem $gcc10_headers/x86_64-linux-gnu/c++/10 -include $compat_header"

export CC="clang --sysroot=$sysroot --gcc-toolchain=$sysroot/usr"
export CXX="clang++ --sysroot=$sysroot --gcc-toolchain=$sysroot/usr"
export AR=llvm-ar
export RANLIB=llvm-ranlib
export LD=ld.lld
export CFLAGS=""
export CXXFLAGS="$cxxflags"
export LDFLAGS="-Wl,--no-as-needed -lpthread"

./gradlew :dexkit:cmakeBuild --console=plain

- name: Run Gradle CMake build (windows)
if: ${{ matrix.sys.name == 'windows' }}
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
.externalNativeBuild
.cxx
local.properties
.dexkit-sysroot/

/docs
/doc
/apk
/apk
165 changes: 165 additions & 0 deletions build-support/linux-sysroot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Linux sysroot builds

This directory contains helper files for building DexKit Linux binaries against
older Ubuntu runtime libraries.

The goal is to keep the generated `libdexkit.so` compatible with the target
distribution's glibc, libstdc++, and libgcc runtime. Do not add
`ppa:ubuntu-toolchain-r/test` to the sysroot: the compatibility target is the
toolchain shipped by the distribution itself. That PPA can upgrade runtime
packages such as `libstdc++6`, so the sysroot would no longer represent the
distribution-provided runtime.

Run the commands from the repository root. `build-linux-sysroot.sh` must run as
root because `debootstrap` creates device nodes and package metadata inside the
sysroot. If a sysroot or headers directory already exists, remove it first.

## Pack sysroot backups

After building a sysroot, it can be packed for backup or reuse. Replace the
path with the target sysroot that was built:

```bash
sudo tar -C .dexkit-sysroot/ubuntu-16.04-x86_64 \
--exclude=./dev \
--exclude=./proc \
--exclude=./sys \
--exclude=./run \
--exclude=./tmp \
--exclude=./var/tmp \
-cJf .dexkit-sysroot/ubuntu-16.04-x86_64.tar.xz .
```

The GCC 10 headers directory can be packed without special excludes:

```bash
tar -C .dexkit-sysroot/gcc10-headers \
-cJf .dexkit-sysroot/gcc10-headers.tar.xz .
```

To unpack a backup:

```bash
rm -rf .dexkit-sysroot/ubuntu-16.04-x86_64
mkdir -p .dexkit-sysroot/ubuntu-16.04-x86_64
tar -C .dexkit-sysroot/ubuntu-16.04-x86_64 \
-xJf .dexkit-sysroot/ubuntu-16.04-x86_64.tar.xz

rm -rf .dexkit-sysroot/gcc10-headers
mkdir -p .dexkit-sysroot/gcc10-headers
tar -C .dexkit-sysroot/gcc10-headers \
-xJf .dexkit-sysroot/gcc10-headers.tar.xz
```

## Ubuntu 20.04 target

Ubuntu 20.04 is the baseline C++20 build. Its sysroot includes GCC 10 headers
and libstdc++.

```bash
sudo env UBUNTU_CODENAME=focal \
bash build-support/linux-sysroot/build-linux-sysroot.sh \
.dexkit-sysroot/ubuntu-20.04-x86_64

SYSROOT="$PWD/.dexkit-sysroot/ubuntu-20.04-x86_64"

export CC="clang --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export CXX="clang++ --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export AR=llvm-ar
export RANLIB=llvm-ranlib
export LD=ld.lld

./gradlew :dexkit:cmakeBuild --console=plain
```

## Ubuntu 18.04 target

Ubuntu 18.04 has an older runtime, but it can use the GCC 10 C++ headers
for C++20 `std::span` and `std::string_view::starts_with` /
`std::string_view::ends_with`.

```bash
sudo env UBUNTU_CODENAME=bionic \
bash build-support/linux-sysroot/build-linux-sysroot.sh \
.dexkit-sysroot/ubuntu-18.04-x86_64

bash build-support/linux-sysroot/fetch-libstdcxx-headers.sh \
.dexkit-sysroot/gcc10-headers

SYSROOT="$PWD/.dexkit-sysroot/ubuntu-18.04-x86_64"
GCC10_HEADERS="$PWD/.dexkit-sysroot/gcc10-headers/usr/include"
COMPAT_HEADER="$PWD/.dexkit-sysroot/compat/glibc-libstdcxx10-compat.h"

mkdir -p "$(dirname "$COMPAT_HEADER")"
cp build-support/linux-sysroot/glibc-libstdcxx10-compat.h "$COMPAT_HEADER"

export CC="clang --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export CXX="clang++ --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export AR=llvm-ar
export RANLIB=llvm-ranlib
export LD=ld.lld
export CFLAGS=
export CXXFLAGS="-nostdinc++ -isystem $GCC10_HEADERS/c++/10 -isystem $GCC10_HEADERS/x86_64-linux-gnu/c++/10 -include $COMPAT_HEADER"
export LDFLAGS="-Wl,--no-as-needed -lpthread"

./gradlew :dexkit:cmakeBuild --console=plain
```

`glibc-libstdcxx10-compat.h` undefines libstdc++ feature macros for pthread
clock APIs that are unavailable in the older glibc target.

`-lpthread` is needed for pre-2.34 glibc targets such as Ubuntu 18.04 because
pthread is still provided by a separate `libpthread.so.0` there. `--no-as-needed`
keeps `libpthread.so.0` in the dynamic dependencies even if the linker only sees
pthread usage indirectly through libstdc++.

`fetch-libstdcxx-headers.sh` downloads `libstdc++-10-dev` by parsing Ubuntu
`Packages.xz` indexes. It defaults to the focal repositories because GCC 10 is
the distro compiler there.

## Ubuntu 16.04 target

Ubuntu 16.04 is the most constrained target. It uses the xenial GCC 5 runtime,
GCC 10 C++ headers for C++20 library declarations, and wrapper headers for
`<thread>` and `<future>` so thread construction uses the GCC 5 `_Impl_base`
ABI. Exceptions are disabled to avoid depending on newer exception ABI symbols.

```bash
sudo env UBUNTU_CODENAME=xenial \
bash build-support/linux-sysroot/build-linux-sysroot.sh \
.dexkit-sysroot/ubuntu-16.04-x86_64

bash build-support/linux-sysroot/fetch-libstdcxx-headers.sh \
.dexkit-sysroot/gcc10-headers

SYSROOT="$PWD/.dexkit-sysroot/ubuntu-16.04-x86_64"
GCC10_HEADERS="$PWD/.dexkit-sysroot/gcc10-headers/usr/include"
COMPAT_DIR="$PWD/.dexkit-sysroot/compat"
COMPAT_HEADER="$COMPAT_DIR/glibc-libstdcxx10-compat.h"
WRAPPER_DIR="$COMPAT_DIR/gcc5-thread-wrapper"

mkdir -p "$WRAPPER_DIR"
cp build-support/linux-sysroot/glibc-libstdcxx10-compat.h "$COMPAT_HEADER"
sed "s|@TARGET_SYSROOT@|$SYSROOT|g" \
build-support/linux-sysroot/gcc5-thread-wrapper/thread.in \
> "$WRAPPER_DIR/thread"
sed "s|@TARGET_SYSROOT@|$SYSROOT|g" \
build-support/linux-sysroot/gcc5-thread-wrapper/future.in \
> "$WRAPPER_DIR/future"

export CC="clang --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export CXX="clang++ --sysroot=$SYSROOT --gcc-toolchain=$SYSROOT/usr"
export AR=llvm-ar
export RANLIB=llvm-ranlib
export LD=ld.lld
export CFLAGS=
export CXXFLAGS="-fno-exceptions -nostdinc++ -isystem $WRAPPER_DIR -isystem $GCC10_HEADERS/c++/10 -isystem $GCC10_HEADERS/x86_64-linux-gnu/c++/10 -include $COMPAT_HEADER"
export LDFLAGS="-Wl,--no-as-needed -lpthread"

./gradlew :dexkit:cmakeBuild --console=plain
```

Compared with 18.04, the 16.04 target additionally needs the GCC 5
`<thread>`/`<future>` wrappers and `-fno-exceptions`. These are only for the
GCC 5 libstdc++ runtime; they can be removed if the minimum target is raised to
18.04 or newer.
Loading