11def _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
13388custom_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+
0 commit comments