Skip to content

Commit

Permalink
Add support for custom module_maps in swift_interop_hint.
Browse files Browse the repository at this point in the history
This allows rules like `cc_library` to associate a custom module map if needed (however, this should be rare and used sparingly). It also eliminates the need for the `swift_c_module`, which will be removed.

Added analysis tests around the propagation of the module map artifacts.

PiperOrigin-RevId: 387195026
  • Loading branch information
allevato authored and swiple-rules-gardener committed Jul 27, 2021
1 parent c42a37a commit 1356365
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 4 deletions.
48 changes: 44 additions & 4 deletions swift/internal/swift_interop_hint.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
)
3 changes: 3 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
@@ -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")

Expand All @@ -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()
Expand Down
82 changes: 82 additions & 0 deletions test/fixtures/interop_hints/BUILD
Original file line number Diff line number Diff line change
@@ -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,
)
1 change: 1 addition & 0 deletions test/fixtures/interop_hints/ImportModuleName.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import ModuleName
1 change: 1 addition & 0 deletions test/fixtures/interop_hints/ImportSubmodule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import TopModule.Submodule
1 change: 1 addition & 0 deletions test/fixtures/interop_hints/header1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Intentionally empty.
1 change: 1 addition & 0 deletions test/fixtures/interop_hints/header2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Intentionally empty.
9 changes: 9 additions & 0 deletions test/fixtures/interop_hints/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module TopModule {
header "header1.h"
export *

module Submodule {
header "header2.h"
export *
}
}
69 changes: 69 additions & 0 deletions test/interop_hints_tests.bzl
Original file line number Diff line number Diff line change
@@ -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],
)

2 comments on commit 1356365

@keith
Copy link
Member

@keith keith commented on 1356365 Sep 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brentleyjones
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.