Skip to content

Commit a57e128

Browse files
authored
Merge pull request #46 from mcpp-community/feat/macos-release
feat: macOS release — bootstrap + self-host + release workflow
2 parents 27d0e97 + 51f37cb commit a57e128

5 files changed

Lines changed: 420 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: bootstrap-macos
2+
3+
# One-shot workflow to produce the first macOS mcpp binary.
4+
# Uses xmake + xlings LLVM to compile mcpp from source.
5+
# Once a macOS binary exists, mcpp can self-host for future releases.
6+
7+
on:
8+
workflow_dispatch:
9+
10+
jobs:
11+
bootstrap:
12+
name: Bootstrap mcpp (macOS ARM64)
13+
runs-on: macos-15
14+
timeout-minutes: 30
15+
env:
16+
XLINGS_NON_INTERACTIVE: '1'
17+
XLINGS_VERSION: '0.4.30'
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: System info
22+
run: |
23+
uname -a
24+
sw_vers
25+
xcrun --show-sdk-path
26+
27+
- name: Install xlings
28+
run: |
29+
WORK=$(mktemp -d)
30+
tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz"
31+
curl -fsSL -o "${WORK}/${tarball}" \
32+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
33+
tar -xzf "${WORK}/${tarball}" -C "${WORK}"
34+
"${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install
35+
echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH"
36+
echo "$HOME/.xlings/bin" >> "$GITHUB_PATH"
37+
38+
- name: Install LLVM + xmake
39+
run: |
40+
xlings install llvm -y || xlings install llvm@20.1.7 -y
41+
brew install xmake
42+
LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang++" | head -1 | xargs dirname | xargs dirname)
43+
echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV"
44+
"$LLVM_ROOT/bin/clang++" --version
45+
xmake --version
46+
47+
- name: Build mcpp with xmake
48+
run: |
49+
# Generate xmake.lua if not present
50+
if [ ! -f xmake.lua ]; then
51+
cat > xmake.lua << 'EOF'
52+
add_rules("mode.release")
53+
set_languages("c++23")
54+
55+
package("cmdline")
56+
set_homepage("https://github.com/mcpplibs/cmdline")
57+
set_description("Modern C++ command-line parsing library")
58+
set_license("Apache-2.0")
59+
add_urls("https://github.com/mcpplibs/cmdline/archive/refs/tags/$(version).tar.gz")
60+
add_versions("0.0.1", "3fb2f5495c1a144485b3cbb2e43e27059151633460f702af0f3851cbff387ef0")
61+
on_install(function (package)
62+
import("package.tools.xmake").install(package)
63+
end)
64+
package_end()
65+
66+
add_requires("cmdline 0.0.1")
67+
68+
target("mcpp")
69+
set_kind("binary")
70+
add_files("src/main.cpp")
71+
add_files("src/**.cppm")
72+
add_packages("cmdline")
73+
add_includedirs("src/libs/json")
74+
set_policy("build.c++.modules", true)
75+
EOF
76+
fi
77+
78+
# Configure with xlings LLVM
79+
xmake f -y -m release --toolchain=llvm --sdk="$LLVM_ROOT"
80+
# Build
81+
xmake build -y mcpp
82+
83+
- name: Verify built binary
84+
run: |
85+
MCPP=$(find build -name mcpp -type f -perm +111 | head -1)
86+
test -x "$MCPP"
87+
file "$MCPP"
88+
"$MCPP" --version
89+
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
90+
91+
- name: Package
92+
id: package
93+
run: |
94+
VERSION=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml)
95+
TARBALL="mcpp-${VERSION}-darwin-arm64.tar.gz"
96+
WRAPPER="mcpp-${VERSION}-darwin-arm64"
97+
98+
mkdir -p "dist/$WRAPPER/bin"
99+
cp "$MCPP" "dist/$WRAPPER/bin/mcpp"
100+
strip "dist/$WRAPPER/bin/mcpp" 2>/dev/null || true
101+
cp LICENSE "dist/$WRAPPER/" 2>/dev/null || true
102+
cp README.md "dist/$WRAPPER/" 2>/dev/null || true
103+
104+
cat > "dist/$WRAPPER/mcpp" << 'LAUNCHER'
105+
#!/bin/sh
106+
exec "$(dirname "$0")/bin/mcpp" "$@"
107+
LAUNCHER
108+
chmod +x "dist/$WRAPPER/mcpp"
109+
110+
# Bundle xlings
111+
XLINGS_BIN="$HOME/.xlings/subos/default/bin/xlings"
112+
if [ -x "$XLINGS_BIN" ]; then
113+
mkdir -p "dist/$WRAPPER/registry/bin"
114+
cp "$XLINGS_BIN" "dist/$WRAPPER/registry/bin/xlings"
115+
fi
116+
117+
(cd dist && tar -czf "$TARBALL" "$WRAPPER")
118+
(cd dist && shasum -a 256 "$TARBALL" > "$TARBALL.sha256")
119+
120+
echo "tarball=dist/$TARBALL" >> "$GITHUB_OUTPUT"
121+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
122+
ls -la dist/
123+
124+
- name: Smoke test
125+
run: |
126+
SMOKE=$(mktemp -d)
127+
tar -xzf "${{ steps.package.outputs.tarball }}" -C "$SMOKE"
128+
VERSION="${{ steps.package.outputs.version }}"
129+
"$SMOKE/mcpp-${VERSION}-darwin-arm64/bin/mcpp" --version
130+
"$SMOKE/mcpp-${VERSION}-darwin-arm64/mcpp" --version
131+
132+
- name: Upload artifact
133+
uses: actions/upload-artifact@v4
134+
with:
135+
name: mcpp-darwin-arm64
136+
path: |
137+
dist/mcpp-*-darwin-arm64.tar.gz
138+
dist/mcpp-*-darwin-arm64.tar.gz.sha256

.github/workflows/ci-macos.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,24 @@ jobs:
267267
Darwin-x86_64) echo "PASS: would select darwin-x86_64" ;;
268268
*) echo "FAIL: unexpected platform"; exit 1 ;;
269269
esac
270+
271+
- name: Install xmake (for bootstrap)
272+
run: |
273+
brew install xmake
274+
xmake --version
275+
276+
- name: Bootstrap mcpp from source (xmake)
277+
run: |
278+
export LLVM_ROOT="$LLVM_ROOT"
279+
bash scripts/bootstrap-macos.sh "$LLVM_ROOT"
280+
./target/bootstrap/bin/mcpp --version
281+
282+
- name: Self-host (mcpp builds mcpp)
283+
run: |
284+
# Put bootstrapped mcpp on PATH so build.ninja can find it for dyndep
285+
export PATH="$PWD/target/bootstrap/bin:$PATH"
286+
mcpp build
287+
SELFHOST=$(find target -path "*/bin/mcpp" -not -path "*/bootstrap/*" -not -path "*/build/*" | head -1)
288+
test -x "$SELFHOST"
289+
"$SELFHOST" --version
290+
echo ":: Self-host successful!"

.github/workflows/release.yml

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,146 @@ jobs:
255255
dist/SHA256SUMS
256256
dist/mcpp-${{ steps.stage.outputs.version }}.tar.gz
257257
dist/mcpp.lua
258+
259+
build-macos:
260+
name: build (macOS / ARM64)
261+
runs-on: macos-15
262+
needs: build-release
263+
permissions:
264+
contents: write
265+
timeout-minutes: 30
266+
steps:
267+
- uses: actions/checkout@v4
268+
with:
269+
fetch-depth: 0
270+
271+
- name: Resolve tag
272+
id: resolve
273+
run: |
274+
if [ "${{ github.event_name }}" = "push" ]; then
275+
TAG="${{ github.ref_name }}"
276+
elif [ -n "${{ github.event.inputs.tag }}" ]; then
277+
TAG="${{ github.event.inputs.tag }}"
278+
else
279+
VER=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml)
280+
TAG="v$VER"
281+
fi
282+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
283+
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
284+
if [ "${{ github.event_name }}" = "workflow_dispatch" ] \
285+
&& git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then
286+
git checkout --detach "refs/tags/$TAG"
287+
fi
288+
289+
- name: Cache xlings
290+
uses: actions/cache@v4
291+
with:
292+
path: ~/.xlings
293+
key: xlings-macos15-release-${{ hashFiles('.xlings.json') }}
294+
restore-keys: |
295+
xlings-macos15-release-
296+
xlings-macos15-arm64-
297+
298+
- name: Bootstrap xlings + LLVM
299+
env:
300+
XLINGS_NON_INTERACTIVE: '1'
301+
XLINGS_VERSION: '0.4.30'
302+
run: |
303+
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
304+
WORK=$(mktemp -d)
305+
tarball="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz"
306+
curl -fsSL -o "${WORK}/${tarball}" \
307+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
308+
tar -xzf "${WORK}/${tarball}" -C "${WORK}"
309+
"${WORK}/xlings-${XLINGS_VERSION}-macosx-arm64/subos/default/bin/xlings" self install
310+
fi
311+
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
312+
xlings --version
313+
# Install LLVM
314+
xlings install llvm -y || xlings install llvm@20.1.7 -y
315+
LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang++" | head -1 | xargs dirname | xargs dirname)
316+
echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV"
317+
318+
- name: Install xmake (for bootstrap)
319+
run: brew install xmake
320+
321+
- name: Bootstrap-compile mcpp (xmake + LLVM)
322+
run: |
323+
export LLVM_ROOT="$LLVM_ROOT"
324+
bash scripts/bootstrap-macos.sh "$LLVM_ROOT"
325+
./target/bootstrap/bin/mcpp --version
326+
327+
- name: Self-host rebuild (mcpp builds mcpp)
328+
run: |
329+
# Put bootstrapped mcpp on PATH so build.ninja can find it for dyndep
330+
export PATH="$PWD/target/bootstrap/bin:$PATH"
331+
mcpp build
332+
# Find the self-hosted binary
333+
SELFHOST=$(find target -path "*/bin/mcpp" -not -path "*/bootstrap/*" -not -path "*/build/*" | head -1)
334+
test -x "$SELFHOST"
335+
"$SELFHOST" --version
336+
echo "SELFHOST=$SELFHOST" >> "$GITHUB_ENV"
337+
338+
- name: Package macOS release
339+
id: stage
340+
run: |
341+
VERSION="${{ steps.resolve.outputs.version }}"
342+
TARBALL_NAME="mcpp-${VERSION}-darwin-arm64.tar.gz"
343+
WRAPPER="mcpp-${VERSION}-darwin-arm64"
344+
345+
# Create release layout
346+
STAGING=$(mktemp -d)
347+
mkdir -p "$STAGING/$WRAPPER/bin"
348+
cp "$SELFHOST" "$STAGING/$WRAPPER/bin/mcpp"
349+
# Strip (Mach-O)
350+
strip "$STAGING/$WRAPPER/bin/mcpp" 2>/dev/null || true
351+
# Copy metadata
352+
cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true
353+
cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true
354+
355+
# Shell launcher (same as Linux)
356+
cat > "$STAGING/$WRAPPER/mcpp" << 'LAUNCHER'
357+
#!/bin/sh
358+
exec "$(dirname "$0")/bin/mcpp" "$@"
359+
LAUNCHER
360+
chmod +x "$STAGING/$WRAPPER/mcpp"
361+
362+
# Bundle xlings for install.sh consumers
363+
XLINGS_BIN="$HOME/.xlings/subos/default/bin/xlings"
364+
if [ -x "$XLINGS_BIN" ]; then
365+
mkdir -p "$STAGING/$WRAPPER/registry/bin"
366+
cp "$XLINGS_BIN" "$STAGING/$WRAPPER/registry/bin/xlings"
367+
chmod +x "$STAGING/$WRAPPER/registry/bin/xlings"
368+
fi
369+
370+
# Create tarball
371+
mkdir -p dist
372+
(cd "$STAGING" && tar -czf "$GITHUB_WORKSPACE/dist/${TARBALL_NAME}" "$WRAPPER")
373+
# Versionless alias
374+
cp "dist/${TARBALL_NAME}" "dist/mcpp-darwin-arm64.tar.gz"
375+
# SHA256
376+
(cd dist && shasum -a 256 "${TARBALL_NAME}" > "${TARBALL_NAME}.sha256")
377+
(cd dist && shasum -a 256 "mcpp-darwin-arm64.tar.gz" > "mcpp-darwin-arm64.tar.gz.sha256")
378+
379+
echo "tarball=${TARBALL_NAME}" >> "$GITHUB_OUTPUT"
380+
ls -la dist/
381+
382+
- name: Smoke-test the tarball
383+
run: |
384+
VERSION="${{ steps.resolve.outputs.version }}"
385+
TARBALL_NAME="${{ steps.stage.outputs.tarball }}"
386+
WRAPPER="${TARBALL_NAME%.tar.gz}"
387+
SMOKE=$(mktemp -d)
388+
tar -xzf "dist/${TARBALL_NAME}" -C "$SMOKE"
389+
"$SMOKE/$WRAPPER/bin/mcpp" --version
390+
"$SMOKE/$WRAPPER/mcpp" --version | grep -q "$VERSION"
391+
392+
- name: Upload macOS artifacts to release
393+
uses: softprops/action-gh-release@v2
394+
with:
395+
tag_name: ${{ steps.resolve.outputs.tag }}
396+
files: |
397+
dist/mcpp-${{ steps.resolve.outputs.version }}-darwin-arm64.tar.gz
398+
dist/mcpp-${{ steps.resolve.outputs.version }}-darwin-arm64.tar.gz.sha256
399+
dist/mcpp-darwin-arm64.tar.gz
400+
dist/mcpp-darwin-arm64.tar.gz.sha256

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ include_dirs = ["src/libs/json"]
1313

1414
[toolchain]
1515
default = "gcc@16.1.0"
16+
macos = "llvm@20.1.7"
1617

1718
# Per-target overrides: `mcpp build --target x86_64-linux-musl` (or the
1819
# four-segment form `x86_64-unknown-linux-musl`) picks musl-gcc 15.1 + full

0 commit comments

Comments
 (0)