Skip to content

Commit e2b098d

Browse files
committed
Initial import
0 parents  commit e2b098d

File tree

18 files changed

+482
-0
lines changed

18 files changed

+482
-0
lines changed

.github/workflows/build.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
6+
env:
7+
CARGO_TERM_COLOR: always
8+
9+
jobs:
10+
build-and-test:
11+
name: Build and test toolchains
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 60
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Docker
20+
uses: docker/setup-docker-action@v4
21+
22+
- name: Install Bubblewrap
23+
run: |
24+
sudo apt-get -y install bubblewrap
25+
26+
# See https://github.com/DevToys-app/DevToys/issues/1373
27+
sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0
28+
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
29+
30+
- name: Prepare configuration
31+
run: |
32+
mkdir work
33+
cd work
34+
35+
# Linux target is excluded to shorten CI execution
36+
cat <<EOF > config.nuon
37+
{
38+
rust_version: "1.87.0",
39+
alpine_version: "3.21",
40+
targets: [
41+
"x86_64-pc-windows-gnu",
42+
"aarch64-apple-darwin"
43+
]
44+
}
45+
EOF
46+
47+
- name: Build toolchains
48+
run: cd work && ../rustx build config.nuon
49+
50+
- name: Build Docker image
51+
run: cd work && docker build --tag rustx-test:latest -f build/Dockerfile .
52+
53+
- name: Test Ring for all targets
54+
run: cd work && ../rustx test config.nuon rustx-test:latest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/build
2+
/rootfs
3+
/toolchains

.nu_version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.104.0

README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# alpine-rustx
2+
[![CI](https://github.com/tindzk/alpine-rustx/actions/workflows/build.yaml/badge.svg)](https://github.com/tindzk/alpine-rustx/actions/workflows/build.yaml)
3+
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
4+
5+
alpine-rustx generates lightweight, Alpine-based Docker images for cross-compiling Rust projects to Linux, macOS and Windows across multiple architectures. The environments are pre-configured for the selected targets, eliminating the usual hassle of setting up toolchains.
6+
7+
## Features
8+
- Cross-compile to Linux, Windows and macOS from a single Docker image
9+
- Images are customisable using a simple [NUON](https://www.nushell.sh/book/loading_data.html#nuon) configuration file
10+
- Toolchain builds are cached across configuration changes for fast iterations
11+
- Configuration metadata is embedded in the Docker image for auditing purposes
12+
- Supports two isolation backends: Bubblewrap, Docker
13+
- Tiny Nushell code base (< 500 LOC), designed with modularity in mind
14+
15+
## Example
16+
A self-contained build environment can be defined using a simple configuration file such as:
17+
18+
```nuon
19+
{
20+
rust_version: "1.86.0"
21+
alpine_version: "3.21"
22+
targets: [
23+
"aarch64-unknown-linux-musl"
24+
"x86_64-pc-windows-gnu"
25+
"aarch64-apple-darwin"
26+
]
27+
}
28+
```
29+
30+
The generated Docker image allows you to compile your Rust project for any of the targets using `cargo build --target <target>`, without needing to set up Cargo to use the correct compiler or linker for each platform.
31+
32+
## Requirements
33+
- Docker
34+
- [Bubblewrap](https://github.com/containers/bubblewrap) (optional)
35+
- A minimum of 15 GB free disk space is recommended
36+
37+
## Installation
38+
Clone the repository:
39+
```shell
40+
git clone https://github.com/tindzk/alpine-rustx.git
41+
cd alpine-rustx
42+
```
43+
44+
Or download the archive:
45+
```shell
46+
wget https://github.com/tindzk/alpine-rustx/archive/refs/heads/main.zip
47+
unzip main.zip
48+
cd alpine-rustx-main
49+
```
50+
51+
Set up the environment:
52+
```shell
53+
export RUSTX_PATH=$(pwd)
54+
export PATH=$RUSTX_PATH:$PATH
55+
```
56+
57+
## Usage
58+
Create a new project and copy the sample configuration:
59+
60+
```shell
61+
mkdir sample-project && cd sample-project
62+
cp $RUSTX_PATH/config.nuon.sample rustx.nuon
63+
```
64+
65+
Edit `rustx.nuon` to match your requirements.
66+
67+
Build all toolchains and generate the `Dockerfile`:
68+
69+
```shell
70+
rustx build rustx.nuon
71+
```
72+
73+
> [!NOTE]
74+
> This automatically fetches the correct Nu version on first run.
75+
76+
Finally, build the Docker image:
77+
78+
```shell
79+
docker build --tag myproject:latest -f build/Dockerfile .
80+
```
81+
82+
## Testing
83+
Validate that the image works correctly by building the [Ring](https://docs.rs/crate/ring/latest) cryptography library which depends on system libraries:
84+
85+
```shell
86+
rustx test rustx.nuon myproject:latest
87+
```
88+
89+
## Credits
90+
alpine-rustx relies on these projects:
91+
- [musl-cross-make](https://github.com/richfelker/musl-cross-make/)
92+
- [mingw-w64-build](https://github.com/Zeranoe/mingw-w64-build)
93+
94+
## Licence
95+
Licenced under the [Apache Licence v2.0](https://www.apache.org/licenses/LICENSE-2.0).

config.nuon.sample

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# alpine-rustx configuration
3+
#
4+
5+
{
6+
rust_version: "1.87.0"
7+
alpine_version: "3.21"
8+
targets: [
9+
"x86_64-unknown-linux-musl"
10+
"aarch64-unknown-linux-musl"
11+
#"x86_64-pc-windows-gnu"
12+
#"i686-pc-windows-gnu"
13+
#"aarch64-apple-darwin"
14+
#"x86_64-apple-darwin"
15+
]
16+
17+
# Install additional Cargo components (default: [])
18+
#
19+
#components: ["clippy", "rustfmt"]
20+
21+
# Install nextest (default: false)
22+
#
23+
#nextest: true
24+
25+
# Run custom commands when building Docker image
26+
#
27+
#commands: [
28+
# "apk add --no-cache nodejs npm"
29+
#]
30+
31+
# Isolation mechanism used for building toolchains
32+
#
33+
# Possible values: bubblewrap (default), docker
34+
#
35+
#isolation: "docker"
36+
}

rustx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
3+
NU_VERSION=$(cat $(dirname $0)/.nu_version)
4+
5+
if [ ! -f build/nu-$NU_VERSION ]; then
6+
name=nu-$NU_VERSION-$(uname -m)-unknown-linux-musl
7+
8+
mkdir -p build/
9+
cd build/
10+
11+
wget "https://github.com/nushell/nushell/releases/download/$NU_VERSION/$name.tar.gz"
12+
tar -zxvf ${name}.tar.gz $name/nu
13+
rm ${name}.tar.gz
14+
mv $name/nu nu-$NU_VERSION
15+
rmdir $name
16+
17+
cd -
18+
fi
19+
20+
script="$(dirname "$0")/src/commands/$1.nu"
21+
shift
22+
23+
exec build/nu-$NU_VERSION $script "$@"

src/build.nu

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env nu
2+
3+
use targets.nu
4+
use platforms/linux.nu
5+
use platforms/windows.nu
6+
use platforms/macos.nu
7+
8+
let config = open /build/config.nuon
9+
10+
'nameserver 8.8.8.8' | save -f /etc/resolv.conf
11+
12+
$env.PATH = ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
13+
14+
let all_targets = (targets parse $config.targets)
15+
16+
for target in $all_targets.linux {
17+
print $"Building ($target)..."
18+
linux build $target
19+
}
20+
21+
for target in $all_targets.windows {
22+
print $"Building ($target)..."
23+
windows build $target
24+
}
25+
26+
if ($all_targets.macos | is-not-empty) {
27+
print "Building macOS..."
28+
macos build
29+
}

src/commands/build.nu

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use ../dockerfile.nu
2+
use ../isolation/bubblewrap.nu
3+
use ../isolation/docker.nu
4+
use ../config.nu
5+
6+
def main [config_path: string] {
7+
let root_path = ($env.FILE_PWD | path join "../../")
8+
let nu_version = (open ($root_path | path join ".nu_version") | str trim)
9+
let cfg = config normalise (open $config_path)
10+
11+
mkdir build
12+
mkdir toolchains
13+
14+
let spec = {
15+
rw_bind: {
16+
$"(pwd)/build": "/build",
17+
$"(pwd)/toolchains": "/toolchains"
18+
}
19+
ro_bind: {
20+
$"(pwd)/build/nu-($nu_version)": "/bin/nu",
21+
($config_path | path expand): "/build/config.nuon",
22+
($root_path | path join "src"): "/src"
23+
}
24+
command: "/src/build.nu"
25+
}
26+
27+
if $cfg.isolation == "bubblewrap" {
28+
bubblewrap run $spec
29+
} else if $cfg.isolation == "docker" {
30+
docker run $spec
31+
} else {
32+
error "Invalid isolation mode"
33+
}
34+
35+
dockerfile generate $cfg | save -f build/Dockerfile
36+
print "Generated build/Dockerfile"
37+
38+
"build/\nrootfs/" | save -f build/Dockerfile.dockerignore
39+
print "Generated build/Dockerfile.dockerignore"
40+
}

src/commands/test.nu

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def main [config: string, image: string] {
2+
let config = open $config
3+
let cargo_commands = ($config.targets | each {|t| $"cargo build --target ($t)"} | str join " && ")
4+
docker run $image sh -c $"cargo new /project && cd /project && cargo add ring && ($cargo_commands)"
5+
}

src/config.nu

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export def normalise [config: record] {
2+
return ($config
3+
| default [] components
4+
| default false nextest
5+
| default [] commands
6+
| default "bubblewrap" isolation
7+
)
8+
}

0 commit comments

Comments
 (0)