diff --git a/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj b/examples/integration/test/fixtures/bwb.xcodeproj/project.pbxproj index 8c26a1d003..f9aaef3c1e 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 e6dc08353c..b3e4424ff9 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/test/internal/custom_toolchain/BUILD b/test/internal/custom_toolchain/BUILD new file mode 100644 index 0000000000..d0aa56b157 --- /dev/null +++ b/test/internal/custom_toolchain/BUILD @@ -0,0 +1,26 @@ +# 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 = { + ":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 0000000000..94146f21ba --- /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 "TestCustomToolchain" "$TOOLCHAIN_DIR/ToolchainInfo.plist"; then + echo "ERROR: ToolchainInfo.plist doesn't contain TestCustomToolchain" + 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 0000000000..ef1872d994 --- /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/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl b/test/internal/pbxproj_partials/write_pbxproj_prefix_tests.bzl index e416fdb56c..5a80e860f1 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/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift b/tools/generators/files_and_groups/src/Generator/CreateBuildFileObject.swift index 212c15d291..d36449042d 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/legacy/src/Generator/CreateProject.swift b/tools/generators/legacy/src/Generator/CreateProject.swift index 11e23b1567..d3ad855945 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 56cc72128f..ed68dcdb7b 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/lib/PBXProj/src/BuildPhase.swift b/tools/generators/lib/PBXProj/src/BuildPhase.swift index 7ddbd302c5..b9fd5092c8 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 5a43620184..405cd854a8 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 8ea0f11b1d..10d5f9105e 100644 --- a/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift +++ b/tools/generators/pbxnativetargets/src/Generator/CalculatePlatformVariantBuildSettings.swift @@ -183,6 +183,24 @@ 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)\"" + } + [ + "\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/lib/darwin\"" + ] + ) + .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 7b3066bb05..fc2e534292 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 61b43433b8..008de1a351 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 867067fb07..db3fcb7964 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,48 @@ extension Generator.CreateBuildPhases { ) } + 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( + 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 0000000000..46e29cad2b --- /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 0000000000..5d90f2fcb8 --- /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 0000000000..a22609fa56 --- /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 54a3d9a21b..f8d1e8c85b 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 2f2fdd30db..fddf564472 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 31149ea0e8..a3d857ea1b 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/tools/generators/pbxproj_prefix/README.md b/tools/generators/pbxproj_prefix/README.md index 6c87afac2e..9139203e8c 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/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift b/tools/generators/pbxproj_prefix/src/Generator/Arguments.swift index 4dd7238ab2..87381ec337 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 2ba7edd366..a785972fca 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 871eb10deb..4d7f792879 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 a5561af959..44b7e398c4 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" @@ -106,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 @@ -135,13 +137,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 46b81a6d7f..62d3e16001 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 f2bfb7f3e5..b27b6fd1ca 100644 --- a/tools/swiftc_stub/main.swift +++ b/tools/swiftc_stub/main.swift @@ -1,10 +1,39 @@ +// #!/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 { case emitModulePath = "-emit-module-path" - case emitObjCHeaderPath = "-emit-objc-header-path" + case emitModuleSourceInfoPath = "-emit-module-source-info-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" } @@ -14,25 +43,31 @@ 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) { + func processArg(_ arg: String) throws{ 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 } 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 @@ -45,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) } } @@ -75,17 +110,22 @@ extension URL { } } -/// Touch the Xcode-required `.d` files -func touchDepsFiles(isWMO: Bool, paths: [PathKey: URL]) throws { - guard let outputFileMapPath = paths[PathKey.outputFileMap] else { return } +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 outputFileMapPaths = paths[PathKey.outputFileMap], let outputFileMapPath = outputFileMapPaths.first 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() } else { let data = try Data(contentsOf: outputFileMapPath) let outputFileMapRaw = try JSONSerialization.jsonObject( @@ -98,33 +138,53 @@ 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() + continue } } } -/// Touch the Xcode-required `.swift{module,doc,sourceinfo}` files -func touchSwiftmoduleArtifacts(paths: [PathKey: URL]) throws { - if var swiftmodulePath = paths[PathKey.emitModulePath] { - var swiftdocPath = swiftmodulePath.deletingPathExtension() - .appendingPathExtension("swiftdoc") - var swiftsourceinfoPath = swiftmodulePath.deletingPathExtension() - .appendingPathExtension("swiftsourceinfo") - var swiftinterfacePath = swiftmodulePath.deletingPathExtension() - .appendingPathExtension("swiftinterface") +/// Touch the Xcode-required `-master-emit-module.d`, `.{d,abi.json}` and `.swift{module,doc,sourceinfo}` files +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 modulePaths = paths[PathKey.emitModuleSourceInfoPath] { + try modulePaths.touch() + } - try swiftmodulePath.touch() - try swiftdocPath.touch() - try swiftsourceinfoPath.touch() - try swiftinterfacePath.touch() + if var dependencyPaths = paths[PathKey.emitDependenciesPath] { + try dependencyPaths.touch() } - if var generatedHeaderPath = paths[PathKey.emitObjCHeaderPath] { - try generatedHeaderPath.touch() + if var abiPaths = paths[PathKey.emitABIDescriptorPath] { + try abiPaths.touch() + } + + if let docPaths = paths[PathKey.emitModuleDocPath] { + for var path in docPaths { + var swiftModulePath = path.deletingPathExtension() + .appendingPathExtension("swiftmodule") + try swiftModulePath.touch() + try path.touch() + } } } @@ -137,8 +197,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 @@ -165,11 +225,23 @@ 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: "BazelRulesXcodeProj") { + let substring = arg[..&1 | grep ^@) + printf "\0%s\0" "$ld_version" > "$arg" + fi +done + while test $# -gt 0 do case $1 in diff --git a/xcodeproj/internal/bazel_integration_files/copy_outputs.sh b/xcodeproj/internal/bazel_integration_files/copy_outputs.sh index 1603b6f9aa..abff32965d 100755 --- a/xcodeproj/internal/bazel_integration_files/copy_outputs.sh +++ b/xcodeproj/internal/bazel_integration_files/copy_outputs.sh @@ -25,10 +25,18 @@ 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/${PRODUCT_NAME}_objs" -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 + chmod 644 $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 diff --git a/xcodeproj/internal/bazel_integration_files/ld b/xcodeproj/internal/bazel_integration_files/ld index 2156250535..691c6068e4 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/custom_toolchain.bzl b/xcodeproj/internal/custom_toolchain.bzl new file mode 100644 index 0000000000..39b4c378f7 --- /dev/null +++ b/xcodeproj/internal/custom_toolchain.bzl @@ -0,0 +1,166 @@ +"""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: + 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: + fail("""\ +`xcode_config.xcode_version` returned an unexpected number of components: {} +""".format(len(version_components))) + + 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_id = "com.rules_xcodeproj.{}.{}".format(toolchain_name_base, xcode_version) + full_toolchain_name = "{}{}".format(toolchain_name_base, xcode_version) + + # 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 = [] + + # 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 stub does not produce any files!") + + if len(files) > 1: + fail("ERROR: Override stub produces multiple files ({}). Each stub must have exactly one file.".format( + len(files), + )) + + 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 + + # 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()]) + + 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 = symlink_script_file, + is_executable = True, + substitutions = { + "%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 = [symlink_toolchain_dir], + tools = [symlink_script_file], + mnemonic = "CreateSymlinkToolchain", + command = symlink_script_file.path, + execution_requirements = { + "local": "1", + "no-cache": "1", + "no-sandbox": "1", + "requires-darwin": "1", + }, + use_default_shell_env = True, + ) + + 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, + identifier = toolchain_id, + ) + + return [ + DefaultInfo( + files = depset([final_toolchain_dir if override_files else symlink_toolchain_dir]), + runfiles = runfiles, + ), + toolchain_provider, + ] + +custom_toolchain = rule( + implementation = _custom_toolchain_impl, + attrs = { + "overrides": attr.label_keyed_string_dict( + allow_files = True, + 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), + "_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"), + ), + "_xcode_config": attr.label( + default = configuration_field( + name = "xcode_config_label", + fragment = "apple", + ), + ), + }, +) diff --git a/xcodeproj/internal/files/incremental_output_files.bzl b/xcodeproj/internal/files/incremental_output_files.bzl index 17faa58283..fedf1ccf97 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 688a050a9c..fffbeeeae0 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 ae9fe65b1a..4b99339d3b 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 9247c9c0f4..fe28f672ae 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) @@ -658,6 +670,7 @@ def _write_pbxproj_prefix( ), colorize, config, + custom_toolchain_id, default_xcode_configuration, execution_root_file, generator_name, @@ -681,6 +694,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 +754,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/processed_targets/incremental_library_targets.bzl b/xcodeproj/internal/processed_targets/incremental_library_targets.bzl index 55a278da39..d11c8e24bf 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 2144804c47..dc46ffcee0 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 cd056790d6..c7fd5f0032 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( diff --git a/xcodeproj/internal/providers.bzl b/xcodeproj/internal/providers.bzl index 397c7e3300..4908e1c3b4 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/bazel_build.sh b/xcodeproj/internal/templates/bazel_build.sh index 4fcc467a2f..de02a4ce5a 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[*]}"} \ diff --git a/xcodeproj/internal/templates/custom_toolchain_override.sh b/xcodeproj/internal/templates/custom_toolchain_override.sh new file mode 100644 index 0000000000..f8e1a62b8a --- /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/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" + + 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 new file mode 100644 index 0000000000..611a61bc93 --- /dev/null +++ b/xcodeproj/internal/templates/custom_toolchain_symlink.sh @@ -0,0 +1,85 @@ +#!/bin/bash +set -euo pipefail + +# 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) + +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" + +# Process all files from the default toolchain +find "$DEFAULT_TOOLCHAIN" -type f -o -type l | while read -r file; do + 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 + + # 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 + should_skip=1 + break + fi + done + + if [[ $should_skip -eq 1 ]]; then + continue + fi + + # 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 + + + + + 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" diff --git a/xcodeproj/internal/templates/xcodeproj.bazelrc b/xcodeproj/internal/templates/xcodeproj.bazelrc index 2ad00575d0..8d820aab50 100644 --- a/xcodeproj/internal/templates/xcodeproj.bazelrc +++ b/xcodeproj/internal/templates/xcodeproj.bazelrc @@ -93,6 +93,10 @@ common:rules_xcodeproj_indexbuild --bes_backend= --bes_results_url= common:rules_xcodeproj_swiftuipreviews --config=rules_xcodeproj +# This is required for Previews to dynamically substitute Swift UI view bodies. +# https://github.com/swiftlang/swift-build/blob/25648e8e5cc5e4f7f91aba666007ff9a00d6bd31/Sources/SwiftBuild/SWBPreviewSupport.swift#L77 +common:rules_xcodeproj --features=swift.experimental.OpaqueTypeErasure + # Allow frameworks to find framework dependencies when running a preview # See `$PROJECT_FILE_PATH/rules_xcodeproj/bazel/copy_outputs.sh` for more info common:rules_xcodeproj_swiftuipreviews --linkopt="-Wl,-rpath,@loader_path/SwiftUIPreviewsFrameworks" diff --git a/xcodeproj/internal/xcodeproj_incremental_rule.bzl b/xcodeproj/internal/xcodeproj_incremental_rule.bzl index 0a87d0b428..52c82930ab 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 @@ -774,7 +778,9 @@ Are you using an `alias`? `xcodeproj.focused_targets` and \ DefaultInfo( executable = installer, files = depset( - transitive = [inputs.important_generated], + transitive = [inputs.important_generated] + [ + ctx.attr._rulesxcodeproj_toolchain.files + ], ), runfiles = ctx.runfiles(files = runfiles), ), @@ -904,6 +910,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(