From d55665a127297bf523befba3249053dbe0e91deb Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Fri, 21 Mar 2025 10:28:57 +0100 Subject: [PATCH 01/13] Add custom toolchain rule and tests Signed-off-by: Karim Alweheshy --- test/internal/custom_toolchain/BUILD | 27 +++++ .../custom_toolchain/custom_toolchain_test.sh | 46 ++++++++ test/internal/custom_toolchain/test_swiftc.sh | 11 ++ xcodeproj/internal/custom_toolchain.bzl | 105 ++++++++++++++++++ .../templates/custom_toolchain_symlink.sh | 86 ++++++++++++++ 5 files changed, 275 insertions(+) create mode 100644 test/internal/custom_toolchain/BUILD create mode 100755 test/internal/custom_toolchain/custom_toolchain_test.sh create mode 100644 test/internal/custom_toolchain/test_swiftc.sh create mode 100644 xcodeproj/internal/custom_toolchain.bzl create mode 100644 xcodeproj/internal/templates/custom_toolchain_symlink.sh diff --git a/test/internal/custom_toolchain/BUILD b/test/internal/custom_toolchain/BUILD new file mode 100644 index 000000000..62c6ce4c7 --- /dev/null +++ b/test/internal/custom_toolchain/BUILD @@ -0,0 +1,27 @@ +# buildifier: disable=bzl-visibility +load("//xcodeproj/internal:custom_toolchain.bzl", "custom_toolchain") + +# Example swiftc override for testing +filegroup( + name = "test_swiftc", + srcs = ["test_swiftc.sh"], + visibility = ["//visibility:public"], +) + +# Test target for custom_toolchain +custom_toolchain( + name = "test_toolchain", + overrides = { + # Key is a label target, value is the tool name + ":test_swiftc": "swiftc", + }, + toolchain_name = "TestCustomToolchain", +) + +# Add a simple test rule that depends on the toolchain +sh_test( + name = "custom_toolchain_test", + srcs = ["custom_toolchain_test.sh"], + args = ["$(location :test_toolchain)"], + data = [":test_toolchain"], +) diff --git a/test/internal/custom_toolchain/custom_toolchain_test.sh b/test/internal/custom_toolchain/custom_toolchain_test.sh new file mode 100755 index 000000000..e4bf9cb2b --- /dev/null +++ b/test/internal/custom_toolchain/custom_toolchain_test.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -euo pipefail + +# The first argument should be the path to the toolchain directory +TOOLCHAIN_DIR="$1" + +echo "Verifying toolchain at: $TOOLCHAIN_DIR" + +# Check that the toolchain directory exists +if [[ ! -d "$TOOLCHAIN_DIR" ]]; then + echo "ERROR: Toolchain directory does not exist: $TOOLCHAIN_DIR" + exit 1 +fi + +# Check that ToolchainInfo.plist exists +if [[ ! -f "$TOOLCHAIN_DIR/ToolchainInfo.plist" ]]; then + echo "ERROR: ToolchainInfo.plist not found in toolchain" + exit 1 +fi + +# Check for correct identifiers in the plist +if ! grep -q "BazelRulesXcodeProj" "$TOOLCHAIN_DIR/ToolchainInfo.plist"; then + echo "ERROR: ToolchainInfo.plist doesn't contain BazelRulesXcodeProj" + exit 1 +fi + +# Check that our custom swiftc is properly linked/copied +if [[ ! -f "$TOOLCHAIN_DIR/usr/bin/swiftc" ]]; then + echo "ERROR: swiftc not found in toolchain" + exit 1 +fi + +# Ensure swiftc is executable +if [[ ! -x "$TOOLCHAIN_DIR/usr/bin/swiftc" ]]; then + echo "ERROR: swiftc is not executable" + exit 1 +fi + +# Test if the swiftc actually runs +if ! "$TOOLCHAIN_DIR/usr/bin/swiftc" --version > /dev/null 2>&1; then + echo "WARN: swiftc doesn't run correctly, but this is expected in tests" +fi + +echo "Custom toolchain validation successful!" +exit 0 \ No newline at end of file diff --git a/test/internal/custom_toolchain/test_swiftc.sh b/test/internal/custom_toolchain/test_swiftc.sh new file mode 100644 index 000000000..ef1872d99 --- /dev/null +++ b/test/internal/custom_toolchain/test_swiftc.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This is a test script that simulates a custom Swift compiler +# It will be used as an override in the custom toolchain + +# Print inputs for debugging +echo "Custom swiftc called with args: $@" >&2 + +# In a real override, you would do something meaningful with the args +# For testing, just exit successfully +exit 0 diff --git a/xcodeproj/internal/custom_toolchain.bzl b/xcodeproj/internal/custom_toolchain.bzl new file mode 100644 index 000000000..a357576ba --- /dev/null +++ b/xcodeproj/internal/custom_toolchain.bzl @@ -0,0 +1,105 @@ +"""Implementation of the `custom_toolchain` rule.""" + +def _get_xcode_product_version(*, xcode_config): + raw_version = str(xcode_config.xcode_version()) + if not raw_version: + fail("""\ +`xcode_config.xcode_version` was not set. This is a bazel bug. Try again. +""") + + version_components = raw_version.split(".") + if len(version_components) < 4: + # This will result in analysis cache misses, but it's better than + # failing + return raw_version + + return version_components[3] + +def _custom_toolchain_impl(ctx): + xcode_version = _get_xcode_product_version( + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig], + ) + + toolchain_name_base = ctx.attr.toolchain_name + toolchain_dir = ctx.actions.declare_directory( + toolchain_name_base + "{}".format(xcode_version) + ".xctoolchain", + ) + + resolved_overrides = {} + override_files = [] + + for tool_target, tool_name in ctx.attr.overrides.items(): + files = tool_target.files.to_list() + if not files: + fail("ERROR: Override for '{}' does not produce any files!".format(tool_name)) + + if len(files) > 1: + fail("ERROR: Override for '{}' produces multiple files ({}). Each override must have exactly one file.".format( + tool_name, + len(files), + )) + + override_file = files[0] + override_files.append(override_file) + resolved_overrides[tool_name] = override_file.path + + overrides_list = " ".join(["{}={}".format(k, v) for k, v in resolved_overrides.items()]) + + script_file = ctx.actions.declare_file(toolchain_name_base + "_setup.sh") + + ctx.actions.expand_template( + template = ctx.file._symlink_template, + output = script_file, + is_executable = True, + substitutions = { + "%overrides_list%": overrides_list, + "%toolchain_dir%": toolchain_dir.path, + "%toolchain_name_base%": toolchain_name_base, + "%xcode_version%": xcode_version, + }, + ) + + ctx.actions.run_shell( + outputs = [toolchain_dir], + inputs = override_files, + tools = [script_file], + mnemonic = "CreateCustomToolchain", + command = script_file.path, + execution_requirements = { + "local": "1", + "no-cache": "1", + "no-sandbox": "1", + "requires-darwin": "1", + }, + use_default_shell_env = True, + ) + + # Create runfiles with the override files and script file + runfiles = ctx.runfiles(files = override_files + [script_file]) + + return [DefaultInfo( + files = depset([toolchain_dir]), + runfiles = runfiles, + )] + +custom_toolchain = rule( + implementation = _custom_toolchain_impl, + attrs = { + "overrides": attr.label_keyed_string_dict( + allow_files = True, + mandatory = False, + default = {}, + ), + "toolchain_name": attr.string(mandatory = True), + "_symlink_template": attr.label( + allow_single_file = True, + default = Label("//xcodeproj/internal/templates:custom_toolchain_symlink.sh"), + ), + "_xcode_config": attr.label( + default = configuration_field( + name = "xcode_config_label", + fragment = "apple", + ), + ), + }, +) diff --git a/xcodeproj/internal/templates/custom_toolchain_symlink.sh b/xcodeproj/internal/templates/custom_toolchain_symlink.sh new file mode 100644 index 000000000..5f27577ca --- /dev/null +++ b/xcodeproj/internal/templates/custom_toolchain_symlink.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -e + +# Define constants within the script +TOOLCHAIN_NAME_BASE="%toolchain_name_base%" +TOOLCHAIN_DIR="%toolchain_dir%" +XCODE_VERSION="%xcode_version%" + +# Get Xcode version and default toolchain path +DEFAULT_TOOLCHAIN=$(xcrun --find clang | sed 's|/usr/bin/clang$||') +XCODE_RAW_VERSION=$(xcodebuild -version | head -n 1) + +# Define toolchain names +HOME_TOOLCHAIN_NAME="BazelRulesXcodeProj${XCODE_VERSION}" +USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/${HOME_TOOLCHAIN_NAME}.xctoolchain" +BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR" + +mkdir -p "$TOOLCHAIN_DIR" + +# Process all files from the default toolchain +find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do + base_name="$(basename "$file")" + rel_path="${file#"$DEFAULT_TOOLCHAIN/"}" + + # Skip ToolchainInfo.plist as we'll create our own + if [[ "$rel_path" == "ToolchainInfo.plist" ]]; then + continue + fi + + # Ensure parent directory exists + mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")" + + # Process overrides + override_found=false + while IFS='=' read -r key value; do + if [[ "$key" == "$base_name" ]]; then + value="$PWD/$value" + cp "$value" "$TOOLCHAIN_DIR/$rel_path" + # Make executable if original is executable + if [[ -x "$file" ]]; then + chmod +x "$TOOLCHAIN_DIR/$rel_path" + fi + override_found=true + break + fi + done <<< "%overrides_list%" + + # If no override found, symlink the original + if [[ "$override_found" == "false" ]]; then + ln -sf "$file" "$TOOLCHAIN_DIR/$rel_path" + fi +done + +# Generate the ToolchainInfo.plist directly with Xcode version information +cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF + + + + + Aliases + + ${HOME_TOOLCHAIN_NAME} + + CFBundleIdentifier + com.rules_xcodeproj.BazelRulesXcodeProj.${XCODE_VERSION} + CompatibilityVersion + 2 + CompatibilityVersionDisplayString + ${XCODE_RAW_VERSION} + DisplayName + ${HOME_TOOLCHAIN_NAME} + ReportProblemURL + https://github.com/MobileNativeFoundation/rules_xcodeproj + ShortDisplayName + ${HOME_TOOLCHAIN_NAME} + Version + 0.1.0 + + +EOF + +mkdir -p "$(dirname "$USER_TOOLCHAIN_PATH")" +if [[ -e "$USER_TOOLCHAIN_PATH" || -L "$USER_TOOLCHAIN_PATH" ]]; then + rm -rf "$USER_TOOLCHAIN_PATH" +fi +ln -sf "$BUILT_TOOLCHAIN_PATH" "$USER_TOOLCHAIN_PATH" From 38a8b2a356a5174f0c9d79e60cc491ea1dacc681 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sat, 22 Mar 2025 21:19:38 +0100 Subject: [PATCH 02/13] improve toolchain --- test/internal/custom_toolchain/BUILD | 1 - .../custom_toolchain/custom_toolchain_test.sh | 4 +- xcodeproj/internal/custom_toolchain.bzl | 58 ++++++++++++------- xcodeproj/internal/providers.bzl | 8 +++ .../templates/custom_toolchain_symlink.sh | 43 +++++++++----- 5 files changed, 78 insertions(+), 36 deletions(-) diff --git a/test/internal/custom_toolchain/BUILD b/test/internal/custom_toolchain/BUILD index 62c6ce4c7..d0aa56b15 100644 --- a/test/internal/custom_toolchain/BUILD +++ b/test/internal/custom_toolchain/BUILD @@ -12,7 +12,6 @@ filegroup( custom_toolchain( name = "test_toolchain", overrides = { - # Key is a label target, value is the tool name ":test_swiftc": "swiftc", }, toolchain_name = "TestCustomToolchain", diff --git a/test/internal/custom_toolchain/custom_toolchain_test.sh b/test/internal/custom_toolchain/custom_toolchain_test.sh index e4bf9cb2b..94146f21b 100755 --- a/test/internal/custom_toolchain/custom_toolchain_test.sh +++ b/test/internal/custom_toolchain/custom_toolchain_test.sh @@ -20,8 +20,8 @@ if [[ ! -f "$TOOLCHAIN_DIR/ToolchainInfo.plist" ]]; then fi # Check for correct identifiers in the plist -if ! grep -q "BazelRulesXcodeProj" "$TOOLCHAIN_DIR/ToolchainInfo.plist"; then - echo "ERROR: ToolchainInfo.plist doesn't contain BazelRulesXcodeProj" +if ! grep -q "TestCustomToolchain" "$TOOLCHAIN_DIR/ToolchainInfo.plist"; then + echo "ERROR: ToolchainInfo.plist doesn't contain TestCustomToolchain" exit 1 fi diff --git a/xcodeproj/internal/custom_toolchain.bzl b/xcodeproj/internal/custom_toolchain.bzl index a357576ba..0aa91b4e0 100644 --- a/xcodeproj/internal/custom_toolchain.bzl +++ b/xcodeproj/internal/custom_toolchain.bzl @@ -1,5 +1,7 @@ """Implementation of the `custom_toolchain` rule.""" +load("//xcodeproj/internal:providers.bzl", "ToolchainInfo") + def _get_xcode_product_version(*, xcode_config): raw_version = str(xcode_config.xcode_version()) if not raw_version: @@ -21,31 +23,39 @@ def _custom_toolchain_impl(ctx): ) toolchain_name_base = ctx.attr.toolchain_name - toolchain_dir = ctx.actions.declare_directory( - toolchain_name_base + "{}".format(xcode_version) + ".xctoolchain", - ) + toolchain_id = "com.rules_xcodeproj.{}.{}".format(toolchain_name_base, xcode_version) + full_toolchain_name = "{}{}".format(toolchain_name_base, xcode_version) + toolchain_dir = ctx.actions.declare_directory(full_toolchain_name + ".xctoolchain") resolved_overrides = {} override_files = [] - for tool_target, tool_name in ctx.attr.overrides.items(): - files = tool_target.files.to_list() + # Process tools from comma-separated list + for stub_target, tools_str in ctx.attr.overrides.items(): + files = stub_target.files.to_list() if not files: - fail("ERROR: Override for '{}' does not produce any files!".format(tool_name)) + fail("ERROR: Override stub does not produce any files!") if len(files) > 1: - fail("ERROR: Override for '{}' produces multiple files ({}). Each override must have exactly one file.".format( - tool_name, + fail("ERROR: Override stub produces multiple files ({}). Each stub must have exactly one file.".format( len(files), )) - override_file = files[0] - override_files.append(override_file) - resolved_overrides[tool_name] = override_file.path + stub_file = files[0] + if stub_file not in override_files: + override_files.append(stub_file) + + # Split comma-separated list of tool names + tool_names = [name.strip() for name in tools_str.split(",")] + + # Add an entry for each tool name + for tool_name in tool_names: + if tool_name: # Skip empty names + resolved_overrides[tool_name] = stub_file.path overrides_list = " ".join(["{}={}".format(k, v) for k, v in resolved_overrides.items()]) - script_file = ctx.actions.declare_file(toolchain_name_base + "_setup.sh") + script_file = ctx.actions.declare_file(full_toolchain_name + "_setup.sh") ctx.actions.expand_template( template = ctx.file._symlink_template, @@ -54,7 +64,8 @@ def _custom_toolchain_impl(ctx): substitutions = { "%overrides_list%": overrides_list, "%toolchain_dir%": toolchain_dir.path, - "%toolchain_name_base%": toolchain_name_base, + "%toolchain_id%": toolchain_id, + "%toolchain_name_base%": full_toolchain_name, "%xcode_version%": xcode_version, }, ) @@ -74,21 +85,28 @@ def _custom_toolchain_impl(ctx): use_default_shell_env = True, ) - # Create runfiles with the override files and script file runfiles = ctx.runfiles(files = override_files + [script_file]) - return [DefaultInfo( - files = depset([toolchain_dir]), - runfiles = runfiles, - )] + toolchain_provider = ToolchainInfo( + name = full_toolchain_name, + identifier = toolchain_id, + ) + + return [ + DefaultInfo( + files = depset([toolchain_dir]), + runfiles = runfiles, + ), + toolchain_provider, + ] custom_toolchain = rule( implementation = _custom_toolchain_impl, attrs = { "overrides": attr.label_keyed_string_dict( allow_files = True, - mandatory = False, - default = {}, + mandatory = True, + doc = "Map from stub target to comma-separated list of tool names that should use that stub", ), "toolchain_name": attr.string(mandatory = True), "_symlink_template": attr.label( diff --git a/xcodeproj/internal/providers.bzl b/xcodeproj/internal/providers.bzl index 397c7e330..4908e1c3b 100644 --- a/xcodeproj/internal/providers.bzl +++ b/xcodeproj/internal/providers.bzl @@ -15,3 +15,11 @@ XcodeProjRunnerOutputInfo = provider( "runner": "The xcodeproj runner.", }, ) + +ToolchainInfo = provider( + doc = "Information about the custom toolchain", + fields = { + "identifier": "The bundle identifier of the toolchain", + "name": "The full name of the toolchain", + }, +) diff --git a/xcodeproj/internal/templates/custom_toolchain_symlink.sh b/xcodeproj/internal/templates/custom_toolchain_symlink.sh index 5f27577ca..54720525a 100644 --- a/xcodeproj/internal/templates/custom_toolchain_symlink.sh +++ b/xcodeproj/internal/templates/custom_toolchain_symlink.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -euo pipefail # Define constants within the script TOOLCHAIN_NAME_BASE="%toolchain_name_base%" @@ -17,6 +17,10 @@ BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR" mkdir -p "$TOOLCHAIN_DIR" +# Parse overrides into a file for safer processing +OVERRIDES_FILE=$(mktemp) +echo "%overrides_list%" > "$OVERRIDES_FILE" + # Process all files from the default toolchain find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do base_name="$(basename "$file")" @@ -30,27 +34,40 @@ find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do # Ensure parent directory exists mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")" - # Process overrides + # Check if this file has an override override_found=false - while IFS='=' read -r key value; do - if [[ "$key" == "$base_name" ]]; then - value="$PWD/$value" - cp "$value" "$TOOLCHAIN_DIR/$rel_path" - # Make executable if original is executable - if [[ -x "$file" ]]; then - chmod +x "$TOOLCHAIN_DIR/$rel_path" - fi + override_value="" + + for override in $(cat "$OVERRIDES_FILE"); do + KEY="${override%%=*}" + VALUE="${override#*=}" + + if [[ "$KEY" == "$base_name" ]]; then + override_value="$VALUE" override_found=true break fi - done <<< "%overrides_list%" + done - # If no override found, symlink the original - if [[ "$override_found" == "false" ]]; then + # Apply the override or create symlink + if [[ "$override_found" == "true" ]]; then + # Make path absolute + override_path="$PWD/$override_value" + cp "$override_path" "$TOOLCHAIN_DIR/$rel_path" + + # Make executable if original is executable + if [[ -x "$file" ]]; then + chmod +x "$TOOLCHAIN_DIR/$rel_path" + fi + else + # If no override found, symlink the original ln -sf "$file" "$TOOLCHAIN_DIR/$rel_path" fi done +# Clean up +rm -f "$OVERRIDES_FILE" + # Generate the ToolchainInfo.plist directly with Xcode version information cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF From 1178567aab3f2d258120616436da6302ec856309 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sun, 23 Mar 2025 21:19:06 +0100 Subject: [PATCH 03/13] make rule incremental to avoid expensive calculation --- xcodeproj/internal/custom_toolchain.bzl | 75 +++++++++++++++---- .../templates/custom_toolchain_override.sh | 64 ++++++++++++++++ .../templates/custom_toolchain_symlink.sh | 66 ++++++---------- 3 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 xcodeproj/internal/templates/custom_toolchain_override.sh diff --git a/xcodeproj/internal/custom_toolchain.bzl b/xcodeproj/internal/custom_toolchain.bzl index 0aa91b4e0..39b4c378f 100644 --- a/xcodeproj/internal/custom_toolchain.bzl +++ b/xcodeproj/internal/custom_toolchain.bzl @@ -10,10 +10,10 @@ def _get_xcode_product_version(*, xcode_config): """) version_components = raw_version.split(".") - if len(version_components) < 4: - # This will result in analysis cache misses, but it's better than - # failing - return raw_version + if len(version_components) != 4: + fail("""\ +`xcode_config.xcode_version` returned an unexpected number of components: {} +""".format(len(version_components))) return version_components[3] @@ -25,7 +25,10 @@ def _custom_toolchain_impl(ctx): toolchain_name_base = ctx.attr.toolchain_name toolchain_id = "com.rules_xcodeproj.{}.{}".format(toolchain_name_base, xcode_version) full_toolchain_name = "{}{}".format(toolchain_name_base, xcode_version) - toolchain_dir = ctx.actions.declare_directory(full_toolchain_name + ".xctoolchain") + + # Create two directories - one for symlinks, one for the final overridden toolchain + symlink_toolchain_dir = ctx.actions.declare_directory(full_toolchain_name + ".symlink.xctoolchain") + final_toolchain_dir = ctx.actions.declare_directory(full_toolchain_name + ".xctoolchain") resolved_overrides = {} override_files = [] @@ -53,29 +56,36 @@ def _custom_toolchain_impl(ctx): if tool_name: # Skip empty names resolved_overrides[tool_name] = stub_file.path + # Instead of passing the full map of overrides, just pass the tool names + # This way, changes to the stubs don't trigger a rebuild + tool_names_list = " ".join(resolved_overrides.keys()) + overrides_list = " ".join(["{}={}".format(k, v) for k, v in resolved_overrides.items()]) - script_file = ctx.actions.declare_file(full_toolchain_name + "_setup.sh") + symlink_script_file = ctx.actions.declare_file(full_toolchain_name + "_symlink.sh") + override_script_file = ctx.actions.declare_file(full_toolchain_name + "_override.sh") + override_marker = ctx.actions.declare_file(full_toolchain_name + ".override.marker") + # Create symlink script ctx.actions.expand_template( template = ctx.file._symlink_template, - output = script_file, + output = symlink_script_file, is_executable = True, substitutions = { - "%overrides_list%": overrides_list, - "%toolchain_dir%": toolchain_dir.path, + "%tool_names_list%": tool_names_list, + "%toolchain_dir%": symlink_toolchain_dir.path, "%toolchain_id%": toolchain_id, "%toolchain_name_base%": full_toolchain_name, "%xcode_version%": xcode_version, }, ) + # First run the symlinking script to set up the toolchain ctx.actions.run_shell( - outputs = [toolchain_dir], - inputs = override_files, - tools = [script_file], - mnemonic = "CreateCustomToolchain", - command = script_file.path, + outputs = [symlink_toolchain_dir], + tools = [symlink_script_file], + mnemonic = "CreateSymlinkToolchain", + command = symlink_script_file.path, execution_requirements = { "local": "1", "no-cache": "1", @@ -85,7 +95,36 @@ def _custom_toolchain_impl(ctx): use_default_shell_env = True, ) - runfiles = ctx.runfiles(files = override_files + [script_file]) + if override_files: + ctx.actions.expand_template( + template = ctx.file._override_template, + output = override_script_file, + is_executable = True, + substitutions = { + "%final_toolchain_dir%": final_toolchain_dir.path, + "%marker_file%": override_marker.path, + "%overrides_list%": overrides_list, + "%symlink_toolchain_dir%": symlink_toolchain_dir.path, + "%tool_names_list%": tool_names_list, + }, + ) + + ctx.actions.run_shell( + inputs = override_files + [symlink_toolchain_dir], + outputs = [final_toolchain_dir, override_marker], + tools = [override_script_file], + mnemonic = "ApplyCustomToolchainOverrides", + command = override_script_file.path, + execution_requirements = { + "local": "1", + "no-cache": "1", + "no-sandbox": "1", + "requires-darwin": "1", + }, + use_default_shell_env = True, + ) + + runfiles = ctx.runfiles(files = override_files + [symlink_script_file, override_script_file, override_marker]) toolchain_provider = ToolchainInfo( name = full_toolchain_name, @@ -94,7 +133,7 @@ def _custom_toolchain_impl(ctx): return [ DefaultInfo( - files = depset([toolchain_dir]), + files = depset([final_toolchain_dir if override_files else symlink_toolchain_dir]), runfiles = runfiles, ), toolchain_provider, @@ -109,6 +148,10 @@ custom_toolchain = rule( doc = "Map from stub target to comma-separated list of tool names that should use that stub", ), "toolchain_name": attr.string(mandatory = True), + "_override_template": attr.label( + allow_single_file = True, + default = Label("//xcodeproj/internal/templates:custom_toolchain_override.sh"), + ), "_symlink_template": attr.label( allow_single_file = True, default = Label("//xcodeproj/internal/templates:custom_toolchain_symlink.sh"), diff --git a/xcodeproj/internal/templates/custom_toolchain_override.sh b/xcodeproj/internal/templates/custom_toolchain_override.sh new file mode 100644 index 000000000..0dd3da469 --- /dev/null +++ b/xcodeproj/internal/templates/custom_toolchain_override.sh @@ -0,0 +1,64 @@ +#!/bin/bash +set -euo pipefail + +SYMLINK_TOOLCHAIN_DIR="%symlink_toolchain_dir%" +FINAL_TOOLCHAIN_DIR="%final_toolchain_dir%" +MARKER_FILE="%marker_file%" + +# Get the default toolchain path +DEFAULT_TOOLCHAIN=$(xcrun --find clang | sed 's|/usr/bin/clang$||') + +OVERRIDES_FILE=$(mktemp) +echo "%overrides_list%" > "$OVERRIDES_FILE" + +TOOL_NAMES_FILE=$(mktemp) +echo "%tool_names_list%" > "$TOOL_NAMES_FILE" + +for tool_name in $(cat "$TOOL_NAMES_FILE"); do + VALUE="" + for override in $(cat "$OVERRIDES_FILE"); do + KEY="${override%%=*}" + if [[ "$KEY" == "$tool_name" ]]; then + VALUE="${override#*=}" + break + fi + done + + if [[ -z "$VALUE" ]]; then + echo "Error: No override found for tool: $tool_name" + echo "ERROR: No override found for tool: $tool_name" >> "$MARKER_FILE" + continue + fi + + find "$DEFAULT_TOOLCHAIN" -name "$tool_name" | while read -r default_tool_path; do + rel_path="${default_tool_path#"$DEFAULT_TOOLCHAIN/"}" + target_file="$FINAL_TOOLCHAIN_DIR/$rel_path" + + mkdir -p "$(dirname "$target_file")" + + override_path="$PWD/$VALUE" + cp "$override_path" "$target_file" + + echo "Copied $override_path to $target_file (rel_path: $rel_path)" >> "$MARKER_FILE" + done +done + +# Clean up temporary files +rm -f "$OVERRIDES_FILE" +rm -f "$TOOL_NAMES_FILE" + +# Copy the symlink toolchain to the final toolchain directory +mkdir -p "$FINAL_TOOLCHAIN_DIR" +cp -RP "$SYMLINK_TOOLCHAIN_DIR/"* "$FINAL_TOOLCHAIN_DIR/" + +# Create a symlink to the toolchain in the user's Library directory +HOME_TOOLCHAIN_NAME=$(basename "$FINAL_TOOLCHAIN_DIR") +USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/$HOME_TOOLCHAIN_NAME" +mkdir -p "$(dirname "$USER_TOOLCHAIN_PATH")" +if [[ -e "$USER_TOOLCHAIN_PATH" || -L "$USER_TOOLCHAIN_PATH" ]]; then + rm -rf "$USER_TOOLCHAIN_PATH" +fi +ln -sf "$PWD/$FINAL_TOOLCHAIN_DIR" "$USER_TOOLCHAIN_PATH" +echo "Created symlink: $USER_TOOLCHAIN_PATH -> $PWD/$FINAL_TOOLCHAIN_DIR" >> "$MARKER_FILE" + + diff --git a/xcodeproj/internal/templates/custom_toolchain_symlink.sh b/xcodeproj/internal/templates/custom_toolchain_symlink.sh index 54720525a..e8fc32230 100644 --- a/xcodeproj/internal/templates/custom_toolchain_symlink.sh +++ b/xcodeproj/internal/templates/custom_toolchain_symlink.sh @@ -6,67 +6,50 @@ TOOLCHAIN_NAME_BASE="%toolchain_name_base%" TOOLCHAIN_DIR="%toolchain_dir%" XCODE_VERSION="%xcode_version%" +# Store the list of tools that will be overridden +TOOL_NAMES_FILE=$(mktemp) +echo "%tool_names_list%" > "$TOOL_NAMES_FILE" + # Get Xcode version and default toolchain path DEFAULT_TOOLCHAIN=$(xcrun --find clang | sed 's|/usr/bin/clang$||') XCODE_RAW_VERSION=$(xcodebuild -version | head -n 1) -# Define toolchain names +# Define toolchain names for reference only HOME_TOOLCHAIN_NAME="BazelRulesXcodeProj${XCODE_VERSION}" -USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/${HOME_TOOLCHAIN_NAME}.xctoolchain" -BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR" mkdir -p "$TOOLCHAIN_DIR" -# Parse overrides into a file for safer processing -OVERRIDES_FILE=$(mktemp) -echo "%overrides_list%" > "$OVERRIDES_FILE" - # Process all files from the default toolchain find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do - base_name="$(basename "$file")" rel_path="${file#"$DEFAULT_TOOLCHAIN/"}" + base_name=$(basename "$rel_path") # Skip ToolchainInfo.plist as we'll create our own if [[ "$rel_path" == "ToolchainInfo.plist" ]]; then continue fi - # Ensure parent directory exists - mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")" - - # Check if this file has an override - override_found=false - override_value="" - - for override in $(cat "$OVERRIDES_FILE"); do - KEY="${override%%=*}" - VALUE="${override#*=}" - - if [[ "$KEY" == "$base_name" ]]; then - override_value="$VALUE" - override_found=true + # Check if this file is in the list of tools to be overridden + should_skip=0 + for tool_name in $(cat "$TOOL_NAMES_FILE"); do + if [[ "$base_name" == "$tool_name" ]]; then + # Skip creating a symlink for overridden tools + echo "Skipping symlink for tool to be overridden: $base_name" + should_skip=1 break fi done - # Apply the override or create symlink - if [[ "$override_found" == "true" ]]; then - # Make path absolute - override_path="$PWD/$override_value" - cp "$override_path" "$TOOLCHAIN_DIR/$rel_path" - - # Make executable if original is executable - if [[ -x "$file" ]]; then - chmod +x "$TOOLCHAIN_DIR/$rel_path" - fi - else - # If no override found, symlink the original - ln -sf "$file" "$TOOLCHAIN_DIR/$rel_path" + if [[ $should_skip -eq 1 ]]; then + continue fi -done -# Clean up -rm -f "$OVERRIDES_FILE" + # Ensure parent directory exists + mkdir -p "$TOOLCHAIN_DIR/$(dirname "$rel_path")" + + # Create symlink to the original file + ln -sf "$file" "$TOOLCHAIN_DIR/$rel_path" +done # Generate the ToolchainInfo.plist directly with Xcode version information cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF @@ -96,8 +79,5 @@ cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF EOF -mkdir -p "$(dirname "$USER_TOOLCHAIN_PATH")" -if [[ -e "$USER_TOOLCHAIN_PATH" || -L "$USER_TOOLCHAIN_PATH" ]]; then - rm -rf "$USER_TOOLCHAIN_PATH" -fi -ln -sf "$BUILT_TOOLCHAIN_PATH" "$USER_TOOLCHAIN_PATH" +# Clean up +rm -f "$TOOL_NAMES_FILE" From 5eee330f5546840c0ff2026b89066d0e71551db7 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sun, 23 Mar 2025 23:45:15 +0100 Subject: [PATCH 04/13] fix copying the overrides correctly --- .../templates/custom_toolchain_override.sh | 2 +- .../templates/custom_toolchain_symlink.sh | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/xcodeproj/internal/templates/custom_toolchain_override.sh b/xcodeproj/internal/templates/custom_toolchain_override.sh index 0dd3da469..f8e1a62b8 100644 --- a/xcodeproj/internal/templates/custom_toolchain_override.sh +++ b/xcodeproj/internal/templates/custom_toolchain_override.sh @@ -30,7 +30,7 @@ for tool_name in $(cat "$TOOL_NAMES_FILE"); do continue fi - find "$DEFAULT_TOOLCHAIN" -name "$tool_name" | while read -r default_tool_path; do + find "$DEFAULT_TOOLCHAIN/usr/bin" -name "$tool_name" | while read -r default_tool_path; do rel_path="${default_tool_path#"$DEFAULT_TOOLCHAIN/"}" target_file="$FINAL_TOOLCHAIN_DIR/$rel_path" diff --git a/xcodeproj/internal/templates/custom_toolchain_symlink.sh b/xcodeproj/internal/templates/custom_toolchain_symlink.sh index e8fc32230..611a61bc9 100644 --- a/xcodeproj/internal/templates/custom_toolchain_symlink.sh +++ b/xcodeproj/internal/templates/custom_toolchain_symlink.sh @@ -6,16 +6,16 @@ TOOLCHAIN_NAME_BASE="%toolchain_name_base%" TOOLCHAIN_DIR="%toolchain_dir%" XCODE_VERSION="%xcode_version%" -# Store the list of tools that will be overridden -TOOL_NAMES_FILE=$(mktemp) -echo "%tool_names_list%" > "$TOOL_NAMES_FILE" - # Get Xcode version and default toolchain path DEFAULT_TOOLCHAIN=$(xcrun --find clang | sed 's|/usr/bin/clang$||') XCODE_RAW_VERSION=$(xcodebuild -version | head -n 1) -# Define toolchain names for reference only +TOOL_NAMES_FILE=$(mktemp) +echo "%tool_names_list%" > "$TOOL_NAMES_FILE" + HOME_TOOLCHAIN_NAME="BazelRulesXcodeProj${XCODE_VERSION}" +USER_TOOLCHAIN_PATH="/Users/$(id -un)/Library/Developer/Toolchains/${HOME_TOOLCHAIN_NAME}.xctoolchain" +BUILT_TOOLCHAIN_PATH="$PWD/$TOOLCHAIN_DIR" mkdir -p "$TOOLCHAIN_DIR" @@ -34,7 +34,6 @@ find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do for tool_name in $(cat "$TOOL_NAMES_FILE"); do if [[ "$base_name" == "$tool_name" ]]; then # Skip creating a symlink for overridden tools - echo "Skipping symlink for tool to be overridden: $base_name" should_skip=1 break fi @@ -79,5 +78,8 @@ cat > "$TOOLCHAIN_DIR/ToolchainInfo.plist" << EOF EOF -# Clean up -rm -f "$TOOL_NAMES_FILE" +mkdir -p "$(dirname "$USER_TOOLCHAIN_PATH")" +if [[ -e "$USER_TOOLCHAIN_PATH" || -L "$USER_TOOLCHAIN_PATH" ]]; then + rm -rf "$USER_TOOLCHAIN_PATH" +fi +ln -sf "$BUILT_TOOLCHAIN_PATH" "$USER_TOOLCHAIN_PATH" From 00fb007ec8d1b1e17cb92680d6ae895aa1e2b8f7 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sun, 23 Mar 2025 10:45:05 +0100 Subject: [PATCH 05/13] inject custom toolchain and make it work --- .../write_pbxproj_prefix_tests.bzl | 4 ++ tools/BUILD | 2 + .../src/Generator/Arguments.swift | 3 + .../src/Generator/Environment.swift | 3 +- .../src/Generator/Generator.swift | 3 +- .../Generator/PBXProjectBuildSettings.swift | 20 +----- .../test/PBXProjectBuildSettingsTests.swift | 11 +--- tools/swiftc_stub/main.swift | 63 ++++++++++++++----- xcodeproj/internal/BUILD | 14 +++++ .../internal/bazel_integration_files/BUILD | 23 ++++++- xcodeproj/internal/bazel_integration_files/ld | 5 ++ xcodeproj/internal/pbxproj_partials.bzl | 5 ++ .../internal/xcodeproj_incremental_rule.bzl | 9 ++- 13 files changed, 121 insertions(+), 44 deletions(-) diff --git a/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl b/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl index e416fdb56..5a80e860f 100644 --- a/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl +++ b/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl @@ -266,6 +266,8 @@ def write_pbxproj_prefix_test_suite(name): "some/path/to/index_import", # resolvedRepositoriesFile "some/path/to/resolved_repositories_file", + # customToolchainID + "com.rules_xcodeproj.BazelRulesXcodeProj.16B40", # minimumXcodeVersion "14.2.1", # importIndexBuildIndexstores @@ -332,6 +334,8 @@ def write_pbxproj_prefix_test_suite(name): "some/path/to/index_import", # resolvedRepositoriesFile "some/path/to/resolved_repositories_file", + # customToolchainID + "com.rules_xcodeproj.BazelRulesXcodeProj.16B40", # minimumXcodeVersion "14.2.1", # importIndexBuildIndexstores diff --git a/tools/BUILD b/tools/BUILD index a25ef9c38..1206d7acb 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -179,6 +179,8 @@ _XCSCHEMES = [ "bazel-out/darwin_arm64-opt-exec-2B5CBBC6/bin/external/_main~non_module_deps~rules_xcodeproj_index_import/index-import", # resolvedRepositoriesFile "bazel-out/darwin_arm64-dbg/bin/external/_main~internal~rules_xcodeproj_generated/generator/tools/generators/xcodeproj/xcodeproj_pbxproj_partials/resolved_repositories", + # customToolchainID + "com.rules_xcodeproj.BazelRulesXcodeProj.16B40", # minimumXcodeVersion "14.0", # defaultXcodeConfiguration diff --git a/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift b/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift index 4dd7238ab..87381ec33 100644 --- a/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift +++ b/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift @@ -42,6 +42,9 @@ setting. ) var resolvedRepositoriesFile: URL + @Argument(help: "The custom toolchain ID.") + var customToolchainID: String + @Argument(help: """ Minimum Xcode version that the generated project supports. """) diff --git a/tools/generators/pbxproj_prefix/src/Generator/Environment.swift b/tools/generators/pbxproj_prefix/src/Generator/Environment.swift index 2ba7edd36..a785972fc 100644 --- a/tools/generators/pbxproj_prefix/src/Generator/Environment.swift +++ b/tools/generators/pbxproj_prefix/src/Generator/Environment.swift @@ -37,7 +37,8 @@ extension Generator { _ projectDir: String, _ resolvedRepositories: String, _ workspace: String, - _ createBuildSettingsAttribute: CreateBuildSettingsAttribute + _ createBuildSettingsAttribute: CreateBuildSettingsAttribute, + _ customToolchainID: String ) -> String let pbxProjectPrefixPartial: ( diff --git a/tools/generators/pbxproj_prefix/src/Generator/Generator.swift b/tools/generators/pbxproj_prefix/src/Generator/Generator.swift index 871eb10de..4d7f79287 100644 --- a/tools/generators/pbxproj_prefix/src/Generator/Generator.swift +++ b/tools/generators/pbxproj_prefix/src/Generator/Generator.swift @@ -62,7 +62,8 @@ struct Generator { ), /*workspace:*/ arguments.workspace, /*createBuildSettingsAttribute:*/ - environment.createBuildSettingsAttribute + environment.createBuildSettingsAttribute, + /*customToolchainID:*/ arguments.customToolchainID ), /*compatibilityVersion:*/ environment.compatibilityVersion( arguments.minimumXcodeVersion diff --git a/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift b/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift index a5561af95..b405ecd4b 100644 --- a/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift +++ b/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift @@ -25,7 +25,8 @@ extension Generator { projectDir: String, resolvedRepositories: String, workspace: String, - createBuildSettingsAttribute: CreateBuildSettingsAttribute + createBuildSettingsAttribute: CreateBuildSettingsAttribute, + customToolchainID: String ) -> String { return createBuildSettingsAttribute(buildSettings: [ .init(key: "ALWAYS_SEARCH_USER_PATHS", value: "NO"), @@ -70,7 +71,6 @@ extension Generator { key: "BUILD_WORKSPACE_DIRECTORY", value: #""$(SRCROOT)""# ), - .init(key: "CC", value: #""$(BAZEL_INTEGRATION_DIR)/clang.sh""#), .init(key: "CLANG_ENABLE_OBJC_ARC", value: "YES"), .init(key: "CLANG_MODULES_AUTOLINK", value: "NO"), .init(key: "CODE_SIGNING_ALLOWED", value: "NO"), @@ -80,7 +80,6 @@ extension Generator { value: #""$(BUILD_DIR)/$(BAZEL_PACKAGE_BIN_DIR)""# ), .init(key: "COPY_PHASE_STRIP", value: "NO"), - .init(key: "CXX", value: #""$(BAZEL_INTEGRATION_DIR)/clang.sh""#), .init(key: "DEBUG_INFORMATION_FORMAT", value: "dwarf"), .init(key: "DSTROOT", value: #""$(PROJECT_TEMP_DIR)""#), .init(key: "ENABLE_DEBUG_DYLIB", value: "NO"), @@ -88,15 +87,7 @@ extension Generator { .init(key: "ENABLE_STRICT_OBJC_MSGSEND", value: "YES"), .init(key: "ENABLE_USER_SCRIPT_SANDBOXING", value: "NO"), .init(key: "GCC_OPTIMIZATION_LEVEL", value: "0"), - .init(key: "LD", value: #""$(BAZEL_INTEGRATION_DIR)/ld""#), - .init( - key: "LDPLUSPLUS", - value: #""$(BAZEL_INTEGRATION_DIR)/ld""# - ), - .init( - key: "LIBTOOL", - value: #""$(BAZEL_INTEGRATION_DIR)/libtool""# - ), + .init(key: "TOOLCHAINS", value: customToolchainID), .init( key: "IMPORT_INDEX_BUILD_INDEXSTORES", value: importIndexBuildIndexstores ? "YES" : "NO" @@ -135,13 +126,8 @@ extension Generator { .init(key: "RULES_XCODEPROJ_BUILD_MODE", value: "bazel"), .init(key: "SRCROOT", value: workspace.pbxProjEscaped), .init(key: "SUPPORTS_MACCATALYST", value: "NO"), - .init( - key: "SWIFT_EXEC", - value: #""$(BAZEL_INTEGRATION_DIR)/swiftc""# - ), .init(key: "SWIFT_OBJC_INTERFACE_HEADER_NAME", value: #""""#), .init(key: "SWIFT_OPTIMIZATION_LEVEL", value: #""-Onone""#), - .init(key: "SWIFT_USE_INTEGRATED_DRIVER", value: "NO"), .init(key: "SWIFT_VERSION", value: "5.0"), .init(key: "TAPI_EXEC", value: "/usr/bin/true"), .init( diff --git a/tools/generators/pbxproj_prefix/test/PBXProjectBuildSettingsTests.swift b/tools/generators/pbxproj_prefix/test/PBXProjectBuildSettingsTests.swift index 46b81a6d7..62d3e1600 100644 --- a/tools/generators/pbxproj_prefix/test/PBXProjectBuildSettingsTests.swift +++ b/tools/generators/pbxproj_prefix/test/PBXProjectBuildSettingsTests.swift @@ -32,14 +32,12 @@ class PBXProjectBuildSettingsTests: XCTestCase { BUILD_DIR = "$(SYMROOT)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; BUILD_MARKER_FILE = "$(OBJROOT)/build_marker"; BUILD_WORKSPACE_DIRECTORY = "$(SRCROOT)"; - CC = "$(BAZEL_INTEGRATION_DIR)/clang.sh"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_MODULES_AUTOLINK = NO; CODE_SIGNING_ALLOWED = NO; CODE_SIGN_STYLE = Manual; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(BAZEL_PACKAGE_BIN_DIR)"; COPY_PHASE_STRIP = NO; - CXX = "$(BAZEL_INTEGRATION_DIR)/clang.sh"; DEBUG_INFORMATION_FORMAT = dwarf; DSTROOT = "$(PROJECT_TEMP_DIR)"; ENABLE_DEBUG_DYLIB = NO; @@ -53,22 +51,18 @@ class PBXProjectBuildSettingsTests: XCTestCase { INDEX_IMPORT = "$(BAZEL_EXTERNAL)/index-import"; INSTALL_PATH = "$(BAZEL_PACKAGE_BIN_DIR)/$(TARGET_NAME)/bin"; INTERNAL_DIR = "$(PROJECT_FILE_PATH)/rules_xcodeproj"; - LD = "$(BAZEL_INTEGRATION_DIR)/ld"; - LDPLUSPLUS = "$(BAZEL_INTEGRATION_DIR)/ld"; LD_DYLIB_INSTALL_NAME = ""; LD_OBJC_ABI_VERSION = ""; LD_RUNPATH_SEARCH_PATHS = ""; - LIBTOOL = "$(BAZEL_INTEGRATION_DIR)/libtool"; + TOOLCHAINS = "com.rules_xcodeproj.BazelRulesXcodeProj.16B40"; ONLY_ACTIVE_ARCH = YES; PROJECT_DIR = "/some/project dir"; RESOLVED_REPOSITORIES = "\"\" \"/tmp/workspace\""; RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = "/Users/TimApple/Star Board"; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; @@ -88,7 +82,8 @@ class PBXProjectBuildSettingsTests: XCTestCase { projectDir: projectDir, resolvedRepositories: resolvedRepositories, workspace: workspace, - createBuildSettingsAttribute: CreateBuildSettingsAttribute() + createBuildSettingsAttribute: CreateBuildSettingsAttribute(), + customToolchainID: "com.rules_xcodeproj.BazelRulesXcodeProj.16B40" ) // Assert diff --git a/tools/swiftc_stub/main.swift b/tools/swiftc_stub/main.swift index f2bfb7f3e..bb717741f 100644 --- a/tools/swiftc_stub/main.swift +++ b/tools/swiftc_stub/main.swift @@ -5,6 +5,11 @@ import Foundation enum PathKey: String { case emitModulePath = "-emit-module-path" case emitObjCHeaderPath = "-emit-objc-header-path" + case emitModuleSourceInfoPath = "-emit-module-source-info-path" + case serializeDiagnosticsPath = "-serialize-diagnostics-path" + case emitDependenciesPath = "-emit-dependencies-path" + case emitABIDescriptorPath = "-emit-abi-descriptor-path" + case emitModuleDocPath = "-emit-module-doc-path" case outputFileMap = "-output-file-map" case sdk = "-sdk" } @@ -75,17 +80,17 @@ extension URL { } } -/// Touch the Xcode-required `.d` files +/// Touch the Xcode-required `.d` and `-master-emit-module.d` files func touchDepsFiles(isWMO: Bool, paths: [PathKey: URL]) throws { guard let outputFileMapPath = paths[PathKey.outputFileMap] else { return } if isWMO { - let dPath = String( - outputFileMapPath.path.dropLast("-OutputFileMap.json".count) + - "-master.d" - ) - var url = URL(fileURLWithPath: dPath) - try url.touch() + let pathNoExtension = String(outputFileMapPath.path.dropLast("-OutputFileMap.json".count)) + var masterDFilePath = URL(fileURLWithPath: pathNoExtension + "-master.d") + try masterDFilePath.touch() + + var dFilePath = URL(fileURLWithPath: pathNoExtension + ".d") + try dFilePath.touch() } else { let data = try Data(contentsOf: outputFileMapPath) let outputFileMapRaw = try JSONSerialization.jsonObject( @@ -98,23 +103,28 @@ func touchDepsFiles(isWMO: Bool, paths: [PathKey: URL]) throws { } for entry in outputFileMap.values { - guard let dPath = entry["dependencies"] as? String else { - continue + if let dPath = entry["dependencies"] as? String { + var url = URL(fileURLWithPath: dPath) + try url.touch() } - var url = URL(fileURLWithPath: dPath) - try url.touch() + if let dPath = entry["emit-module-dependencies"] as? String { + var url = URL(fileURLWithPath: dPath) + try url.touch() + } + continue } } } -/// Touch the Xcode-required `.swift{module,doc,sourceinfo}` files +/// Touch the Xcode-required `-master-emit-module.d`, `.{d,abi.json}` and `.swift{module,doc,sourceinfo}` files func touchSwiftmoduleArtifacts(paths: [PathKey: URL]) throws { if var swiftmodulePath = paths[PathKey.emitModulePath] { - var swiftdocPath = swiftmodulePath.deletingPathExtension() + let pathNoExtension = swiftmodulePath.deletingPathExtension() + var swiftdocPath = pathNoExtension .appendingPathExtension("swiftdoc") - var swiftsourceinfoPath = swiftmodulePath.deletingPathExtension() + var swiftsourceinfoPath = pathNoExtension .appendingPathExtension("swiftsourceinfo") - var swiftinterfacePath = swiftmodulePath.deletingPathExtension() + var swiftinterfacePath = pathNoExtension .appendingPathExtension("swiftinterface") try swiftmodulePath.touch() @@ -126,6 +136,29 @@ func touchSwiftmoduleArtifacts(paths: [PathKey: URL]) throws { if var generatedHeaderPath = paths[PathKey.emitObjCHeaderPath] { try generatedHeaderPath.touch() } + + if var path = paths[PathKey.emitModuleSourceInfoPath] { + try path.touch() + } + + if var path = paths[PathKey.serializeDiagnosticsPath] { + try path.touch() + } + + if var path = paths[PathKey.emitDependenciesPath] { + try path.touch() + } + + if var path = paths[PathKey.emitABIDescriptorPath] { + try path.touch() + } + + if var path = paths[PathKey.emitModuleDocPath] { + var swiftModulePath = path.deletingPathExtension() + .appendingPathExtension("swiftmodule") + try swiftModulePath.touch() + try path.touch() + } } func runSubProcess(executable: String, args: [String]) throws -> Int32 { diff --git a/xcodeproj/internal/BUILD b/xcodeproj/internal/BUILD index 292144382..2e47b80cc 100644 --- a/xcodeproj/internal/BUILD +++ b/xcodeproj/internal/BUILD @@ -1,4 +1,5 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//xcodeproj/internal:custom_toolchain.bzl", "custom_toolchain") bzl_library( name = "bazel_tools", @@ -46,3 +47,16 @@ filegroup( tags = ["manual"], visibility = ["//:__subpackages__"], ) + +custom_toolchain( + name = "rulesxcodeproj_toolchain", + overrides = { + # Use universal_swiftc_stub for all tools + "//" + package_name() + "/bazel_integration_files:clang_binary": "clang,clang++,cc,c++", + "//" + package_name() + "/bazel_integration_files:libtool_binary": "libtool", + "//" + package_name() + "/bazel_integration_files:ld_binary": "ld", + "//tools/swiftc_stub:universal_swiftc_stub": "swiftc,swift-frontend", + }, + toolchain_name = "BazelRulesXcodeProj", + visibility = ["//visibility:public"], +) diff --git a/xcodeproj/internal/bazel_integration_files/BUILD b/xcodeproj/internal/bazel_integration_files/BUILD index 287a22049..4ff69a33e 100644 --- a/xcodeproj/internal/bazel_integration_files/BUILD +++ b/xcodeproj/internal/bazel_integration_files/BUILD @@ -132,8 +132,29 @@ fi ], ) -# Release +filegroup( + name = "libtool_binary", + srcs = ["libtool"], + tags = ["manual"], + # This is made public for internal use only + visibility = ["//:__subpackages__"], +) +filegroup( + name = "ld_binary", + srcs = ["ld"], + tags = ["manual"], + visibility = ["//:__subpackages__"], +) + +filegroup( + name = "clang_binary", + srcs = ["clang.sh"], + tags = ["manual"], + visibility = ["//:__subpackages__"], +) + +# Release filegroup( name = "release_files", srcs = glob( diff --git a/xcodeproj/internal/bazel_integration_files/ld b/xcodeproj/internal/bazel_integration_files/ld index 215625053..691c6068e 100755 --- a/xcodeproj/internal/bazel_integration_files/ld +++ b/xcodeproj/internal/bazel_integration_files/ld @@ -7,6 +7,11 @@ passthrough_args=("${@:1}") while test $# -gt 0 do case $1 in + -version_details) + # Pass through for version details + exec "$(xcrun --find ld)" -version_details + ;; + *_dependency_info.dat) ld_version=$(ld -v 2>&1 | grep ^@) printf "\0%s\0" "$ld_version" > "$1" diff --git a/xcodeproj/internal/pbxproj_partials.bzl b/xcodeproj/internal/pbxproj_partials.bzl index 9247c9c0f..62bdfcd0e 100644 --- a/xcodeproj/internal/pbxproj_partials.bzl +++ b/xcodeproj/internal/pbxproj_partials.bzl @@ -658,6 +658,7 @@ def _write_pbxproj_prefix( ), colorize, config, + custom_toolchain_id, default_xcode_configuration, execution_root_file, generator_name, @@ -681,6 +682,7 @@ def _write_pbxproj_prefix( apple_platform_to_platform_name: Exposed for testing. Don't set. colorize: A `bool` indicating whether to colorize the output. config: The name of the `.bazelrc` config. + custom_toolchain_id: The custom toolchain ID. default_xcode_configuration: The name of the the Xcode configuration to use when building, if not overridden by custom schemes. execution_root_file: A `File` containing the absolute path to the Bazel @@ -740,6 +742,9 @@ def _write_pbxproj_prefix( # resolvedRepositoriesFile args.add(resolved_repositories_file) + # customToolchainID + args.add(custom_toolchain_id) + # minimumXcodeVersion args.add(minimum_xcode_version) diff --git a/xcodeproj/internal/xcodeproj_incremental_rule.bzl b/xcodeproj/internal/xcodeproj_incremental_rule.bzl index 0a87d0b42..1544e62f3 100644 --- a/xcodeproj/internal/xcodeproj_incremental_rule.bzl +++ b/xcodeproj/internal/xcodeproj_incremental_rule.bzl @@ -29,6 +29,7 @@ load( "write_extension_point_identifiers_file", ) load(":incremental_xcode_targets.bzl", xcode_targets_module = "incremental_xcode_targets") +load(":providers.bzl", "ToolchainInfo") load(":selected_model_versions.bzl", "write_selected_model_versions_file") load(":target_id.bzl", "write_target_ids_list") load(":xcodeprojinfo.bzl", "XcodeProjInfo") @@ -348,7 +349,8 @@ def _write_project_contents( xcode_configurations, xcode_target_configurations, xcode_targets, - xcode_targets_by_label): + xcode_targets_by_label, + toolchain_info): execution_root_file = write_execution_root_file( actions = actions, bin_dir_path = bin_dir_path, @@ -444,6 +446,7 @@ def _write_project_contents( actions = actions, colorize = colorize, config = config, + custom_toolchain_id = toolchain_info.identifier, default_xcode_configuration = default_xcode_configuration, execution_root_file = execution_root_file, generator_name = name, @@ -701,6 +704,7 @@ Are you using an `alias`? `xcodeproj.focused_targets` and \ xcode_configurations = xcode_configurations, xcode_targets = xcode_targets, xcode_targets_by_label = xcode_targets_by_label, + toolchain_info = ctx.attr._rulesxcodeproj_toolchain[ToolchainInfo], ) # Schemes @@ -904,6 +908,9 @@ def _xcodeproj_incremental_attrs( ), executable = True, ), + "_rulesxcodeproj_toolchain": attr.label( + default = Label(":rulesxcodeproj_toolchain"), + ), "_selected_model_versions_generator": attr.label( cfg = "exec", default = Label( From ca124116270ffa33d3e81d3fb4342c720821ecfa Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sun, 23 Mar 2025 19:10:43 +0100 Subject: [PATCH 06/13] always ignore TOOLCHAINS in bazel_build --- xcodeproj/internal/templates/bazel_build.sh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/xcodeproj/internal/templates/bazel_build.sh b/xcodeproj/internal/templates/bazel_build.sh index 4fcc467a2..de02a4ce5 100755 --- a/xcodeproj/internal/templates/bazel_build.sh +++ b/xcodeproj/internal/templates/bazel_build.sh @@ -94,15 +94,6 @@ readonly base_pre_config_flags=( "--bes_upload_mode=NOWAIT_FOR_UPLOAD_COMPLETE" ) -# Custom Swift toolchains - -if [[ -n "${TOOLCHAINS-}" ]]; then - toolchain="${TOOLCHAINS%% *}" - if [[ "$toolchain" == "com.apple.dt.toolchain.XcodeDefault" ]]; then - unset toolchain - fi -fi - # Build echo "Starting Bazel build" @@ -114,7 +105,6 @@ echo "Starting Bazel build" ${build_pre_config_flags:+"${build_pre_config_flags[@]}"} \ --config="$config" \ --color=yes \ - ${toolchain:+--action_env=TOOLCHAINS="$toolchain"} \ "$output_groups_flag" \ "%generator_label%" \ ${labels:+"--build_metadata=PATTERN=${labels[*]}"} \ From 6f53ddef3d44feb30bb9daa0f05fac3827464007 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Sun, 23 Mar 2025 23:46:25 +0100 Subject: [PATCH 07/13] fix creating files and add the ld clang args to it --- tools/swiftc_stub/main.swift | 98 +++++++++---------- .../internal/bazel_integration_files/clang.sh | 8 ++ 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/tools/swiftc_stub/main.swift b/tools/swiftc_stub/main.swift index bb717741f..ec69d5b8d 100644 --- a/tools/swiftc_stub/main.swift +++ b/tools/swiftc_stub/main.swift @@ -4,9 +4,7 @@ import Foundation enum PathKey: String { case emitModulePath = "-emit-module-path" - case emitObjCHeaderPath = "-emit-objc-header-path" case emitModuleSourceInfoPath = "-emit-module-source-info-path" - case serializeDiagnosticsPath = "-serialize-diagnostics-path" case emitDependenciesPath = "-emit-dependencies-path" case emitABIDescriptorPath = "-emit-abi-descriptor-path" case emitModuleDocPath = "-emit-module-doc-path" @@ -19,18 +17,23 @@ func processArgs( ) async throws -> ( isPreviewThunk: Bool, isWMO: Bool, - paths: [PathKey: URL] + paths: [PathKey: [URL]] ) { var isPreviewThunk = false var isWMO = false - var paths: [PathKey: URL] = [:] + var paths: [PathKey: [URL]] = [:] var previousArg: String? func processArg(_ arg: String) { if let rawPathKey = previousArg, let key = PathKey(rawValue: rawPathKey) { - paths[key] = URL(fileURLWithPath: arg) + let url = URL(fileURLWithPath: arg) + if paths[key] != nil { + paths[key]?.append(url) + } else { + paths[key] = [url] + } previousArg = nil return } @@ -80,17 +83,22 @@ extension URL { } } +extension Array where Element == URL { + mutating func touch() throws { + for var url in self { + try url.touch() + } + } +} + /// Touch the Xcode-required `.d` and `-master-emit-module.d` files -func touchDepsFiles(isWMO: Bool, paths: [PathKey: URL]) throws { - guard let outputFileMapPath = paths[PathKey.outputFileMap] else { return } +func touchDepsFiles(isWMO: Bool, paths: [PathKey: [URL]]) throws { + guard let outputFileMapPaths = paths[PathKey.outputFileMap], let outputFileMapPath = outputFileMapPaths.first else { return } if isWMO { let pathNoExtension = String(outputFileMapPath.path.dropLast("-OutputFileMap.json".count)) var masterDFilePath = URL(fileURLWithPath: pathNoExtension + "-master.d") try masterDFilePath.touch() - - var dFilePath = URL(fileURLWithPath: pathNoExtension + ".d") - try dFilePath.touch() } else { let data = try Data(contentsOf: outputFileMapPath) let outputFileMapRaw = try JSONSerialization.jsonObject( @@ -107,57 +115,49 @@ func touchDepsFiles(isWMO: Bool, paths: [PathKey: URL]) throws { var url = URL(fileURLWithPath: dPath) try url.touch() } - if let dPath = entry["emit-module-dependencies"] as? String { - var url = URL(fileURLWithPath: dPath) - try url.touch() - } continue } } } /// Touch the Xcode-required `-master-emit-module.d`, `.{d,abi.json}` and `.swift{module,doc,sourceinfo}` files -func touchSwiftmoduleArtifacts(paths: [PathKey: URL]) throws { - if var swiftmodulePath = paths[PathKey.emitModulePath] { - let pathNoExtension = swiftmodulePath.deletingPathExtension() - var swiftdocPath = pathNoExtension - .appendingPathExtension("swiftdoc") - var swiftsourceinfoPath = pathNoExtension - .appendingPathExtension("swiftsourceinfo") - var swiftinterfacePath = pathNoExtension - .appendingPathExtension("swiftinterface") - - try swiftmodulePath.touch() - try swiftdocPath.touch() - try swiftsourceinfoPath.touch() - try swiftinterfacePath.touch() - } - - if var generatedHeaderPath = paths[PathKey.emitObjCHeaderPath] { - try generatedHeaderPath.touch() - } - - if var path = paths[PathKey.emitModuleSourceInfoPath] { - try path.touch() +func touchSwiftmoduleArtifacts(paths: [PathKey: [URL]]) throws { + if let swiftmodulePaths = paths[PathKey.emitModulePath] { + for var swiftmodulePath in swiftmodulePaths { + let pathNoExtension = swiftmodulePath.deletingPathExtension() + var swiftdocPath = pathNoExtension + .appendingPathExtension("swiftdoc") + var swiftsourceinfoPath = pathNoExtension + .appendingPathExtension("swiftsourceinfo") + var swiftinterfacePath = pathNoExtension + .appendingPathExtension("swiftinterface") + + try swiftmodulePath.touch() + try swiftdocPath.touch() + try swiftsourceinfoPath.touch() + try swiftinterfacePath.touch() + } } - if var path = paths[PathKey.serializeDiagnosticsPath] { - try path.touch() + if var modulePaths = paths[PathKey.emitModuleSourceInfoPath] { + try modulePaths.touch() } - if var path = paths[PathKey.emitDependenciesPath] { - try path.touch() + if var dependencyPaths = paths[PathKey.emitDependenciesPath] { + try dependencyPaths.touch() } - if var path = paths[PathKey.emitABIDescriptorPath] { - try path.touch() + if var abiPaths = paths[PathKey.emitABIDescriptorPath] { + try abiPaths.touch() } - if var path = paths[PathKey.emitModuleDocPath] { - var swiftModulePath = path.deletingPathExtension() - .appendingPathExtension("swiftmodule") - try swiftModulePath.touch() - try path.touch() + if let docPaths = paths[PathKey.emitModuleDocPath] { + for var path in docPaths { + var swiftModulePath = path.deletingPathExtension() + .appendingPathExtension("swiftmodule") + try swiftModulePath.touch() + try path.touch() + } } } @@ -170,8 +170,8 @@ func runSubProcess(executable: String, args: [String]) throws -> Int32 { return task.terminationStatus } -func handleXcodePreviewThunk(args: [String], paths: [PathKey: URL]) throws -> Never { - guard let sdkPath = paths[PathKey.sdk]?.path else { +func handleXcodePreviewThunk(args: [String], paths: [PathKey: [URL]]) throws -> Never { + guard let sdkPath = paths[PathKey.sdk]?.first?.path else { fputs( "error: No such argument '-sdk'. Using /usr/bin/swiftc.", stderr diff --git a/xcodeproj/internal/bazel_integration_files/clang.sh b/xcodeproj/internal/bazel_integration_files/clang.sh index a327a2a3a..233ecb9f5 100755 --- a/xcodeproj/internal/bazel_integration_files/clang.sh +++ b/xcodeproj/internal/bazel_integration_files/clang.sh @@ -2,6 +2,14 @@ set -euo pipefail +# find the first argument that has a _dependency_info.dat extension +for arg in "$@"; do + if [[ "$arg" == *_dependency_info.dat ]]; then + ld_version=$(ld -v 2>&1 | grep ^@) + printf "\0%s\0" "$ld_version" > "$arg" + fi +done + while test $# -gt 0 do case $1 in From 93a4a17b8dfe72143a6c9237a6a957946ec01525 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Mon, 24 Mar 2025 19:55:58 +0100 Subject: [PATCH 08/13] Add linked libraries and search paths to generated xcodeproj targets --- .../src/Generator/CreateBuildFileObject.swift | 2 +- .../lib/PBXProj/src/BuildPhase.swift | 2 + .../lib/PBXProj/src/Identifiers.swift | 13 ++++ ...alculatePlatformVariantBuildSettings.swift | 14 ++++ .../Generator/CalculatePlatformVariants.swift | 8 ++- ...ulateXcodeConfigurationBuildSettings.swift | 2 +- .../src/Generator/CreateBuildPhases.swift | 67 ++++++++++++++++++- .../CreateFrameworkBuildFileObject.swift | 50 ++++++++++++++ .../src/Generator/CreateFrameworkObject.swift | 49 ++++++++++++++ ...kBinaryWithLibrariesBuildPhaseObject.swift | 59 ++++++++++++++++ .../src/Generator/Environment.swift | 7 +- .../src/Generator/Target.swift | 2 + .../src/Generator/TargetArguments.swift | 16 ++++- .../files/incremental_output_files.bzl | 36 +++++++--- .../internal/files/linker_input_files.bzl | 34 ++++++++++ .../internal/incremental_xcode_targets.bzl | 10 ++- xcodeproj/internal/pbxproj_partials.bzl | 12 ++++ .../incremental_library_targets.bzl | 5 ++ .../incremental_top_level_targets.bzl | 4 ++ .../mixed_language_library_targets.bzl | 4 ++ 20 files changed, 376 insertions(+), 20 deletions(-) create mode 100644 tools/generators/pbxnativetargets/src/Generator/CreateFrameworkBuildFileObject.swift create mode 100644 tools/generators/pbxnativetargets/src/Generator/CreateFrameworkObject.swift create mode 100644 tools/generators/pbxnativetargets/src/Generator/CreateLinkBinaryWithLibrariesBuildPhaseObject.swift diff --git a/tools/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift b/tools/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift index 212c15d29..d36449042 100644 --- a/tools/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift +++ b/tools/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift @@ -42,7 +42,7 @@ extension Generator.CreateBuildFileObject { settings = #"settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; "# case .compileStub, .source: settings = "" - case .product, .watchKitExtension: + case .product, .watchKitExtension, .framework: // Handled in `CreateProductBuildFileObject` and // `CreateProductObject` preconditionFailure() diff --git a/tools/generators/lib/PBXProj/src/BuildPhase.swift b/tools/generators/lib/PBXProj/src/BuildPhase.swift index 7ddbd302c..b9fd5092c 100644 --- a/tools/generators/lib/PBXProj/src/BuildPhase.swift +++ b/tools/generators/lib/PBXProj/src/BuildPhase.swift @@ -5,6 +5,7 @@ public enum BuildPhase { case sources case copySwiftGeneratedHeader case embedAppExtensions + case linkBinaryWithLibraries public var name: String { switch self { @@ -16,6 +17,7 @@ Copy Bazel Outputs / Generate Bazel Dependencies (Index Build) case .sources: return "Sources" case .copySwiftGeneratedHeader: return "Copy Swift Generated Header" case .embedAppExtensions: return "Embed App Extensions" + case .linkBinaryWithLibraries: return "Frameworks" } } } diff --git a/tools/generators/lib/PBXProj/src/Identifiers.swift b/tools/generators/lib/PBXProj/src/Identifiers.swift index 5a4362018..405cd854a 100644 --- a/tools/generators/lib/PBXProj/src/Identifiers.swift +++ b/tools/generators/lib/PBXProj/src/Identifiers.swift @@ -89,6 +89,9 @@ FF01000000000000000001\#(byteHexStrings[index]!) \# /// The product reference for a target. case product = "P" + /// The framework reference for a target in libraries to link build phase. + case framework = "f" + /// A normal file referenced in a `BuildPhase.sources` build phase. case source = "0" @@ -186,6 +189,14 @@ FF01000000000000000001\#(byteHexStrings[index]!) \# return #""" \#(subIdentifier.shard)00\#(subIdentifier.hash)0000000000FF \# /* \#(subIdentifier.path.path) */ +"""# + + case .framework: + let basename = subIdentifier.path.path + .split(separator: "/").last! + return #""" +\#(subIdentifier.shard)A8\#(subIdentifier.hash) \# +/* \#(basename) in Frameworks */ """# case .compileStub: @@ -535,6 +546,7 @@ private extension Identifiers.BuildFiles.FileType { var buildPhase: BuildPhase { switch self { case .product: preconditionFailure() // product reference used as build file + case .framework: return .linkBinaryWithLibraries case .source: return .sources case .nonArcSource: return .sources case .compileStub: return .sources @@ -566,6 +578,7 @@ extension BuildPhase { case .sources: return "06" case .copySwiftGeneratedHeader: return "07" case .embedAppExtensions: return "08" + case .linkBinaryWithLibraries: return "09" } } } diff --git a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift index 8ea0f11b1..831a87bac 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift @@ -183,6 +183,20 @@ extension Generator.CalculatePlatformVariantBuildSettings { ) } + buildSettings.append( + .init( + key: "LIBRARY_SEARCH_PATHS", + value: platformVariant.librarySearchPaths + .map { + let path = $0.path.split(separator: "/").dropFirst().joined(separator: "/") + return "\"$(BAZEL_OUT)/\(path)\"" + } + .sorted() + .joined(separator: " ") + .pbxProjEscaped + ) + ) + buildSettings.append(contentsOf: platformVariant.buildSettingsFromFile) return buildSettings diff --git a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariants.swift b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariants.swift index 7b3066bb0..fc2e53429 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariants.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariants.swift @@ -59,6 +59,7 @@ extension Generator.CalculatePlatformVariants { ) { var srcs: [[BazelPath]] = [] var nonArcSrcs: [[BazelPath]] = [] + var librariesToLinkPaths: [[BazelPath]] = [] var excludableFilesKeysWithValues: [(TargetID, Set)] = [] for id in ids { let targetArguments = try targetArguments.value( @@ -68,6 +69,7 @@ extension Generator.CalculatePlatformVariants { srcs.append(targetArguments.srcs) nonArcSrcs.append(targetArguments.nonArcSrcs) + librariesToLinkPaths.append(targetArguments.librariesToLinkPaths) excludableFilesKeysWithValues.append( ( @@ -134,14 +136,16 @@ extension Generator.CalculatePlatformVariants { .flatMap { unitTestHosts[$0] }, dSYMPathsBuildSetting: targetArguments.dSYMPathsBuildSetting.isEmpty ? - nil : targetArguments.dSYMPathsBuildSetting + nil : targetArguments.dSYMPathsBuildSetting, + librarySearchPaths: Set(targetArguments.librarySearchPaths) ) ) } let consolidatedInputs = Target.ConsolidatedInputs( srcs: consolidatePaths(srcs), - nonArcSrcs: consolidatePaths(nonArcSrcs) + nonArcSrcs: consolidatePaths(nonArcSrcs), + librariesToLinkPaths: consolidatePaths(librariesToLinkPaths) ) return (platformVariants, allConditionalFiles, consolidatedInputs) diff --git a/tools/generators/pbxnativetargets/src/Generator/CalculateXcodeConfigurationBuildSettings.swift b/tools/generators/pbxnativetargets/src/Generator/CalculateXcodeConfigurationBuildSettings.swift index 61b43433b..008de1a35 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CalculateXcodeConfigurationBuildSettings.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CalculateXcodeConfigurationBuildSettings.swift @@ -236,7 +236,7 @@ private extension Platform { } } -private extension String { +extension String { var quoteIfNeeded: String { guard !contains(" ") else { return #""\#(self)""# diff --git a/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift b/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift index 867067fb0..d4c78dda2 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift @@ -13,6 +13,10 @@ extension Generator { CreateEmbedAppExtensionsBuildPhaseObject private let createProductBuildFileObject: CreateProductBuildFileObject private let createSourcesBuildPhaseObject: CreateSourcesBuildPhaseObject + private let createLinkBinaryWithLibrariesBuildPhaseObject: + CreateLinkBinaryWithLibrariesBuildPhaseObject + private let createFrameworkObject: CreateFrameworkObject + private let createFrameworkBuildFileObject: CreateFrameworkBuildFileObject private let callable: Callable @@ -31,6 +35,10 @@ extension Generator { CreateEmbedAppExtensionsBuildPhaseObject, createProductBuildFileObject: CreateProductBuildFileObject, createSourcesBuildPhaseObject: CreateSourcesBuildPhaseObject, + createLinkBinaryWithLibrariesBuildPhaseObject: + CreateLinkBinaryWithLibrariesBuildPhaseObject, + createFrameworkObject: CreateFrameworkObject, + createFrameworkBuildFileObject: CreateFrameworkBuildFileObject, callable: @escaping Callable = Self.defaultCallable ) { self.createBazelIntegrationBuildPhaseObject = @@ -44,6 +52,9 @@ extension Generator { createEmbedAppExtensionsBuildPhaseObject self.createProductBuildFileObject = createProductBuildFileObject self.createSourcesBuildPhaseObject = createSourcesBuildPhaseObject + self.createLinkBinaryWithLibrariesBuildPhaseObject = createLinkBinaryWithLibrariesBuildPhaseObject + self.createFrameworkObject = createFrameworkObject + self.createFrameworkBuildFileObject = createFrameworkBuildFileObject self.callable = callable } @@ -86,7 +97,10 @@ extension Generator { /*createEmbedAppExtensionsBuildPhaseObject:*/ createEmbedAppExtensionsBuildPhaseObject, /*createProductBuildFileObject:*/ createProductBuildFileObject, - /*createSourcesBuildPhaseObject:*/ createSourcesBuildPhaseObject + /*createSourcesBuildPhaseObject:*/ createSourcesBuildPhaseObject, + /*createLinkBinaryWithLibrariesBuildPhaseObject:*/ createLinkBinaryWithLibrariesBuildPhaseObject, + /*createFrameworkObject:*/ createFrameworkObject, + /*createFrameworkBuildFileObject:*/ createFrameworkBuildFileObject ) } } @@ -116,7 +130,11 @@ extension Generator.CreateBuildPhases { _ createEmbedAppExtensionsBuildPhaseObject: Generator.CreateEmbedAppExtensionsBuildPhaseObject, _ createProductBuildFileObject: Generator.CreateProductBuildFileObject, - _ createSourcesBuildPhaseObject: Generator.CreateSourcesBuildPhaseObject + _ createSourcesBuildPhaseObject: Generator.CreateSourcesBuildPhaseObject, + _ createLinkBinaryWithLibrariesBuildPhaseObject: + Generator.CreateLinkBinaryWithLibrariesBuildPhaseObject, + _ createFrameworkObject: Generator.CreateFrameworkObject, + _ createFrameworkBuildFileObject: Generator.CreateFrameworkBuildFileObject ) -> ( buildPhases: [Object], buildFileObjects: [Object], @@ -144,7 +162,11 @@ extension Generator.CreateBuildPhases { createEmbedAppExtensionsBuildPhaseObject: Generator.CreateEmbedAppExtensionsBuildPhaseObject, createProductBuildFileObject: Generator.CreateProductBuildFileObject, - createSourcesBuildPhaseObject: Generator.CreateSourcesBuildPhaseObject + createSourcesBuildPhaseObject: Generator.CreateSourcesBuildPhaseObject, + createLinkBinaryWithLibrariesBuildPhaseObject: + Generator.CreateLinkBinaryWithLibrariesBuildPhaseObject, + createFrameworkObject: Generator.CreateFrameworkObject, + createFrameworkBuildFileObject: Generator.CreateFrameworkBuildFileObject ) -> ( buildPhases: [Object], buildFileObjects: [Object], @@ -259,6 +281,45 @@ extension Generator.CreateBuildPhases { ) } + let librariesToLinkSubIdentifiers = consolidatedInputs.librariesToLinkPaths.map { bazelPath in + return ( + bazelPath, + createBuildFileSubIdentifier( + BazelPath(bazelPath.path.split(separator: "/").last.map(String.init)!), + type: .framework, + shard: shard + ), + createBuildFileSubIdentifier( + bazelPath, + type: .framework, + shard: shard + ) + ) + } + librariesToLinkSubIdentifiers + .forEach { bazelPath, buildSubIdentifier, frameworkSubIdentifier in + buildFileObjects.append( + createFrameworkBuildFileObject( + frameworkSubIdentifier: frameworkSubIdentifier, + subIdentifier: buildSubIdentifier + ) + ) + buildFileObjects.append( + createFrameworkObject( + frameworkPath: bazelPath, + subIdentifier: frameworkSubIdentifier + ) + ) + } + buildPhases.append( + createLinkBinaryWithLibrariesBuildPhaseObject( + subIdentifier: identifier.subIdentifier, + librariesToLinkIdentifiers: librariesToLinkSubIdentifiers + .map { $0.1 } + .map { Identifiers.BuildFiles.id(subIdentifier: $0) } + ) + ) + return (buildPhases, buildFileObjects, buildFileSubIdentifiers) } } diff --git a/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkBuildFileObject.swift b/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkBuildFileObject.swift new file mode 100644 index 000000000..46e29cad2 --- /dev/null +++ b/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkBuildFileObject.swift @@ -0,0 +1,50 @@ +import PBXProj + +extension Generator { + struct CreateFrameworkBuildFileObject { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Creates a `PBXBuildFile` element. + func callAsFunction( + frameworkSubIdentifier: Identifiers.BuildFiles.SubIdentifier, + subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object { + return callable( + /*frameworkSubIdentifier:*/ frameworkSubIdentifier, + /*subIdentifier:*/ subIdentifier + ) + } + } +} + +// MARK: - CreateFrameworkBuildFileObject.Callable + +extension Generator.CreateFrameworkBuildFileObject { + typealias Callable = ( + _ frameworkSubIdentifier: Identifiers.BuildFiles.SubIdentifier, + _ subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object + + static func defaultCallable( + frameworkSubIdentifier: Identifiers.BuildFiles.SubIdentifier, + subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object { + let fileRef = Identifiers.BuildFiles + .id(subIdentifier: frameworkSubIdentifier) + let content = #""" +{isa = PBXBuildFile; fileRef = \#(fileRef); } +"""# + + return Object( + identifier: Identifiers.BuildFiles.id(subIdentifier: subIdentifier), + content: content + ) + } +} diff --git a/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkObject.swift b/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkObject.swift new file mode 100644 index 000000000..5d90f2fcb --- /dev/null +++ b/tools/generators/pbxnativetargets/src/Generator/CreateFrameworkObject.swift @@ -0,0 +1,49 @@ +import PBXProj + +extension Generator { + struct CreateFrameworkObject { + private let callable: Callable + private static var existingFrameworkPaths = [BazelPath]() + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Creates a `PBXBuildFile` element. + func callAsFunction( + frameworkPath: BazelPath, + subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object { + return callable( + /*frameworkPath:*/ frameworkPath, + /*subIdentifier:*/ subIdentifier + ) + } + } +} + +// MARK: - CreateProductObject.Callable + +extension Generator.CreateFrameworkObject { + typealias Callable = ( + _ frameworkPath: BazelPath, + _ subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object + + static func defaultCallable( + frameworkPath: BazelPath, + subIdentifier: Identifiers.BuildFiles.SubIdentifier + ) -> Object { + let content = #""" +{isa = PBXFileReference; lastKnownFileType = archive.ar; name = "\#(frameworkPath.path.split(separator: "/").last!)"; path = "\#(frameworkPath.path)"; sourceTree = ""; } +"""# + + return Object( + identifier: Identifiers.BuildFiles.id(subIdentifier: subIdentifier), + content: content + ) + } +} diff --git a/tools/generators/pbxnativetargets/src/Generator/CreateLinkBinaryWithLibrariesBuildPhaseObject.swift b/tools/generators/pbxnativetargets/src/Generator/CreateLinkBinaryWithLibrariesBuildPhaseObject.swift new file mode 100644 index 000000000..a22609fa5 --- /dev/null +++ b/tools/generators/pbxnativetargets/src/Generator/CreateLinkBinaryWithLibrariesBuildPhaseObject.swift @@ -0,0 +1,59 @@ +import PBXProj + +extension Generator { + struct CreateLinkBinaryWithLibrariesBuildPhaseObject { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Creates the `PBXSourcesBuildPhase` object for a target. + func callAsFunction( + subIdentifier: Identifiers.Targets.SubIdentifier, + librariesToLinkIdentifiers: [String] + ) -> Object { + return callable( + /*subIdentifier:*/ subIdentifier, + /*buildFileIdentifiers:*/ librariesToLinkIdentifiers + ) + } + } +} + +// MARK: - CreateSourcesBuildPhaseObject.Callable + +extension Generator.CreateLinkBinaryWithLibrariesBuildPhaseObject { + typealias Callable = ( + _ subIdentifier: Identifiers.Targets.SubIdentifier, + _ librariesToLinkIdentifiers: [String] + ) -> Object + + static func defaultCallable( + subIdentifier: Identifiers.Targets.SubIdentifier, + librariesToLinkIdentifiers: [String] + ) -> Object { + // The tabs for indenting are intentional + let content = #""" +{ + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( +\#(librariesToLinkIdentifiers.map { "\t\t\t\t\($0),\n" }.joined())\# + ); + runOnlyForDeploymentPostprocessing = 0; + } +"""# + + return Object( + identifier: Identifiers.Targets.buildPhase( + .linkBinaryWithLibraries, + subIdentifier: subIdentifier + ), + content: content + ) + } +} diff --git a/tools/generators/pbxnativetargets/src/Generator/Environment.swift b/tools/generators/pbxnativetargets/src/Generator/Environment.swift index 54a3d9a21..f8d1e8c85 100644 --- a/tools/generators/pbxnativetargets/src/Generator/Environment.swift +++ b/tools/generators/pbxnativetargets/src/Generator/Environment.swift @@ -32,7 +32,12 @@ extension Generator.Environment { createProductBuildFileObject: Generator.CreateProductBuildFileObject(), createSourcesBuildPhaseObject: - Generator.CreateSourcesBuildPhaseObject() + Generator.CreateSourcesBuildPhaseObject(), + createLinkBinaryWithLibrariesBuildPhaseObject: + Generator.CreateLinkBinaryWithLibrariesBuildPhaseObject(), + createFrameworkObject: Generator.CreateFrameworkObject(), + createFrameworkBuildFileObject: + Generator.CreateFrameworkBuildFileObject() ), createProductObject: Generator.CreateProductObject(), createTargetObject: Generator.CreateTargetObject(), diff --git a/tools/generators/pbxnativetargets/src/Generator/Target.swift b/tools/generators/pbxnativetargets/src/Generator/Target.swift index 2f2fdd30d..fddf56447 100644 --- a/tools/generators/pbxnativetargets/src/Generator/Target.swift +++ b/tools/generators/pbxnativetargets/src/Generator/Target.swift @@ -7,6 +7,7 @@ enum Target { struct ConsolidatedInputs: Equatable { var srcs: [BazelPath] var nonArcSrcs: [BazelPath] + var librariesToLinkPaths: [BazelPath] } struct Host: Equatable { @@ -33,6 +34,7 @@ enum Target { let linkParams: String? let unitTestHost: UnitTestHost? let dSYMPathsBuildSetting: String? + let librarySearchPaths: Set } struct UnitTestHost: Equatable { diff --git a/tools/generators/pbxnativetargets/src/Generator/TargetArguments.swift b/tools/generators/pbxnativetargets/src/Generator/TargetArguments.swift index 31149ea0e..a3d857ea1 100644 --- a/tools/generators/pbxnativetargets/src/Generator/TargetArguments.swift +++ b/tools/generators/pbxnativetargets/src/Generator/TargetArguments.swift @@ -29,6 +29,8 @@ struct TargetArguments: Equatable { let nonArcSrcs: [BazelPath] let dSYMPathsBuildSetting: String + let librarySearchPaths: [BazelPath] + let librariesToLinkPaths: [BazelPath] } extension Dictionary { @@ -92,6 +94,16 @@ extension Dictionary { "xcode-configurations", in: url ) + let librarySearchPaths = try rawArgs.consumeArgs( + "library-search-paths", + as: BazelPath.self, + in: url + ) + let librariesToLinkPaths = try rawArgs.consumeArgs( + "libraries_to_link_paths", + as: BazelPath.self, + in: url + ) var buildSettings: [PlatformVariantBuildSetting] = [] if let buildSettingsFile { @@ -130,7 +142,9 @@ extension Dictionary { hasCxxParams: hasCxxParams, srcs: srcs, nonArcSrcs: nonArcSrcs, - dSYMPathsBuildSetting: dSYMPathsBuildSetting + dSYMPathsBuildSetting: dSYMPathsBuildSetting, + librarySearchPaths: librarySearchPaths, + librariesToLinkPaths: librariesToLinkPaths ) ) ) diff --git a/xcodeproj/internal/files/incremental_output_files.bzl b/xcodeproj/internal/files/incremental_output_files.bzl index 17faa5828..fedf1ccf9 100644 --- a/xcodeproj/internal/files/incremental_output_files.bzl +++ b/xcodeproj/internal/files/incremental_output_files.bzl @@ -328,6 +328,16 @@ def _collect_mixed_language_output_files( ], ) + # TODO: Once BwB mode no longer has target dependencies, remove + # transitive products. Until then we need them, to allow `Copy Bazel + # Outputs` to be able to copy the products of transitive dependencies. + transitive_products = memory_efficient_depset( + transitive = [ + info.outputs._transitive_products + for info in mixed_target_infos + ], + ) + transitive_compile_params = memory_efficient_depset( compile_params_files, transitive = [ @@ -352,13 +362,15 @@ def _collect_mixed_language_output_files( ) products_depset = memory_efficient_depset( - ( - # We don't want to declare indexstore files as outputs, because they - # expand to individual files and blow up the BEP. Instead they are - # declared as inputs to `indexstores_filelist`, ensuring they are - # downloaded as needed. - [indexstores_filelist] - ), + # We don't want to declare indexstore files as outputs, because they + # expand to individual files and blow up the BEP. Instead they are + # declared as inputs to `indexstores_filelist`, ensuring they are + # downloaded as needed. + [indexstores_filelist], + transitive = [ + info.outputs._transitive_products + for info in mixed_target_infos + ], ) direct_group_list = [ @@ -380,8 +392,7 @@ def _collect_mixed_language_output_files( _transitive_indexstores = transitive_indexstores, _transitive_infoplists = transitive_infoplists, _transitive_link_params = transitive_link_params, - # Only top-level targets will have products or dSYM files - _transitive_products = EMPTY_DEPSET, + _transitive_products = products_depset, ), struct( _direct_group_list = direct_group_list, @@ -422,7 +433,12 @@ def _merge_output_files(*, transitive_infos): for info in transitive_infos ], ), - _transitive_products = EMPTY_DEPSET, + _transitive_products = memory_efficient_depset( + transitive = [ + info.outputs._transitive_products + for info in transitive_infos + ], + ), ) # Output groups diff --git a/xcodeproj/internal/files/linker_input_files.bzl b/xcodeproj/internal/files/linker_input_files.bzl index 688a050a9..fffbeeeae 100644 --- a/xcodeproj/internal/files/linker_input_files.bzl +++ b/xcodeproj/internal/files/linker_input_files.bzl @@ -63,14 +63,46 @@ def _collect_linker_inputs( ) top_level_values = None + all_libs = objc_libraries + [ + lib.static_library if lib.static_library else lib.dynamic_library + for linker_input in cc_linker_inputs + for lib in linker_input.libraries + ] + + libraries = [ + lib + for lib in all_libs + if (lib.basename.endswith(".a") or + lib.basename.endswith(".dylib")) and + lib.owner != target.label + ] + + linker_inputs_for_libs_search_paths = depset([ + lib.dirname + for lib in libraries + ]) + + framework_files = depset([ + lib.path + for lib in libraries + ]) + return struct( _cc_linker_inputs = tuple(cc_linker_inputs), _compilation_providers = compilation_providers, _objc_libraries = tuple(objc_libraries), _primary_static_library = primary_static_library, _top_level_values = top_level_values, + _linker_inputs_for_libs_search_paths = linker_inputs_for_libs_search_paths, + _framework_files = framework_files, ) +def _get_linker_inputs_for_libs_search_paths(linker_inputs): + return linker_inputs._linker_inputs_for_libs_search_paths + +def _get_libraries_path_to_link(linker_inputs): + return linker_inputs._framework_files + def _merge_linker_inputs(*, compilation_providers): return _collect_linker_inputs( target = None, @@ -386,4 +418,6 @@ linker_input_files = struct( _get_transitive_static_libraries_for_bwx ), to_input_files = _to_input_files, + get_linker_inputs_for_libs_search_paths = _get_linker_inputs_for_libs_search_paths, + get_libraries_path_to_link = _get_libraries_path_to_link, ) diff --git a/xcodeproj/internal/incremental_xcode_targets.bzl b/xcodeproj/internal/incremental_xcode_targets.bzl index ae9fe65b1..4b99339d3 100644 --- a/xcodeproj/internal/incremental_xcode_targets.bzl +++ b/xcodeproj/internal/incremental_xcode_targets.bzl @@ -83,7 +83,9 @@ def _make_incremental_xcode_target( test_host = None, transitive_dependencies, unfocus_if_not_test_host = False, - watchkit_extension = None): + watchkit_extension = None, + linker_inputs_for_libs_search_paths, + libraries_path_to_link): """Creates the internal data structure of the `xcode_targets` module. Args: @@ -119,6 +121,10 @@ def _make_incremental_xcode_target( isn't another target's test host. watchkit_extension: the target ID of this target's WatchKit extension, or `None`. + linker_inputs_for_libs_search_paths: Used to generated the + `LIBRARY_SEARCH_PATHS` build setting. + libraries_path_to_link: A depset of libraries paths to link to the + target. """ if not is_top_level: compile_stub_needed = False @@ -167,6 +173,8 @@ def _make_incremental_xcode_target( test_host = test_host, watchkit_extension = watchkit_extension, transitive_dependencies = transitive_dependencies, + linker_inputs_for_libs_search_paths = linker_inputs_for_libs_search_paths, + libraries_path_to_link = libraries_path_to_link, ) def _merge_xcode_inputs(*, dest_inputs, mergeable_info): diff --git a/xcodeproj/internal/pbxproj_partials.bzl b/xcodeproj/internal/pbxproj_partials.bzl index 62bdfcd0e..fe28f672a 100644 --- a/xcodeproj/internal/pbxproj_partials.bzl +++ b/xcodeproj/internal/pbxproj_partials.bzl @@ -316,6 +316,18 @@ def _write_consolidation_map_targets( terminate_with = "", ) + targets_args.add_all( + xcode_target.linker_inputs_for_libs_search_paths.to_list(), + omit_if_empty = False, + terminate_with = "", + ) + + targets_args.add_all( + xcode_target.libraries_path_to_link.to_list(), + omit_if_empty = False, + terminate_with = "", + ) + # `outputs.product_path` is only set for top-level targets if xcode_target.outputs.product_path: top_level_targets_args.add(xcode_target.id) diff --git a/xcodeproj/internal/processed_targets/incremental_library_targets.bzl b/xcodeproj/internal/processed_targets/incremental_library_targets.bzl index 55a278da3..d11c8e24b 100644 --- a/xcodeproj/internal/processed_targets/incremental_library_targets.bzl +++ b/xcodeproj/internal/processed_targets/incremental_library_targets.bzl @@ -171,6 +171,7 @@ def _process_incremental_library_target( ) = output_files.collect( actions = actions, compile_params_files = params_files, + copy_product_transitively = True, debug_outputs = debug_outputs, id = id, name = label.name, @@ -235,6 +236,10 @@ def _process_incremental_library_target( platform = platform, product = product.xcode_product, transitive_dependencies = transitive_dependencies, + linker_inputs_for_libs_search_paths = linker_input_files + .get_linker_inputs_for_libs_search_paths(linker_inputs), + libraries_path_to_link = linker_input_files + .get_libraries_path_to_link(linker_inputs), ) else: mergeable_infos = depset( diff --git a/xcodeproj/internal/processed_targets/incremental_top_level_targets.bzl b/xcodeproj/internal/processed_targets/incremental_top_level_targets.bzl index 2144804c4..dc46ffcee 100644 --- a/xcodeproj/internal/processed_targets/incremental_top_level_targets.bzl +++ b/xcodeproj/internal/processed_targets/incremental_top_level_targets.bzl @@ -632,6 +632,10 @@ def _process_focused_top_level_target( transitive_dependencies = transitive_dependencies, unfocus_if_not_test_host = unfocus_if_not_test_host, watchkit_extension = watchkit_extension, + linker_inputs_for_libs_search_paths = linker_input_files + .get_linker_inputs_for_libs_search_paths(linker_inputs), + libraries_path_to_link = linker_input_files + .get_libraries_path_to_link(linker_inputs), ), ) diff --git a/xcodeproj/internal/processed_targets/mixed_language_library_targets.bzl b/xcodeproj/internal/processed_targets/mixed_language_library_targets.bzl index cd056790d..c7fd5f003 100644 --- a/xcodeproj/internal/processed_targets/mixed_language_library_targets.bzl +++ b/xcodeproj/internal/processed_targets/mixed_language_library_targets.bzl @@ -218,6 +218,10 @@ def _process_mixed_language_library_target( platform = platform, product = product.xcode_product, transitive_dependencies = transitive_dependencies, + linker_inputs_for_libs_search_paths = linker_input_files + .get_linker_inputs_for_libs_search_paths(linker_inputs), + libraries_path_to_link = linker_input_files + .get_libraries_path_to_link(linker_inputs), ) else: mergeable_infos = depset( From 121f1ad7d38990bb8dbc4f2f4490a91e5bcc1604 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Mon, 24 Mar 2025 19:55:58 +0100 Subject: [PATCH 09/13] Add linked libraries and search paths to generated xcodeproj targets --- tools/swiftc_stub/main.swift | 50 +++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/tools/swiftc_stub/main.swift b/tools/swiftc_stub/main.swift index ec69d5b8d..c855482dd 100644 --- a/tools/swiftc_stub/main.swift +++ b/tools/swiftc_stub/main.swift @@ -1,5 +1,31 @@ +// #!/usr/bin/env swift + import Foundation +// Log command and arguments +let logMessage = """ +\(CommandLine.arguments.joined(separator: " ")) +""" +try logMessage.appendLineToURL(fileURL: URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("rulesxcodeproj_ld.log")) + +extension String { + func appendLineToURL(fileURL: URL) throws { + try (self + "\n").appendToURL(fileURL: fileURL) + } + + func appendToURL(fileURL: URL) throws { + let data = self.data(using: String.Encoding.utf8)! + if FileManager.default.fileExists(atPath: fileURL.path) { + let fileHandle = try FileHandle(forWritingTo: fileURL) + fileHandle.seekToEndOfFile() + fileHandle.write(data) + fileHandle.closeFile() + } else { + try self.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8) + } + } +} + // MARK: - Helpers enum PathKey: String { @@ -24,7 +50,7 @@ func processArgs( var paths: [PathKey: [URL]] = [:] var previousArg: String? - func processArg(_ arg: String) { + func processArg(_ arg: String) throws{ if let rawPathKey = previousArg, let key = PathKey(rawValue: rawPathKey) { @@ -40,7 +66,8 @@ func processArgs( if arg == "-wmo" || arg == "-whole-module-optimization" { isWMO = true - } else if arg.hasSuffix(".preview-thunk.swift") { + } else if arg.hasSuffix(".preview-thunk.o") { + try "isPreviewThunk".appendLineToURL(fileURL: URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("rulesxcodeproj_ld.log")) isPreviewThunk = true } else { previousArg = arg @@ -53,13 +80,13 @@ func processArgs( = URL(fileURLWithPath: String(arg.dropFirst())) for try await line in argumentFileURL.lines { if line.hasPrefix(#"""#) && line.hasSuffix(#"""#) { - processArg(String(line.dropFirst().dropLast())) + try processArg(String(line.dropFirst().dropLast())) } else { - processArg(String(line)) + try processArg(String(line)) } } } else { - processArg(arg) + try processArg(arg) } } @@ -198,11 +225,22 @@ error: Failed to parse DEVELOPER_DIR from '-sdk'. Using /usr/bin/swiftc. } let developerDir = sdkPath[range] + let processedArgs = args.dropFirst().map { arg in + if let range = arg.range(of: "BazelRulesXcodeProj16B40.xctoolchain") { + let substring = arg[.. Date: Mon, 24 Mar 2025 19:32:49 +0100 Subject: [PATCH 10/13] getting it to runtime until linking error --- .../test/fixtures/bwb.xcodeproj/project.pbxproj | 4 ---- .../test/fixtures/bwb.xcodeproj/project.pbxproj | 2 -- .../generators/legacy/src/Generator/CreateProject.swift | 2 -- tools/generators/legacy/test/CreateProjectTests.swift | 2 -- tools/generators/pbxproj_prefix/README.md | 4 ---- .../internal/bazel_integration_files/copy_outputs.sh | 9 ++++++++- 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj b/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj index 8c26a1d00..f9aaef3c1 100644 --- a/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj +++ b/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj @@ -15638,10 +15638,8 @@ RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = ../../../../..; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; @@ -18230,10 +18228,8 @@ RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = ../../../../..; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; diff --git a/examples/rules_ios/test/fixtures/bwb.xcodeproj/project.pbxproj b/examples/rules_ios/test/fixtures/bwb.xcodeproj/project.pbxproj index e6dc08353..b3e4424ff 100644 --- a/examples/rules_ios/test/fixtures/bwb.xcodeproj/project.pbxproj +++ b/examples/rules_ios/test/fixtures/bwb.xcodeproj/project.pbxproj @@ -5023,10 +5023,8 @@ RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = ../../../../..; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; diff --git a/tools/generators/legacy/src/Generator/CreateProject.swift b/tools/generators/legacy/src/Generator/CreateProject.swift index 11e23b156..d3ad85594 100644 --- a/tools/generators/legacy/src/Generator/CreateProject.swift +++ b/tools/generators/legacy/src/Generator/CreateProject.swift @@ -137,8 +137,6 @@ $(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME) "LD": "$(BAZEL_INTEGRATION_DIR)/ld", "LDPLUSPLUS": "$(BAZEL_INTEGRATION_DIR)/ld", "LIBTOOL": "$(BAZEL_INTEGRATION_DIR)/libtool", - "SWIFT_EXEC": "$(BAZEL_INTEGRATION_DIR)/swiftc", - "SWIFT_USE_INTEGRATED_DRIVER": false, "TAPI_EXEC": "/usr/bin/true", ], uniquingKeysWith: { _, r in r }) } else { diff --git a/tools/generators/legacy/test/CreateProjectTests.swift b/tools/generators/legacy/test/CreateProjectTests.swift index 56cc72128..ed68dcdb7 100644 --- a/tools/generators/legacy/test/CreateProjectTests.swift +++ b/tools/generators/legacy/test/CreateProjectTests.swift @@ -232,11 +232,9 @@ $(BUILD_DIR)/$(BAZEL_PACKAGE_BIN_DIR) "RULES_XCODEPROJ_BUILD_MODE": "bazel", "SRCROOT": directories.workspace.string, "SUPPORTS_MACCATALYST": false, - "SWIFT_EXEC": "$(BAZEL_INTEGRATION_DIR)/swiftc", "TAPI_EXEC": "/usr/bin/true", "SWIFT_OBJC_INTERFACE_HEADER_NAME": "", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "SWIFT_USE_INTEGRATED_DRIVER": false, "SWIFT_VERSION": "5.0", "TARGET_TEMP_DIR": """ $(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME) diff --git a/tools/generators/pbxproj_prefix/README.md b/tools/generators/pbxproj_prefix/README.md index 6c87afac2..9139203e8 100644 --- a/tools/generators/pbxproj_prefix/README.md +++ b/tools/generators/pbxproj_prefix/README.md @@ -232,10 +232,8 @@ Here is an example output: RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = /tmp/workspace; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; @@ -292,10 +290,8 @@ Here is an example output: RULES_XCODEPROJ_BUILD_MODE = bazel; SRCROOT = /tmp/workspace; SUPPORTS_MACCATALYST = NO; - SWIFT_EXEC = "$(BAZEL_INTEGRATION_DIR)/swiftc"; SWIFT_OBJC_INTERFACE_HEADER_NAME = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_USE_INTEGRATED_DRIVER = NO; SWIFT_VERSION = 5.0; TAPI_EXEC = /usr/bin/true; TARGET_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(BAZEL_PACKAGE_BIN_DIR)/$(COMPILE_TARGET_NAME)"; diff --git a/xcodeproj/internal/bazel_integration_files/copy_outputs.sh b/xcodeproj/internal/bazel_integration_files/copy_outputs.sh index 1603b6f9a..0a3b0ca58 100755 --- a/xcodeproj/internal/bazel_integration_files/copy_outputs.sh +++ b/xcodeproj/internal/bazel_integration_files/copy_outputs.sh @@ -25,10 +25,17 @@ if [[ "$ACTION" != indexbuild ]]; then if [[ -n ${BAZEL_OUTPUTS_PRODUCT:-} ]]; then cd "${BAZEL_OUTPUTS_PRODUCT%/*}" + # Symlink .o files from BAZEL_PACKAGE_BIN_DIR to OBJECT_FILE_DIR_normal/arm64 + find "$PWD" -name '*.o' -exec sh -c ' + TARGET_FILE="$OBJECT_FILE_DIR_normal/arm64/$(basename "$1" | sed "s/\.swift//")" + rm -f $TARGET_FILE + cp "$1" $TARGET_FILE + ' _ {} \; + if [[ -f "$BAZEL_OUTPUTS_PRODUCT_BASENAME" ]]; then # Product is a binary, so symlink instead of rsync, to allow for Bazel-set # rpaths to work - ln -sfh "$PWD/$BAZEL_OUTPUTS_PRODUCT_BASENAME" "$TARGET_BUILD_DIR/$PRODUCT_NAME" + ln -sfh "$PWD/$BAZEL_OUTPUTS_PRODUCT_BASENAME" "$TARGET_BUILD_DIR/lib$PRODUCT_NAME.a" else if [[ $(sw_vers -productVersion | cut -d '.' -f 1-2) == "15.4" ]]; then # 15.4's `rsync` has a bug that requires the src to have write From 031b401d1786d37df390301f27ecd161489d404f Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Mon, 31 Mar 2025 11:28:47 +0200 Subject: [PATCH 11/13] draft --- .../src/Generator/PBXProjectBuildSettings.swift | 11 +++++++++++ tools/swiftc_stub/main.swift | 7 ++++--- .../internal/bazel_integration_files/copy_outputs.sh | 3 ++- xcodeproj/internal/templates/xcodeproj.bazelrc | 4 ++++ xcodeproj/internal/xcodeproj_incremental_rule.bzl | 4 +++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift b/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift index b405ecd4b..44b7e398c 100644 --- a/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift +++ b/tools/generators/pbxproj_prefix/src/Generator/PBXProjectBuildSettings.swift @@ -97,6 +97,17 @@ extension Generator { value: #""$(INDEX_DATA_STORE_DIR)""# ), .init(key: "INDEX_FORCE_SCRIPT_EXECUTION", value: "YES"), + // .init(key: "CC", value: #""$(BAZEL_INTEGRATION_DIR)/clang.sh""#), + // .init(key: "CXX", value: #""$(BAZEL_INTEGRATION_DIR)/clang.sh""#), + // .init(key: "LD", value: #""$(BAZEL_INTEGRATION_DIR)/ld""#), + // .init( + // key: "LDPLUSPLUS", + // value: #""$(BAZEL_INTEGRATION_DIR)/ld""# + // ), + // .init( + // key: "LIBTOOL", + // value: #""$(BAZEL_INTEGRATION_DIR)/libtool""# + // ), .init( key: "INDEX_IMPORT", value: indexImport diff --git a/tools/swiftc_stub/main.swift b/tools/swiftc_stub/main.swift index c855482dd..b27b6fd1c 100644 --- a/tools/swiftc_stub/main.swift +++ b/tools/swiftc_stub/main.swift @@ -226,16 +226,17 @@ error: Failed to parse DEVELOPER_DIR from '-sdk'. Using /usr/bin/swiftc. let developerDir = sdkPath[range] let processedArgs = args.dropFirst().map { arg in - if let range = arg.range(of: "BazelRulesXcodeProj16B40.xctoolchain") { + if let range = arg.range(of: "BazelRulesXcodeProj") { let substring = arg[.. Date: Thu, 3 Apr 2025 02:00:57 +0200 Subject: [PATCH 12/13] remove unnecessary value --- tools/BUILD | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/BUILD b/tools/BUILD index 1206d7acb..a25ef9c38 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -179,8 +179,6 @@ _XCSCHEMES = [ "bazel-out/darwin_arm64-opt-exec-2B5CBBC6/bin/external/_main~non_module_deps~rules_xcodeproj_index_import/index-import", # resolvedRepositoriesFile "bazel-out/darwin_arm64-dbg/bin/external/_main~internal~rules_xcodeproj_generated/generator/tools/generators/xcodeproj/xcodeproj_pbxproj_partials/resolved_repositories", - # customToolchainID - "com.rules_xcodeproj.BazelRulesXcodeProj.16B40", # minimumXcodeVersion "14.0", # defaultXcodeConfiguration From 93c1f9e43e90196215aff513456378374f2ef540 Mon Sep 17 00:00:00 2001 From: Karim Alweheshy Date: Thu, 3 Apr 2025 02:01:10 +0200 Subject: [PATCH 13/13] hardcode rt builtin linking --- .../CalculatePlatformVariantBuildSettings.swift | 14 +++++++++----- .../src/Generator/CreateBuildPhases.swift | 5 ++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift index 831a87bac..10d5f9105 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift @@ -186,11 +186,15 @@ extension Generator.CalculatePlatformVariantBuildSettings { buildSettings.append( .init( key: "LIBRARY_SEARCH_PATHS", - value: platformVariant.librarySearchPaths - .map { - let path = $0.path.split(separator: "/").dropFirst().joined(separator: "/") - return "\"$(BAZEL_OUT)/\(path)\"" - } + value: ( + platformVariant.librarySearchPaths + .map { + let path = $0.path.split(separator: "/").dropFirst().joined(separator: "/") + return "\"$(BAZEL_OUT)/\(path)\"" + } + [ + "\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/lib/darwin\"" + ] + ) .sorted() .joined(separator: " ") .pbxProjEscaped diff --git a/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift b/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift index d4c78dda2..db3fcb796 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CreateBuildPhases.swift @@ -281,7 +281,10 @@ extension Generator.CreateBuildPhases { ) } - let librariesToLinkSubIdentifiers = consolidatedInputs.librariesToLinkPaths.map { bazelPath in + let libs = consolidatedInputs.librariesToLinkPaths + [ + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/lib/darwin/libclang_rt.iossim.a" + ] + let librariesToLinkSubIdentifiers = libs.map { bazelPath in return ( bazelPath, createBuildFileSubIdentifier(