Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
icedragon is a tool for easy cross (but also native) builds using LLVM
(clang, Rust).

It consists of:

- Container images which provide the toolchains for each target.
- A CLI tool, which is a wrapper around a container engine (Docker,
  podman) and allows easy execution of the toolchain.
  • Loading branch information
vadorovsky committed Feb 8, 2025
1 parent a6cf419 commit c7d9e17
Show file tree
Hide file tree
Showing 16 changed files with 944 additions and 3 deletions.
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: 00 4 * * *

env:
CARGO_TERM_COLOR: always

jobs:
lint-stable:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rust-src

- name: Run clippy
run: cargo clippy --all-targets --workspace -- --deny warnings

lint-nightly:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rustfmt, rust-src

- name: Check formatting
run: cargo fmt --all -- --check

build-container-image:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-musl
name: container ${{ matrix.type }} ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Pull gentoo/stage3 image
run: |
docker pull docker.io/gentoo/stage3:musl-llvm
- name: Build container image
if: github.ref == 'refs/heads/main'
run: |
cargo run build-container-image \
--tag ghcr.io/${{ github.repository }}/${{ matrix.target }}:latest \
--target ${{ matrix.target }} \
--push
- name: Build container image
if: github.ref != 'refs/heads/main'
run: |
cargo run build-container-image \
--tag ghcr.io/${{ github.repository }}/${{ matrix.target }}:${{ github.head_ref }} \
--target ${{ matrix.target }} \
--push
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ Cargo.lock
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "icedragon"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = { version = "1.0.89", default-features = false }
chrono = { version = "0.4", default-features = false }
clap = { version = "4.5", default-features = false, features = ["derive", "std"] }
env_logger = { version = "0.11", default-features = false }
log = { version = "0.4", default-features = false }
target-lexicon = { version = "0.12", default-features = false }
thiserror = { version = "1.0.64", default-features = false }
uuid = { version = "1.10", default-features = false, features = ["v4"] }
which = { version = "6.0", default-features = false }

[dev-dependencies]
assert_cmd = { version = "2.0", default-features = false }
git2 = { version = "0.20", default-features = false }
tempfile = { version = "3.16", default-features = false }

[[bin]]
name = "icedragon"
130 changes: 128 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,128 @@
# cross-llvm
Containerized (cross) LLVM toolchains
# icedragon

Linux cross-compilation suite for building portable, statically linked
software.

Currently supports the following languages:

* C
* C++
* Rust

Icedragon consists of two parts:

* Container images, with a "zero-setup" set of toolchains and essential
libraries.
* CLI, which leverages the container images, but feels like using a regular
compiler on your host.

It's based on:

* [Gentoo] ([musl-llvm] flavor), which is used as the base system for the
containers.
* [crossdev], which manages [Gentoo] sysroots for different
architectures.
* [musl] libc, which, unlike [glibc], can be statically linked
without imposing any runtime dependencies.
* [LLVM] compiler infrastructure.
* [rustup], which is used for managing [Rust] toolchains.

## How is icedragon different from Alpine Linux?

Let's start with similarities. Both icedragon and [Alpine] are using
[musl] as the C standard library. Both can be used to build portable,
statically linked binaries.

The most important difference is that icedragon is not a Linux distribution.
It's just [Gentoo] with specific configuration, provided as ready to use
containers. All packaging-related work necessary for icedragon to work is done
upstream.

The second difference is that [Alpine] uses [GCC] and [GNU C++ library].
Icedragon uses [LLVM] and [LLVM C++ library] and doesn't come with [GCC].

The last difference is strong focus on cross-compilation in icedragon, which
provides sysroots and toolchains for foreign architectures out of the box. It
does so thanks to [crossdev], which allows management and installation of
packages inside sysroots. There is no such tooling on Alpine.

## Featured libraries

Icedragon comes with a set of static libraries which can be considered "build
essentials" for the most of C/C++ software on Linux, as well as for Rust
crates, which don't vendor C dependencies and expect them to be present in the
system.

* Compression libraries:
* brotli
* zstd
* zlib
* Cryptography libraries:
* gpgme
* OpenSSL
* JSON
* json-c
* Key-value stores
* rocksdb
* Linux-specific libraries and utilities:
* util-linux
* Network libraries:
* c-ares
* libcurl
* Regular expressions:
* libpcre2

## GNU extensions

musl aims to stay compatible with the [POSIX C standard] for the sake of
portability. [glibc], on the other hand, adds so called [GNU extensions] -
additional modules and functions which are not part of the standard. As a
result, software making use of GNU extensions doesn't build with musl.

However, there are projects which provide standalone, musl-compatible ports of
various GNU extensions:

* [argp-standalone]
* [error-standalone]
* [musl-fts]

They still don't provide 100% compatibility with glibc, but they are good enough
to make building of the most [Gentoo] packages possible.

These ports can be linked statically and don't issue any `dlopen` calls.

A similar incompatibility exists between [compiler-rt] (the runtime library
provided by [LLVM], used in icedragon), and [libgcc] (a similar library
provided by [GCC]). [libgcc] comes with extensions, which are not included by
default in [compiler-rt]. It also exports symbols from [libunwind] ([GCC]'s
unwinder library).

LLVM community addressed that problem by creating [llvm-libgcc] subproject,
which:

* Builds a copy of [compiler-rt] with GNU extensions enabled.
* Uses [LLVM libunwind], which is compatible with GCC's unwinder library.
* Links them together, providing a drop-in replacement for [libgcc].

icedragon provides all the GNU extension ports mentioned above.

[Gentoo]: https://www.gentoo.org
[crossdev]: https://wiki.gentoo.org/wiki/Crossdev
[musl]: https://musl.libc.org
[glibc]: https://www.gnu.org/software/libc
[LLVM]: https://llvm.org
[rustup]: https://rustup.rs
[Rust]: https://www.rust-lang.org
[Alpine]: https://www.alpinelinux.org
[GCC]: https://gcc.gnu.org
[GNU C++ library]: https://gcc.gnu.org/onlinedocs/libstdc++
[LLVM C++ library]: https://libcxx.llvm.org
[POSIX C library]: https://en.wikipedia.org/wiki/C_POSIX_library
[GNU extensions]: https://www.gnu.org/software/gnulib/manual/html_node/Glibc-Function-Substitutes.html
[argp-standalone]: https://github.com/ericonr/argp-standalone
[error-standalone]: https://hacktivis.me/git/error-standalone
[musl-fts]: https://github.com/void-linux/musl-fts
[libgcc]: https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html
[libunwind]: https://libunwind.nongnu.org/docs.html
[llvm-libgcc]: https://github.com/llvm/llvm-project/tree/main/llvm-libgcc
[LLVM libunwind]: https://github.com/llvm/llvm-project/tree/main/libunwind
111 changes: 111 additions & 0 deletions containers/Dockerfile.cross-aarch64-unknown-linux-musl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
FROM docker.io/gentoo/stage3:musl-llvm

ARG LLVM_VERSION=19

ENV PATH="/root/.cargo/bin:/usr/lib/llvm/${LLVM_VERSION}/bin:${PATH}"
# Install only aarch64 user-space wrapper when installing app-emulation/qemu.
ENV QEMU_USER_TARGETS="aarch64"
# Enable static libraries for installed packages (zstd, zlib etc.).
ENV USE="static-libs"

COPY package.accept_keywords/* /etc/portage/package.accept_keywords/
COPY package.use/* /etc/portage/package.use/

# Install QEMU, which can be used for running foreign binaries and therefore is
# useful for running `cargo test` for foreign targets.
#
# Install the following dependencies, which can be considered "build essentials"
# for the most of C/C++ software on Linux, as well as for Rust crates, which
# don't vendor C dependencies and expect them to be present in the system. All of
# these dependencies provide static libraries.
#
# * Ports of GNU, non-POSIX functionality:
# * llvm-libgcc[0] - LLVM's runtime library with GNU symbols. Many
# third-party binaries, including Rust toolchains from rustup, rely on them
# and wouldn't work with vanilla LLVM compiler runtime[1][2].
# * musl-compatible ports of glibc's GNU extensions:
# * argp-standalone
# * error-standalone
# * musl-fts (packaged as fts-standalone in Gentoo)
# * Compression libraries:
# * brotli
# * zstd
# * zlib
# * Cryptography libraries:
# * gpgme
# * OpenSSL
# * JSON
# * json-c
# * Key-value stores
# * rocksdb
# * Linux-specific libraries and utilities:
# * util-linux
# * Network libraries:
# * c-ares
# * libcurl
# * Regular expressions:
# * libpcre2
#
# Create a cross sysroot using crossdev[3], which also creates wrappers for:
# * clang, which can be used for compiling C/C++ projects without doing the
# whole dance with `--target` and `--sysroot` arguments.
# * emerge, which let you install packages in the cross sysroot.
#
# Unpack the stage3 tarball into that sysroot to avoiding compilation of the
# whole base system from scratch. Otherwise, bootstraping the base sysroot with
# `emerge-aarch64-unknown-linux-musl @system` would take an eternity to run on
# free GitHub runners.
#
# [0] https://github.com/llvm/llvm-project/tree/main/llvm-libgcc
# [1] https://github.com/rust-lang/rust/issues/119504
# [2] https://github.com/rust-lang/rustup/issues/2213#issuecomment-1888615413
# [3] https://wiki.gentoo.org/wiki/Crossdev
RUN emerge-webrsync \
&& emerge \
app-emulation/qemu \
app-eselect/eselect-repository \
llvm-runtimes/libgcc \
sys-devel/crossdev \
&& eselect repository create crossdev \
&& crossdev --llvm --target aarch64-unknown-linux-musl \
&& curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/$(\
curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/latest-stage3-arm64-musl-llvm.txt" | \
grep tar.xz | cut -d ' ' -f 1)" | \
tar -xJpf - -C /usr/aarch64-unknown-linux-musl --exclude=dev --skip-old-files \
&& ln -s \
/etc/portage/repos.conf \
/usr/aarch64-unknown-linux-musl/etc/portage/repos.conf \
&& ln -s \
/etc/portage/package.accept_keywords \
/usr/aarch64-unknown-linux-musl/etc/portage/package.accept_keywords \
&& PORTAGE_CONFIGROOT=/usr/aarch64-unknown-linux-musl eselect profile set \
default/linux/arm64/23.0/musl/llvm \
&& aarch64-unknown-linux-musl-emerge \
app-arch/brotli \
app-arch/zstd \
app-crypt/gpgme \
dev-libs/json-c \
dev-libs/libpcre2 \
dev-libs/openssl \
dev-libs/rocksdb \
llvm-runtimes/libgcc \
net-dns/c-ares \
net-misc/curl \
sys-apps/util-linux \
sys-libs/argp-standalone \
sys-libs/error-standalone \
sys-libs/fts-standalone \
sys-libs/zlib \
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& rustup toolchain install stable --component rust-src \
&& rustup toolchain install beta --component rust-src \
&& rustup toolchain install nightly --component rust-src \
&& rustup target add aarch64-unknown-linux-musl \
&& rustup +beta target add aarch64-unknown-linux-musl \
&& rustup +nightly target add aarch64-unknown-linux-musl \
&& cargo install btfdump \
&& rm -rf \
/var/cache/binpkgs/* \
/var/cache/distfiles/* \
/var/db/repos/* \
/var/tmp/portage/*
Loading

0 comments on commit c7d9e17

Please sign in to comment.