Skip to content

Commit 4e1ef38

Browse files
author
Karim Alweheshy
committed
Add custom toolchain rule and tests
Signed-off-by: Karim Alweheshy <[email protected]>
1 parent d78f143 commit 4e1ef38

File tree

5 files changed

+275
-0
lines changed

5 files changed

+275
-0
lines changed

test/internal/custom_toolchain/BUILD

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# buildifier: disable=bzl-visibility
2+
load("//xcodeproj/internal:custom_toolchain.bzl", "custom_toolchain")
3+
4+
# Example swiftc override for testing
5+
filegroup(
6+
name = "test_swiftc",
7+
srcs = ["test_swiftc.sh"],
8+
visibility = ["//visibility:public"],
9+
)
10+
11+
# Test target for custom_toolchain
12+
custom_toolchain(
13+
name = "test_toolchain",
14+
overrides = {
15+
# Key is a label target, value is the tool name
16+
":test_swiftc": "swiftc",
17+
},
18+
toolchain_name = "TestCustomToolchain",
19+
)
20+
21+
# Add a simple test rule that depends on the toolchain
22+
sh_test(
23+
name = "custom_toolchain_test",
24+
srcs = ["custom_toolchain_test.sh"],
25+
args = ["$(location :test_toolchain)"],
26+
data = [":test_toolchain"],
27+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# The first argument should be the path to the toolchain directory
6+
TOOLCHAIN_DIR="$1"
7+
8+
echo "Verifying toolchain at: $TOOLCHAIN_DIR"
9+
10+
# Check that the toolchain directory exists
11+
if [[ ! -d "$TOOLCHAIN_DIR" ]]; then
12+
echo "ERROR: Toolchain directory does not exist: $TOOLCHAIN_DIR"
13+
exit 1
14+
fi
15+
16+
# Check that ToolchainInfo.plist exists
17+
if [[ ! -f "$TOOLCHAIN_DIR/ToolchainInfo.plist" ]]; then
18+
echo "ERROR: ToolchainInfo.plist not found in toolchain"
19+
exit 1
20+
fi
21+
22+
# Check for correct identifiers in the plist
23+
if ! grep -q "BazelRulesXcodeProj" "$TOOLCHAIN_DIR/ToolchainInfo.plist"; then
24+
echo "ERROR: ToolchainInfo.plist doesn't contain BazelRulesXcodeProj"
25+
exit 1
26+
fi
27+
28+
# Check that our custom swiftc is properly linked/copied
29+
if [[ ! -f "$TOOLCHAIN_DIR/usr/bin/swiftc" ]]; then
30+
echo "ERROR: swiftc not found in toolchain"
31+
exit 1
32+
fi
33+
34+
# Ensure swiftc is executable
35+
if [[ ! -x "$TOOLCHAIN_DIR/usr/bin/swiftc" ]]; then
36+
echo "ERROR: swiftc is not executable"
37+
exit 1
38+
fi
39+
40+
# Test if the swiftc actually runs
41+
if ! "$TOOLCHAIN_DIR/usr/bin/swiftc" --version > /dev/null 2>&1; then
42+
echo "WARN: swiftc doesn't run correctly, but this is expected in tests"
43+
fi
44+
45+
echo "Custom toolchain validation successful!"
46+
exit 0
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
# This is a test script that simulates a custom Swift compiler
4+
# It will be used as an override in the custom toolchain
5+
6+
# Print inputs for debugging
7+
echo "Custom swiftc called with args: $@" >&2
8+
9+
# In a real override, you would do something meaningful with the args
10+
# For testing, just exit successfully
11+
exit 0
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Implementation of the `custom_toolchain` rule."""
2+
3+
def _get_xcode_product_version(*, xcode_config):
4+
raw_version = str(xcode_config.xcode_version())
5+
if not raw_version:
6+
fail("""\
7+
`xcode_config.xcode_version` was not set. This is a bazel bug. Try again.
8+
""")
9+
10+
version_components = raw_version.split(".")
11+
if len(version_components) < 4:
12+
# This will result in analysis cache misses, but it's better than
13+
# failing
14+
return raw_version
15+
16+
return version_components[3]
17+
18+
def _custom_toolchain_impl(ctx):
19+
xcode_version = _get_xcode_product_version(
20+
xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig],
21+
)
22+
23+
toolchain_name_base = ctx.attr.toolchain_name
24+
toolchain_dir = ctx.actions.declare_directory(
25+
toolchain_name_base + "{}".format(xcode_version) + ".xctoolchain",
26+
)
27+
28+
resolved_overrides = {}
29+
override_files = []
30+
31+
for tool_target, tool_name in ctx.attr.overrides.items():
32+
files = tool_target.files.to_list()
33+
if not files:
34+
fail("ERROR: Override for '{}' does not produce any files!".format(tool_name))
35+
36+
if len(files) > 1:
37+
fail("ERROR: Override for '{}' produces multiple files ({}). Each override must have exactly one file.".format(
38+
tool_name,
39+
len(files),
40+
))
41+
42+
override_file = files[0]
43+
override_files.append(override_file)
44+
resolved_overrides[tool_name] = override_file.path
45+
46+
overrides_list = " ".join(["{}={}".format(k, v) for k, v in resolved_overrides.items()])
47+
48+
script_file = ctx.actions.declare_file(toolchain_name_base + "_setup.sh")
49+
50+
ctx.actions.expand_template(
51+
template = ctx.file._symlink_template,
52+
output = script_file,
53+
is_executable = True,
54+
substitutions = {
55+
"%overrides_list%": overrides_list,
56+
"%toolchain_dir%": toolchain_dir.path,
57+
"%toolchain_name_base%": toolchain_name_base,
58+
"%xcode_version%": xcode_version,
59+
},
60+
)
61+
62+
ctx.actions.run_shell(
63+
outputs = [toolchain_dir],
64+
inputs = override_files,
65+
tools = [script_file],
66+
mnemonic = "CreateCustomToolchain",
67+
command = script_file.path,
68+
execution_requirements = {
69+
"local": "1",
70+
"no-cache": "1",
71+
"no-sandbox": "1",
72+
"requires-darwin": "1",
73+
},
74+
use_default_shell_env = True,
75+
)
76+
77+
# Create runfiles with the override files and script file
78+
runfiles = ctx.runfiles(files = override_files + [script_file])
79+
80+
return [DefaultInfo(
81+
files = depset([toolchain_dir]),
82+
runfiles = runfiles,
83+
)]
84+
85+
custom_toolchain = rule(
86+
implementation = _custom_toolchain_impl,
87+
attrs = {
88+
"overrides": attr.label_keyed_string_dict(
89+
allow_files = True,
90+
mandatory = False,
91+
default = {},
92+
),
93+
"toolchain_name": attr.string(mandatory = True),
94+
"_symlink_template": attr.label(
95+
allow_single_file = True,
96+
default = Label("//xcodeproj/internal/templates:custom_toolchain_symlink.sh"),
97+
),
98+
"_xcode_config": attr.label(
99+
default = configuration_field(
100+
name = "xcode_config_label",
101+
fragment = "apple",
102+
),
103+
),
104+
},
105+
)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Define constants within the script
5+
TOOLCHAIN_NAME_BASE="%toolchain_name_base%"
6+
TOOLCHAIN_DIR="%toolchain_dir%"
7+
XCODE_VERSION="%xcode_version%"
8+
9+
# Get Xcode version and default toolchain path
10+
DEFAULT_TOOLCHAIN=$(xcrun --find clang | sed 's|/usr/bin/clang$||')
11+
XCODE_RAW_VERSION=$(xcodebuild -version | head -n 1)
12+
13+
# Define toolchain names
14+
HOME_TOOLCHAIN_NAME="BazelRulesXcodeProj${XCODE_VERSION}"
15+
USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/${HOME_TOOLCHAIN_NAME}.xctoolchain"
16+
BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR"
17+
18+
mkdir -p "$TOOLCHAIN_DIR"
19+
20+
# Process all files from the default toolchain
21+
find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do
22+
base_name="$(basename "$file")"
23+
rel_path="${file#"$DEFAULT_TOOLCHAIN/"}"
24+
25+
# Skip ToolchainInfo.plist as we'll create our own
26+
if [[ "$rel_path" == "ToolchainInfo.plist" ]]; then
27+
continue
28+
fi
29+
30+
# Ensure parent directory exists
31+
mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")"
32+
33+
# Process overrides
34+
override_found=false
35+
while IFS='=' read -r key value; do
36+
if [[ "$key" == "$base_name" ]]; then
37+
value="$PWD/$value"
38+
cp "$value" "$TOOLCHAIN_DIR/$rel_path"
39+
# Make executable if original is executable
40+
if [[ -x "$file" ]]; then
41+
chmod +x "$TOOLCHAIN_DIR/$rel_path"
42+
fi
43+
override_found=true
44+
break
45+
fi
46+
done <<< "%overrides_list%"
47+
48+
# If no override found, symlink the original
49+
if [[ "$override_found" == "false" ]]; then
50+
ln -sf "$file" "$TOOLCHAIN_DIR/$rel_path"
51+
fi
52+
done
53+
54+
# Generate the ToolchainInfo.plist directly with Xcode version information
55+
cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF
56+
<?xml version="1.0" encoding="UTF-8"?>
57+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
58+
<plist version="1.0">
59+
<dict>
60+
<key>Aliases</key>
61+
<array>
62+
<string>${HOME_TOOLCHAIN_NAME}</string>
63+
</array>
64+
<key>CFBundleIdentifier</key>
65+
<string>com.rules_xcodeproj.BazelRulesXcodeProj.${XCODE_VERSION}</string>
66+
<key>CompatibilityVersion</key>
67+
<integer>2</integer>
68+
<key>CompatibilityVersionDisplayString</key>
69+
<string>${XCODE_RAW_VERSION}</string>
70+
<key>DisplayName</key>
71+
<string>${HOME_TOOLCHAIN_NAME}</string>
72+
<key>ReportProblemURL</key>
73+
<string>https://github.com/MobileNativeFoundation/rules_xcodeproj</string>
74+
<key>ShortDisplayName</key>
75+
<string>${HOME_TOOLCHAIN_NAME}</string>
76+
<key>Version</key>
77+
<string>0.1.0</string>
78+
</dict>
79+
</plist>
80+
EOF
81+
82+
mkdir -p "$(dirname "$USER_TOOLCHAIN_PATH")"
83+
if [[ -e "$USER_TOOLCHAIN_PATH" || -L "$USER_TOOLCHAIN_PATH" ]]; then
84+
rm -rf "$USER_TOOLCHAIN_PATH"
85+
fi
86+
ln -sf "$BUILT_TOOLCHAIN_PATH" "$USER_TOOLCHAIN_PATH"

0 commit comments

Comments
 (0)