diff --git a/swift/internal/swift_interop_hint.bzl b/swift/internal/swift_interop_hint.bzl index bed915e4b..e9db0f51f 100644 --- a/swift/internal/swift_interop_hint.bzl +++ b/swift/internal/swift_interop_hint.bzl @@ -17,16 +17,30 @@ load(":swift_common.bzl", "swift_common") def _swift_interop_hint_impl(ctx): - # TODO(b/194733180): For now, this rule only supports interop via an - # auto-derived module name or an explicit module name, but still using the - # auto-generated module name. Add support for manual module maps and other - # features, like APINotes, later. + # TODO(b/194733180): Take advantage of the richer API to add support for + # other features, like APINotes, later. return swift_common.create_swift_interop_info( + module_map = ctx.file.module_map, module_name = ctx.attr.module_name, ) swift_interop_hint = rule( attrs = { + "module_map": attr.label( + allow_single_file = True, + doc = """\ +An optional custom `.modulemap` file that defines the Clang module for the +headers in the target to which this hint is applied. + +If this attribute is omitted, a module map will be automatically generated based +on the headers in the hinted target. + +If this attribute is provided, then `module_name` must also be provided and +match the name of the desired top-level module in the `.modulemap` file. (A +single `.modulemap` file may define multiple top-level modules.) +""", + mandatory = False, + ), "module_name": attr.string( doc = """\ The name that will be used to import the hinted module into Swift. @@ -96,6 +110,32 @@ swift_interop_hint( When this `cc_library` is a dependency of a Swift target, a module map will be generated for it with the module name `CSomeLib`. + +#### Using a custom module map + +In rare cases, the automatically generated module map may not be suitable. For +example, a Swift module may depend on a C module that defines specific +submodules, and this is not handled by the Swift build rules. In this case, you +can provide the module map file using the `module_map` attribute. + +When setting the `module_map` attribute, `module_name` must also be set to the +name of the desired top-level module; it cannot be omitted. + +```build +# //my/project/BUILD +cc_library( + name = "somelib", + srcs = ["somelib.c"], + hdrs = ["somelib.h"], + aspect_hints = [":somelib_swift_interop"], +) + +swift_interop_hint( + name = "somelib_swift_interop", + module_map = "module.modulemap", + module_name = "CSomeLib", +) +``` """, implementation = _swift_interop_hint_impl, ) diff --git a/test/BUILD b/test/BUILD index 38d7e1de4..4a8e807e9 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,6 +1,7 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load(":debug_settings_tests.bzl", "debug_settings_test_suite") load(":generated_header_tests.bzl", "generated_header_test_suite") +load(":interop_hints_tests.bzl", "interop_hints_test_suite") load(":private_deps_tests.bzl", "private_deps_test_suite") load(":swift_through_non_swift_tests.bzl", "swift_through_non_swift_test_suite") @@ -10,6 +11,8 @@ debug_settings_test_suite() generated_header_test_suite() +interop_hints_test_suite() + private_deps_test_suite() swift_through_non_swift_test_suite() diff --git a/test/fixtures/interop_hints/BUILD b/test/fixtures/interop_hints/BUILD new file mode 100644 index 000000000..2b33cb36f --- /dev/null +++ b/test/fixtures/interop_hints/BUILD @@ -0,0 +1,82 @@ +load( + "//swift:swift.bzl", + "swift_interop_hint", + "swift_library", +) +load("//test/fixtures:common.bzl", "FIXTURE_TAGS") + +package( + default_visibility = ["//test:__subpackages__"], +) + +licenses(["notice"]) + +swift_library( + name = "import_module_name_swift", + srcs = ["ImportModuleName.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_custom_module_name"], +) + +cc_library( + name = "cc_lib_custom_module_name", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_custom_module_name_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_custom_module_name_hint", + module_name = "ModuleName", + tags = FIXTURE_TAGS, +) + +swift_library( + name = "import_submodule_swift", + srcs = ["ImportSubmodule.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_submodule"], +) + +cc_library( + name = "cc_lib_submodule", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_submodule_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_submodule_hint", + module_map = "module.modulemap", + module_name = "TopModule", + tags = FIXTURE_TAGS, +) + +swift_library( + name = "invalid_swift", + srcs = ["ImportSubmodule.swift"], + tags = FIXTURE_TAGS, + deps = [":cc_lib_invalid"], +) + +cc_library( + name = "cc_lib_invalid", + hdrs = [ + "header1.h", + "header2.h", + ], + aspect_hints = [":cc_lib_invalid_hint"], + tags = FIXTURE_TAGS, +) + +swift_interop_hint( + name = "cc_lib_invalid_hint", + module_map = "module.modulemap", + tags = FIXTURE_TAGS, +) diff --git a/test/fixtures/interop_hints/ImportModuleName.swift b/test/fixtures/interop_hints/ImportModuleName.swift new file mode 100644 index 000000000..f5e291cdd --- /dev/null +++ b/test/fixtures/interop_hints/ImportModuleName.swift @@ -0,0 +1 @@ +import ModuleName diff --git a/test/fixtures/interop_hints/ImportSubmodule.swift b/test/fixtures/interop_hints/ImportSubmodule.swift new file mode 100644 index 000000000..856f8cbb2 --- /dev/null +++ b/test/fixtures/interop_hints/ImportSubmodule.swift @@ -0,0 +1 @@ +import TopModule.Submodule diff --git a/test/fixtures/interop_hints/header1.h b/test/fixtures/interop_hints/header1.h new file mode 100644 index 000000000..bd9ec079d --- /dev/null +++ b/test/fixtures/interop_hints/header1.h @@ -0,0 +1 @@ +// Intentionally empty. diff --git a/test/fixtures/interop_hints/header2.h b/test/fixtures/interop_hints/header2.h new file mode 100644 index 000000000..bd9ec079d --- /dev/null +++ b/test/fixtures/interop_hints/header2.h @@ -0,0 +1 @@ +// Intentionally empty. diff --git a/test/fixtures/interop_hints/module.modulemap b/test/fixtures/interop_hints/module.modulemap new file mode 100644 index 000000000..5338c14d8 --- /dev/null +++ b/test/fixtures/interop_hints/module.modulemap @@ -0,0 +1,9 @@ +module TopModule { + header "header1.h" + export * + + module Submodule { + header "header2.h" + export * + } +} diff --git a/test/interop_hints_tests.bzl b/test/interop_hints_tests.bzl new file mode 100644 index 000000000..31d6d5633 --- /dev/null +++ b/test/interop_hints_tests.bzl @@ -0,0 +1,69 @@ +# Copyright 2021 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. + +"""Tests for `swift_interop_hint`.""" + +load( + "@build_bazel_rules_swift//test/rules:analysis_failure_test.bzl", + "analysis_failure_test", +) +load( + "@build_bazel_rules_swift//test/rules:provider_test.bzl", + "provider_test", +) + +def interop_hints_test_suite(): + """Test suite for `swift_interop_hint`.""" + name = "interop_hints" + + # Verify that a hint with only a custom module name causes the `cc_library` + # to propagate a `SwiftInfo` info with the expected auto-generated module + # map. + provider_test( + name = "{}_hint_with_custom_module_name_builds".format(name), + expected_files = [ + "test/fixtures/interop_hints/cc_lib_custom_module_name.swift.modulemap", + ], + field = "transitive_modules.clang.module_map!", + provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:import_module_name_swift", + ) + + # Verify that a hint with a custom module map file causes the `cc_library` + # to propagate a `SwiftInfo` info with that file. + provider_test( + name = "{}_hint_with_custom_module_map_builds".format(name), + expected_files = [ + "test/fixtures/interop_hints/module.modulemap", + ], + field = "transitive_modules.clang.module_map!", + provider = "SwiftInfo", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:import_submodule_swift", + ) + + # Verify that the build fails if a hint provides `module_map` without + # `module_name`. + analysis_failure_test( + name = "{}_fails_when_module_map_provided_without_module_name".format(name), + expected_message = "'module_name' must be specified when 'module_map' is specified.", + tags = [name], + target_under_test = "@build_bazel_rules_swift//test/fixtures/interop_hints:invalid_swift", + ) + + native.test_suite( + name = name, + tags = [name], + )