Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and assert swift imported header propagation #333

Merged
merged 8 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ jobs:
run: sudo xcode-select -s /Applications/Xcode_12.2.app
- name: Build and Test
run: |
# Host config
bazelisk test --local_test_jobs=1 -- //... -//tests/ios/...

# `deleted_packages` is needed below in order to override the value of the .bazelrc file
bazelisk test --local_test_jobs=1 --apple_platform_type=ios --deleted_packages='' -- //tests/ios/...
- uses: actions/upload-artifact@v2
Expand All @@ -28,15 +30,24 @@ jobs:
# Build the entire tree with this feature enabled. Longer term, we'll likely
# consider merging this feature into the default behavior and can re-align
# the CI job
name: Build ( Virtual Frameworks )
name: Build and Test ( Virtual Frameworks )
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Select Xcode 12.2
run: sudo xcode-select -s /Applications/Xcode_12.2.app
- name: Build and Test
run: |
bazelisk build //... --features apple.virtualize_frameworks
# Host config
bazelisk test --features apple.virtualize_frameworks --local_test_jobs=1 -- //... -//tests/ios/...

# `deleted_packages` is needed below in order to override the value of the .bazelrc file
bazelisk test --features apple.virtualize_frameworks \
--local_test_jobs=1 \
--apple_platform_type=ios \
--deleted_packages='' \
-- //tests/ios/... \
-//tests/ios/frameworks/sources-with-prebuilt-binaries/... # Needs more work for pre-built binaries
Copy link
Contributor

Choose a reason for hiding this comment

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

What work? Maybe more details here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Want to file an issue to see what is wrong with it? I don't have an idea about what to do ATM

- uses: actions/upload-artifact@v2
if: failure()
with:
Expand Down
Empty file.
82 changes: 82 additions & 0 deletions rules/analysis_tests/identical_outputs_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")

_TestFiles = provider(
fields = {
"files": "A glob of files collected for later assertions",
},
)

def _identical_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
all_files = []
asserts.true(env, len(ctx.attr.deps) > 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why > 1 and not > 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test doesn't assert the problem to have 1 dev because it asserts identical outputs amongst multiple deps https://github.com/bazel-ios/rules_ios/pull/333/files/5138482e5b1fcd470f76b793ef40e6ebc930cc73#diff-fa2c728306ed72d291b632ffa2b0458ea872ba83445c911667ac90ff0c4263a0R56

This is the only way to ensure that bazelbuild/bazel#12171 isn't happening internal to a rule!


for dep in ctx.attr.deps:
if not _TestFiles in dep:
continue

# The input root is the part of the file usually rooted in bazel-out.
# For starlark transitions output dirs are fingerprinted by the hash of the
# relevant configuration keys.
# src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome, thx for linking this 🙏

for input in dep[_TestFiles].files.to_list():
all_files.append(input.root.path)

# Expect that we have received multiple swiftmodules
asserts.true(env, len(all_files) > 1)

# Assert all swiftmodules have identical outputs ( and most importantly an
# identical output directory )
asserts.equals(env, 1, len(depset(all_files).to_list()))
return analysistest.end(env)

def _collect_transitive_outputs_impl(target, ctx):
# Collect trans swift_library outputs
out = []
if ctx.rule.kind == "swift_library":
out.extend(target[DefaultInfo].files.to_list())

if _TestFiles in target:
out.extend(target[_TestFiles].files.to_list())
if hasattr(ctx.rule.attr, "srcs"):
for d in ctx.rule.attr.srcs:
if _TestFiles in d:
out.extend(d[_TestFiles].files.to_list())
if hasattr(ctx.rule.attr, "deps"):
for d in ctx.rule.attr.deps:
if _TestFiles in d:
out.extend(d[_TestFiles].files.to_list())
return _TestFiles(files = depset(out))

_collect_transitive_outputs = aspect(
implementation = _collect_transitive_outputs_impl,
attr_aspects = ["deps", "srcs"],
)

# This test asserts that transitive dependencies have identical outputs for
# different transition paths. In particular, a rules_apple ios_application and
# an a apple_framework that share a swift_library,
# //tests/ios/app:SwiftLib_swift. This test ensures that the actions in both
# builds have functionally equal transitions applied by normalizing their output
# directories into a set.
#
# For instance these tests will fail if there is any delta and requires both:
# - adding apple_common.multi_arch_split to apple_framework.deps - #188
# - the transition yields the same result when used w/rules_apple - #196
Comment on lines +64 to +65
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'd put the links here and not just the number #123


# Note:
# The gist of Bazel's configuration resolver is that it will apply
# relevant transitions to keys that are used by a given action. e.g. ios_multi_cpus.
# src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
#
# In order to get the same configuration for a rule, a given transition has
# to produce the same values for dependent keys for all possible combinations
identical_outputs_test = analysistest.make(
_identical_outputs_test_impl,
expect_failure = False,
attrs = {
"deps": attr.label_list(
aspects = [_collect_transitive_outputs],
),
},
)
31 changes: 31 additions & 0 deletions rules/analysis_tests/transitive_header_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")

def _transitive_header_test_impl(ctx):
env = analysistest.begin(ctx)
asserts.true(env, len(ctx.attr.deps) > 0)
target_under_test = analysistest.target_under_test(env)
target_headers = target_under_test[CcInfo].compilation_context.headers.to_list()
for dep in ctx.attr.deps:
asserts.true(env, CcInfo in dep)
dep_headers = dep[CcInfo].compilation_context.headers.to_list()
for dep_header in dep_headers:
# Assert that all of the dep headers are in the target
if not dep_header.extension == "h":
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we care about .hh or .hpp here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could by default have that and better expose this to the user to match other extensions if they'd like

continue

has_header = dep_header in target_headers
if not has_header:
print("Missing header", dep_header, target_headers)
asserts.true(env, has_header)
return analysistest.end(env)

# The headers test allows a user to assert that tests are propagated to actions
# from arbitrary deps. Given a target_under_test, supply transitive deps or
# virtually any file group.
transitive_header_test = analysistest.make(
_transitive_header_test_impl,
expect_failure = False,
attrs = {
"deps": attr.label_list(allow_empty = False),
},
)
1 change: 1 addition & 0 deletions rules/apple_patched.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def _get_framework_info_providers(ctx, old_cc_info, old_objc_provider):
private_hdrs = [],
has_swift = False,
framework_name = imported_framework_name,
extra_search_paths = imported_framework_name,
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see this being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

extra_search_paths is an argument to the rule framework_vfs_overlay so used internally of that rule

)
framework_info = FrameworkInfo(
vfsoverlay_infos = [vfs.vfs_info],
Expand Down
25 changes: 10 additions & 15 deletions rules/framework.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,16 @@ def _get_virtual_framework_info(ctx, framework_files, compilation_context_fields
# We need to map all the deps here - for both swift headers and others
fw_dep_vfsoverlays = []
for dep in transitive_deps + deps:
if not FrameworkInfo in dep:
continue
framework_info = dep[FrameworkInfo]
fw_dep_vfsoverlays.extend(framework_info.vfsoverlay_infos)
framework_headers = depset(framework_info.headers + framework_info.modulemap + framework_info.private_headers)
propagated_interface_headers.append(framework_headers)

# Collect generated headers. consider exposing all required generated
# headers in respective providers: -Swift, modulemap, -umbrella.h
if not CcInfo in dep:
continue
for h in dep[CcInfo].compilation_context.headers.to_list():
if h.is_source:
continue
propagated_interface_headers.append(depset([h]))
# Collect transitive headers. For now, this needs to include all of the
# transitive headers
if CcInfo in dep:
compilation_context = dep[CcInfo].compilation_context
propagated_interface_headers.append(compilation_context.headers)
if FrameworkInfo in dep:
framework_info = dep[FrameworkInfo]
fw_dep_vfsoverlays.extend(framework_info.vfsoverlay_infos)
framework_headers = depset(framework_info.headers + framework_info.modulemap + framework_info.private_headers)
propagated_interface_headers.append(framework_headers)

outputs = framework_files.outputs
vfs = make_vfsoverlay(
Expand Down
86 changes: 2 additions & 84 deletions tests/ios/app/analysis-tests.bzl
Original file line number Diff line number Diff line change
@@ -1,89 +1,7 @@
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")

_TestFiles = provider(
fields = {
"files": "A glob of files collected for later assertions",
},
)

def _identical_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
all_files = []
asserts.true(env, len(ctx.attr.deps) > 1)

for dep in ctx.attr.deps:
if not _TestFiles in dep:
continue

# The input root is the part of the file usually rooted in bazel-out.
# For starlark transitions output dirs are fingerprinted by the hash of the
# relevant configuration keys.
# src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java
for input in dep[_TestFiles].files.to_list():
all_files.append(input.root.path)

# Expect that we have recieved multiple swiftmodules
asserts.true(env, len(all_files) > 1)

# Assert all swiftmodules have identical outputs ( and most importantly an
# identical output directory )
asserts.equals(env, 1, len(depset(all_files).to_list()))
return analysistest.end(env)

def _collect_transitive_outputs_impl(target, ctx):
# Collect trans swift_library outputs
out = []
if ctx.rule.kind == "swift_library":
out.extend(target[DefaultInfo].files.to_list())

if _TestFiles in target:
out.extend(target[_TestFiles].files.to_list())
if hasattr(ctx.rule.attr, "srcs"):
for d in ctx.rule.attr.srcs:
if _TestFiles in d:
out.extend(d[_TestFiles].files.to_list())
if hasattr(ctx.rule.attr, "deps"):
for d in ctx.rule.attr.deps:
if _TestFiles in d:
out.extend(d[_TestFiles].files.to_list())
return _TestFiles(files = depset(out))

_collect_transitive_outputs = aspect(
implementation = _collect_transitive_outputs_impl,
attr_aspects = ["deps", "srcs"],
)

_identical_outputs_test = analysistest.make(
_identical_outputs_test_impl,
expect_failure = False,
attrs = {
"deps": attr.label_list(
aspects = [_collect_transitive_outputs],
),
},
)
load("//rules/analysis_tests:identical_outputs_test.bzl", "identical_outputs_test")

def make_tests():
# This test asserts that transitive dependencies have identical outputs for
# different transition paths. In particular, a rules_apple ios_application and an a
# apple_framework that share a swift_library, :SwiftLib_swift. This test ensures
# that the actions in both builds have functionally equal transitions
# applied by normalizing their output directories into a set.
#
# For instance these tests will fail if there is any delta and requires both:
# - adding apple_common.multi_arch_split to apple_framework.deps - #188
# - the transition yields the same result when used w/rules_apple - #196

# Note:
# The gist of Bazel's configuration resolver is that it will apply
# relevant transitions to keys that are used by a given action. e.g. ios_multi_cpus.
# src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationResolver.java
#
# In order to get the same configuration for a rule, a given transition has
# to produce the same values for dependent keys for all possible combinations
# of edges

_identical_outputs_test(
identical_outputs_test(
name = "test_DependencyEquivilance",
target_under_test = ":AppWithSelectableCopts",

Expand Down
22 changes: 12 additions & 10 deletions tests/ios/unit-test/test-imports-app/BUILD.GoogleMobileAds
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@

load(
"@build_bazel_rules_ios//rules:apple_patched.bzl",
"apple_static_framework_import"
"apple_static_framework_import",
)

apple_static_framework_import(
name = "GoogleMobileAds",
framework_imports = glob(["Frameworks/GoogleMobileAdsFramework-Current/GoogleMobileAds.xcframework/ios-arm64_x86_64-simulator/GoogleMobileAds.framework/**"], allow_empty = False),
framework_imports = glob(
["Frameworks/GoogleMobileAdsFramework-Current/GoogleMobileAds.xcframework/ios-arm64_x86_64-simulator/GoogleMobileAds.framework/**"],
allow_empty = False,
),
sdk_dylibs = [
"libsqlite3",
"libz",
],
sdk_frameworks = [
"AudioToolbox",
"AVFoundation",
Expand All @@ -21,17 +27,13 @@ apple_static_framework_import(
"QuartzCore",
"Security",
"StoreKit",
"SystemConfiguration"
"SystemConfiguration",
],
visibility = ["//visibility:public"],
weak_sdk_frameworks = [
"AdSupport",
"JavaScriptCore",
"SafariServices",
"WebKit"
"WebKit",
],
sdk_dylibs = [
"libsqlite3",
"libz"
],
visibility = ["//visibility:public"],
)
4 changes: 4 additions & 0 deletions tests/ios/unit-test/test-imports-app/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("//rules:app.bzl", "ios_application")
load("//rules:framework.bzl", "apple_framework", "apple_framework_packaging")
load("//rules:test.bzl", "ios_unit_test")
load(":analysis-tests.bzl", "make_tests")

apple_framework(
name = "SomeFramework",
Expand All @@ -9,6 +10,7 @@ apple_framework(
"ios": "12.0",
},
visibility = ["//visibility:public"],
deps = ["//tests/ios/unit-test/test-imports-app/frameworks/Basic"],
)

ios_application(
Expand Down Expand Up @@ -93,3 +95,5 @@ ios_unit_test(
"//conditions:default": [],
}),
)

make_tests()
22 changes: 22 additions & 0 deletions tests/ios/unit-test/test-imports-app/analysis-tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load("//rules/analysis_tests:transitive_header_test.bzl", "transitive_header_test")

def make_tests():
transitive_header_test(
name = "test_SomeFramework_SwiftCompilationHeaders",
target_under_test = ":SomeFramework_swift",
deps = ["//tests/ios/unit-test/test-imports-app/frameworks/Basic"],
)

transitive_header_test(
name = "test_App_SwiftCompilationHeaders",
target_under_test = ":TestImports-App_swift",
deps = ["//tests/ios/unit-test/test-imports-app/frameworks/Basic", "@TensorFlowLiteC//:TensorFlowLiteC"],
)

native.test_suite(
name = "AnalysisTests",
tests = [
":test_App_SwiftCompilationHeaders",
":test_SomeFramework_SwiftCompilationHeaders",
],
)
Empty file.
13 changes: 13 additions & 0 deletions tests/ios/unit-test/test-imports-app/frameworks/Basic/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//rules:framework.bzl", "apple_framework")

apple_framework(
name = "Basic",
# Note: it is totally possible that a user would write a glob like this..
# srcs = glob([
# "sources/Basic/**/*.h",
# ]),
Comment on lines +5 to +8
Copy link
Contributor

Choose a reason for hiding this comment

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

So the ideal is to keep this as a note for someone trying to understand what is under test right? Why is it relevant to know that such a glob could be written?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it is possible a user to craft an redundant build file which duplicates srcs and framework_imports - probably better to have the comment but I can remove it doesn't make sense. The rule will handle it anyways.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with keeping it. Was just curious to know the details of the edge case you're mentioning here 👍

objc_copts = ["-fmodules-disable-diagnostic-validation"],
platforms = {"ios": "12.0"},
vendored_static_frameworks = ["ios/Basic.framework"],
visibility = ["//visibility:public"],
)
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import Foundation;

typedef NS_ENUM(NSInteger, BasicVal) {
BasicVal_Unknown = 0,
BasicVal_FinishActivating = 1,
BasicVal_DownloadTheApp = 2,
};

static const NSString *BasicString = @"BasicString";
Loading