Skip to content

Commit 6e90843

Browse files
committed
feat: add is_msvc_target() predicate + ci-fresh-install workflow
Refactor: - Extract is_msvc_target() to model.cppm alongside is_musl_target() - Replace 3 scattered tc.targetTriple.find("msvc") checks in clang.cppm, detect.cppm, provider.cppm CI: - Add ci-fresh-install.yml: validates first-time user install flow on all platforms (Linux, macOS, Windows) with zero cache. - Tests: xlings install mcpp → self-host build → mcpp new → mcpp run - Tests: import std with both GCC and LLVM (Linux), LLVM (macOS), LLVM+MSVC STL (Windows) - Catches issues that cached CI misses: incomplete sysroot, stale cfg paths, missing xpkg dependencies
1 parent 774b827 commit 6e90843

5 files changed

Lines changed: 239 additions & 5 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
name: ci-fresh-install
2+
3+
# Fresh install CI — validates the first-time user experience on all platforms.
4+
# No caches: simulates a clean machine where a user runs `xlings install mcpp`
5+
# for the first time, creates a project, and builds it.
6+
#
7+
# This catches issues that cached CI misses:
8+
# - Incomplete sysroot (missing linux kernel headers)
9+
# - Stale toolchain cfg/specs paths
10+
# - Missing xpkg dependencies (glibc, linux-headers)
11+
12+
on:
13+
push:
14+
branches: [ main ]
15+
pull_request:
16+
branches: [ main ]
17+
workflow_dispatch:
18+
19+
concurrency:
20+
group: ci-fresh-install-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
# ──────────────────────────────────────────────────────────────────
25+
# Linux: fresh install → new project → build with GCC + LLVM
26+
# ──────────────────────────────────────────────────────────────────
27+
linux-fresh:
28+
name: Linux fresh install
29+
runs-on: ubuntu-24.04
30+
timeout-minutes: 30
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Install xlings (clean, no cache)
35+
env:
36+
XLINGS_NON_INTERACTIVE: '1'
37+
XLINGS_VERSION: '0.4.30'
38+
run: |
39+
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
40+
curl -fsSL -o "/tmp/${tarball}" \
41+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
42+
tar -xzf "/tmp/${tarball}" -C /tmp
43+
"/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install
44+
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
45+
xlings --version
46+
xlings install mcpp -y
47+
echo "MCPP=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV"
48+
echo "PATH=$HOME/.xlings/subos/default/bin:$PATH" >> "$GITHUB_ENV"
49+
50+
- name: Build freshly-built mcpp from source (self-host)
51+
run: |
52+
# Use the xlings-installed mcpp to build from source, then
53+
# switch to the freshly-built binary for remaining tests.
54+
"$MCPP" self config --mirror GLOBAL
55+
"$MCPP" build
56+
MCPP_FRESH=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)")
57+
echo "MCPP=$MCPP_FRESH" >> "$GITHUB_ENV"
58+
59+
- name: "Fresh user: mcpp new → build → run (default GCC)"
60+
run: |
61+
TMP=$(mktemp -d)
62+
cd "$TMP"
63+
"$MCPP" new hello_gcc
64+
cd hello_gcc
65+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "Hello"
66+
67+
- name: "Fresh user: install LLVM → new → build → run"
68+
run: |
69+
"$MCPP" toolchain install llvm 20.1.7
70+
"$MCPP" toolchain default llvm@20.1.7
71+
TMP=$(mktemp -d)
72+
cd "$TMP"
73+
"$MCPP" new hello_llvm
74+
cd hello_llvm
75+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "Hello"
76+
77+
- name: "Fresh user: import std (GCC)"
78+
run: |
79+
"$MCPP" toolchain default gcc
80+
TMP=$(mktemp -d)
81+
cd "$TMP"
82+
mkdir -p src
83+
cat > mcpp.toml <<'EOF'
84+
[package]
85+
name = "std_test"
86+
version = "0.1.0"
87+
EOF
88+
cat > src/main.cpp <<'EOF'
89+
import std;
90+
int main() { std::println("import std works"); }
91+
EOF
92+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "import std works"
93+
94+
- name: "Fresh user: import std (LLVM)"
95+
run: |
96+
"$MCPP" toolchain default llvm@20.1.7
97+
TMP=$(mktemp -d)
98+
cd "$TMP"
99+
mkdir -p src
100+
cat > mcpp.toml <<'EOF'
101+
[package]
102+
name = "std_test_llvm"
103+
version = "0.1.0"
104+
EOF
105+
cat > src/main.cpp <<'EOF'
106+
import std;
107+
int main() { std::println("llvm import std works"); }
108+
EOF
109+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "llvm import std works"
110+
111+
# ──────────────────────────────────────────────────────────────────
112+
# macOS: fresh install → new project → build with LLVM
113+
# ──────────────────────────────────────────────────────────────────
114+
macos-fresh:
115+
name: macOS fresh install
116+
runs-on: macos-15
117+
timeout-minutes: 30
118+
steps:
119+
- uses: actions/checkout@v4
120+
121+
- name: Install xlings (clean, no cache)
122+
env:
123+
XLINGS_NON_INTERACTIVE: '1'
124+
XLINGS_VERSION: '0.4.30'
125+
run: |
126+
tarball="xlings-${XLINGS_VERSION}-macos-aarch64.tar.gz"
127+
curl -fsSL -o "/tmp/${tarball}" \
128+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
129+
tar -xzf "/tmp/${tarball}" -C /tmp
130+
"/tmp/xlings-${XLINGS_VERSION}-macos-aarch64/subos/default/bin/xlings" self install
131+
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
132+
xlings --version
133+
xlings install mcpp -y
134+
echo "MCPP=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV"
135+
echo "PATH=$HOME/.xlings/subos/default/bin:$PATH" >> "$GITHUB_ENV"
136+
137+
- name: Build freshly-built mcpp from source
138+
run: |
139+
"$MCPP" self config --mirror GLOBAL
140+
"$MCPP" build
141+
MCPP_FRESH=$(realpath "$(find target -type f -name mcpp | head -1)")
142+
echo "MCPP=$MCPP_FRESH" >> "$GITHUB_ENV"
143+
144+
- name: "Fresh user: mcpp new → build → run (LLVM)"
145+
run: |
146+
TMP=$(mktemp -d)
147+
cd "$TMP"
148+
"$MCPP" new hello_mac
149+
cd hello_mac
150+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "Hello"
151+
152+
- name: "Fresh user: import std (LLVM)"
153+
run: |
154+
TMP=$(mktemp -d)
155+
cd "$TMP"
156+
mkdir -p src
157+
cat > mcpp.toml <<'EOF'
158+
[package]
159+
name = "std_test_mac"
160+
version = "0.1.0"
161+
EOF
162+
cat > src/main.cpp <<'EOF'
163+
import std;
164+
int main() { std::println("macos import std works"); }
165+
EOF
166+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "macos import std works"
167+
168+
# ──────────────────────────────────────────────────────────────────
169+
# Windows: fresh install → new project → build with LLVM + MSVC STL
170+
# ──────────────────────────────────────────────────────────────────
171+
windows-fresh:
172+
name: Windows fresh install
173+
runs-on: windows-latest
174+
timeout-minutes: 30
175+
steps:
176+
- uses: actions/checkout@v4
177+
178+
- name: Install xlings (clean, no cache)
179+
env:
180+
XLINGS_NON_INTERACTIVE: '1'
181+
XLINGS_VERSION: '0.4.30'
182+
shell: bash
183+
run: |
184+
curl -fsSL -o /tmp/xlings.zip \
185+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/xlings-${XLINGS_VERSION}-windows-x86_64.zip"
186+
unzip -o /tmp/xlings.zip -d /tmp/xlings-extract
187+
XLINGS_DIR=$(find /tmp/xlings-extract -maxdepth 1 -type d -name 'xlings-*' | head -1)
188+
"$XLINGS_DIR/subos/default/bin/xlings.exe" self install
189+
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
190+
xlings --version
191+
xlings install mcpp -y
192+
MCPP="$USERPROFILE/.xlings/subos/default/bin/mcpp.exe"
193+
test -f "$MCPP"
194+
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
195+
196+
- name: Build freshly-built mcpp from source
197+
shell: bash
198+
run: |
199+
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
200+
"$MCPP" self config --mirror GLOBAL
201+
"$MCPP" build
202+
MCPP_FRESH=$(find target -type f -name 'mcpp.exe' | head -1)
203+
echo "MCPP=$(realpath "$MCPP_FRESH")" >> "$GITHUB_ENV"
204+
205+
- name: "Fresh user: mcpp new → build → run"
206+
shell: bash
207+
run: |
208+
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
209+
TMP=$(mktemp -d)
210+
cd "$TMP"
211+
"$MCPP" new hello_win
212+
cd hello_win
213+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "Hello"
214+
215+
- name: "Fresh user: import std"
216+
shell: bash
217+
run: |
218+
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
219+
TMP=$(mktemp -d)
220+
cd "$TMP"
221+
mkdir -p src
222+
cat > mcpp.toml <<'EOF'
223+
[package]
224+
name = "std_test_win"
225+
version = "0.1.0"
226+
EOF
227+
cat > src/main.cpp <<'EOF'
228+
import std;
229+
int main() { std::println("windows import std works"); }
230+
EOF
231+
"$MCPP" run 2>&1 | tee /dev/stderr | grep -q "windows import std works"

src/toolchain/clang.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ std::optional<std::filesystem::path> find_libcxx_std_module_source(
134134

135135
void enrich_toolchain(Toolchain& tc, const std::string& envPrefix) {
136136
// Clang targeting MSVC uses MSVC STL, not libc++.
137-
bool msvTarget = tc.targetTriple.find("msvc") != std::string::npos;
137+
bool msvTarget = is_msvc_target(tc);
138138
tc.stdlibId = msvTarget ? "msvc-stl" : "libc++";
139139
tc.stdlibVersion = tc.version.empty() ? "unknown" : tc.version;
140140
tc.linkRuntimeDirs = mcpp::toolchain::discover_link_runtime_dirs(

src/toolchain/detect.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ detect(const std::filesystem::path& explicit_compiler) {
7676
// patches (e.g. 19.44.35226 → 35227), causing stale BMI cache hits.
7777
// Query the effective triple which includes the actual MSVC version.
7878
if (tc.compiler == CompilerId::Clang
79-
&& tc.targetTriple.find("msvc") != std::string::npos) {
79+
&& is_msvc_target(tc)) {
8080
auto vr = run_capture(std::format(
8181
"{}{} -print-effective-triple 2>NUL",
8282
envPrefix,

src/toolchain/model.cppm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct DetectError { std::string message; };
5151
bool is_gcc(const Toolchain& tc);
5252
bool is_clang(const Toolchain& tc);
5353
bool is_musl_target(const Toolchain& tc);
54+
bool is_msvc_target(const Toolchain& tc);
5455

5556
struct BmiTraits {
5657
std::string_view bmiDir; // "gcm.cache" | "pcm.cache"
@@ -79,6 +80,10 @@ bool is_musl_target(const Toolchain& tc) {
7980
return tc.targetTriple.find("-musl") != std::string::npos;
8081
}
8182

83+
bool is_msvc_target(const Toolchain& tc) {
84+
return tc.targetTriple.find("msvc") != std::string::npos;
85+
}
86+
8287
BmiTraits bmi_traits(const Toolchain& tc) {
8388
if (is_clang(tc)) {
8489
return {

src/toolchain/provider.cppm

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@ ProviderCapabilities capabilities_for(const Toolchain& tc) {
8080

8181
case CompilerId::Clang: {
8282
// Clang targeting MSVC uses MSVC STL, not libc++.
83-
// We detect this the same way clang.cppm's enrich_toolchain does:
84-
// by checking the target triple for "msvc".
85-
bool msvc_target = tc.targetTriple.find("msvc") != std::string::npos;
83+
bool msvc_target = is_msvc_target(tc);
8684

8785
caps.has_scan_deps = true; // clang-scan-deps lives beside clang++
8886
caps.stdlib_id = msvc_target ? "msvc-stl" : "libc++";

0 commit comments

Comments
 (0)