From 1ffa4b1cc847c841b762ee4e764a6ad4f8ed40d0 Mon Sep 17 00:00:00 2001 From: Luis Padron Date: Fri, 25 Oct 2024 11:48:51 -0400 Subject: [PATCH] feat: add `swift_package_tool` generated targets (#1307) This adds a `swift_package_tool` rule which will be generated by the `swift_deps` bzlmod extension. These targets can be run using `bazel run` to update/resolve the `Package.swift`. For example: ```sh cd examples/interesting_deps bazel run @swift_package//:update bazel run @swift_package//:resolve bazel run @swift_package//:update -- GeoSwift ``` --- README.md | 49 ++++++++++ docs/bzlmod_extensions_overview.md | 21 +++- examples/firebase_example/MODULE.bazel | 1 + examples/google_maps_example/MODULE.bazel | 3 + examples/interesting_deps/MODULE.bazel | 12 +++ examples/interesting_deps/do_test | 5 +- examples/ios_sim/MODULE.bazel | 1 + examples/lottie_ios_example/MODULE.bazel | 1 + examples/messagekit_example/MODULE.bazel | 1 + examples/nimble_example/MODULE.bazel | 1 + examples/objc_code/MODULE.bazel | 1 + examples/phone_number_kit/MODULE.bazel | 1 + examples/pkg_manifest_minimal/MODULE.bazel | 1 + examples/resources_example/MODULE.bazel | 1 + examples/shake_ios_example/MODULE.bazel | 1 + examples/snapkit_example/MODULE.bazel | 1 + examples/soto_example/MODULE.bazel | 1 + examples/stripe_example/MODULE.bazel | 1 + examples/symlink_example/MODULE.bazel | 1 + examples/tca_example/MODULE.bazel | 1 + examples/vapor_example/MODULE.bazel | 1 + examples/xcmetrics_example/MODULE.bazel | 1 + swiftpkg/BUILD.bazel | 1 + swiftpkg/bzlmod/BUILD.bazel | 3 + swiftpkg/bzlmod/swift_deps.bzl | 59 ++++++++++- swiftpkg/defs.bzl | 2 + swiftpkg/internal/BUILD.bazel | 21 ++++ swiftpkg/internal/repository_utils.bzl | 20 ++++ swiftpkg/internal/swift_package_tool.bzl | 98 +++++++++++++++++++ swiftpkg/internal/swift_package_tool_repo.bzl | 58 +++++++++++ .../swift_package_tool_runner_template.sh | 54 ++++++++++ 31 files changed, 420 insertions(+), 3 deletions(-) create mode 100644 swiftpkg/internal/swift_package_tool.bzl create mode 100644 swiftpkg/internal/swift_package_tool_repo.bzl create mode 100644 swiftpkg/internal/swift_package_tool_runner_template.sh diff --git a/README.md b/README.md index ae2c420f4..59a3a8c0b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ development inside a Bazel workspace. * [Quickstart](#quickstart) * [1. Enable bzlmod](#1-enable-bzlmod) * [2. Configure your `MODULE.bazel` to use rules_swift_package_manager.](#2-configure-your-modulebazel-to-use-rules_swift_package_manager) + * [(Optional) Use `swift_package` repository for updating packages](#optional-use-swift_package-repository-for-updating-packages) * [(Optional) Enable `swift_deps_info` generation for the Gazelle plugin](#optional-enable-swift_deps_info-generation-for-the-gazelle-plugin) * [3. Create a minimal `Package.swift` file.](#3-create-a-minimal-packageswift-file) * [4. Run `swift package update`](#4-run-swift-package-update) @@ -121,6 +122,54 @@ NOTE: Some Swift package manager features (e.g., resources) use rules from [rule dependency for `rules_swift_package_manager`. However, you do not need to declare it unless you use any of the rules in your project. +#### (Optional) Use `swift_package` repository for updating packages + +The `swift_deps` module extension will by default generate a `swift_package` repository which can be used to execute `swift package` commands. +This is useful if you'd like to control the flags and behavior of `swift package`, as well as for using the correct `swift` binary according to the Bazel configured toolchain. + +For example, to resolve the `Package.swift` file: + +```sh +bazel run @swift_package//:resolve +``` + +To update packages to their latest supported version: + +```sh +bazel run @swift_package//:update +``` + +Both targets support passing arguments as well, so for example, you could update a single package: + +```sh +bazel run @swift_package//:update -- MyPackage +``` + +These targets will update the `Package.resolved` file defined in `swift_deps.from_package`. +The targets come with default flags applied to enable the best Bazel compatibility, if you wish to configure it further, you can do so with `configure_swift_package`: + +```starlark +# MODULE.bazel + +swift_deps.configure_swift_package( + build_path = "spm-build", + cache_path = "spm-cache", + dependency_caching = "false", + manifest_cache = "none", + manifest_caching = "false", +) +``` + +If you do not want to use the `swift_package` repository you can disable it in the `swift_deps.from_package` call: + +```starlark +swift_deps.from_package( + declare_swift_package = False, # <=== Disable the `swift_package` repository + resolved = "//:Package.resolved", + swift = "//:Package.swift", +) +``` + #### (Optional) Enable `swift_deps_info` generation for the Gazelle plugin If you will be using the Gazelle plugin for Swift, you will need to enable the generation of diff --git a/docs/bzlmod_extensions_overview.md b/docs/bzlmod_extensions_overview.md index 8c0a2e2a9..4565644d6 100755 --- a/docs/bzlmod_extensions_overview.md +++ b/docs/bzlmod_extensions_overview.md @@ -17,7 +17,9 @@ On this page: swift_deps = use_extension("@rules_swift_package_manager//:extensions.bzl", "swift_deps") swift_deps.configure_package(name, init_submodules, patch_args, patch_cmds, patch_cmds_win, patch_tool, patches, recursive_init_submodules) -swift_deps.from_package(declare_swift_deps_info, resolved, swift) +swift_deps.configure_swift_package(build_path, cache_path, dependency_caching, manifest_cache, + manifest_caching) +swift_deps.from_package(declare_swift_deps_info, declare_swift_package, resolved, swift) @@ -42,6 +44,22 @@ Used to add or override settings for a particular Swift package. | patches | A list of files that are to be applied as patches after extracting the archive. By default, it uses the Bazel-native patch implementation which doesn't support fuzz match and binary patch, but Bazel will fall back to use patch command line tool if `patch_tool` attribute is specified or there are arguments other than `-p` in `patch_args` attribute. | List of labels | optional | `[]` | | recursive_init_submodules | Whether to clone submodules recursively in the repository. | Boolean | optional | `True` | + + +### configure_swift_package + +Used to configure the flags used when running the `swift package` binary. + +**Attributes** + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| build_path | The relative path within the runfiles tree for the Swift Package Manager build directory. | String | optional | `".build"` | +| cache_path | The relative path within the runfiles tree for the shared Swift Package Manager cache directory. | String | optional | `".cache"` | +| dependency_caching | Whether to enable the dependency cache. | String | optional | `"true"` | +| manifest_cache | Caching mode of Package.swift manifests (shared: shared cache, local: package's build directory, none: disabled) | String | optional | `"shared"` | +| manifest_caching | Whether to enable build manifest caching. | String | optional | `"true"` | + ### from_package @@ -53,6 +71,7 @@ Load Swift packages from `Package.swift` and `Package.resolved` files. | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | declare_swift_deps_info | Declare a `swift_deps_info` repository that is used by external tooling (e.g. Swift Gazelle plugin). | Boolean | optional | `False` | +| declare_swift_package | Declare a `swift_package_tool` repository named `swift_package` which defines two targets: `update` and `resolve`. These targets run can be used to run the `swift package` binary in a Bazel context. The flags used when running the underlying `swift package` can be configured using the `configure_swift_package` tag.

They can be `bazel run` to update/resolve the `resolved` file:

bazel run @swift_package//:update
bazel run @swift_package//:resolve
| Boolean | optional | `True` | | resolved | A `Package.resolved`. | Label | optional | `None` | | swift | A `Package.swift`. | Label | required | | diff --git a/examples/firebase_example/MODULE.bazel b/examples/firebase_example/MODULE.bazel index 04df47574..2b91f2eeb 100644 --- a/examples/firebase_example/MODULE.bazel +++ b/examples/firebase_example/MODULE.bazel @@ -59,6 +59,7 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_firebase_ios_sdk", "swiftpkg_reachability.swift", ) diff --git a/examples/google_maps_example/MODULE.bazel b/examples/google_maps_example/MODULE.bazel index b32069811..8705c56f6 100644 --- a/examples/google_maps_example/MODULE.bazel +++ b/examples/google_maps_example/MODULE.bazel @@ -43,8 +43,11 @@ swift_deps = use_extension( "@rules_swift_package_manager//:extensions.bzl", "swift_deps", ) + +# Example showing that `swift_package` repo can be disabled. swift_deps.from_package( declare_swift_deps_info = True, + declare_swift_package = False, resolved = "//:Package.resolved", swift = "//:Package.swift", ) diff --git a/examples/interesting_deps/MODULE.bazel b/examples/interesting_deps/MODULE.bazel index 51d8c9c25..31e218d8c 100644 --- a/examples/interesting_deps/MODULE.bazel +++ b/examples/interesting_deps/MODULE.bazel @@ -51,9 +51,21 @@ swift_deps.from_package( resolved = "//:Package.resolved", swift = "//:Package.swift", ) + +# Example showing customization of the `swift_package` repo tool. +# To resolve: `bazel run @swift_package//:resolve` +# To update: `bazel run @swift_package//:update` +swift_deps.configure_swift_package( + build_path = "spm-build", + cache_path = "spm-cache", + dependency_caching = "false", + manifest_cache = "none", + manifest_caching = "false", +) use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_cocoalumberjack", "swiftpkg_geoswift", "swiftpkg_libwebp_xcode", diff --git a/examples/interesting_deps/do_test b/examples/interesting_deps/do_test index bf114c0d8..903dbf5f4 100755 --- a/examples/interesting_deps/do_test +++ b/examples/interesting_deps/do_test @@ -2,12 +2,15 @@ set -o errexit -o nounset -o pipefail -# Use the Bazel binary specified by the integration test. Otherise, fall back +# Use the Bazel binary specified by the integration test. Otherise, fall back # to bazel. bazel="${BIT_BAZEL_BINARY:-bazel}" # Generate Swift external deps and update build files "${bazel}" run //:tidy +# Test resolving the package via the `swift_package` repo. +"${bazel}" run @swift_package//:resolve + # Ensure that it builds and tests pass "${bazel}" test //... diff --git a/examples/ios_sim/MODULE.bazel b/examples/ios_sim/MODULE.bazel index 67683ea72..b1d79ca69 100644 --- a/examples/ios_sim/MODULE.bazel +++ b/examples/ios_sim/MODULE.bazel @@ -66,6 +66,7 @@ swift_deps.configure_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_swift_markdown", "swiftpkg_swift_nio", ) diff --git a/examples/lottie_ios_example/MODULE.bazel b/examples/lottie_ios_example/MODULE.bazel index c70d9cafb..580659f29 100644 --- a/examples/lottie_ios_example/MODULE.bazel +++ b/examples/lottie_ios_example/MODULE.bazel @@ -51,5 +51,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_lottie_spm", ) diff --git a/examples/messagekit_example/MODULE.bazel b/examples/messagekit_example/MODULE.bazel index 80864cb24..41df89ef4 100644 --- a/examples/messagekit_example/MODULE.bazel +++ b/examples/messagekit_example/MODULE.bazel @@ -51,6 +51,7 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_kingfisher", "swiftpkg_messagekit", ) diff --git a/examples/nimble_example/MODULE.bazel b/examples/nimble_example/MODULE.bazel index a3ceef933..913af88f4 100644 --- a/examples/nimble_example/MODULE.bazel +++ b/examples/nimble_example/MODULE.bazel @@ -51,6 +51,7 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_nimble", "swiftpkg_quick", ) diff --git a/examples/objc_code/MODULE.bazel b/examples/objc_code/MODULE.bazel index e7943cd5a..61fb28529 100644 --- a/examples/objc_code/MODULE.bazel +++ b/examples/objc_code/MODULE.bazel @@ -54,5 +54,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_trustkit", ) diff --git a/examples/phone_number_kit/MODULE.bazel b/examples/phone_number_kit/MODULE.bazel index e7d715098..d81c855d3 100644 --- a/examples/phone_number_kit/MODULE.bazel +++ b/examples/phone_number_kit/MODULE.bazel @@ -51,5 +51,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_phonenumberkit", ) diff --git a/examples/pkg_manifest_minimal/MODULE.bazel b/examples/pkg_manifest_minimal/MODULE.bazel index f7067e75b..e4f161bdf 100644 --- a/examples/pkg_manifest_minimal/MODULE.bazel +++ b/examples/pkg_manifest_minimal/MODULE.bazel @@ -49,6 +49,7 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_my_local_package", "swiftpkg_notthatamazingmodule", "swiftpkg_swift_argument_parser", diff --git a/examples/resources_example/MODULE.bazel b/examples/resources_example/MODULE.bazel index 19a65c274..814ff3323 100644 --- a/examples/resources_example/MODULE.bazel +++ b/examples/resources_example/MODULE.bazel @@ -49,6 +49,7 @@ swift_deps.from_package( ) use_repo( swift_deps, + "swift_package", "swiftpkg_another_package_with_resources", "swiftpkg_app_lovin_sdk", "swiftpkg_googlesignin_ios", diff --git a/examples/shake_ios_example/MODULE.bazel b/examples/shake_ios_example/MODULE.bazel index 60c1742bc..e5f41cc35 100644 --- a/examples/shake_ios_example/MODULE.bazel +++ b/examples/shake_ios_example/MODULE.bazel @@ -51,5 +51,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_shake_ios", ) diff --git a/examples/snapkit_example/MODULE.bazel b/examples/snapkit_example/MODULE.bazel index 8f8d32532..d918a89c1 100644 --- a/examples/snapkit_example/MODULE.bazel +++ b/examples/snapkit_example/MODULE.bazel @@ -51,5 +51,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_snapkit", ) diff --git a/examples/soto_example/MODULE.bazel b/examples/soto_example/MODULE.bazel index 007d715ca..33fd1b1f4 100644 --- a/examples/soto_example/MODULE.bazel +++ b/examples/soto_example/MODULE.bazel @@ -54,5 +54,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_soto", ) diff --git a/examples/stripe_example/MODULE.bazel b/examples/stripe_example/MODULE.bazel index f0d9bf3d7..34d159781 100644 --- a/examples/stripe_example/MODULE.bazel +++ b/examples/stripe_example/MODULE.bazel @@ -54,5 +54,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_stripe_ios", ) diff --git a/examples/symlink_example/MODULE.bazel b/examples/symlink_example/MODULE.bazel index d0d7c88c8..27925b707 100644 --- a/examples/symlink_example/MODULE.bazel +++ b/examples/symlink_example/MODULE.bazel @@ -50,5 +50,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_empty_framework", ) diff --git a/examples/tca_example/MODULE.bazel b/examples/tca_example/MODULE.bazel index ce187e3c2..db9cb7cc1 100644 --- a/examples/tca_example/MODULE.bazel +++ b/examples/tca_example/MODULE.bazel @@ -54,5 +54,6 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_swift_composable_architecture", ) diff --git a/examples/vapor_example/MODULE.bazel b/examples/vapor_example/MODULE.bazel index c97b0aa03..535b5a919 100644 --- a/examples/vapor_example/MODULE.bazel +++ b/examples/vapor_example/MODULE.bazel @@ -54,6 +54,7 @@ swift_deps.from_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_fluent", "swiftpkg_fluent_sqlite_driver", "swiftpkg_vapor", diff --git a/examples/xcmetrics_example/MODULE.bazel b/examples/xcmetrics_example/MODULE.bazel index d1885a498..b91f80801 100644 --- a/examples/xcmetrics_example/MODULE.bazel +++ b/examples/xcmetrics_example/MODULE.bazel @@ -70,5 +70,6 @@ swift_deps.configure_package( use_repo( swift_deps, "swift_deps_info", + "swift_package", "swiftpkg_xcmetrics", ) diff --git a/swiftpkg/BUILD.bazel b/swiftpkg/BUILD.bazel index 9545db478..86852ce4a 100644 --- a/swiftpkg/BUILD.bazel +++ b/swiftpkg/BUILD.bazel @@ -13,6 +13,7 @@ bzl_library( "//swiftpkg/internal:swift_deps_index", "//swiftpkg/internal:swift_deps_info", "//swiftpkg/internal:swift_package", + "//swiftpkg/internal:swift_package_tool", ], ) diff --git a/swiftpkg/bzlmod/BUILD.bazel b/swiftpkg/bzlmod/BUILD.bazel index 7ca7f8bf3..2aa85d703 100644 --- a/swiftpkg/bzlmod/BUILD.bazel +++ b/swiftpkg/bzlmod/BUILD.bazel @@ -17,7 +17,10 @@ bzl_library( "//swiftpkg/internal:bazel_repo_names", "//swiftpkg/internal:local_swift_package", "//swiftpkg/internal:pkginfos", + "//swiftpkg/internal:repository_utils", "//swiftpkg/internal:swift_deps_info", "//swiftpkg/internal:swift_package", + "//swiftpkg/internal:swift_package_tool", + "//swiftpkg/internal:swift_package_tool_repo", ], ) diff --git a/swiftpkg/bzlmod/swift_deps.bzl b/swiftpkg/bzlmod/swift_deps.bzl index b852abc26..c5c83465b 100644 --- a/swiftpkg/bzlmod/swift_deps.bzl +++ b/swiftpkg/bzlmod/swift_deps.bzl @@ -3,20 +3,24 @@ load("//swiftpkg/internal:bazel_repo_names.bzl", "bazel_repo_names") load("//swiftpkg/internal:local_swift_package.bzl", "local_swift_package") load("//swiftpkg/internal:pkginfos.bzl", "pkginfos") +load("//swiftpkg/internal:repository_utils.bzl", "repository_utils") load("//swiftpkg/internal:swift_deps_info.bzl", "swift_deps_info") load("//swiftpkg/internal:swift_package.bzl", "PATCH_ATTRS", "swift_package") +load("//swiftpkg/internal:swift_package_tool.bzl", "SWIFT_PACKAGE_CONFIG_ATTRS") +load("//swiftpkg/internal:swift_package_tool_repo.bzl", "swift_package_tool_repo") # MARK: - swift_deps bzlmod Extension _DO_WHILE_RANGE = range(1000) -def _declare_pkgs_from_package(module_ctx, from_package, config_pkgs): +def _declare_pkgs_from_package(module_ctx, from_package, config_pkgs, config_swift_package): """Declare Swift packages from `Package.swift` and `Package.resolved`. Args: module_ctx: An instance of `module_ctx`. from_package: The data from the `from_package` tag. config_pkgs: The data from the `configure_package` tag. + config_swift_package: The data from the `configure_swift_package` tag. """ # Read Package.resolved. @@ -75,6 +79,15 @@ the Swift package to make it available.\ ) direct_dep_repo_names.append(swift_deps_info_repo_name) + if from_package.declare_swift_package: + swift_package_repo_name = "swift_package" + _declare_swift_package_repo( + name = swift_package_repo_name, + from_package = from_package, + config_swift_package = config_swift_package, + ) + direct_dep_repo_names.append(swift_package_repo_name) + # Ensure that we add all of the transitive source control deps from the # resolved file. for pin_map in resolved_pkg_map.get("pins", []): @@ -183,11 +196,30 @@ def _declare_pkg_from_dependency(dep, config_pkg): dependencies_index = None, ) +def _declare_swift_package_repo(name, from_package, config_swift_package): + config_swift_package_kwargs = repository_utils.struct_to_kwargs( + struct = config_swift_package, + keys = SWIFT_PACKAGE_CONFIG_ATTRS, + ) + + swift_package_tool_repo( + name = name, + package = "{package}/{name}".format( + package = from_package.swift.package, + name = from_package.swift.name, + ), + **config_swift_package_kwargs + ) + def _swift_deps_impl(module_ctx): config_pkgs = {} for mod in module_ctx.modules: for config_pkg in mod.tags.configure_package: config_pkgs[config_pkg.name] = config_pkg + config_swift_package = None + for mod in module_ctx.modules: + for config_swift_package in mod.tags.configure_swift_package: + config_swift_package = config_swift_package direct_dep_repo_names = [] for mod in module_ctx.modules: for from_package in mod.tags.from_package: @@ -196,6 +228,7 @@ def _swift_deps_impl(module_ctx): module_ctx, from_package, config_pkgs, + config_swift_package, ), ) return module_ctx.extension_metadata( @@ -209,6 +242,24 @@ _from_package_tag = tag_class( doc = """\ Declare a `swift_deps_info` repository that is used by external tooling (e.g. \ Swift Gazelle plugin).\ +""", + ), + "declare_swift_package": attr.bool( + default = True, + doc = """\ +Declare a `swift_package_tool` repository named `swift_package` which defines two targets: +`update` and `resolve`.\ + +These targets run can be used to run the `swift package` binary in a Bazel context. +The flags used when running the underlying `swift package` can be configured \ +using the `configure_swift_package` tag. + +They can be `bazel run` to update/resolve the `resolved` file: + +``` +bazel run @swift_package//:update +bazel run @swift_package//:resolve +``` """, ), "resolved": attr.label( @@ -244,10 +295,16 @@ The identity (i.e., name in the package's manifest) for the Swift package.\ doc = "Used to add or override settings for a particular Swift package.", ) +_configure_swift_package_tag = tag_class( + attrs = SWIFT_PACKAGE_CONFIG_ATTRS, + doc = "Used to configure the flags used when running the `swift package` binary.", +) + swift_deps = module_extension( implementation = _swift_deps_impl, tag_classes = { "configure_package": _configure_package_tag, + "configure_swift_package": _configure_swift_package_tag, "from_package": _from_package_tag, }, ) diff --git a/swiftpkg/defs.bzl b/swiftpkg/defs.bzl index 45d3cd0ac..0483d66e1 100644 --- a/swiftpkg/defs.bzl +++ b/swiftpkg/defs.bzl @@ -4,6 +4,7 @@ load("//swiftpkg/internal:local_swift_package.bzl", _local_swift_package = "loca load("//swiftpkg/internal:swift_deps_index.bzl", _swift_deps_index = "swift_deps_index") load("//swiftpkg/internal:swift_deps_info.bzl", _swift_deps_info = "swift_deps_info") load("//swiftpkg/internal:swift_package.bzl", _swift_package = "swift_package") +load("//swiftpkg/internal:swift_package_tool.bzl", _swift_package_tool = "swift_package_tool") # Repository rules swift_package = _swift_package @@ -12,3 +13,4 @@ swift_deps_info = _swift_deps_info # Rules swift_deps_index = _swift_deps_index +swift_package_tool = _swift_package_tool diff --git a/swiftpkg/internal/BUILD.bazel b/swiftpkg/internal/BUILD.bazel index fb4233164..b21d05f06 100644 --- a/swiftpkg/internal/BUILD.bazel +++ b/swiftpkg/internal/BUILD.bazel @@ -297,6 +297,27 @@ bzl_library( ], ) +bzl_library( + name = "swift_package_tool", + srcs = ["swift_package_tool.bzl"], + visibility = ["//swiftpkg:__subpackages__"], + deps = [ + "@bazel_skylib//lib:dicts", + "@bazel_skylib//lib:paths", + "@build_bazel_rules_swift//swift", + ], +) + +bzl_library( + name = "swift_package_tool_repo", + srcs = ["swift_package_tool_repo.bzl"], + visibility = ["//swiftpkg:__subpackages__"], + deps = [ + ":repository_utils", + "@bazel_skylib//lib:dicts", + ], +) + bzl_library( name = "bazel_repo_names", srcs = ["bazel_repo_names.bzl"], diff --git a/swiftpkg/internal/repository_utils.bzl b/swiftpkg/internal/repository_utils.bzl index 538dc14cb..1dad0607a 100644 --- a/swiftpkg/internal/repository_utils.bzl +++ b/swiftpkg/internal/repository_utils.bzl @@ -107,9 +107,29 @@ def _package_name(repository_ctx): return repository_ctx.attr.bazel_package_name return repository_ctx.name +def _struct_to_kwargs(*, struct, keys): + """Convert a struct to a kwargs dict, where the keys are the struct's attrs and the values are the struct's values. + + Example: given a struct like: `struct(a = "foo", b = "bar")` and keys `["a"]`, the result will be `{"a": "foo"}`. + + Args: + struct: The struct to convert. + keys: The keys to include in the kwargs dict. + + Returns: + A kwargs dict. + """ + kwargs = {} + for k in keys: + v = getattr(struct, k, None) + if v != None: + kwargs[k] = v + return kwargs + repository_utils = struct( exec_spm_command = _execute_spm_command, is_macos = _is_macos, package_name = _package_name, parsed_json_from_spm_command = _parsed_json_from_spm_command, + struct_to_kwargs = _struct_to_kwargs, ) diff --git a/swiftpkg/internal/swift_package_tool.bzl b/swiftpkg/internal/swift_package_tool.bzl new file mode 100644 index 000000000..c5d8896da --- /dev/null +++ b/swiftpkg/internal/swift_package_tool.bzl @@ -0,0 +1,98 @@ +"""Implementation for the `swift_package_tool` rule used by the `swift_deps` bzlmod extension.""" + +load("@bazel_skylib//lib:dicts.bzl", "dicts") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_common") + +# The name of the runner script. +_RUNNER_SCRIPT_NAME = "swift_package.sh" + +def _swift_package_tool_impl(ctx): + build_path = ctx.attr.build_path + cache_path = ctx.attr.cache_path + cmd = ctx.attr.cmd + package = ctx.attr.package + package_path = paths.dirname(package) + + toolchain = swift_common.get_toolchain(ctx) + swift = toolchain.swift_worker + + runner_script = ctx.actions.declare_file(_RUNNER_SCRIPT_NAME) + template_dict = ctx.actions.template_dict() + template_dict.add("%(swift_worker)s", swift.executable.short_path) + template_dict.add("%(cmd)s", cmd) + template_dict.add("%(package_path)s", package_path) + template_dict.add("%(build_path)s", build_path) + template_dict.add("%(cache_path)s", cache_path) + template_dict.add("%(enable_build_manifest_caching)s", ctx.attr.manifest_caching) + template_dict.add("%(enable_dependency_cache)s", ctx.attr.dependency_caching) + template_dict.add("%(manifest_cache)s", ctx.attr.manifest_cache) + + ctx.actions.expand_template( + template = ctx.file._runner_template, + is_executable = True, + output = runner_script, + computed_substitutions = template_dict, + ) + + return [ + DefaultInfo( + executable = runner_script, + files = depset([runner_script]), + runfiles = ctx.runfiles(files = [swift.executable]), + ), + ] + +SWIFT_PACKAGE_CONFIG_ATTRS = { + "build_path": attr.string( + doc = "The relative path within the runfiles tree for the Swift Package Manager build directory.", + default = ".build", + ), + "cache_path": attr.string( + doc = "The relative path within the runfiles tree for the shared Swift Package Manager cache directory.", + default = ".cache", + ), + "dependency_caching": attr.string( + doc = "Whether to enable the dependency cache.", + default = "true", + values = ["true", "false"], + ), + "manifest_cache": attr.string( + doc = """Caching mode of Package.swift manifests \ +(shared: shared cache, local: package's build directory, none: disabled) +""", + default = "shared", + values = ["shared", "local", "none"], + ), + "manifest_caching": attr.string( + doc = "Whether to enable build manifest caching.", + default = "true", + values = ["true", "false"], + ), +} + +swift_package_tool = rule( + implementation = _swift_package_tool_impl, + doc = "Defines a rule that can be used to execute the `swift package` tool.", + attrs = dicts.add( + swift_common.toolchain_attrs(), + { + "cmd": attr.string( + doc = "The `swift package` command to execute.", + mandatory = True, + values = ["update", "resolve"], + ), + "package": attr.string( + doc = "The relative path to the `Package.swift` file from the workspace root.", + mandatory = True, + ), + "_runner_template": attr.label( + doc = "The template for the runner script.", + allow_single_file = True, + default = Label("//swiftpkg/internal:swift_package_tool_runner_template.sh"), + ), + }, + SWIFT_PACKAGE_CONFIG_ATTRS, + ), + executable = True, +) diff --git a/swiftpkg/internal/swift_package_tool_repo.bzl b/swiftpkg/internal/swift_package_tool_repo.bzl new file mode 100644 index 000000000..54403dcd9 --- /dev/null +++ b/swiftpkg/internal/swift_package_tool_repo.bzl @@ -0,0 +1,58 @@ +"""Defines the `swift_package_tool_repo` repository rule that creates `swift_package_tool` targets.""" + +load("@bazel_skylib//lib:dicts.bzl", "dicts") +load("//swiftpkg/internal:repository_utils.bzl", "repository_utils") +load("//swiftpkg/internal:swift_package_tool.bzl", "SWIFT_PACKAGE_CONFIG_ATTRS") + +def _swift_package_tool_repo_impl(repository_ctx): + package_path = repository_ctx.attr.package + + # Construct the list of keyword arguments for the `swift_package_tool` rule. + # String should be "key = \"value\"" + # NOTE: only supports string typed values as they are all quoted + kwargs = repository_utils.struct_to_kwargs( + struct = repository_ctx.attr, + keys = SWIFT_PACKAGE_CONFIG_ATTRS, + ) + kwarg_content = ",\n".join([ + " {key} = \"{value}\"".format(key = k, value = v) + for k, v in kwargs.items() + ]) + + repository_ctx.file( + "BUILD.bazel", + content = """ +load("@rules_swift_package_manager//swiftpkg:defs.bzl", "swift_package_tool") + +swift_package_tool( + name = "update", + cmd = "update", + package = "{package}", +{kwarg_content} +) + +swift_package_tool( + name = "resolve", + cmd = "resolve", + package = "{package}", +{kwarg_content} +) +""".format( + package = package_path, + kwarg_content = kwarg_content, + ), + ) + +swift_package_tool_repo = repository_rule( + implementation = _swift_package_tool_repo_impl, + attrs = dicts.add( + { + "package": attr.string( + doc = "The relative path to the `Package.swift` file to operate on.", + mandatory = True, + ), + }, + SWIFT_PACKAGE_CONFIG_ATTRS, + ), + doc = "Declares a `@swift_package` repository for using the `swift_package_tool` targets.", +) diff --git a/swiftpkg/internal/swift_package_tool_runner_template.sh b/swiftpkg/internal/swift_package_tool_runner_template.sh new file mode 100644 index 000000000..0fe61cc94 --- /dev/null +++ b/swiftpkg/internal/swift_package_tool_runner_template.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -euo pipefail + +# +# This is a templated script which runs `swift package `. +# +# The expected template keys are: +# %(swift_worker)s - The path to the Swift worker executable. +# %(cmd)s - The command to run. +# %(package)s - The path to the package to run the command on. +# %(build_path)s - The path to the build directory. +# %(cache_path)s - The path to the cache directory. + +if [ -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]; then + echo "BUILD_WORKSPACE_DIRECTORY is not set, this target may only be \`bazel run\`" + exit 1 +fi + +# Collect template values. +swift_worker="%(swift_worker)s" +cmd="%(cmd)s" +package_path="$BUILD_WORKSPACE_DIRECTORY/%(package_path)s" +build_path="%(build_path)s" +cache_path="%(cache_path)s" +enable_build_manifest_caching="%(enable_build_manifest_caching)s" +enable_dependency_cache="%(enable_dependency_cache)s" +manifest_cache="%(manifest_cache)s" + +# Construct dynamic arguments. +args=() + +if [ "$enable_build_manifest_caching" = "true" ]; then + args+=("--enable-build-manifest-caching") +else + args+=("--disable-build-manifest-caching") +fi + +if [ "$enable_dependency_cache" = "true" ]; then + args+=("--enable-dependency-cache") +else + args+=("--disable-dependency-cache") +fi + +args+=("--manifest-cache=$manifest_cache") + +# Run the command. +"$swift_worker" swift package \ + --package-path "$package_path" \ + --build-path "$build_path" \ + --cache-path "$cache_path" \ + "$cmd" \ + "${args[@]}" \ + "$@"