Skip to content

Commit b95a380

Browse files
author
Karim Alweheshy
committed
Fix custom_toolchain rule
1 parent 17d7db9 commit b95a380

File tree

2 files changed

+160
-111
lines changed

2 files changed

+160
-111
lines changed
Lines changed: 71 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,89 @@
11
def _custom_toolchain_impl(ctx):
2-
toolchain_dir = ctx.actions.declare_directory(ctx.attr.toolchain_name + ".xctoolchain")
3-
toolchain_plist_file = ctx.actions.declare_file(ctx.attr.toolchain_name + "_ToolchainInfo.plist")
2+
toolchain_name_base = ctx.attr.toolchain_name
43

5-
default_toolchain_path = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain"
6-
user_toolchain_path = "$(eval echo ~)/Library/Developer/Toolchains/{}.xctoolchain".format(ctx.attr.toolchain_name)
7-
built_toolchain_path = "$(eval pwd)/"+toolchain_dir.path
4+
# Create a file to store Xcode version
5+
xcode_version_file = ctx.actions.declare_file(toolchain_name_base + "_xcode_version.txt")
86

9-
resolved_overrides = {}
7+
# Run xcodebuild to get the Xcode version
8+
ctx.actions.run_shell(
9+
outputs = [xcode_version_file],
10+
command = """
11+
# Get Xcode version and clean it for use in filenames
12+
xcodebuild -version | head -n 1 | sed 's/Xcode //' | tr -d '.' | tr ' ' '_' > {outfile}
13+
""".format(outfile = xcode_version_file.path),
14+
mnemonic = "GetXcodeVersion",
15+
execution_requirements = {"no-sandbox": "1"},
16+
)
17+
18+
# Create a file to store the default toolchain path
19+
default_toolchain_path_file = ctx.actions.declare_file(toolchain_name_base + "_default_toolchain_path.txt")
1020

11-
for tool_name, label_target in ctx.attr.overrides.items():
12-
print("DEBUG: Processing override '{}' -> '{}'".format(tool_name, label_target))
21+
# Run xcrun to get the default toolchain path
22+
ctx.actions.run_shell(
23+
outputs = [default_toolchain_path_file],
24+
command = "xcrun --find clang | sed 's|/usr/bin/clang$||' > {outfile}".format(
25+
outfile = default_toolchain_path_file.path
26+
),
27+
mnemonic = "GetDefaultToolchainPath",
28+
execution_requirements = {"no-sandbox": "1"}, # Allow xcrun to access system paths
29+
)
1330

14-
# Check if the target produces valid files
15-
if hasattr(label_target, "files"):
16-
files = label_target.files.to_list()
17-
else:
18-
files = []
31+
# Declare the output directory for the toolchain
32+
toolchain_dir = ctx.actions.declare_directory(toolchain_name_base + ".xctoolchain")
1933

34+
resolved_overrides = {}
35+
override_files = [] # Collect all override files to include in inputs
36+
37+
for tool_target, tool_name in ctx.attr.overrides.items():
38+
# The key is the target (label), the value is the tool name (string)
39+
files = tool_target.files.to_list()
2040
if not files:
21-
fail("ERROR: Override '{}' does not produce any files! Ensure it is wrapped in a `filegroup`.".format(tool_name))
41+
fail("ERROR: Override for '{}' does not produce any files!".format(tool_name))
2242

23-
# Extract the first file from the filegroup
24-
resolved_path = files[0].path
25-
resolved_overrides[tool_name] = resolved_path
43+
if len(files) > 1:
44+
fail("ERROR: Override for '{}' produces multiple files ({}). Each override must have exactly one file.".format(
45+
tool_name, len(files)))
2646

27-
# Debugging: Print resolved paths
28-
print("Resolved overrides:", resolved_overrides)
47+
override_file = files[0]
48+
override_files.append(override_file) # Add to list of input files
49+
resolved_overrides[tool_name] = override_file.path
2950

3051
# Generate symlink creation commands dynamically, excluding plist files
3152
overrides_list = " ".join(["{}={}".format(k, v) for k, v in resolved_overrides.items()])
3253

33-
symlink_script = """#!/bin/bash
34-
set -e
35-
36-
mkdir -p "{toolchain_dir}"
37-
38-
# Process overrides manually (avoiding associative arrays)
39-
while IFS='=' read -r key value; do
40-
if [[ -n "$key" && -n "$value" ]]; then
41-
overrides="$overrides $key=$value"
42-
fi
43-
done <<< "{overrides_list}"
44-
45-
find "{default_toolchain}" -type f -o -type l | while read file; do
46-
base_name="$(basename "$file")"
47-
rel_path="${{file#"{default_toolchain}/"}}"
48-
49-
# Check if an override exists
50-
override_path=""
51-
for entry in $overrides; do
52-
o_key="${{entry%%=*}}"
53-
o_value="${{entry#*=}}"
54-
echo "Looking for Override: $o_key -> $o_value"
55-
if [[ "$o_key" == "$base_name" ]]; then
56-
echo "Found Override: $entry -> $o_value"
57-
override_path="$o_value"
58-
break
59-
fi
60-
done
61-
62-
if [[ -n "$override_path" ]]; then
63-
mkdir -p "{toolchain_dir}/$(dirname "$rel_path")"
64-
cp "$override_path" "{toolchain_dir}/$rel_path"
65-
continue
66-
fi
67-
68-
# Symlink everything else
69-
if [[ "$rel_path" != "ToolchainInfo.plist" ]]; then
70-
mkdir -p "{toolchain_dir}/$(dirname "$rel_path")"
71-
ln -s "$file" "{toolchain_dir}/$rel_path"
72-
fi
73-
done
74-
75-
mv "{toolchain_plist}" "{toolchain_dir}/ToolchainInfo.plist"
76-
77-
# Remove existing symlink if present and create a new one in the user directory
78-
if [ -e "{user_toolchain_path}" ]; then
79-
rm -f "{user_toolchain_path}"
80-
fi
81-
ln -s "{built_toolchain_path}" "{user_toolchain_path}"
82-
""".format(
83-
toolchain_dir=toolchain_dir.path,
84-
default_toolchain=default_toolchain_path,
85-
overrides_list=overrides_list,
86-
toolchain_plist=toolchain_plist_file.path,
87-
user_toolchain_path=user_toolchain_path,
88-
built_toolchain_path=built_toolchain_path
89-
)
90-
91-
script_file = ctx.actions.declare_file(ctx.attr.toolchain_name + "_setup.sh")
92-
ctx.actions.write(output=script_file, content=symlink_script, is_executable=True)
93-
94-
# Generate ToolchainInfo.plist
95-
toolchain_plist_content = """<?xml version="1.0" encoding="UTF-8"?>
96-
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
97-
<plist version="1.0">
98-
<dict>
99-
<key>Aliases</key>
100-
<array>
101-
<string>{name}</string>
102-
</array>
103-
<key>CFBundleIdentifier</key>
104-
<string>com.example.{name}</string>
105-
<key>CompatibilityVersion</key>
106-
<integer>2</integer>
107-
<key>CompatibilityVersionDisplayString</key>
108-
<string>Xcode 13.0</string>
109-
<key>DisplayName</key>
110-
<string>{name}</string>
111-
<key>ReportProblemURL</key>
112-
<string>https://github.com/MobileNativeFoundation/rules_xcodeproj</string>
113-
<key>ShortDisplayName</key>
114-
<string>{name}</string>
115-
<key>Version</key>
116-
<string>0.0.1</string>
117-
</dict>
118-
</plist>
119-
""".format(name=ctx.attr.toolchain_name)
120-
121-
ctx.actions.write(output=toolchain_plist_file, content=toolchain_plist_content)
54+
script_file = ctx.actions.declare_file(toolchain_name_base + "_setup.sh")
55+
56+
# Use expand_template with simplified substitutions
57+
ctx.actions.expand_template(
58+
template = ctx.file._symlink_template,
59+
output = script_file,
60+
is_executable = True,
61+
substitutions = {
62+
"%toolchain_name_base%": toolchain_name_base,
63+
"%toolchain_dir%": toolchain_dir.path,
64+
"%overrides_list%": overrides_list,
65+
"%default_toolchain_path_file%": default_toolchain_path_file.path,
66+
"%xcode_version_file%": xcode_version_file.path,
67+
},
68+
)
12269

12370
# Run the generated shell script
12471
ctx.actions.run_shell(
12572
outputs=[toolchain_dir],
126-
inputs=[toolchain_plist_file],
73+
inputs=[default_toolchain_path_file, xcode_version_file] + override_files,
12774
tools=[script_file],
128-
command=script_file.path
75+
mnemonic = "SymlinkDefaultXcodeToolchain",
76+
command=script_file.path,
77+
execution_requirements = {"no-sandbox": "1"},
12978
)
13079

131-
return [DefaultInfo(files=depset([toolchain_dir]))]
80+
# Create runfiles with the override files and script file
81+
runfiles = ctx.runfiles(files=override_files + [script_file, default_toolchain_path_file, xcode_version_file])
82+
83+
return [DefaultInfo(
84+
files=depset([toolchain_dir]),
85+
runfiles=runfiles,
86+
)]
13287

13388
custom_toolchain = rule(
13489
implementation=_custom_toolchain_impl,
@@ -137,5 +92,10 @@ custom_toolchain = rule(
13792
"overrides": attr.label_keyed_string_dict(
13893
allow_files=True, mandatory=False, default={}
13994
),
95+
"_symlink_template": attr.label(
96+
allow_single_file=True,
97+
default=Label("//xcodeproj/internal/templates:custom_toolchain_symlink.sh"),
98+
),
14099
},
141100
)
101+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
DEFAULT_TOOLCHAIN_PATH_FILE="%default_toolchain_path_file%"
8+
XCODE_VERSION_FILE="%xcode_version_file%"
9+
XCODE_VERSION=$(cat "$XCODE_VERSION_FILE")
10+
DEFAULT_TOOLCHAIN=$(cat "$DEFAULT_TOOLCHAIN_PATH_FILE")
11+
XCODE_RAW_VERSION=$(xcodebuild -version | head -n 1)
12+
TOOLCHAIN_NAME="${TOOLCHAIN_NAME_BASE}"
13+
HOME_TOOLCHAIN_NAME="BazelRulesXcodeProj ${XCODE_VERSION}"
14+
USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/${HOME_TOOLCHAIN_NAME}.xctoolchain"
15+
BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR"
16+
17+
mkdir -p "$TOOLCHAIN_DIR"
18+
19+
while IFS='=' read -r key value; do
20+
value="$PWD/$value"
21+
overrides="$overrides $key=$value"
22+
done <<< "%overrides_list%"
23+
24+
find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read file; do
25+
base_name="$(basename "$file")"
26+
rel_path="${file#"$DEFAULT_TOOLCHAIN/"}"
27+
28+
# Check if an override exists
29+
override_path=""
30+
for entry in $overrides; do
31+
o_key="${entry%%=*}"
32+
o_value="${entry#*=}"
33+
if [[ "$o_key" == "$base_name" ]]; then
34+
override_path="$o_value"
35+
break
36+
fi
37+
done
38+
39+
if [[ -n "$override_path" ]]; then
40+
mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")"
41+
cp "$override_path" "$TOOLCHAIN_DIR/$rel_path"
42+
# Make the override executable if the original was executable
43+
if [[ -x "$file" ]]; then
44+
chmod +x "$TOOLCHAIN_DIR/$rel_path"
45+
fi
46+
continue
47+
fi
48+
49+
# Symlink everything else
50+
if [[ "$rel_path" != "ToolchainInfo.plist" ]]; then
51+
mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")"
52+
ln -s "$file" "$TOOLCHAIN_DIR/$rel_path"
53+
fi
54+
done
55+
56+
# Generate the ToolchainInfo.plist directly with Xcode version information
57+
cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF
58+
<?xml version="1.0" encoding="UTF-8"?>
59+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
60+
<plist version="1.0">
61+
<dict>
62+
<key>Aliases</key>
63+
<array>
64+
<string>BazelRulesXcodeProj</string>
65+
</array>
66+
<key>CFBundleIdentifier</key>
67+
<string>com.rules_xcodeproj.BazelRulesXcodeProj</string>
68+
<key>CompatibilityVersion</key>
69+
<integer>2</integer>
70+
<key>CompatibilityVersionDisplayString</key>
71+
<string>${XCODE_RAW_VERSION}</string>
72+
<key>DisplayName</key>
73+
<string>BazelRulesXcodeProj (${XCODE_RAW_VERSION})</string>
74+
<key>ReportProblemURL</key>
75+
<string>https://github.com/MobileNativeFoundation/rules_xcodeproj</string>
76+
<key>ShortDisplayName</key>
77+
<string>BazelRulesXcodeProj</string>
78+
<key>Version</key>
79+
<string>0.1.0</string>
80+
</dict>
81+
</plist>
82+
EOF
83+
84+
# Remove existing symlink if present and create a new one in the user directory
85+
if [ -e "$USER_TOOLCHAIN_PATH" ]; then
86+
rm -f "$USER_TOOLCHAIN_PATH"
87+
fi
88+
ln -s "$BUILT_TOOLCHAIN_PATH" "$USER_TOOLCHAIN_PATH"
89+

0 commit comments

Comments
 (0)