diff --git a/doc/rules.md b/doc/rules.md index 08c42c885..12c894e87 100755 --- a/doc/rules.md +++ b/doc/rules.md @@ -41,7 +41,7 @@ On this page:
 swift_binary(name, deps, srcs, data, additional_linker_inputs, copts, defines, env, linkopts,
-             malloc, module_name, package_name, plugins, stamp, swiftc_inputs)
+             linkshared, malloc, module_name, package_name, plugins, stamp, swiftc_inputs)
 
Compiles and links Swift code into an executable binary. @@ -58,6 +58,9 @@ please use one of the platform-specific application rules in [rules_apple](https://github.com/bazelbuild/rules_apple) instead of `swift_binary`. +Setting `linkshared = True` links a shared library instead of an executable; +see the `linkshared` attribute. + **ATTRIBUTES** @@ -72,6 +75,7 @@ please use one of the platform-specific application rules in | defines | A list of defines to add to the compilation command line.

Note that unlike C-family languages, Swift defines do not have values; they are simply identifiers that are either defined or undefined. So strings in this list should be simple identifiers, **not** `name=value` pairs.

Each string is prepended with `-D` and added to the command line. Unlike `copts`, these flags are added for the target and every target that depends on it, so use this attribute with caution. It is preferred that you add defines directly to `copts`, only using this feature in the rare case that a library needs to propagate a symbol up to those that depend on it. | List of strings | optional | `[]` | | env | Specifies additional environment variables to set when the test is executed by `bazel run` or `bazel test`.

The values of these environment variables are subject to `$(location)` and "Make variable" substitution.

NOTE: The environment variables are not set when you run the target outside of Bazel (for example, by manually executing the binary in `bazel-bin/`). | Dictionary: String -> String | optional | `{}` | | linkopts | Additional linker options that should be passed to `clang`. These strings are subject to `$(location ...)` expansion. | List of strings | optional | `[]` | +| linkshared | If `True`, link the target as a shared library / loadable module instead of an executable, similar to `cc_binary`'s `linkshared`. The binary has no `main` entry point and the renamed-entry-point machinery is disabled.

This produces a dynamic library named `lib.so` (`.dylib` on Apple platforms) suitable for loading with `dlopen` / `System.loadLibrary` (e.g. an Android JNI library; export functions with `@_cdecl`). | Boolean | optional | `False` | | malloc | Override the default dependency on `malloc`.

By default, Swift binaries are linked against `@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use libc's `malloc`. This label must refer to a `cc_library` rule. | Label | optional | `"@bazel_tools//tools/cpp:malloc"` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | diff --git a/examples/xplatform/shared_library/BUILD b/examples/xplatform/shared_library/BUILD new file mode 100644 index 000000000..91e071230 --- /dev/null +++ b/examples/xplatform/shared_library/BUILD @@ -0,0 +1,11 @@ +load("//swift:swift_binary.bzl", "swift_binary") + +licenses(["notice"]) + +# `linkshared` links a native dynamic library instead of an executable: a +# `.dylib` on Apple platforms or a `.so` on Linux. +swift_binary( + name = "shared_library", + srcs = ["greeting.swift"], + linkshared = True, +) diff --git a/examples/xplatform/shared_library/greeting.swift b/examples/xplatform/shared_library/greeting.swift new file mode 100644 index 000000000..41a9a8e71 --- /dev/null +++ b/examples/xplatform/shared_library/greeting.swift @@ -0,0 +1,18 @@ +// Copyright 2024 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@_cdecl("greeting") +public func greeting() -> Int32 { + return 42 +} diff --git a/swift/internal/feature_names.bzl b/swift/internal/feature_names.bzl index e371f611a..c5b8928ae 100644 --- a/swift/internal/feature_names.bzl +++ b/swift/internal/feature_names.bzl @@ -321,6 +321,13 @@ SWIFT_FEATURE_DECLARE_SWIFTSOURCEINFO = "swift.emit_swiftsourceinfo" # system command line limit. SWIFT_FEATURE_NO_EMBED_DEBUG_MODULE = "swift.no_embed_debug_module" +# If enabled, the entry point of a `swift_binary` is not renamed to a +# target-specific symbol (which is otherwise aliased back to `main` at link +# time so that the binary's code can also be linked into another binary, such +# as a test executable). Toolchains whose linkers cannot create such aliases +# (e.g. wasm-ld, which has no `--defsym`) should enable this feature. +SWIFT_FEATURE_NO_ENTRY_POINT_RENAME = "swift.no_entry_point_rename" + # If enabled, the toolchain will directly generate from the raw proto files # and not from the DescriptorSets. # diff --git a/swift/swift_binary.bzl b/swift/swift_binary.bzl index 10689482c..2186bc9e4 100644 --- a/swift/swift_binary.bzl +++ b/swift/swift_binary.bzl @@ -14,6 +14,7 @@ """Implementation of the `swift_binary` rule.""" +load("@bazel_skylib//lib:dicts.bzl", "dicts") load("@bazel_skylib//lib:paths.bzl", "paths") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") @@ -22,6 +23,7 @@ load("//swift/internal:compiling.bzl", "compile") load( "//swift/internal:feature_names.bzl", "SWIFT_FEATURE_ADD_TARGET_NAME_TO_OUTPUT", + "SWIFT_FEATURE_NO_ENTRY_POINT_RENAME", ) load("//swift/internal:features.bzl", "is_feature_enabled") load( @@ -85,6 +87,15 @@ def _swift_binary_impl(ctx): unsupported_features = ctx.disabled_features, ) + # A binary linked as a shared object (`linkshared`) has no `main`, so the + # entry-point rename (and the matching `--defsym main=...` at link time) + # must be skipped, just as it is when the toolchain requests it via + # `swift.no_entry_point_rename`. + skip_entry_point = ctx.attr.linkshared or is_feature_enabled( + feature_configuration = feature_configuration, + feature_name = SWIFT_FEATURE_NO_ENTRY_POINT_RENAME, + ) + srcs = ctx.files.srcs output_groups = {} module_contexts = [] @@ -99,7 +110,21 @@ def _swift_binary_impl(ctx): ctx.label, feature_configuration = feature_configuration, ) - entry_point_name = entry_point_function_name(module_name) + + if skip_entry_point: + entry_point_name = None + entry_point_copts = [] + else: + # Use a custom entry point name so that the binary's code can + # also be linked into another process (like a test executable) + # without having its main function collide. + entry_point_name = entry_point_function_name(module_name) + entry_point_copts = [ + "-Xfrontend", + "-entry-point-function-name", + "-Xfrontend", + entry_point_name, + ] include_dev_srch_paths = include_developer_search_paths(ctx.attr) @@ -111,15 +136,7 @@ def _swift_binary_impl(ctx): ctx, ctx.attr.copts, ctx.attr.swiftc_inputs, - ) + _maybe_parse_as_library_copts(srcs) + [ - # Use a custom entry point name so that the binary's code can - # also be linked into another process (like a test executable) - # without having its main function collide. - "-Xfrontend", - "-entry-point-function-name", - "-Xfrontend", - entry_point_name, - ], + ) + _maybe_parse_as_library_copts(srcs) + entry_point_copts, defines = ctx.attr.defines, feature_configuration = feature_configuration, include_dev_srch_paths = include_dev_srch_paths, @@ -178,6 +195,13 @@ def _swift_binary_impl(ctx): else: name = ctx.label.name + # `linkshared` produces a real dynamic library (`lib.so` / `.dylib`), + # matching `cc_binary`'s `linkshared`; otherwise an executable. + if ctx.attr.linkshared: + output_type = "dynamic_library" + else: + output_type = "executable" + linking_outputs = register_link_binary_action( actions = ctx.actions, additional_inputs = ctx.files.additional_linker_inputs, @@ -189,18 +213,27 @@ def _swift_binary_impl(ctx): label = ctx.label, module_contexts = module_contexts, name = name, - output_type = "executable", + output_type = output_type, stamp = ctx.attr.stamp, toolchains = toolchains, user_link_flags = binary_link_flags + entry_point_linkopts, variables_extension = variables_extension, ) + if output_type == "dynamic_library": + library_to_link = linking_outputs.library_to_link + output_file = ( + library_to_link.resolved_symlink_dynamic_library or + library_to_link.dynamic_library + ) + else: + output_file = linking_outputs.executable + providers = [ DefaultInfo( - executable = linking_outputs.executable, + executable = output_file, files = depset( - [linking_outputs.executable] + additional_debug_outputs, + [output_file] + additional_debug_outputs, ), runfiles = ctx.runfiles( collect_data = True, @@ -278,9 +311,25 @@ def _swift_binary_impl(ctx): return providers swift_binary = rule( - attrs = binary_rule_attrs( - additional_deps_providers = [[SwiftCompilerPluginInfo]], - stamp_default = -1, + attrs = dicts.add( + binary_rule_attrs( + additional_deps_providers = [[SwiftCompilerPluginInfo]], + stamp_default = -1, + ), + { + "linkshared": attr.bool( + default = False, + doc = """\ +If `True`, link the target as a shared library / loadable module instead of an +executable, similar to `cc_binary`'s `linkshared`. The binary has no `main` +entry point and the renamed-entry-point machinery is disabled. + +This produces a dynamic library named `lib.so` (`.dylib` on Apple +platforms) suitable for loading with `dlopen` / `System.loadLibrary` (e.g. an +Android JNI library; export functions with `@_cdecl`). +""", + ), + }, ), doc = """\ Compiles and links Swift code into an executable binary. @@ -296,6 +345,9 @@ If you want to create a multi-architecture binary or a bundled application, please use one of the platform-specific application rules in [rules_apple](https://github.com/bazelbuild/rules_apple) instead of `swift_binary`. + +Setting `linkshared = True` links a shared library instead of an executable; +see the `linkshared` attribute. """, exec_groups = { # The `plugins` attribute associates its `exec` transition with this