Skip to content

Commit

Permalink
fix: do not generate a modulemap for Objc targets, if they provide one (
Browse files Browse the repository at this point in the history
#710)

The goal of this PR is to selectively generate modulemap files for ObjC
targets. If an ObjC target provides a modulemap, we will not generate a
modulemap. This avoids `redefinition of module` errors when running
outside of the Bazel action sandbox. While we may not generate
modulemaps for some ObjC targets, we must continue to create the
`generate_modulemap` targets. The logic that assigns dependencies has no
way to know whether a modulemap target has been generated (i.e., the
ObjC target may be a dependency in another Swift package). Hence,
`generate_modulemap` has been modified to have a noop mode.

- Add `noop` attribute to `generate_modulemap` rule. If `True`, a
modulemap is generated and the appropriate providers are populated. If
`False, a modulemap is not generated and empty providers are returned.
- Update `clang_files.collect_files()` to return the path of the
modulemap file, if one is found.
- Update the build file generation to set the `noop` attribute for
`generate_modulemap` based upon whether a modulemap file was found for
the module.
- Update `firebase_example` to use `rules_xcodeproj` to generate an
Xcode project and ensure that it builds using the `rules_xcodeproj`
recommended build API>


Related to
MobileNativeFoundation/rules_xcodeproj#2703.
Closes #712.
  • Loading branch information
cgrindel authored Nov 3, 2023
1 parent f749b50 commit 4464d4d
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 5 deletions.
3 changes: 2 additions & 1 deletion docs/internal_rules_and_macros_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ On this page:
## generate_modulemap

<pre>
generate_modulemap(<a href="#generate_modulemap-name">name</a>, <a href="#generate_modulemap-deps">deps</a>, <a href="#generate_modulemap-hdrs">hdrs</a>, <a href="#generate_modulemap-module_name">module_name</a>)
generate_modulemap(<a href="#generate_modulemap-name">name</a>, <a href="#generate_modulemap-deps">deps</a>, <a href="#generate_modulemap-hdrs">hdrs</a>, <a href="#generate_modulemap-module_name">module_name</a>, <a href="#generate_modulemap-noop">noop</a>)
</pre>

Generate a modulemap for an Objective-C module.
Expand All @@ -31,6 +31,7 @@ Generate a modulemap for an Objective-C module.
| <a id="generate_modulemap-deps"></a>deps | The module maps that this module uses. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="generate_modulemap-hdrs"></a>hdrs | The public headers for this module. | <a href="https://bazel.build/concepts/labels">List of labels</a> | required | |
| <a id="generate_modulemap-module_name"></a>module_name | The name of the module. | String | optional | <code>""</code> |
| <a id="generate_modulemap-noop"></a>noop | Designates whether a modulemap should be generated. If <code>False</code>, a modulemap is generated. If <code>True</code>, a modulemap file is not generated and the returned providers are empty. | Boolean | optional | <code>False</code> |


<a id="resource_bundle_accessor"></a>
Expand Down
5 changes: 5 additions & 0 deletions examples/firebase_example/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ bazel_dep(
repo_name = "build_bazel_rules_apple",
)

bazel_dep(
name = "rules_xcodeproj",
version = "1.12.1",
dev_dependency = True,
)
bazel_dep(
name = "bazel_skylib_gazelle_plugin",
version = "1.4.2",
Expand Down
14 changes: 14 additions & 0 deletions examples/firebase_example/analytics/AnalyticsExample/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"xcodeproj",
)

swift_library(
name = "AnalyticsExample",
Expand All @@ -27,6 +31,7 @@ ios_application(
infoplists = [":Info.plist"],
launch_storyboard = "//analytics/AnalyticsExample:Base.lproj/LaunchScreen.storyboard",
minimum_os_version = "13.0",
visibility = ["//visibility:public"],
deps = [":AnalyticsExample"],
)

Expand All @@ -36,3 +41,12 @@ build_test(
":iosapp",
],
)

xcodeproj(
name = "xcodeproj",
project_name = "AnalyticsExample",
tags = ["manual"],
top_level_targets = [
":iosapp",
],
)
13 changes: 13 additions & 0 deletions examples/firebase_example/do_test
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,16 @@ assert_match() {

# Ensure that it builds and tests pass
"${bazel}" test //...

# The analytics/AnalyticsExample generates an Xcode project using
# rules_xcodeproj. The following ensures that the project generates properly
# and that the project builds as rules_xcodeproj executes the build.
# Related:
# https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/2703

# Generate the Xcode project
"${bazel}" run //analytics/AnalyticsExample:xcodeproj

# Build the workspace
"${bazel}" run //analytics/AnalyticsExample:xcodeproj -- \
--generator_output_groups=all_targets 'build --remote_download_minimal'
31 changes: 31 additions & 0 deletions swiftpkg/internal/generate_modulemap.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,33 @@ ModuleMapInfo = provider(

def _generate_modulemap_impl(ctx):
module_name = ctx.attr.module_name

# Decide whether we should generate the modulemap. If we do not generate
# the modulemap, we still need to return expected providers.
if ctx.attr.noop:
return [
DefaultInfo(files = depset([])),
ModuleMapInfo(
module_name = module_name,
modulemap_file = None,
),
apple_common.new_objc_provider(
module_map = depset([]),
),
CcInfo(
compilation_context = cc_common.create_compilation_context(
headers = depset([]),
includes = depset([]),
),
),
]

uses = [
dep[ModuleMapInfo].module_name
for dep in ctx.attr.deps
if ModuleMapInfo in dep
]

out_filename = "{}/module.modulemap".format(module_name)
modulemap_file = ctx.actions.declare_file(out_filename)

Expand Down Expand Up @@ -75,6 +98,14 @@ generate_modulemap = rule(
"module_name": attr.string(
doc = "The name of the module.",
),
"noop": attr.bool(
doc = """\
Designates whether a modulemap should be generated. If `False`, a modulemap is \
generated. If `True`, a modulemap file is not generated and the returned \
providers are empty.\
""",
default = False,
),
},
doc = "Generate a modulemap for an Objective-C module.",
)
5 changes: 4 additions & 1 deletion swiftpkg/internal/pkginfos.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ def _new_clang_src_info_from_sources(
textual_hdrs = textual_hdrs,
public_includes = public_includes,
private_includes = private_includes,
modulemap_path = organized_files.modulemap,
)

def _new_clang_src_info(
Expand All @@ -980,14 +981,16 @@ def _new_clang_src_info(
hdrs = [],
textual_hdrs = [],
public_includes = [],
private_includes = []):
private_includes = [],
modulemap_path = None):
return struct(
srcs = srcs,
explicit_srcs = explicit_srcs,
hdrs = hdrs,
textual_hdrs = textual_hdrs,
public_includes = public_includes,
private_includes = private_includes,
modulemap_path = modulemap_path,
)

# MARK: - Objc Source Info
Expand Down
13 changes: 10 additions & 3 deletions swiftpkg/internal/swiftpkg_build_files.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -321,27 +321,34 @@ def _clang_target_build_file(repository_ctx, pkg_ctx, target):
sdk_framework_bzl_selects,
)

modulemap_deps = _collect_modulemap_deps(deps)

# There is a known issue with Objective-C library targets not
# supporting the `@import` of modules defined in other Objective-C
# targets. As a workaround, we will define two targets. One is the
# `objc_library` target. The other is a `generate_modulemap`
# target. This second target generates a `module.modulemap` file and
# provides information about that generated file to `objc_library`
# targets.
# targets, if `noop` is `False`. If `noop` is `True`, the target
# generates nothing and returns "empty" providers.
#
# Why not skip adding the `generate_modulemap` if `noop` is `True`?
# The logic that assigns dependencies for other targets has no way to
# know whether the modulemap target exists. Hence, we ensure that it
# always exists but does nothing.
#
# See `deps_indexes.bzl` for the logic that resolves the dependency
# labels.
# See `generate_modulemap.bzl` for details on the modulemap generation.
# See `//swiftpkg/tests/generate_modulemap_tests` package for a usage
# example.
modulemap_deps = _collect_modulemap_deps(deps)
load_stmts = [swiftpkg_generate_modulemap_load_stmt]
modulemap_target_name = pkginfo_targets.modulemap_label_name(bzl_target_name)
noop_modulemap = clang_src_info.modulemap_path != None
modulemap_attrs = {
"deps": bzl_selects.to_starlark(modulemap_deps),
"hdrs": clang_src_info.hdrs,
"module_name": target.c99name,
"noop": noop_modulemap,
"visibility": ["//visibility:public"],
}
decls = [
Expand Down
110 changes: 110 additions & 0 deletions swiftpkg/tests/swiftpkg_build_files_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,47 @@ _pkg_info = pkginfos.new(
],
),
),
pkginfos.new_target(
name = "ObjcLibraryWithModulemap",
type = "regular",
c99name = "ObjcLibraryWithModulemap",
module_type = "ClangTarget",
path = ".",
# NOTE: SPM does not report header files in the sources for clang
# targets. The `swift_package` code reads the filesystem to find
# the sources.
sources = [
"src/foo.m",
"src/foo.h",
],
source_paths = [
"src/",
],
public_hdrs_path = "include",
dependencies = [
pkginfos.new_target_dependency(
by_name = pkginfos.new_by_name_reference("ObjcLibraryDep"),
),
],
repo_name = _repo_name,
clang_src_info = pkginfos.new_clang_src_info(
hdrs = ["include/external.h"],
srcs = [
"src/foo.h",
"src/foo.m",
],
public_includes = ["include"],
private_includes = ["src"],
textual_hdrs = ["src/foo.m"],
modulemap_path = "include/module.modulemap",
),
objc_src_info = pkginfos.new_objc_src_info(
builtin_frameworks = [
"Foundation",
"UIKit",
],
),
),
pkginfos.new_target(
name = "SwiftLibraryWithConditionalDep",
type = "regular",
Expand Down Expand Up @@ -490,6 +531,14 @@ _deps_index_json = """
"package_identity": "mypackage",
"product_memberships": []
},
{
"name": "ObjcLibraryWithModulemap",
"c99name": "ObjcLibraryWithModulemap",
"src_type": "objc",
"label": "@swiftpkg_mypackage//:ObjcLibraryWithModulemap",
"package_identity": "mypackage",
"product_memberships": []
},
{
"name": "SwiftLibraryWithConditionalDep",
"c99name": "SwiftLibraryWithConditionalDep",
Expand Down Expand Up @@ -679,6 +728,7 @@ generate_modulemap(
deps = ["@swiftpkg_mypackage//:ObjcLibraryDep_modulemap"],
hdrs = ["include/external.h"],
module_name = "ObjcLibrary",
noop = False,
visibility = ["//visibility:public"],
)
Expand Down Expand Up @@ -724,6 +774,65 @@ objc_library(
textual_hdrs = ["src/foo.m"],
visibility = ["//visibility:public"],
)
""",
),
struct(
msg = "Objc target with a modulemap",
name = "ObjcLibraryWithModulemap",
exp = """\
load("@rules_swift_package_manager//swiftpkg:build_defs.bzl", "generate_modulemap")
generate_modulemap(
name = "ObjcLibraryWithModulemap_modulemap",
deps = ["@swiftpkg_mypackage//:ObjcLibraryDep_modulemap"],
hdrs = ["include/external.h"],
module_name = "ObjcLibraryWithModulemap",
noop = True,
visibility = ["//visibility:public"],
)
objc_library(
name = "ObjcLibraryWithModulemap",
copts = [
"-fblocks",
"-fobjc-arc",
"-fPIC",
"-fmodule-name=ObjcLibraryWithModulemap",
"-Iexternal/bzlmodmangled~swiftpkg_mypackage/src",
],
defines = ["SWIFT_PACKAGE=1"],
deps = [
"@swiftpkg_mypackage//:ObjcLibraryDep",
"@swiftpkg_mypackage//:ObjcLibraryDep_modulemap",
],
enable_modules = True,
hdrs = ["include/external.h"],
includes = ["include"],
module_name = "ObjcLibraryWithModulemap",
sdk_frameworks = select({
"@rules_swift_package_manager//config_settings/spm/platform:ios": [
"Foundation",
"UIKit",
],
"@rules_swift_package_manager//config_settings/spm/platform:macos": ["Foundation"],
"@rules_swift_package_manager//config_settings/spm/platform:tvos": [
"Foundation",
"UIKit",
],
"@rules_swift_package_manager//config_settings/spm/platform:watchos": [
"Foundation",
"UIKit",
],
"//conditions:default": [],
}),
srcs = [
"src/foo.h",
"src/foo.m",
],
tags = ["swift_module=ObjcLibraryWithModulemap"],
textual_hdrs = ["src/foo.m"],
visibility = ["//visibility:public"],
)
""",
),
struct(
Expand Down Expand Up @@ -870,6 +979,7 @@ generate_modulemap(
deps = [],
hdrs = ["include/external.h"],
module_name = "ObjcLibraryWithResources",
noop = False,
visibility = ["//visibility:public"],
)
Expand Down

0 comments on commit 4464d4d

Please sign in to comment.