From bd2e1dcedfe8263da000ebd5369eb9556781907b Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Wed, 1 Nov 2023 13:07:38 -0500 Subject: [PATCH] Add `target_build_settings` generator Signed-off-by: Brentley Jones --- tools/BUILD | 164 +++++++++ tools/generators/BUILD | 1 + tools/generators/README.md | 8 + tools/generators/target_build_settings/BUILD | 94 +++++ .../target_build_settings/BUILD.release.bazel | 8 + .../target_build_settings/README.md | 259 ++++++++++++++ .../src/Generator/Environment.swift | 49 +++ .../src/Generator/Generator.swift | 120 +++++++ .../ParseTransitiveSwiftDebugSettings.swift | 98 +++++ .../src/Generator/ProcessArgs.swift | 257 +++++++++++++ .../src/Generator/ProcessCArgs.swift | 125 +++++++ .../src/Generator/ProcessCcArg.swift | 74 ++++ .../src/Generator/ProcessCcArgs.swift | 151 ++++++++ .../src/Generator/ProcessCxxArgs.swift | 126 +++++++ .../src/Generator/ProcessSwiftArg.swift | 160 +++++++++ .../src/Generator/ProcessSwiftArgs.swift | 337 ++++++++++++++++++ .../src/Generator/ProcessSwiftClangArg.swift | 164 +++++++++ .../Generator/ProcessSwiftFrontendArg.swift | 83 +++++ .../src/Generator/String+Extensions.swift | 64 ++++ .../src/Generator/WriteBuildSettings.swift | 52 +++ .../src/TargetBuildSettings.swift | 27 ++ .../target_build_settings/test/Test.swift | 0 22 files changed, 2421 insertions(+) create mode 100644 tools/generators/target_build_settings/BUILD create mode 100644 tools/generators/target_build_settings/BUILD.release.bazel create mode 100644 tools/generators/target_build_settings/README.md create mode 100644 tools/generators/target_build_settings/src/Generator/Environment.swift create mode 100644 tools/generators/target_build_settings/src/Generator/Generator.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ParseTransitiveSwiftDebugSettings.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessArgs.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessCArgs.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessCcArg.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessCcArgs.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessCxxArgs.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessSwiftArg.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessSwiftArgs.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessSwiftClangArg.swift create mode 100644 tools/generators/target_build_settings/src/Generator/ProcessSwiftFrontendArg.swift create mode 100644 tools/generators/target_build_settings/src/Generator/String+Extensions.swift create mode 100644 tools/generators/target_build_settings/src/Generator/WriteBuildSettings.swift create mode 100644 tools/generators/target_build_settings/src/TargetBuildSettings.swift create mode 100644 tools/generators/target_build_settings/test/Test.swift diff --git a/tools/BUILD b/tools/BUILD index d7fef54b80..66ef4dba26 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -9,6 +9,7 @@ _TOOLS = { "pbxproj_prefix": "//tools/generators/pbxproj_prefix", "pbxtargetdependencies": "//tools/generators/pbxtargetdependencies", "swiftc_stub": "//tools/swiftc_stub:swiftc", + "target_build_settings": "//tools/generators/target_build_settings", "xcschemes": "//tools/generators/xcschemes", } @@ -25,6 +26,9 @@ _TESTS = { "//tools/generators/lib/PBXProj:PBXProjTests", "//tools/generators/pbxtargetdependencies:pbxtargetdependencies_tests", ], + "target_build_settings": [ + "//tools/generators/target_build_settings:target_build_settings_tests", + ], "xcschemes": [ "//tools/generators/lib/PBXProj:PBXProjTests", "//tools/generators/lib/XCScheme:XCSchemeTests", @@ -199,6 +203,163 @@ _SCHEMES = [ build_configuration = "Release", ), ), + xcode_schemes.scheme( + name = "target_build_settings", + launch_action = xcode_schemes.launch_action( + _TOOLS["target_build_settings"], + args = [ + # colorize + "0", + # buildSettingsOutputPath + "/tmp/pbxproj_partials/target_build_settings", + # swiftDebugSettingsOutputPath + "/tmp/pbxproj_partials/swift_debug_settings", + # includeSelfSwiftDebugSettings + "1", + # transitiveSwiftDebugSettingPaths + "2", + "/tmp/pbxproj_partials/transitive_swift_debug_settings_0", + "/tmp/pbxproj_partials/transitive_swift_debug_settings_1", + # deviceFamily + "4", + # extensionSafe + "0", + # generatesDsyms + "1", + # infoPlist + "bazel-out/generated/Info.plist", + # entitlements + "project/level/app.entitlements", + # certificateName + "", + # provisioningProfileName + "", + # teamID + "", + # provisioningProfileIsXcodeManaged + "0", + # packageBinDir + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib", + # previewFrameworkPaths + "", + # previewsIncludePath + "", + # swiftArgs + "-target", + "arm64_32-apple-watchos7.0", + "-sdk", + "__BAZEL_XCODE_SDKROOT__", + "-debug-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-file-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-emit-object", + "-output-file-map", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.output_file_map.json", + "-Xfrontend", + "-no-clang-module-breadcrumbs", + "-swift-version", + "4.2", + "-emit-module-path", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.swiftmodule", + "-emit-objc-header-path", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/generated/Lib-Swift.h", + "-enable-bare-slash-regex", + "-DNDEBUG", + "-O", + "-whole-module-optimization", + "-Xfrontend", + "-no-serialize-debugging-options", + "-g", + "-Xwrapped-swift=-debug-prefix-pwd-is-dot", + "-Xwrapped-swift=-file-prefix-pwd-is-dot", + "-module-cache-path", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/_swift_module_cache", + "-Fexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k", + "-Xcc", + "-Fexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k", + "-Xcc", + "-iquoteexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift", + "-Xcc", + "-iquotebazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/external/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift", + "-Xcc", + "-iquote.", + "-Xcc", + "-iquotebazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin", + "-Xcc", + "-fmodule-map-file=external/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k/CryptoSwift.framework/Modules/module.modulemap", + "-Xfrontend", + "-color-diagnostics", + "-num-threads", + "12", + "-module-name", + "Lib", + "-Xwrapped-swift=-global-index-store-import-path=bazel-out/_global_index_store", + "-index-store-path", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.indexstore", + "-Xfrontend", + "-disable-autolink-framework", + "-Xfrontend", + "CryptoSwift", + "-Xfrontend", + "-vfsoverlay", + "-Xfrontend", + "project/relative/overlay.yaml", + "-Xcc", + "-ivfsoverlay=external/overlay.yaml", + "-vfsoverlay", + "bazel-out/generated/overlay.yaml", + "-Xfrontend", + "-explicit-swift-module-map-file", + "-Xfrontend", + "bazel-out/generated/map.json", + "-Xfrontend", + "-load-plugin-executable", + "-Xfrontend", + "bazel-out/generated/macro", + "-parse-as-library", + "-application-extension", + "-static", + "-Xcc", + "-Os", + "-Xcc", + "-DNDEBUG=1", + "-Xcc", + "-Wno-unused-variable", + "-Xcc", + "-Winit-self", + "-Xcc", + "-Wno-extra", + "Lib/Resources.swift", + "bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.swift", + # cParamsOutputPath + "---", + "/tmp/pbxproj_partials/c.compile.params", + # cArgs + "-Os", + "-ivfsoverlay=external/overlay.yaml", + "--config", + "some.cfg", + # cxxParamsOutputPath + "---", + "/tmp/pbxproj_partials/cxx.compile.params", + # cxxArgs + "-Os", + "-D_FORTIFY_SOURCE=2", + "-ivfsoverlay", + "bazel-out/generated/overlay.yaml", + ], + diagnostics = _SCHEME_DIAGNOSTICS, + ), + profile_action = xcode_schemes.profile_action( + _TOOLS["target_build_settings"], + build_configuration = "Release", + ), + test_action = xcode_schemes.test_action( + _TESTS["target_build_settings"], + diagnostics = _SCHEME_DIAGNOSTICS, + ), + ), xcode_schemes.scheme( name = "xcschemes", launch_action = xcode_schemes.launch_action( @@ -274,6 +435,9 @@ xcodeproj( "//tools/generators/pbxtargetdependencies": [ "//tools/generators/pbxtargetdependencies:README.md", ], + "//tools/generators/target_build_settings": [ + "//tools/generators/target_build_settings:README.md", + ], "//tools/generators/xcschemes": [ "//tools/generators/xcschemes:README.md", ], diff --git a/tools/generators/BUILD b/tools/generators/BUILD index 7b4045d0ed..f63eb5c158 100644 --- a/tools/generators/BUILD +++ b/tools/generators/BUILD @@ -12,6 +12,7 @@ filegroup( # "//" + package_name() + "/pbxproj_prefix:release_files", # "//" + package_name() + "/pbxtargetdependencies:release_files", # "//" + package_name() + "/selected_model_versions:release_files", + # "//" + package_name() + "/target_build_settings:release_files", # "//" + package_name() + "/xcschemes:release_files", ], tags = ["manual"], diff --git a/tools/generators/README.md b/tools/generators/README.md index b84954b8d7..a7a0786700 100644 --- a/tools/generators/README.md +++ b/tools/generators/README.md @@ -49,6 +49,14 @@ information. - The `PBXProject.targets` property - Closes the `PBXProject` element - A set of files, each detailing how a set of configured targets are consolidated together +- [`target_build_settings`](target_build_settings/README.md): + - Run once for each target + - Each target generates a file that contains one or more build settings: + - `DEBUG_INFORMATION_FORMAT` + - `OTHER_CFLAGS` + - `OTHER_SWIFT_FLAGS` + - `SWIFT_COMPILATION_MODE` + - etc. - `pbxnativetargets`: - Run once on each shard of all the targets - All of the `PBXNativeTarget` related objects: diff --git a/tools/generators/target_build_settings/BUILD b/tools/generators/target_build_settings/BUILD new file mode 100644 index 0000000000..48374a3ca3 --- /dev/null +++ b/tools/generators/target_build_settings/BUILD @@ -0,0 +1,94 @@ +load("@build_bazel_rules_apple//apple:apple.bzl", "apple_universal_binary") +load( + "@build_bazel_rules_apple//apple:macos.bzl", + "macos_command_line_application", + "macos_unit_test", +) +load( + "@build_bazel_rules_swift//swift:swift.bzl", + "swift_binary", + "swift_library", +) + +exports_files(["README.md"]) + +# Generator + +swift_library( + name = "target_build_settings.library", + srcs = glob(["src/**/*.swift"]), + module_name = "target_build_settings", + deps = [ + "//tools/generators/lib/PBXProj", + "//tools/lib/ToolCommon", + "@com_github_apple_swift_collections//:OrderedCollections", + ], +) + +# This target exists to keep configurations the same between the generator +# and the tests, which makes the Xcode development experience better. If we used +# `swift_binary` or `apple_universal_binary` in `xcodeproj`, then the +# `macos_unit_test` transition (which is used to be able to set a minimum os +# version on the tests) will create slightly different configurations for our +# `swift_library`s. Maybe https://github.com/bazelbuild/bazel/issues/6526 will +# fix that for us. +macos_command_line_application( + name = "target_build_settings", + minimum_os_version = "13.0", + visibility = ["//visibility:public"], + deps = [":target_build_settings.library"], +) + +swift_binary( + name = "target_build_settings_binary", + deps = [":target_build_settings.library"], +) + +apple_universal_binary( + name = "universal_target_build_settings", + binary = ":target_build_settings_binary", + forced_cpus = [ + "x86_64", + "arm64", + ], + minimum_os_version = "13.0", + platform_type = "macos", + visibility = ["//visibility:public"], +) + +# Tests + +swift_library( + name = "target_build_settings_tests.library", + testonly = True, + srcs = glob(["test/**/*.swift"]), + module_name = "target_build_settings_tests", + deps = [ + ":target_build_settings.library", + "@com_github_pointfreeco_swift_custom_dump//:CustomDump", + ], +) + +macos_unit_test( + name = "target_build_settings_tests", + minimum_os_version = "13.0", + visibility = [ + "//test:__subpackages__", + "@rules_xcodeproj//xcodeproj:generated", + ], + deps = [ + ":target_build_settings_tests.library", + ], +) + +# Release + +filegroup( + name = "release_files", + srcs = [ + "BUILD.release.bazel", + ":universal_target_build_settings", + ], + tags = ["manual"], + visibility = ["//:__subpackages__"], +) diff --git a/tools/generators/target_build_settings/BUILD.release.bazel b/tools/generators/target_build_settings/BUILD.release.bazel new file mode 100644 index 0000000000..902e5bd80b --- /dev/null +++ b/tools/generators/target_build_settings/BUILD.release.bazel @@ -0,0 +1,8 @@ +load("@bazel_skylib//rules:native_binary.bzl", "native_binary") + +native_binary( + name = "universal_target_build_settings", + src = "prebuilt_universal_target_build_settings", + out = "universal_target_build_settings", + visibility = ["//visibility:public"], +) diff --git a/tools/generators/target_build_settings/README.md b/tools/generators/target_build_settings/README.md new file mode 100644 index 0000000000..42ad13b886 --- /dev/null +++ b/tools/generators/target_build_settings/README.md @@ -0,0 +1,259 @@ +# Compiler build settings generator + +The `target_build_settings` generator calculates some build settings (e.g. +`DEBUG_INFORMATION_FORMAT`, `OTHER_CFLAGS`, `OTHER_SWIFT_FLAGS`, +`SWIFT_COMPILATION_MODE`, etc.) for a target and write them to a file. It also +calculates the Swift debug settings for a target and write them to a file. + +## Inputs + +The generator accepts the following command-line arguments: + +- Positional `colorize` +- Positional `build-settings-output-path` +- Positional `swift-debug-settings-output-path` +- If `swift-debug-settings-output-path` is set: positional `include-self-swift-debug-settings` +- If `swift-debug-settings-output-path` is set: positional `transitive-swift-debug-setting-paths-count` +- If `swift-debug-settings-output-path` is set: positional list ` ...` +- Positional `device-family` +- Positional `extension-safe` +- Positional `generates-dsyms` +- Positional `info-plist` +- Positional `entitlements` +- Positional `certificate-name` +- Positional `provisioning-profile-name` +- Positional `team-id` +- Positional `provisioning-profile-is-xcode-managed` +- Positional `package-bin-dir` +- Positional `preview-framework-paths` +- Positional `previews-include-path` +- The command-line arguments for a Bazel `SwiftCompile` +action +- `---` to signify the end of the Swift arguments +- Positional `c-params-output-path` +- The command-line arguments for a C/Objective-C `CppCompile`/`ObjcCompile` action +- `---` to signify the end of the C arguments +- Positional `cxx-params-output-path` +- The command-line arguments for a +C++/Objective-C++ `CppCompile`/`ObjcCompile` action + +Here is an example invocation: + +```shell +$ target_build_settings \ + 0 \ + /tmp/pbxproj_partials/target_build_settings \ + /tmp/pbxproj_partials/swift_debug_settings \ + 1 \ + 2 \ + /tmp/pbxproj_partials/transitive_swift_debug_settings_0 \ + /tmp/pbxproj_partials/transitive_swift_debug_settings_1 \ + 4 \ + 0 \ + 1 \ + bazel-out/generated/Info.plist \ + project/level/app.entitlements \ + '' \ + '' \ + '' \ + 0 \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib \ + '' \ + '' \ + -target \ + arm64_32-apple-watchos7.0 \ + -sdk \ + __BAZEL_XCODE_SDKROOT__ \ + -debug-prefix-map \ + __BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR \ + -file-prefix-map \ + __BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR \ + -emit-object \ + -output-file-map \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.output_file_map.json \ + -Xfrontend \ + -no-clang-module-breadcrumbs \ + -swift-version \ + 4.2 \ + -emit-module-path \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.swiftmodule \ + -emit-objc-header-path \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/generated/Lib-Swift.h \ + -enable-bare-slash-regex \ + -DNDEBUG \ + -O \ + -whole-module-optimization \ + -Xfrontend \ + -no-serialize-debugging-options \ + -g \ + -Xwrapped-swift=-debug-prefix-pwd-is-dot \ + -Xwrapped-swift=-file-prefix-pwd-is-dot \ + -module-cache-path \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/_swift_module_cache \ + -Fexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k \ + -Xcc \ + -Fexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k \ + -Xcc \ + -iquoteexternal/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift \ + -Xcc \ + -iquotebazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/external/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift \ + -Xcc \ + -iquote. \ + -Xcc \ + -iquotebazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin \ + -Xcc \ + -fmodule-map-file=external/_main~non_module_deps~com_github_krzyzanowskim_cryptoswift/CryptoSwift.xcframework/watchos-arm64_32_armv7k/CryptoSwift.framework/Modules/module.modulemap \ + -Xfrontend \ + -color-diagnostics \ + -num-threads \ + 12 \ + -module-name \ + Lib \ + -Xwrapped-swift=-global-index-store-import-path=bazel-out/_global_index_store \ + -index-store-path \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.indexstore \ + -Xfrontend \ + -disable-autolink-framework \ + -Xfrontend \ + CryptoSwift \ + -Xfrontend \ + -vfsoverlay \ + -Xfrontend \ + project/relative/overlay.yaml \ + -Xcc \ + -ivfsoverlay=external/overlay.yaml \ + -vfsoverlay \ + bazel-out/generated/overlay.yaml \ + -Xfrontend \ + -explicit-swift-module-map-file \ + -Xfrontend \ + bazel-out/generated/map.json \ + -Xfrontend \ + -load-plugin-executable \ + -Xfrontend \ + bazel-out/generated/macro \ + -parse-as-library \ + -application-extension \ + -static \ + -Xcc \ + -Os \ + -Xcc \ + -DNDEBUG=1 \ + -Xcc \ + -Wno-unused-variable \ + -Xcc \ + -Winit-self \ + -Xcc \ + -Wno-extra \ + Lib/Resources.swift \ + bazel-out/watchos-arm64_32-min7.0-applebin_watchos-watchos_arm64_32-opt-ST-c58d72818890/bin/Lib/Lib.swift \ + --- \ + /tmp/pbxproj_partials/c.compile.params \ + -Os \ + -ivfsoverlay=external/overlay.yaml \ + --config \ + some.cfg \ + --- \ + /tmp/pbxproj_partials/cxx.compile.params \ + -Os \ + -D_FORTIFY_SOURCE=2 \ + -ivfsoverlay \ + bazel-out/generated/overlay.yaml +``` + +## Output + +Here is an example output: + +### `target_build_settings` + +``` +OTHER_SWIFT_FLAGS "-Xcc -working-directory -Xcc $(PROJECT_DIR) -working-directory $(PROJECT_DIR) -Xcc -ivfsoverlay$(OBJROOT)/bazel-out-overlay.yaml -vfsoverlay $(OBJROOT)/bazel-out-overlay.yaml -DNDEBUG -O -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_apple_swift_argument_parser -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/tools/lib/ToolCommon -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_apple_swift_collections -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_kylef_pathkit -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjson -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_tadija_aexml -I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_tuist_xcodeproj -Xcc -I -Xcc $(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/Sources/JJLISO8601DateFormatter/include -Xcc -I -Xcc $(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/Sources/JJLISO8601DateFormatter/include -Xcc -I -Xcc $(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/Sources/ZippyJSONCFamily/include -Xcc -I -Xcc $(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/Sources/ZippyJSONCFamily/include -Xcc -iquote -Xcc $(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter -Xcc -iquote -Xcc $(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter -Xcc -iquote -Xcc $(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily -Xcc -iquote -Xcc $(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily -Xcc -iquote -Xcc $(PROJECT_DIR) -Xcc -iquote -Xcc $(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin -Xcc -fmodule-map-file=$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/JJLISO8601DateFormatter.swift.modulemap -Xcc -fmodule-map-file=$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/ZippyJSONCFamily.swift.modulemap -static -Xcc -Os -Xcc -DNDEBUG=1 -Xcc -Wno-unused-variable -Xcc -Winit-self -Xcc -Wno-extra" +SWIFT_COMPILATION_MODE wholemodule + +``` + +### `swift_debug_settings` + +``` +69 +-I$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/Sources/JJLISO8601DateFormatter/include +-I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/Sources/JJLISO8601DateFormatter/include +-I$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/Sources/ZippyJSONCFamily/include +-I$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/Sources/ZippyJSONCFamily/include +-iquote$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter +-iquote$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-fmodule-map-file=$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter/JJLISO8601DateFormatter.swift.modulemap +-fmodule-map-file=$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily/ZippyJSONCFamily.swift.modulemap +-Os +-DNDEBUG=1 +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_jjliso8601dateformatter +-iquote$(BAZEL_EXTERNAL)/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjsoncfamily +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +-iquote$(PROJECT_DIR) +-iquote$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin +-Os +-Wno-unused-variable +-Winit-self +-Wno-extra +0 +7 +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_apple_swift_argument_parser +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/tools/lib/ToolCommon +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_apple_swift_collections +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_kylef_pathkit +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_michaeleisel_zippyjson +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_tadija_aexml +$(BAZEL_OUT)/macos-arm64-min12.0-applebin_macos-darwin_arm64-opt-ST-89c7f8a7bb2e/bin/external/_main~non_module_deps~com_github_tuist_xcodeproj + +``` diff --git a/tools/generators/target_build_settings/src/Generator/Environment.swift b/tools/generators/target_build_settings/src/Generator/Environment.swift new file mode 100644 index 0000000000..8c785e1ad5 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/Environment.swift @@ -0,0 +1,49 @@ +import PBXProj + +extension Generator { + /// Provides the callable dependencies for `Generator`. + /// + /// The main purpose of `Environment` is to enable dependency injection, + /// allowing for different implementations to be used in tests. + struct Environment { + let processArgs: ProcessArgs + let writeBuildSettings: WriteBuildSettings + let writeTargetSwiftDebugSettings: WriteTargetSwiftDebugSettings + } +} + +extension Generator.Environment { + static let `default` = { + let processCcArg = Generator.ProcessCcArg() + let write = Write() + + return Self( + processArgs: Generator.ProcessArgs( + processCArgs: Generator.ProcessCArgs( + processCcArgs: Generator.ProcessCcArgs( + processCcArg: processCcArg + ), + write: write + ), + processCxxArgs: Generator.ProcessCxxArgs( + processCcArgs: Generator.ProcessCcArgs( + processCcArg: processCcArg + ), + write: write + ), + processSwiftArgs: Generator.ProcessSwiftArgs( + parseTransitiveSwiftDebugSettings: + Generator.ParseTransitiveSwiftDebugSettings( + readTargetSwiftDebugSettingsFile: + ReadTargetSwiftDebugSettingsFile() + ), + processSwiftArg: Generator.ProcessSwiftArg(), + processSwiftClangArg: Generator.ProcessSwiftClangArg(), + processSwiftFrontendArg: Generator.ProcessSwiftFrontendArg() + ) + ), + writeBuildSettings: Generator.WriteBuildSettings(), + writeTargetSwiftDebugSettings: WriteTargetSwiftDebugSettings() + ) + }() +} diff --git a/tools/generators/target_build_settings/src/Generator/Generator.swift b/tools/generators/target_build_settings/src/Generator/Generator.swift new file mode 100644 index 0000000000..cf30f56f15 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/Generator.swift @@ -0,0 +1,120 @@ +import ArgumentParser +import Foundation +import OrderedCollections +import PBXProj +import ToolCommon + +/// A type that generates and writes to disk a file containing certain build +/// settings for a target, to be consumed by `pbxnativetargets`. It also +/// generates and writes to disk a file containing information about Swift +/// debug settings, to be consumed by this generator on other targets and +/// finally by `swift_debug_settings`. +/// +/// The `Generator` type is stateless. It can be used to generate files for +/// multiple targets. The `generate()` method is passed all the inputs needed to +/// generate the files. +struct Generator { + static let argsSeparator = "---" + + private let environment: Environment + + init(environment: Environment = .default) { + self.environment = environment + } + + /// Calculates the build settings and Swift debug settings and writes them + /// to disk. + func generate(rawArguments: ArraySlice) async throws { + var rawArguments = rawArguments + + let buildSettingsOutputPath = try rawArguments + .consumeArg("build-settings-output-path", as: URL?.self) + let swiftDebugSettingsOutputPath = try rawArguments + .consumeArg("swift-debug-settings-output-path", as: URL?.self) + + let includeSelfSwiftDebugSettings: Bool + let transitiveSwiftDebugSettingPaths: [URL] + if swiftDebugSettingsOutputPath != nil { + includeSelfSwiftDebugSettings = try rawArguments.consumeArg( + "include-self-swift-debug-settings", + as: Bool.self + ) + transitiveSwiftDebugSettingPaths = try rawArguments.consumeArgs( + "transitive-swift-debug-setting-paths", + as: URL.self + ) + } else { + includeSelfSwiftDebugSettings = false + transitiveSwiftDebugSettingPaths = [] + } + + let (buildSettings, clangArgs, frameworkIncludes, swiftIncludes) = + try await environment.processArgs( + rawArguments: rawArguments, + generateBuildSettings: buildSettingsOutputPath != nil, + includeSelfSwiftDebugSettings: includeSelfSwiftDebugSettings, + transitiveSwiftDebugSettingPaths: + transitiveSwiftDebugSettingPaths + ) + + let writeBuildSettingsTask = Task { + guard let buildSettingsOutputPath else { return } + + try environment.writeBuildSettings( + buildSettings, + to: buildSettingsOutputPath + ) + } + + let writeSwiftDebugSettingsTask = Task { + guard let swiftDebugSettingsOutputPath else { return } + + try environment.writeTargetSwiftDebugSettings( + clangArgs: clangArgs, + frameworkIncludes: frameworkIncludes, + swiftIncludes: swiftIncludes, + to: swiftDebugSettingsOutputPath + ) + } + + try await writeBuildSettingsTask.value + try await writeSwiftDebugSettingsTask.value + } +} + +private extension ArraySlice { + mutating func consumeArg( + _ name: String, + as type: URL?.Type, + file: StaticString = #filePath, + line: UInt = #line + ) throws -> URL? { + return try consumeArg( + name, + as: type, + transform: { path in + guard !path.isEmpty else { + return nil + } + return URL(fileURLWithPath: path, isDirectory: false) + }, + file: file, + line: line + ) + } + + mutating func consumeArgs( + _ name: String, + as type: URL.Type, + file: StaticString = #filePath, + line: UInt = #line + ) throws -> [URL] { + return try consumeArgs( + name, + as: type, + transform: { URL(fileURLWithPath: $0, isDirectory: false) }, + file: file, + line: line + ) + } +} diff --git a/tools/generators/target_build_settings/src/Generator/ParseTransitiveSwiftDebugSettings.swift b/tools/generators/target_build_settings/src/Generator/ParseTransitiveSwiftDebugSettings.swift new file mode 100644 index 0000000000..7fc29507e0 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ParseTransitiveSwiftDebugSettings.swift @@ -0,0 +1,98 @@ +import Foundation +import OrderedCollections +import PBXProj +import ToolCommon + +extension Generator { + struct ParseTransitiveSwiftDebugSettings { + private let readTargetSwiftDebugSettingsFile: + ReadTargetSwiftDebugSettingsFile + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + readTargetSwiftDebugSettingsFile: ReadTargetSwiftDebugSettingsFile, + callable: @escaping Callable = Self.defaultCallable + ) { + self.readTargetSwiftDebugSettingsFile = + readTargetSwiftDebugSettingsFile + + self.callable = callable + } + + /// Reads transitive Swift debug settings to set Swift debug settings + /// for the current target. + func callAsFunction( + _ transitiveSwiftDebugSettingPaths: [URL], + clangArgs: inout [String], + frameworkIncludes: inout OrderedSet, + onceClangArgs: inout Set, + swiftIncludes: inout OrderedSet + ) async throws { + try await callable( + /*transitiveSwiftDebugSettingPaths:*/ + transitiveSwiftDebugSettingPaths, + /*clangArgs:*/ &clangArgs, + /*frameworkIncludes:*/ &frameworkIncludes, + /*onceClangArgs:*/ &onceClangArgs, + /*swiftIncludes:*/ &swiftIncludes, + /*readTargetSwiftDebugSettingsFile:*/ + readTargetSwiftDebugSettingsFile + ) + } + } +} + +// MARK: - ParseTransitiveSwiftDebugSettings.Callable + +extension Generator.ParseTransitiveSwiftDebugSettings { + typealias Callable = ( + _ transitiveSwiftDebugSettingPaths: [URL], + _ clangArgs: inout [String], + _ frameworkIncludes: inout OrderedSet, + _ onceClangArgs: inout Set, + _ swiftIncludes: inout OrderedSet, + _ readTargetSwiftDebugSettingsFile: ReadTargetSwiftDebugSettingsFile + ) async throws -> Void + + static func defaultCallable( + _ transitiveSwiftDebugSettingPaths: [URL], + clangArgs: inout [String], + frameworkIncludes: inout OrderedSet, + onceClangArgs: inout Set, + swiftIncludes: inout OrderedSet, + readTargetSwiftDebugSettingsFile: ReadTargetSwiftDebugSettingsFile + ) async throws { + for url in transitiveSwiftDebugSettingPaths { + let swiftDebugSettings = + try await readTargetSwiftDebugSettingsFile(from: url) + + frameworkIncludes.formUnion(swiftDebugSettings.frameworkIncludes) + swiftIncludes.formUnion(swiftDebugSettings.swiftIncludes) + + argsLoop: for arg in swiftDebugSettings.clangArgs { + for onceArgPrefixes in clangOnceArgPrefixes { + if arg.hasPrefix(onceArgPrefixes) { + guard !onceClangArgs.contains(arg) else { + continue argsLoop + } + onceClangArgs.insert(arg) + break + } + } + clangArgs.append(arg) + } + } + } +} + +private let clangOnceArgPrefixes = [ + "-F", + "-D", + "-I", + "-fmodule-map-file=", + "-ivfsoverlay", +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessArgs.swift b/tools/generators/target_build_settings/src/Generator/ProcessArgs.swift new file mode 100644 index 0000000000..67677be4e3 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessArgs.swift @@ -0,0 +1,257 @@ +import Foundation +import OrderedCollections + +extension Generator { + struct ProcessArgs { + private let processCArgs: ProcessCArgs + private let processCxxArgs: ProcessCxxArgs + private let processSwiftArgs: ProcessSwiftArgs + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + processCArgs: ProcessCArgs, + processCxxArgs: ProcessCxxArgs, + processSwiftArgs: ProcessSwiftArgs, + callable: @escaping Callable = Self.defaultCallable + ) { + self.processCArgs = processCArgs + self.processCxxArgs = processCxxArgs + self.processSwiftArgs = processSwiftArgs + + self.callable = callable + } + + /// Processes all of the Swift, C, and/or C++ arguments. + func callAsFunction( + rawArguments: Array.SubSequence, + generateBuildSettings: Bool, + includeSelfSwiftDebugSettings: Bool, + transitiveSwiftDebugSettingPaths: [URL] + ) async throws -> ( + buildSettings: [(key: String, value: String)], + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) { + try await callable( + /*rawArguments:*/ rawArguments, + /*generateBuildSettings:*/ generateBuildSettings, + /*includeSelfSwiftDebugSettings:*/ + includeSelfSwiftDebugSettings, + /*transitiveSwiftDebugSettingPaths:*/ + transitiveSwiftDebugSettingPaths, + /*processCArgs:*/ processCArgs, + /*processCxxArgs:*/ processCxxArgs, + /*processSwiftArgs:*/ processSwiftArgs + ) + } + } +} + +// MARK: - ProcessArgs.Callable + +extension Generator.ProcessArgs { + typealias Callable = ( + _ rawArguments: Array.SubSequence, + _ generateBuildSettings: Bool, + _ includeSelfSwiftDebugSettings: Bool, + _ transitiveSwiftDebugSettingPaths: [URL], + _ processCArgs: Generator.ProcessCArgs, + _ processCxxArgs: Generator.ProcessCxxArgs, + _ processSwiftArgs: Generator.ProcessSwiftArgs + ) async throws -> ( + buildSettings: [(key: String, value: String)], + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) + + static func defaultCallable( + rawArguments: Array.SubSequence, + generateBuildSettings: Bool, + includeSelfSwiftDebugSettings: Bool, + transitiveSwiftDebugSettingPaths: [URL], + processCArgs: Generator.ProcessCArgs, + processCxxArgs: Generator.ProcessCxxArgs, + processSwiftArgs: Generator.ProcessSwiftArgs + ) async throws -> ( + buildSettings: [(key: String, value: String)], + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) { + var rawArguments = rawArguments + + let deviceFamily = try rawArguments.consumeArg("device-family") + let extensionSafe = + try rawArguments.consumeArg("extension-safe", as: Bool.self) + let generatesDsyms = + try rawArguments.consumeArg("generates-dsyms", as: Bool.self) + let infoPlist = try rawArguments.consumeArg("info-plist") + let entitlements = try rawArguments.consumeArg("entitlements") + let skipCodesigning = + try rawArguments.consumeArg("skip-codesigning", as: Bool.self) + let certificateName = try rawArguments.consumeArg("certificate-name") + let provisioningProfileName = + try rawArguments.consumeArg("provisioning-profile-name") + let teamID = try rawArguments.consumeArg("team-id") + let provisioningProfileIsXcodeManaged = try rawArguments.consumeArg( + "provisioning-profile-is-xcode-managed", + as: Bool.self + ) + let previewsFrameworkPaths = + try rawArguments.consumeArg("previews-framework-paths") + let previewsIncludePath = + try rawArguments.consumeArg("previews-include-path") + + let argsStream = argsStream(from: rawArguments) + + var buildSettings: [(key: String, value: String)] = [] + + let ( + swiftHasDebugInfo, + clangArgs, + frameworkIncludes, + swiftIncludes + ) = try await processSwiftArgs( + argsStream: argsStream, + buildSettings: &buildSettings, + includeSelfSwiftDebugSettings: includeSelfSwiftDebugSettings, + previewsFrameworkPaths: previewsFrameworkPaths, + previewsIncludePath: previewsIncludePath, + transitiveSwiftDebugSettingPaths: transitiveSwiftDebugSettingPaths + ) + + guard generateBuildSettings else { + return ([], clangArgs, frameworkIncludes, swiftIncludes) + } + + let cHasDebugInfo = try await processCArgs( + argsStream: argsStream, + buildSettings: &buildSettings + ) + + let cxxHasDebugInfo = try await processCxxArgs( + argsStream: argsStream, + buildSettings: &buildSettings + ) + + if generatesDsyms || swiftHasDebugInfo || cHasDebugInfo || + cxxHasDebugInfo + { + // Set to dwarf, because Bazel will generate the dSYMs. We don't set + // "DEBUG_INFORMATION_FORMAT" to "dwarf", as we set that at the + // project level + } else { + buildSettings.append( + ("DEBUG_INFORMATION_FORMAT", #""""#) + ) + } + + if !deviceFamily.isEmpty { + buildSettings.append( + ("TARGETED_DEVICE_FAMILY", deviceFamily.pbxProjEscaped) + ) + } + + if extensionSafe { + buildSettings.append(("APPLICATION_EXTENSION_API_ONLY", "YES")) + } + + if !infoPlist.isEmpty { + buildSettings.append( + ( + "INFOPLIST_FILE", + infoPlist.buildSettingPath().quoteIfNeeded().pbxProjEscaped + ) + ) + } + + if !entitlements.isEmpty { + buildSettings.append( + ( + "CODE_SIGN_ENTITLEMENTS", + entitlements.buildSettingPath().quoteIfNeeded() + .pbxProjEscaped + ) + ) + + // This is required because otherwise Xcode can fails the build + // due to a generated entitlements file being modified by the + // Bazel build script. We only set this for BwB mode though, + // because when this is set, Xcode uses the entitlements as + // provided instead of modifying them, which is needed in BwX + // mode. + buildSettings.append( + ("CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION", "YES") + ) + } + + if skipCodesigning { + buildSettings.append(("CODE_SIGNING_ALLOWED", "NO")) + } + + if !certificateName.isEmpty { + buildSettings.append( + ("CODE_SIGN_IDENTITY", certificateName.pbxProjEscaped) + ) + } + + if !teamID.isEmpty { + buildSettings.append(("DEVELOPMENT_TEAM", teamID.pbxProjEscaped)) + } + + if !provisioningProfileName.isEmpty { + buildSettings.append( + ( + "PROVISIONING_PROFILE_SPECIFIER", + provisioningProfileName.pbxProjEscaped + ) + ) + } + + if provisioningProfileIsXcodeManaged { + buildSettings.append(("CODE_SIGN_STYLE", "Automatic")) + } + + return (buildSettings, clangArgs, frameworkIncludes, swiftIncludes) + } +} + +private func argsStream( + from rawArguments: Array.SubSequence +) -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + let argsTask = Task { + for arg in rawArguments { + guard !arg.starts(with: "@") else { + let path = String(arg.dropFirst()) + for try await line in URL(fileURLWithPath: path).lines { + // Change params files from `shell` to `multiline` + // format + // https://bazel.build/versions/6.1.0/rules/lib/Args#set_param_file_format.format + if line.hasPrefix("'") && line.hasSuffix("'") { + let startIndex = line + .index(line.startIndex, offsetBy: 1) + let endIndex = line.index(before: line.endIndex) + continuation + .yield(String(line[startIndex ..< endIndex])) + } else { + continuation.yield(line) + } + } + continue + } + continuation.yield(arg) + } + continuation.finish() + } + continuation.onTermination = { @Sendable _ in + argsTask.cancel() + } + } +} diff --git a/tools/generators/target_build_settings/src/Generator/ProcessCArgs.swift b/tools/generators/target_build_settings/src/Generator/ProcessCArgs.swift new file mode 100644 index 0000000000..c402af9fbd --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessCArgs.swift @@ -0,0 +1,125 @@ +import Foundation +import PBXProj + +extension Generator { + struct ProcessCArgs { + private let processCcArgs: ProcessCcArgs + private let write: Write + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + processCcArgs: ProcessCcArgs, + write: Write, + callable: @escaping Callable = Self.defaultCallable + ) { + self.processCcArgs = processCcArgs + self.write = write + + self.callable = callable + } + + /// Processes all the C/Objective-C arguments. + func callAsFunction( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)] + ) async throws -> Bool { + try await callable( + /*argsStream:*/ argsStream, + /*buildSettings:*/ &buildSettings, + /*processCcArgs:*/ processCcArgs, + /*write:*/ write + ) + } + } +} + +// MARK: - ProcessCArgs.Callable + +extension Generator.ProcessCArgs { + typealias Callable = ( + _ argsStream: AsyncThrowingStream, + _ buildSettings: inout [(key: String, value: String)], + _ processCcArgs: Generator.ProcessCcArgs, + _ write: Write + ) async throws -> Bool + + static func defaultCallable( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)], + processCcArgs: Generator.ProcessCcArgs, + write: Write + ) async throws -> Bool { + var iterator = argsStream.makeAsyncIterator() + + guard let outputPath = try await iterator.next() else { + return false + } + + guard outputPath != Generator.argsSeparator else { + return false + } + + // First argument is `wrapped_clang` + _ = try await iterator.next() + + let (args, hasDebugInfo, fortifySourceLevel) = try await processCcArgs( + argsStream: argsStream + ) + + let content = args.map { $0 + "\n" }.joined() + try write(content, to: URL(fileURLWithPath: String(outputPath))) + + buildSettings.append( + ( + "C_PARAMS_FILE", + #""" +"$(BAZEL_OUT)\#(outputPath.dropFirst(9))" +"""# + ) + ) + + if fortifySourceLevel > 0 { + // ASAN doesn't work with `-D_FORTIFY_SOURCE=1`, so we need to only + // include that when not building with ASAN + buildSettings.append( + ("ASAN_OTHER_CFLAGS__", #""$(ASAN_OTHER_CFLAGS__NO)""#) + ) + buildSettings.append( + ( + "ASAN_OTHER_CFLAGS__NO", + #""" +"@$(DERIVED_FILE_DIR)/c.compile.params \# +-D_FORTIFY_SOURCE=\#(fortifySourceLevel)" +"""# + ) + ) + buildSettings.append( + ( + "ASAN_OTHER_CFLAGS__YES", + #""@$(DERIVED_FILE_DIR)/c.compile.params""# + ) + ) + buildSettings.append( + ( + "OTHER_CFLAGS", + #""" +"$(ASAN_OTHER_CFLAGS__$(CLANG_ADDRESS_SANITIZER))" +"""# + ) + ) + } else { + buildSettings.append( + ( + "OTHER_CFLAGS", + #""@$(DERIVED_FILE_DIR)/c.compile.params""# + ) + ) + } + + return hasDebugInfo + } +} diff --git a/tools/generators/target_build_settings/src/Generator/ProcessCcArg.swift b/tools/generators/target_build_settings/src/Generator/ProcessCcArg.swift new file mode 100644 index 0000000000..9c4650e3ba --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessCcArg.swift @@ -0,0 +1,74 @@ +extension Generator { + struct ProcessCcArg { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Processes a single C/C++/Objective-C argument. + func callAsFunction( + _ arg: String, + previousArg: String?, + args: inout [String] + ) throws { + try callable( + /*arg:*/ arg, + /*previousArg:*/ previousArg, + /*args:*/ &args + ) + } + } +} + +// MARK: - ProcessCcArg.Callable + +extension Generator.ProcessCcArg { + typealias Callable = ( + _ arg: String, + _ previousArg: String?, + _ args: inout [String] + ) throws -> Void + + static func defaultCallable( + _ arg: String, + previousArg: String?, + args: inout [String] + ) throws { + // `-ivfsoverlay` and `--config` don't apply `-working_directory=`, so + // we need to prefix it ourselves + for prefix in cNeedsAbsolutePathArgs { + if arg.hasPrefix(prefix) { + var path = arg.dropFirst(12) + + guard !path.isEmpty else { + args.append(arg) + return + } + + if path.hasPrefix("=") { + path = path.dropFirst() + } + + let absoluteArg = prefix + path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + return + } + } + + if let previousArg, cNeedsAbsolutePathArgs.contains(previousArg) { + args.append(arg.buildSettingPath().quoteIfNeeded()) + return + } + + args.append(arg.substituteBazelPlaceholders().quoteIfNeeded()) + } +} + +private let cNeedsAbsolutePathArgs: Set = [ + "--config", + "-ivfsoverlay", +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessCcArgs.swift b/tools/generators/target_build_settings/src/Generator/ProcessCcArgs.swift new file mode 100644 index 0000000000..505fc8208a --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessCcArgs.swift @@ -0,0 +1,151 @@ +extension Generator { + struct ProcessCcArgs { + private let processCcArg: ProcessCcArg + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + processCcArg: ProcessCcArg, + callable: @escaping Callable = Self.defaultCallable + ) { + self.processCcArg = processCcArg + + self.callable = callable + } + + /// Processes all of the common flags for C or C++ arguments. + func callAsFunction( + argsStream: AsyncThrowingStream + ) async throws -> ( + args: [String], + hasDebugInfo: Bool, + fortifySourceLevel: Int + ) { + try await callable( + /*argsStream:*/ argsStream, + /*processCcArg:*/ processCcArg + ) + } + } +} + +// MARK: - ProcessCcArgs.Callable + +extension Generator.ProcessCcArgs { + typealias Callable = ( + _ argsStream: AsyncThrowingStream, + _ processCcArg: Generator.ProcessCcArg + ) async throws -> ( + args: [String], + hasDebugInfo: Bool, + fortifySourceLevel: Int + ) + + static func defaultCallable( + argsStream: AsyncThrowingStream, + processCcArg: Generator.ProcessCcArg + ) async throws -> ( + args: [String], + hasDebugInfo: Bool, + fortifySourceLevel: Int + ) { + var previousArg: String? = nil + var skipNext = 0 + + var args: [String] = [ + "-working-directory", + "$(PROJECT_DIR)", + "-ivfsoverlay", + "$(OBJROOT)/bazel-out-overlay.yaml", + ] + + var hasDebugInfo = false + var fortifySourceLevel = 0 + for try await arg in argsStream { + guard arg != Generator.argsSeparator else { + break + } + + if skipNext != 0 { + skipNext -= 1 + continue + } + + // Track previous argument + defer { + previousArg = arg + } + + // Skip based on flag + let rootArg = arg.split(separator: "=", maxSplits: 1).first! + + if let thisSkipNext = skipCCArgs[rootArg] { + skipNext = thisSkipNext - 1 + continue + } + + if arg == "-g" { + hasDebugInfo = true + continue + } + + if arg.hasPrefix("-D_FORTIFY_SOURCE=") { + if let level = Int(arg.dropFirst(18)) { + fortifySourceLevel = level + } else { + fortifySourceLevel = 1 + } + continue + } + + try processCcArg( + arg, + previousArg: previousArg, + args: &args + ) + } + + return (args, hasDebugInfo, fortifySourceLevel) + } +} + +private let skipCCArgs: [Substring: Int] = [ + // Xcode sets these, and no way to unset them + "-isysroot": 2, + "-mios-simulator-version-min": 1, + "-miphoneos-version-min": 1, + "-mmacosx-version-min": 1, + "-mtvos-simulator-version-min": 1, + "-mtvos-version-min": 1, + "-mwatchos-simulator-version-min": 1, + "-mwatchos-version-min": 1, + "-target": 2, + + // Xcode sets input and output paths + "-c": 2, + "-o": 2, + + // We set this in the generator + "-fobjc-arc": 1, + "-fno-objc-arc": 1, + + // We want to use Xcode's dependency file handling + "-MD": 1, + "-MF": 2, + + // We want to use Xcode's normal indexing handling + "-index-ignore-system-symbols": 1, + "-index-store-path": 2, + + // We want Xcode's normal debug handling + "-fdebug-prefix-map": 2, + + // We want Xcode to control coloring + "-fcolor-diagnostics": 1, + + // This is wrapped_clang specific, and we don't want to translate it for BwX + "DEBUG_PREFIX_MAP_PWD": 1, +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessCxxArgs.swift b/tools/generators/target_build_settings/src/Generator/ProcessCxxArgs.swift new file mode 100644 index 0000000000..2a6c04d2c2 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessCxxArgs.swift @@ -0,0 +1,126 @@ +import Foundation +import PBXProj + +extension Generator { + struct ProcessCxxArgs { + private let processCcArgs: ProcessCcArgs + private let write: Write + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + processCcArgs: ProcessCcArgs, + write: Write, + callable: @escaping Callable = Self.defaultCallable + ) { + self.processCcArgs = processCcArgs + self.write = write + + self.callable = callable + } + + /// Processes all the C++/Objective-C++ arguments. + func callAsFunction( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)] + ) async throws -> Bool { + try await callable( + /*argsStream:*/ argsStream, + /*buildSettings:*/ &buildSettings, + /*processCcArgs:*/ processCcArgs, + /*write:*/ write + ) + } + } +} + +// MARK: - ProcessCxxArgs.Callable + +extension Generator.ProcessCxxArgs { + typealias Callable = ( + _ argsStream: AsyncThrowingStream, + _ buildSettings: inout [(key: String, value: String)], + _ processCcArgs: Generator.ProcessCcArgs, + _ write: Write + ) async throws -> Bool + + static func defaultCallable( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)], + processCcArgs: Generator.ProcessCcArgs, + write: Write + ) async throws -> Bool { + var iterator = argsStream.makeAsyncIterator() + + guard let outputPath = try await iterator.next() else { + return false + } + + guard outputPath != Generator.argsSeparator else { + return false + } + + // First argument is `wrapped_clang_pp` + _ = try await iterator.next() + + let (args, hasDebugInfo, fortifySourceLevel) = try await processCcArgs( + argsStream: argsStream + ) + + let content = args.map { $0 + "\n" }.joined() + try write(content, to: URL(fileURLWithPath: String(outputPath))) + + buildSettings.append( + ( + "CXX_PARAMS_FILE", + #""$(BAZEL_OUT)\#(outputPath.dropFirst(9))""# + ) + ) + + if fortifySourceLevel > 0 { + // ASAN doesn't work with `-D_FORTIFY_SOURCE=1`, so we need to only + // include that when not building with ASAN + buildSettings.append( + ( + "ASAN_OTHER_CPLUSPLUSFLAGS__", + #""$(ASAN_OTHER_CPLUSPLUSFLAGS__NO)""# + ) + ) + buildSettings.append( + ( + "ASAN_OTHER_CPLUSPLUSFLAGS__NO", + #""" +"@$(DERIVED_FILE_DIR)/cxx.compile.params \# +-D_FORTIFY_SOURCE=\#(fortifySourceLevel)" +"""# + ) + ) + buildSettings.append( + ( + "ASAN_OTHER_CPLUSPLUSFLAGS__YES", + #""@$(DERIVED_FILE_DIR)/cxx.compile.params""# + ) + ) + buildSettings.append( + ( + "OTHER_CPLUSPLUSFLAGS", + #""" +"$(ASAN_OTHER_CPLUSPLUSFLAGS__$(CLANG_ADDRESS_SANITIZER))" +"""# + ) + ) + } else { + buildSettings.append( + ( + "OTHER_CPLUSPLUSFLAGS", + #""@$(DERIVED_FILE_DIR)/cxx.compile.params""# + ) + ) + } + + return hasDebugInfo + } +} diff --git a/tools/generators/target_build_settings/src/Generator/ProcessSwiftArg.swift b/tools/generators/target_build_settings/src/Generator/ProcessSwiftArg.swift new file mode 100644 index 0000000000..ee9a8f88b3 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessSwiftArg.swift @@ -0,0 +1,160 @@ +import OrderedCollections + +extension Generator { + struct ProcessSwiftArg { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Processes a single Swift argument. + func callAsFunction( + _ arg: String, + previousArg: String?, + previousFrontendArg: String?, + args: inout [String], + buildSettings: inout [(key: String, value: String)], + frameworkIncludes: inout OrderedSet, + includeSelfSwiftDebugSettings: Bool, + swiftIncludes: inout OrderedSet + ) throws { + try callable( + /*arg:*/ arg, + /*previousArg:*/ previousArg, + /*previousFrontendArg:*/ previousFrontendArg, + /*args:*/ &args, + /*buildSettings:*/ &buildSettings, + /*frameworkIncludes:*/ &frameworkIncludes, + /*includeSelfSwiftDebugSettings:*/ + includeSelfSwiftDebugSettings, + /*swiftIncludes:*/ &swiftIncludes + ) + } + } +} + +// MARK: - ProcessSwiftArg.Callable + +extension Generator.ProcessSwiftArg { + typealias Callable = ( + _ arg: String, + _ previousArg: String?, + _ previousFrontendArg: String?, + _ args: inout [String], + _ buildSettings: inout [(key: String, value: String)], + _ frameworkIncludes: inout OrderedSet, + _ includeSelfSwiftDebugSettings: Bool, + _ swiftIncludes: inout OrderedSet + ) throws -> Void + + static func defaultCallable( + _ arg: String, + previousArg: String?, + previousFrontendArg: String?, + args: inout [String], + buildSettings: inout [(key: String, value: String)], + frameworkIncludes: inout OrderedSet, + includeSelfSwiftDebugSettings: Bool, + swiftIncludes: inout OrderedSet + ) throws { + let appendIncludes: + (_ set: inout OrderedSet, _ path: String) -> Void + if includeSelfSwiftDebugSettings { + appendIncludes = { set, path in + set.append(path.escapingForDebugSettings()) + } + } else { + appendIncludes = { _, _ in } + } + + if let compilationMode = compilationModeArgs[arg] { + buildSettings.append(("SWIFT_COMPILATION_MODE", compilationMode)) + return + } + + if previousArg == "-swift-version" { + if arg != "5.0" { + buildSettings.append(("SWIFT_VERSION", String(arg))) + } + return + } + + if arg.hasPrefix("-I") { + let path = arg.dropFirst(2) + guard !path.isEmpty else { + args.append(arg) + return + } + + let absolutePath = path.buildSettingPath() + let absoluteArg = "-I" + absolutePath + args.append(absoluteArg.quoteIfNeeded()) + appendIncludes(&swiftIncludes, absolutePath) + return + } + + if previousArg == "-I" { + let absolutePath = arg.buildSettingPath() + args.append(absolutePath.quoteIfNeeded()) + appendIncludes(&swiftIncludes, absolutePath) + return + } + + if previousArg == "-F" { + let absolutePath = arg.buildSettingPath() + args.append(absolutePath.quoteIfNeeded()) + appendIncludes(&frameworkIncludes, absolutePath) + return + } + + if arg.hasPrefix("-F") { + let path = arg.dropFirst(2) + + guard !path.isEmpty else { + args.append(arg) + return + } + + let absolutePath = path.buildSettingPath() + let absoluteArg = "-F" + absolutePath + args.append(absoluteArg.quoteIfNeeded()) + appendIncludes(&frameworkIncludes, absolutePath) + return + } + + if arg.hasPrefix("-vfsoverlay") { + var path = arg.dropFirst(11) + + guard !path.isEmpty else { + args.append(arg) + return + } + + if path.hasPrefix("=") { + path = path.dropFirst() + } + + let absoluteArg = "-vfsoverlay" + path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + return + } + + if previousArg == "-vfsoverlay" { + args.append(arg.buildSettingPath().quoteIfNeeded()) + return + } + + args.append(arg.substituteBazelPlaceholders().quoteIfNeeded()) + } +} + +private let compilationModeArgs: [String: String] = [ + "-incremental": "singlefile", + "-no-whole-module-optimization": "singlefile", + "-whole-module-optimization": "wholemodule", + "-wmo": "wholemodule", +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessSwiftArgs.swift b/tools/generators/target_build_settings/src/Generator/ProcessSwiftArgs.swift new file mode 100644 index 0000000000..9a61c2eb33 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessSwiftArgs.swift @@ -0,0 +1,337 @@ +import Foundation +import OrderedCollections + +extension Generator { + struct ProcessSwiftArgs { + private let parseTransitiveSwiftDebugSettings: + ParseTransitiveSwiftDebugSettings + private let processSwiftArg: ProcessSwiftArg + private let processSwiftClangArg: ProcessSwiftClangArg + private let processSwiftFrontendArg: ProcessSwiftFrontendArg + + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init( + parseTransitiveSwiftDebugSettings: + ParseTransitiveSwiftDebugSettings, + processSwiftArg: ProcessSwiftArg, + processSwiftClangArg: ProcessSwiftClangArg, + processSwiftFrontendArg: ProcessSwiftFrontendArg, + callable: @escaping Callable = Self.defaultCallable + ) { + self.parseTransitiveSwiftDebugSettings = + parseTransitiveSwiftDebugSettings + self.processSwiftArg = processSwiftArg + self.processSwiftClangArg = processSwiftClangArg + self.processSwiftFrontendArg = processSwiftFrontendArg + + self.callable = callable + } + + /// Processes all the Swift arguments. + func callAsFunction( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)], + includeSelfSwiftDebugSettings: Bool, + previewsFrameworkPaths: String, + previewsIncludePath: String, + transitiveSwiftDebugSettingPaths: [URL] + ) async throws -> ( + hasDebugInfo: Bool, + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) { + try await callable( + /*argsStream:*/ argsStream, + /*buildSettings:*/ &buildSettings, + /*includeSelfSwiftDebugSettings:*/ + includeSelfSwiftDebugSettings, + /*previewsFrameworkPaths:*/ previewsFrameworkPaths, + /*previewsIncludePath:*/ previewsIncludePath, + /*transitiveSwiftDebugSettingPaths:*/ + transitiveSwiftDebugSettingPaths, + /*parseTransitiveSwiftDebugSettings:*/ + parseTransitiveSwiftDebugSettings, + /*processSwiftArg:*/ processSwiftArg, + /*processSwiftClangArg:*/ processSwiftClangArg, + /*processSwiftFrontendArg:*/ processSwiftFrontendArg + ) + } + } +} + +// MARK: - ProcessSwiftArgs.Callable + +extension Generator.ProcessSwiftArgs { + typealias Callable = ( + _ argsStream: AsyncThrowingStream, + _ buildSettings: inout [(key: String, value: String)], + _ includeSelfSwiftDebugSettings: Bool, + _ previewsFrameworkPaths: String, + _ previewsIncludePath: String, + _ transitiveSwiftDebugSettingPaths: [URL], + _ parseTransitiveSwiftDebugSettings: + Generator.ParseTransitiveSwiftDebugSettings, + _ processSwiftArg: Generator.ProcessSwiftArg, + _ processSwiftClangArg: Generator.ProcessSwiftClangArg, + _ processSwiftFrontendArg: Generator.ProcessSwiftFrontendArg + ) async throws -> ( + hasDebugInfo: Bool, + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) + + static func defaultCallable( + argsStream: AsyncThrowingStream, + buildSettings: inout [(key: String, value: String)], + includeSelfSwiftDebugSettings: Bool, + previewsFrameworkPaths: String, + previewsIncludePath: String, + transitiveSwiftDebugSettingPaths: [URL], + parseTransitiveSwiftDebugSettings: + Generator.ParseTransitiveSwiftDebugSettings, + processSwiftArg: Generator.ProcessSwiftArg, + processSwiftClangArg: Generator.ProcessSwiftClangArg, + processSwiftFrontendArg: Generator.ProcessSwiftFrontendArg + ) async throws -> ( + hasDebugInfo: Bool, + clangArgs: [String], + frameworkIncludes: OrderedSet, + swiftIncludes: OrderedSet + ) { + var previousArg: String? = nil + var previousClangArg: String? = nil + var previousFrontendArg: String? = nil + var skipNext = 0 + + // First two arguments are `swift_worker` and `clang` + var iterator = argsStream.makeAsyncIterator() + guard let tool = try await iterator.next(), + tool != Generator.argsSeparator + else { + return (false, [], [], []) + } + _ = try await iterator.next() + + var args: [String] = [ + // Work around stubbed swiftc messing with Indexing setting of + // `-working-directory` incorrectly + "-Xcc", + "-working-directory", + "-Xcc", + "$(PROJECT_DIR)", + "-working-directory", + "$(PROJECT_DIR)", + + "-Xcc", + "-ivfsoverlay$(OBJROOT)/bazel-out-overlay.yaml", + "-vfsoverlay", + "$(OBJROOT)/bazel-out-overlay.yaml", + ] + var clangArgs: [String] = [] + var frameworkIncludes: OrderedSet = [] + var onceClangArgs: Set = [] + var swiftIncludes: OrderedSet = [] + + if !previewsFrameworkPaths.isEmpty { + buildSettings.append( + ( + "PREVIEW_FRAMEWORK_PATHS", + previewsFrameworkPaths.pbxProjEscaped + ) + ) + } + + if !previewsIncludePath.isEmpty { + buildSettings.append(("PREVIEWS_SWIFT_INCLUDE__", #""""#)) + buildSettings.append(("PREVIEWS_SWIFT_INCLUDE__NO", #""""#)) + buildSettings.append( + ( + "PREVIEWS_SWIFT_INCLUDE__YES", + "-I\(Substring(previewsIncludePath).buildSettingPath())" + .pbxProjEscaped + ) + ) + + args.append("$(PREVIEWS_SWIFT_INCLUDE__$(ENABLE_PREVIEWS))") + } + + var hasDebugInfo = false + for try await arg in argsStream { + guard arg != Generator.argsSeparator else { + break + } + + if skipNext != 0 { + skipNext -= 1 + continue + } + + let isClangArg = previousArg == "-Xcc" + let isFrontendArg = previousArg == "-Xfrontend" + let isFrontend = arg == "-Xfrontend" + let isXcc = arg == "-Xcc" + + // Track previous argument + defer { + if isClangArg { + previousClangArg = arg + } else if !isXcc { + previousClangArg = nil + } + + if isFrontendArg { + previousFrontendArg = arg + } else if !isFrontend { + previousFrontendArg = nil + } + + previousArg = arg + } + + // Handle Clang (-Xcc) args + if isXcc { + args.append(arg) + continue + } + + if isClangArg { + try processSwiftClangArg( + arg, + previousClangArg: previousClangArg, + args: &args, + clangArgs: &clangArgs, + includeSelfSwiftDebugSettings: + includeSelfSwiftDebugSettings, + onceClangArgs: &onceClangArgs + ) + continue + } + + // Skip based on flag + let rootArg = arg.split(separator: "=", maxSplits: 1).first! + + if let thisSkipNext = skipSwiftArgs[rootArg] { + skipNext = thisSkipNext - 1 + continue + } + + if isFrontendArg { + if let thisSkipNext = skipFrontendArgs[rootArg] { + skipNext = thisSkipNext - 1 + continue + } + + // We filter out `-Xfrontend`, so we need to add it back if the + // current arg wasn't filtered out + args.append("-Xfrontend") + + try processSwiftFrontendArg( + arg, + previousFrontendArg: previousFrontendArg, + args: &args + ) + continue + } + + if arg == "-g" { + hasDebugInfo = true + continue + } + + if !arg.hasPrefix("-") && arg.hasSuffix(".swift") { + // These are the files to compile, not options. They are seen + // here because of the way we collect Swift compiler options. + // Ideally in the future we could collect Swift compiler options + // similar to how we collect C and C++ compiler options. + continue + } + + try processSwiftArg( + arg, + previousArg: previousArg, + previousFrontendArg: previousFrontendArg, + args: &args, + buildSettings: &buildSettings, + frameworkIncludes: &frameworkIncludes, + includeSelfSwiftDebugSettings: includeSelfSwiftDebugSettings, + swiftIncludes: &swiftIncludes + ) + } + + try await parseTransitiveSwiftDebugSettings( + transitiveSwiftDebugSettingPaths, + clangArgs: &clangArgs, + frameworkIncludes: &frameworkIncludes, + onceClangArgs: &onceClangArgs, + swiftIncludes: &swiftIncludes + ) + + buildSettings.append( + ("OTHER_SWIFT_FLAGS", args.joined(separator: " ").pbxProjEscaped) + ) + + return (hasDebugInfo, clangArgs, frameworkIncludes, swiftIncludes) + } +} + +private let skipSwiftArgs: [Substring: Int] = [ + // Xcode sets output paths + "-emit-module-path": 2, + "-emit-object": 1, + "-output-file-map": 2, + + // Xcode sets these, and no way to unset them + "-enable-bare-slash-regex": 1, + "-module-name": 2, + "-num-threads": 2, + "-parse-as-library": 1, + "-sdk": 2, + "-target": 2, + + // We want to use Xcode's normal PCM handling + "-module-cache-path": 2, + + // We want Xcode's normal debug handling + "-debug-prefix-map": 2, + "-file-prefix-map": 2, + "-gline-tables-only": 1, + + // We want to use Xcode's normal indexing handling + "-index-ignore-system-modules": 1, + "-index-store-path": 2, + + // We set Xcode build settings to control these + "-enable-batch-mode": 1, + + // We don't want to translate this for BwX + "-emit-symbol-graph-dir": 2, + + // These are fully handled in a `previousArg` check + "-swift-version": 1, + + // We filter out `-Xfrontend`, then add it back only if the current arg + // wasn't filtered out + "-Xfrontend": 1, + + // This is rules_swift specific, and we don't want to translate it for BwX + "-Xwrapped-swift": 1, +] + +private let skipFrontendArgs: [Substring: Int] = [ + // We want Xcode to control coloring + "-color-diagnostics": 1, + + // We want Xcode's normal debug handling + "-no-clang-module-breadcrumbs": 1, + "-no-serialize-debugging-options": 1, + "-serialize-debugging-options": 1, + + // We don't want to translate this for BwX + "-emit-symbol-graph": 1, +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessSwiftClangArg.swift b/tools/generators/target_build_settings/src/Generator/ProcessSwiftClangArg.swift new file mode 100644 index 0000000000..98b6e54482 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessSwiftClangArg.swift @@ -0,0 +1,164 @@ +extension Generator { + struct ProcessSwiftClangArg { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Processes a single Swift clang (`-Xcc`) argument. + func callAsFunction( + _ arg: String, + previousClangArg: String?, + args: inout [String], + clangArgs: inout [String], + includeSelfSwiftDebugSettings: Bool, + onceClangArgs: inout Set + ) throws { + try callable( + /*arg:*/ arg, + /*previousClangArg:*/ previousClangArg, + /*args:*/ &args, + /*clangArgs:*/ &clangArgs, + /*includeSelfSwiftDebugSettings:*/ + includeSelfSwiftDebugSettings, + /*onceClangArgs:*/ &onceClangArgs + ) + } + } +} + +// MARK: - ProcessSwiftClangArg.Callable + +extension Generator.ProcessSwiftClangArg { + typealias Callable = ( + _ arg: String, + _ previousClangArg: String?, + _ args: inout [String], + _ clangArgs: inout [String], + _ includeSelfSwiftDebugSettings: Bool, + _ onceClangArgs: inout Set + ) throws -> Void + + static func defaultCallable( + _ arg: String, + previousClangArg: String?, + args: inout [String], + clangArgs: inout [String], + includeSelfSwiftDebugSettings: Bool, + onceClangArgs: inout Set + ) throws { + func appendClangArg( + _ clangArg: String, + disallowMultiples: Bool = true + ) { + guard includeSelfSwiftDebugSettings else { + return + } + if disallowMultiples { + guard !onceClangArgs.contains(clangArg) else { + return + } + onceClangArgs.insert(clangArg) + } + clangArgs.append(clangArg) + } + + if arg.hasPrefix("-fmodule-map-file=") { + let path = arg.dropFirst(18) + let absoluteArg: Substring = + "-fmodule-map-file=" + path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg(absoluteArg.escapingForDebugSettings()) + return + } + + if arg.hasPrefix("-D") { + let absoluteArg = arg.substituteBazelPlaceholders() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg(absoluteArg.escapingForDebugSettings()) + return + } + + for (searchArg, disallowMultiples) in clangSearchPathArgs { + if arg.hasPrefix(searchArg) { + let path = arg.dropFirst(searchArg.count) + + guard !path.isEmpty else { + args.append(arg) + return + } + + args.append(searchArg) + args.append("-Xcc") + + let absoluteArg = path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg( + (searchArg + absoluteArg).escapingForDebugSettings(), + disallowMultiples: disallowMultiples + ) + return + } + } + + if let previousClangArg, + let disallowMultiples = clangSearchPathArgs[previousClangArg] + { + let absoluteArg = arg.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg( + (previousClangArg + absoluteArg).escapingForDebugSettings(), + disallowMultiples: disallowMultiples + ) + } + + // `-ivfsoverlay` doesn't apply `-working_directory=`, so we need to + // prefix it ourselves + if previousClangArg == "-ivfsoverlay" { + let absolutePath = arg.buildSettingPath() + args.append(absolutePath.quoteIfNeeded()) + appendClangArg( + ("-ivfsoverlay" + absolutePath).escapingForDebugSettings() + ) + return + } + + if arg.hasPrefix("-ivfsoverlay") { + var path = arg.dropFirst(12) + + guard !path.isEmpty else { + args.append(arg) + return + } + + if path.hasPrefix("=") { + path = path.dropFirst() + } + + let absoluteArg: Substring = + "-ivfsoverlay" + path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg(absoluteArg.escapingForDebugSettings()) + return + } + + let absoluteArg = arg.substituteBazelPlaceholders() + args.append(absoluteArg.quoteIfNeeded()) + appendClangArg( + absoluteArg.escapingForDebugSettings(), + disallowMultiples: false + ) + } +} + +// Maps arg -> multiples not allowed in clangArgs +private let clangSearchPathArgs: [String: Bool] = [ + "-F": true, + "-I": true, + "-iquote": false, + "-isystem": false, +] diff --git a/tools/generators/target_build_settings/src/Generator/ProcessSwiftFrontendArg.swift b/tools/generators/target_build_settings/src/Generator/ProcessSwiftFrontendArg.swift new file mode 100644 index 0000000000..b9922501e4 --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/ProcessSwiftFrontendArg.swift @@ -0,0 +1,83 @@ +extension Generator { + struct ProcessSwiftFrontendArg { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Processes a single Swift frontend (`-Xfrontend`) argument. + func callAsFunction( + _ arg: String, + previousFrontendArg: String?, + args: inout [String] + ) throws { + try callable( + /*arg:*/ arg, + /*previousFrontendArg:*/ previousFrontendArg, + /*args:*/ &args + ) + } + } +} + +// MARK: - ProcessSwiftFrontendArg.Callable + +extension Generator.ProcessSwiftFrontendArg { + typealias Callable = ( + _ arg: String, + _ previousFrontendArg: String?, + _ args: inout [String] + ) throws -> Void + + static func defaultCallable( + _ arg: String, + previousFrontendArg: String?, + args: inout [String] + ) throws { + if let previousFrontendArg { + if overlayArgs.contains(previousFrontendArg) { + args.append(arg.buildSettingPath().quoteIfNeeded()) + return + } + + if loadPluginsArgs.contains(previousFrontendArg) { + args.append(arg.buildSettingPath().quoteIfNeeded()) + return + } + } + + if arg.hasPrefix("-vfsoverlay") { + var path = arg.dropFirst(11) + + guard !path.isEmpty else { + args.append(arg) + return + } + + if path.hasPrefix("=") { + path = path.dropFirst() + } + + let absoluteArg: Substring = + "-vfsoverlay" + path.buildSettingPath() + args.append(absoluteArg.quoteIfNeeded()) + return + } + + args.append(arg.substituteBazelPlaceholders().quoteIfNeeded()) + } +} + +private let loadPluginsArgs: Set = [ + "-load-plugin-executable", + "-load-plugin-library", +] + +private let overlayArgs: Set = [ + "-explicit-swift-module-map-file", + "-vfsoverlay", +] diff --git a/tools/generators/target_build_settings/src/Generator/String+Extensions.swift b/tools/generators/target_build_settings/src/Generator/String+Extensions.swift new file mode 100644 index 0000000000..bf3d64f55e --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/String+Extensions.swift @@ -0,0 +1,64 @@ +extension StringProtocol { + func buildSettingPath() -> String { + if self == "bazel-out" || hasPrefix("bazel-out/") { + // Dropping "bazel-out" prefix + return "$(BAZEL_OUT)\(dropFirst(9))" + } + + if self == "external" || hasPrefix("external/") { + // Dropping "external" prefix + return "$(BAZEL_EXTERNAL)\(dropFirst(8))" + } + + if self == ".." || hasPrefix("../") { + // Dropping ".." prefix + return "$(BAZEL_EXTERNAL)\(dropFirst(2))" + } + + if self == "." { + // We need to use Bazel's execution root for ".", since includes can + // reference things like "external/" and "bazel-out" + return "$(PROJECT_DIR)" + } + + let substituted = substituteBazelPlaceholders() + + if substituted.hasPrefix("/") || substituted.hasPrefix("$(") { + return substituted + } + + return "$(SRCROOT)/\(substituted)" + } + + func escapingForDebugSettings() -> String { + return replacingOccurrences(of: " ", with: #"\ "#) + .replacingOccurrences(of: #"""#, with: #"\""#) + // These nulls will become newlines with `.nullsToNewlines` in + // `pbxnativetargets`. We need to escape them in order to be + // able to split on newlines. + .replacingOccurrences(of: "\n", with: "\0") + } + + // FIXME: Use `escapingForDebugSettings` instead? + func quoteIfNeeded() -> String { + // Quote the arg if it contains spaces + guard !contains(" ") else { + return "'\(self)'" + } + return String(self) + } + + func substituteBazelPlaceholders() -> String { + return + // Use Xcode set `DEVELOPER_DIR` + replacingOccurrences( + of: "__BAZEL_XCODE_DEVELOPER_DIR__", + with: "$(DEVELOPER_DIR)" + ) + // Use Xcode set `SDKROOT` + .replacingOccurrences( + of: "__BAZEL_XCODE_SDKROOT__", + with: "$(SDKROOT)" + ) + } +} diff --git a/tools/generators/target_build_settings/src/Generator/WriteBuildSettings.swift b/tools/generators/target_build_settings/src/Generator/WriteBuildSettings.swift new file mode 100644 index 0000000000..ac15c3a3ad --- /dev/null +++ b/tools/generators/target_build_settings/src/Generator/WriteBuildSettings.swift @@ -0,0 +1,52 @@ +import Foundation + +extension Generator { + struct WriteBuildSettings { + private let callable: Callable + + /// - Parameters: + /// - callable: The function that will be called in + /// `callAsFunction()`. + init(callable: @escaping Callable = Self.defaultCallable) { + self.callable = callable + } + + /// Writes the build settings to disk. + func callAsFunction( + _ buildSettings: [(key: String, value: String)], + to url: URL + ) throws { + try callable(/*buildSettings:*/ buildSettings, /*url:*/ url) + } + } +} + +// MARK: - WriteBuildSettings.Callable + +extension Generator.WriteBuildSettings { + typealias Callable = ( + _ buildSettings: [(key: String, value: String)], + _ url: URL + ) throws -> Void + + static func defaultCallable( + _ buildSettings: [(key: String, value: String)], + to url: URL + ) throws { + var data = Data() + + for (key, value) in buildSettings + .sorted(by: { $0.key < $1.key }) + { + data.append(Data(key.utf8)) + data.append(subSeparator) + data.append(Data(value.utf8)) + data.append(separator) + } + + try data.write(to: url) + } +} + +private let separator = Data([0x0a]) // Newline +private let subSeparator = Data([0x09]) // Tab diff --git a/tools/generators/target_build_settings/src/TargetBuildSettings.swift b/tools/generators/target_build_settings/src/TargetBuildSettings.swift new file mode 100644 index 0000000000..977d581bf1 --- /dev/null +++ b/tools/generators/target_build_settings/src/TargetBuildSettings.swift @@ -0,0 +1,27 @@ +import Darwin +import ToolCommon + +@main +struct TargetBuildSettings { + static func main() async { + let logger = DefaultLogger( + standardError: StderrOutputStream(), + standardOutput: StdoutOutputStream(), + colorize: false + ) + + do { + // First argument is executable name + var rawArguments = CommandLine.arguments.dropFirst() + + if try rawArguments.consumeArg("colorize", as: Bool.self) { + logger.enableColors() + } + + try await Generator().generate(rawArguments: rawArguments) + } catch { + logger.logError(error.localizedDescription) + Darwin.exit(1) + } + } +} diff --git a/tools/generators/target_build_settings/test/Test.swift b/tools/generators/target_build_settings/test/Test.swift new file mode 100644 index 0000000000..e69de29bb2