Skip to content

Commit 9794727

Browse files
abueideclaude
andcommitted
fix(ios): strip Nix stdenv build environment for native Xcode builds
Nix's mkShell/stdenv sets ~80 build variables (NIX_CFLAGS_COMPILE, NIX_LDFLAGS, DEVELOPER_DIR pointing to Nix's apple-sdk, clang wrapper in PATH, etc.) that interfere with xcodebuild targeting iOS Simulator. The old ios_setup_native_toolchain() only unset 3 variables (LD, LDFLAGS, CFLAGS), leaving behind the Nix compiler wrapper and include paths that caused "unknown type name 'uint8_t'" errors in RN iOS CI. Extends the function to fully strip the Nix stdenv environment: unsets all NIX_* compiler variables, build tool overrides (AR, AS, NM, etc.), SDK/deployment variables, and filters Nix clang-wrapper/cctools/xcbuild from PATH. This is equivalent to devbox shellenv --omit-nix-env but without depending on that undocumented flag. Adds test-native-toolchain.sh (23 assertions) that validates the before/after: loads raw Nix env via devbox shellenv, verifies pollution is present, applies cleanup, then verifies all Nix stdenv vars are gone and iOS Simulator compilation succeeds without Nix wrapper warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1bad94c commit 9794727

3 files changed

Lines changed: 280 additions & 5 deletions

File tree

plugins/ios/virtenv/scripts/platform/core.sh

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,47 @@ ios_resolve_devbox_bin() {
140140
# ============================================================================
141141

142142
# Setup Darwin environment for iOS (use system Xcode/tools instead of Nix)
143+
#
144+
# Nix's mkShell/stdenv sets ~80 build environment variables (CC, NIX_CFLAGS_COMPILE,
145+
# DEVELOPER_DIR pointing to Nix's apple-sdk, etc.) that interfere with Xcode's native
146+
# build system. This function strips those variables so xcodebuild uses Apple's toolchain.
147+
#
148+
# This is equivalent to what `devbox shellenv --omit-nix-env` does, but implemented
149+
# explicitly so we don't depend on an undocumented flag.
143150
ios_setup_native_toolchain() {
144151
if [ "${IOS_NATIVE_TOOLCHAIN_APPLIED:-}" = "1" ]; then
145152
return 0
146153
fi
147154

148155
if [ "$(uname -s)" = "Darwin" ]; then
149-
# Unset standard build variables that Xcode tools read
150-
unset LD LDFLAGS CFLAGS
156+
# --- Unset Nix stdenv build variables ---
157+
158+
# Standard build tool overrides
159+
unset AR AS LD NM OBJCOPY OBJDUMP RANLIB SIZE STRINGS STRIP
160+
161+
# Compiler flags (Nix injects -isystem and -L paths to Nix store)
162+
unset CFLAGS LDFLAGS
163+
unset NIX_CFLAGS_COMPILE NIX_LDFLAGS
164+
165+
# Nix toolchain pointers
166+
unset NIX_CC NIX_BINTOOLS
167+
unset NIX_HARDENING_ENABLE
168+
unset NIX_ENFORCE_NO_NATIVE
169+
unset NIX_DONT_SET_RPATH NIX_DONT_SET_RPATH_FOR_BUILD NIX_NO_SELF_RPATH
170+
unset NIX_IGNORE_LD_THROUGH_GCC NIX_BUILD_CORES
171+
unset NIX_APPLE_SDK_VERSION
172+
173+
# Unset platform-specific Nix wrapper target variables
174+
# (e.g., NIX_CC_WRAPPER_TARGET_HOST_arm64_apple_darwin)
175+
for _ntc_var in $(env 2>/dev/null | sed -n 's/^\(NIX_[A-Z_]*WRAPPER_TARGET_HOST[^=]*\)=.*/\1/p'); do
176+
unset "$_ntc_var"
177+
done
178+
179+
# SDK/deployment variables (let Xcode resolve these from DEVELOPER_DIR)
180+
# DEVELOPER_DIR from Nix points to Nix's apple-sdk, not real Xcode
181+
unset SDKROOT DEVELOPER_DIR MACOSX_DEPLOYMENT_TARGET LD_DYLD_PATH
182+
183+
# --- Set native Apple toolchain ---
151184

152185
if [ -x /usr/bin/clang ]; then
153186
CC=/usr/bin/clang
@@ -159,12 +192,33 @@ ios_setup_native_toolchain() {
159192
if [ -n "$dev_dir" ]; then
160193
DEVELOPER_DIR="$dev_dir"
161194
export DEVELOPER_DIR
162-
PATH="$DEVELOPER_DIR/usr/bin:$PATH"
163195
fi
164196

165-
PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
197+
# --- Clean PATH: remove Nix build toolchain, keep packages ---
198+
# Nix stdenv adds clang-wrapper, cctools, xcbuild to PATH which shadow
199+
# system/Xcode tools. Filter those out while keeping everything else.
200+
# Nix package binaries remain accessible via .devbox/nix/profile/default/bin.
201+
_ntc_clean=""
202+
_ntc_oifs="$IFS"
203+
IFS=":"
204+
for _ntc_dir in $PATH; do
205+
case "$_ntc_dir" in
206+
/nix/store/*clang-wrapper*) continue ;;
207+
/nix/store/*clang-[0-9]*) continue ;;
208+
/nix/store/*cctools*) continue ;;
209+
/nix/store/*xcbuild*) continue ;;
210+
esac
211+
_ntc_clean="${_ntc_clean:+$_ntc_clean:}$_ntc_dir"
212+
done
213+
IFS="$_ntc_oifs"
214+
215+
# Prepend Xcode and system tool paths
216+
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
217+
if [ -n "${DEVELOPER_DIR:-}" ]; then
218+
PATH="$DEVELOPER_DIR/usr/bin:$PATH"
219+
fi
220+
PATH="$PATH:$_ntc_clean"
166221
export PATH
167-
unset SDKROOT
168222
fi
169223

170224
export IOS_NATIVE_TOOLCHAIN_APPLIED=1
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env bash
2+
# iOS Plugin - Native Toolchain Isolation Tests
3+
#
4+
# Verifies that ios_setup_native_toolchain() properly strips Nix stdenv
5+
# build environment variables so Xcode/clang uses Apple's native toolchain.
6+
#
7+
# Background: Nix's mkShell sets ~80 build variables (CC, NIX_CFLAGS_COMPILE,
8+
# DEVELOPER_DIR pointing to Nix's apple-sdk, etc.) that break xcodebuild when
9+
# targeting iOS Simulator. The native toolchain function must strip these.
10+
11+
set -euo pipefail
12+
13+
script_dir="$(cd "$(dirname "$0")" && pwd)"
14+
. "$script_dir/../test-framework.sh"
15+
setup_logging
16+
17+
echo "========================================"
18+
echo "iOS Native Toolchain Isolation"
19+
echo "========================================"
20+
21+
if [ "$(uname -s)" != "Darwin" ]; then
22+
echo "Skipping (not macOS)"
23+
exit 0
24+
fi
25+
26+
ios_example="$script_dir/../../../examples/ios"
27+
28+
if [ ! -d "$ios_example" ]; then
29+
echo "Skipping (examples/ios not found)"
30+
exit 0
31+
fi
32+
33+
# Use project-local scratch dir for test artifacts
34+
scratch_dir="${REPO_ROOT}/reports/scratch/native-toolchain"
35+
mkdir -p "$scratch_dir"
36+
37+
# ============================================================================
38+
# Setup: Load raw Nix environment (devbox shellenv without init_hooks)
39+
# ============================================================================
40+
41+
# shellenv sets all Nix stdenv variables without running plugin init_hooks,
42+
# giving us the "polluted" state that ios_setup_native_toolchain() must fix.
43+
eval "$(cd "$ios_example" && devbox shellenv 2>/dev/null)"
44+
45+
# ============================================================================
46+
# Phase 1: Verify Nix stdenv pollution is present (before cleanup)
47+
# ============================================================================
48+
49+
start_test "Nix stdenv sets NIX_CFLAGS_COMPILE"
50+
assert_not_empty "${NIX_CFLAGS_COMPILE:-}" "NIX_CFLAGS_COMPILE should be set by Nix stdenv"
51+
52+
start_test "Nix stdenv sets NIX_LDFLAGS"
53+
assert_not_empty "${NIX_LDFLAGS:-}" "NIX_LDFLAGS should be set by Nix stdenv"
54+
55+
start_test "Nix stdenv sets NIX_CC to Nix clang wrapper"
56+
assert_contains "${NIX_CC:-}" "/nix/store/" "NIX_CC should point to Nix store"
57+
58+
start_test "Nix stdenv sets DEVELOPER_DIR to Nix apple-sdk"
59+
assert_contains "${DEVELOPER_DIR:-}" "/nix/store/" "DEVELOPER_DIR should point to Nix SDK"
60+
61+
start_test "Nix clang wrapper is first in PATH"
62+
clang_path="$(command -v clang 2>/dev/null || true)"
63+
assert_contains "${clang_path:-}" "/nix/store/" "clang should resolve to Nix wrapper"
64+
65+
# If iOS Simulator SDK is available, verify Nix clang warns about cross-compilation
66+
SIM_SDK=$(/usr/bin/xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null || true)
67+
if [ -n "$SIM_SDK" ]; then
68+
test_c="$scratch_dir/test_uint8.c"
69+
cat > "$test_c" << 'EOF'
70+
#include <stdint.h>
71+
int main(void) { uint8_t x = 42; return (int)x - 42; }
72+
EOF
73+
74+
start_test "Nix clang wrapper warns about iOS cross-compilation"
75+
nix_output="$(clang -isysroot "$SIM_SDK" -target arm64-apple-ios17.0-simulator \
76+
-c "$test_c" -o /dev/null 2>&1 || true)"
77+
assert_contains "$nix_output" "nix-wrapped compiler" \
78+
"Nix wrapper should warn about cross-target compilation"
79+
fi
80+
81+
# ============================================================================
82+
# Phase 2: Apply native toolchain cleanup
83+
# ============================================================================
84+
85+
# Reset one-shot guards so we can apply the cleanup in this shell
86+
unset IOS_NATIVE_TOOLCHAIN_APPLIED
87+
unset IOS_CORE_LOADED IOS_CORE_LOADED_PID
88+
89+
export IOS_SCRIPTS_DIR="$ios_example/.devbox/virtenv/ios/scripts"
90+
. "$IOS_SCRIPTS_DIR/lib/lib.sh"
91+
. "$IOS_SCRIPTS_DIR/platform/core.sh"
92+
ios_setup_native_toolchain
93+
94+
# ============================================================================
95+
# Phase 3: Verify Nix stdenv pollution is gone (after cleanup)
96+
# ============================================================================
97+
98+
start_test "NIX_CFLAGS_COMPILE is unset after cleanup"
99+
assert_equal "" "${NIX_CFLAGS_COMPILE:-}" "NIX_CFLAGS_COMPILE should be empty"
100+
101+
start_test "NIX_LDFLAGS is unset after cleanup"
102+
assert_equal "" "${NIX_LDFLAGS:-}" "NIX_LDFLAGS should be empty"
103+
104+
start_test "NIX_CC is unset after cleanup"
105+
assert_equal "" "${NIX_CC:-}" "NIX_CC should be empty"
106+
107+
start_test "NIX_HARDENING_ENABLE is unset after cleanup"
108+
assert_equal "" "${NIX_HARDENING_ENABLE:-}" "NIX_HARDENING_ENABLE should be empty"
109+
110+
start_test "NIX_APPLE_SDK_VERSION is unset after cleanup"
111+
assert_equal "" "${NIX_APPLE_SDK_VERSION:-}" "NIX_APPLE_SDK_VERSION should be empty"
112+
113+
start_test "SDKROOT is unset after cleanup"
114+
assert_equal "" "${SDKROOT:-}" "SDKROOT should be empty (let Xcode resolve)"
115+
116+
start_test "MACOSX_DEPLOYMENT_TARGET is unset after cleanup"
117+
assert_equal "" "${MACOSX_DEPLOYMENT_TARGET:-}" "MACOSX_DEPLOYMENT_TARGET should be empty"
118+
119+
start_test "CC points to /usr/bin/clang"
120+
assert_equal "/usr/bin/clang" "${CC:-}" "CC should be system clang"
121+
122+
start_test "CXX points to /usr/bin/clang++"
123+
assert_equal "/usr/bin/clang++" "${CXX:-}" "CXX should be system clang++"
124+
125+
start_test "DEVELOPER_DIR does not point to Nix store"
126+
if echo "${DEVELOPER_DIR:-unset}" | grep -q "/nix/store/"; then
127+
echo " ✗ FAIL: DEVELOPER_DIR still points to Nix: ${DEVELOPER_DIR}"
128+
test_failed=$((test_failed + 1))
129+
else
130+
echo " ✓ PASS: DEVELOPER_DIR=${DEVELOPER_DIR:-unset}"
131+
test_passed=$((test_passed + 1))
132+
fi
133+
134+
start_test "clang resolves to /usr/bin/clang (not Nix wrapper)"
135+
clang_after="$(command -v clang 2>/dev/null || true)"
136+
assert_equal "/usr/bin/clang" "$clang_after" "clang should be system binary"
137+
138+
start_test "No Nix clang-wrapper paths in PATH"
139+
if echo "$PATH" | tr ':' '\n' | grep -q "clang-wrapper"; then
140+
echo " ✗ FAIL: PATH still contains clang-wrapper"
141+
echo " $(echo "$PATH" | tr ':' '\n' | grep "clang-wrapper")"
142+
test_failed=$((test_failed + 1))
143+
else
144+
echo " ✓ PASS: No clang-wrapper in PATH"
145+
test_passed=$((test_passed + 1))
146+
fi
147+
148+
start_test "No Nix cctools paths in PATH"
149+
if echo "$PATH" | tr ':' '\n' | grep -q "cctools"; then
150+
echo " ✗ FAIL: PATH still contains cctools"
151+
test_failed=$((test_failed + 1))
152+
else
153+
echo " ✓ PASS: No cctools in PATH"
154+
test_passed=$((test_passed + 1))
155+
fi
156+
157+
start_test "No Nix xcbuild paths in PATH"
158+
if echo "$PATH" | tr ':' '\n' | grep -q "xcbuild"; then
159+
echo " ✗ FAIL: PATH still contains xcbuild"
160+
test_failed=$((test_failed + 1))
161+
else
162+
echo " ✓ PASS: No xcbuild in PATH"
163+
test_passed=$((test_passed + 1))
164+
fi
165+
166+
start_test "Build tool vars unset (AR, AS, LD, NM)"
167+
all_unset=true
168+
for var in AR AS LD NM OBJCOPY OBJDUMP RANLIB SIZE STRINGS STRIP; do
169+
val="$(eval "printf '%s' \"\${$var:-}\"")"
170+
if [ -n "$val" ]; then
171+
echo "$var still set: $val"
172+
all_unset=false
173+
fi
174+
done
175+
if $all_unset; then
176+
echo " ✓ PASS: All build tool vars unset"
177+
test_passed=$((test_passed + 1))
178+
else
179+
echo " ✗ FAIL: Some build tool vars still set"
180+
test_failed=$((test_failed + 1))
181+
fi
182+
183+
# If iOS Simulator SDK is available, verify clean compilation
184+
if [ -n "$SIM_SDK" ] && [ -f "$scratch_dir/test_uint8.c" ]; then
185+
start_test "iOS Simulator compilation succeeds after cleanup"
186+
compile_out="$(clang -isysroot "$SIM_SDK" -target arm64-apple-ios17.0-simulator \
187+
-c "$scratch_dir/test_uint8.c" -o "$scratch_dir/test_uint8.o" 2>&1)"
188+
compile_exit=$?
189+
if [ $compile_exit -eq 0 ]; then
190+
echo " ✓ PASS: Compilation succeeded (exit 0)"
191+
test_passed=$((test_passed + 1))
192+
else
193+
echo " ✗ FAIL: Compilation failed (exit $compile_exit)"
194+
echo " $compile_out"
195+
test_failed=$((test_failed + 1))
196+
fi
197+
198+
start_test "No Nix wrapper warnings in compilation output"
199+
if echo "$compile_out" | grep -q "nix-wrapped"; then
200+
echo " ✗ FAIL: Nix wrapper warning still present"
201+
echo " $compile_out"
202+
test_failed=$((test_failed + 1))
203+
else
204+
echo " ✓ PASS: No Nix wrapper warnings"
205+
test_passed=$((test_passed + 1))
206+
fi
207+
fi
208+
209+
# Cleanup scratch
210+
rm -rf "$scratch_dir"
211+
212+
# Summary
213+
test_summary

tests/unit-tests.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ processes:
5353
availability:
5454
restart: "no"
5555

56+
test-ios-native-toolchain:
57+
command: "bash plugins/tests/ios/test-native-toolchain.sh"
58+
availability:
59+
restart: "no"
60+
5661
# iOS integration tests
5762
test-ios-device-mgmt:
5863
command: "bash tests/integration/ios/test-device-mgmt.sh"
@@ -96,6 +101,7 @@ processes:
96101
echo " - lib.sh"
97102
echo " - devices.sh"
98103
echo " - app-resolution"
104+
echo " - native-toolchain"
99105
echo " - device-mgmt (integration)"
100106
echo " - cache (integration)"
101107
echo ""
@@ -136,6 +142,8 @@ processes:
136142
condition: process_completed_successfully
137143
test-ios-app-resolution:
138144
condition: process_completed_successfully
145+
test-ios-native-toolchain:
146+
condition: process_completed_successfully
139147
test-ios-device-mgmt:
140148
condition: process_completed_successfully
141149
test-ios-cache:

0 commit comments

Comments
 (0)