From c5e08a229bfea257de38c5a17fe3650470d81027 Mon Sep 17 00:00:00 2001 From: Chuck Grindel Date: Sun, 31 Oct 2021 14:07:29 -0600 Subject: [PATCH] Initial files (#1) Added src_utils API and filter_srcs rule. Added documentation generation. Added Github Actions workflow --- .bazelrc | 8 ++ .github/workflows/bazel.yml | 36 ++++++++ .gitignore | 3 + BUILD.bazel | 4 + README.md | 41 +++++++++- WORKSPACE | 17 ++++ ci.bazelrc | 14 ++++ deps.bzl | 21 +++++ doc/BUILD.bazel | 109 +++++++++++++++++++++++++ doc/README.md | 4 + doc/api.md | 7 ++ doc/filter_srcs.md | 25 ++++++ doc/rules.md | 7 ++ doc/src_utils.md | 48 +++++++++++ lib/BUILD.bazel | 8 ++ lib/src_utils.bzl | 26 ++++++ rules/BUILD.bazel | 8 ++ rules/filter_srcs.bzl | 40 +++++++++ shared.bazelrc | 7 ++ tests/BUILD.bazel | 6 ++ tests/filter_srcs_tests.bzl | 158 ++++++++++++++++++++++++++++++++++++ tests/src_utils_tests.bzl | 35 ++++++++ 22 files changed, 630 insertions(+), 2 deletions(-) create mode 100644 .bazelrc create mode 100644 .github/workflows/bazel.yml create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 WORKSPACE create mode 100644 ci.bazelrc create mode 100644 deps.bzl create mode 100644 doc/BUILD.bazel create mode 100644 doc/README.md create mode 100755 doc/api.md create mode 100755 doc/filter_srcs.md create mode 100755 doc/rules.md create mode 100755 doc/src_utils.md create mode 100644 lib/BUILD.bazel create mode 100644 lib/src_utils.bzl create mode 100644 rules/BUILD.bazel create mode 100644 rules/filter_srcs.bzl create mode 100644 shared.bazelrc create mode 100644 tests/BUILD.bazel create mode 100644 tests/filter_srcs_tests.bzl create mode 100644 tests/src_utils_tests.bzl diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000..c3744bf8 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,8 @@ +# Import Shared settings +import %workspace%/shared.bazelrc + +# Import CI settings. +import %workspace%/ci.bazelrc + +# Try to import a local.rc file; typically, written by CI +try-import %workspace%/local.bazelrc diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 00000000..b02d9f04 --- /dev/null +++ b/.github/workflows/bazel.yml @@ -0,0 +1,36 @@ +name: Build + +on: + pull_request: + branches: [ main ] + +jobs: + macos_build: + + runs-on: macos-11.0 + + steps: + - uses: actions/checkout@v2 + + - name: Write local.bazelrc File + shell: bash + run: | + cat >local.bazelrc < +# Build API + +The APIs listed below are available in this repository. + + * [src_utils](/doc/src_utils.md) + diff --git a/doc/filter_srcs.md b/doc/filter_srcs.md new file mode 100755 index 00000000..b2017b49 --- /dev/null +++ b/doc/filter_srcs.md @@ -0,0 +1,25 @@ + +# `filter_srcs` Rule + + + + +## filter_srcs + +
+filter_srcs(name, expected_count, filename_ends_with, srcs)
+
+ +Filters the provided inputs using the specified criteria. + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| expected_count | The expected number of results. | Integer | optional | -1 | +| filename_ends_with | The suffix of the path will be compared to this value. | String | optional | "" | +| srcs | The inputs that will be evaluated by the filter. | List of labels | required | | + + diff --git a/doc/rules.md b/doc/rules.md new file mode 100755 index 00000000..1d0071e7 --- /dev/null +++ b/doc/rules.md @@ -0,0 +1,7 @@ + +# Rules + +The rules listed below are available in this repository. + + * [filter_srcs](/doc/filter_srcs.md) + diff --git a/doc/src_utils.md b/doc/src_utils.md new file mode 100755 index 00000000..fe2d70ac --- /dev/null +++ b/doc/src_utils.md @@ -0,0 +1,48 @@ + +# `src_utils` API + + + + +## src_utils.is_path + +
+src_utils.is_path(src)
+
+ +Determines whether the provided string is a path. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| src | A string value. | none | + +**RETURNS** + +A `bool` specifying whether the `string` value looks like a path. + + + + +## src_utils.is_label + +
+src_utils.is_label(src)
+
+ +Determines whether the provided string is a label. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| src | A string value. | none | + +**RETURNS** + +A `bool` specifying whether the `string` value looks like a label. + + diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel new file mode 100644 index 00000000..b622be5f --- /dev/null +++ b/lib/BUILD.bazel @@ -0,0 +1,8 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package(default_visibility = ["//visibility:public"]) + +bzl_library( + name = "src_utils", + srcs = ["src_utils.bzl"], +) diff --git a/lib/src_utils.bzl b/lib/src_utils.bzl new file mode 100644 index 00000000..76f3f4b8 --- /dev/null +++ b/lib/src_utils.bzl @@ -0,0 +1,26 @@ +def _is_label(src): + """Determines whether the provided string is a label. + + Args: + src: A `string` value. + + Returns: + A `bool` specifying whether the `string` value looks like a label. + """ + return src.find("//") > -1 or src.find(":") > -1 + +def _is_path(src): + """Determines whether the provided string is a path. + + Args: + src: A `string` value. + + Returns: + A `bool` specifying whether the `string` value looks like a path. + """ + return not _is_label(src) + +src_utils = struct( + is_path = _is_path, + is_label = _is_label, +) diff --git a/rules/BUILD.bazel b/rules/BUILD.bazel new file mode 100644 index 00000000..31322e38 --- /dev/null +++ b/rules/BUILD.bazel @@ -0,0 +1,8 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package(default_visibility = ["//visibility:public"]) + +bzl_library( + name = "filter_srcs", + srcs = ["filter_srcs.bzl"], +) diff --git a/rules/filter_srcs.bzl b/rules/filter_srcs.bzl new file mode 100644 index 00000000..f5df1ad2 --- /dev/null +++ b/rules/filter_srcs.bzl @@ -0,0 +1,40 @@ +def _do_filename_ends_with(ctx, files): + suffix = ctx.attr.filename_ends_with + return [f for f in files if f.path.endswith(suffix)] + +def _filter_srcs_impl(ctx): + files = ctx.files.srcs + if ctx.attr.filename_ends_with != "": + files = _do_filename_ends_with(ctx, files) + else: + fail("No filter criteria were provided.") + + expected_count = ctx.attr.expected_count + if expected_count > -1 and len(files) != expected_count: + fail( + "Expected {expected_count} items, but found {actual_count}.".format( + expected_count = expected_count, + actual_count = len(files), + ), + ) + + return [DefaultInfo(files = depset(files))] + +filter_srcs = rule( + implementation = _filter_srcs_impl, + attrs = { + "srcs": attr.label_list( + allow_files = True, + mandatory = True, + doc = "The inputs that will be evaluated by the filter.", + ), + "filename_ends_with": attr.string( + doc = "The suffix of the path will be compared to this value.", + ), + "expected_count": attr.int( + default = -1, + doc = "The expected number of results.", + ), + }, + doc = "Filters the provided inputs using the specified criteria.", +) diff --git a/shared.bazelrc b/shared.bazelrc new file mode 100644 index 00000000..77f1598c --- /dev/null +++ b/shared.bazelrc @@ -0,0 +1,7 @@ +# Verbose Failures +build --verbose_failures + +# Strict PATH. Helps prevent build cache invalidation due to PATH differences. +build --incompatible_strict_action_env=true + + diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 00000000..885ef58d --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,6 @@ +load(":filter_srcs_tests.bzl", "filter_srcs_test_suite") +load(":src_utils_tests.bzl", "src_utils_test_suite") + +filter_srcs_test_suite() + +src_utils_test_suite() diff --git a/tests/filter_srcs_tests.bzl b/tests/filter_srcs_tests.bzl new file mode 100644 index 00000000..a2d1ebcf --- /dev/null +++ b/tests/filter_srcs_tests.bzl @@ -0,0 +1,158 @@ +load("//rules:filter_srcs.bzl", "filter_srcs") +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("@bazel_skylib//rules:write_file.bzl", "write_file") + +# MARK: - Set up inputs to the analysis tests. + +def _setup_src_file_targets(): + write_file( + name = "foo_a", + out = "foo.a", + content = ["This is foo.a"], + ) + write_file( + name = "foo_b", + out = "foo.b", + content = ["This is foo.b"], + ) + write_file( + name = "bar_a", + out = "bar.a", + content = ["This is bar.a"], + ) + +# MARK: - Filename ends with test + +def _filename_ends_with_test_impl(ctx): + env = analysistest.begin(ctx) + + target_under_test = analysistest.target_under_test(env) + results = [f.basename for f in target_under_test[DefaultInfo].files.to_list()] + asserts.equals(env, ["foo.a", "bar.a"], results) + + return analysistest.end(env) + +filename_ends_with_test = analysistest.make(_filename_ends_with_test_impl) + +def _test_filename_ends_with(): + filter_srcs( + name = "filename_ends_with_subject", + srcs = [ + ":foo_a", + ":foo_b", + ":bar_a", + ], + filename_ends_with = ".a", + tags = ["manual"], + ) + filename_ends_with_test( + name = "filename_ends_with_test", + target_under_test = ":filename_ends_with_subject", + ) + +# MARK: - Fail if no criteria test + +def _fail_if_no_criteria_test_impl(ctx): + env = analysistest.begin(ctx) + + asserts.expect_failure(env, "No filter criteria were provided.") + + return analysistest.end(env) + +fail_if_no_criteria_test = analysistest.make( + _fail_if_no_criteria_test_impl, + expect_failure = True, +) + +def _test_fail_if_no_criteria(): + filter_srcs( + name = "fail_if_no_criteria_subject", + srcs = [ + ":foo_a", + ], + tags = ["manual"], + ) + fail_if_no_criteria_test( + name = "fail_if_no_criteria_test", + target_under_test = ":fail_if_no_criteria_subject", + ) + +# MARK: - Expected Count Success Test + +def _expected_count_success_test_impl(ctx): + env = analysistest.begin(ctx) + + target_under_test = analysistest.target_under_test(env) + results = [f.basename for f in target_under_test[DefaultInfo].files.to_list()] + asserts.equals(env, ["foo.a", "bar.a"], results) + + return analysistest.end(env) + +expected_count_success_test = analysistest.make(_expected_count_success_test_impl) + +def _test_expected_count_success(): + filter_srcs( + name = "expected_count_success_subject", + srcs = [ + ":foo_a", + ":foo_b", + ":bar_a", + ], + filename_ends_with = ".a", + expected_count = 2, + tags = ["manual"], + ) + expected_count_success_test( + name = "expected_count_success_test", + target_under_test = ":expected_count_success_subject", + ) + +# MARK: - Expected Count Failure Test + +def _expected_count_failure_test_impl(ctx): + env = analysistest.begin(ctx) + + asserts.expect_failure(env, "Expected 1 items, but found 2.") + + return analysistest.end(env) + +expected_count_failure_test = analysistest.make( + _expected_count_failure_test_impl, + expect_failure = True, +) + +def _test_expected_count_failure(): + filter_srcs( + name = "expected_count_failure_subject", + srcs = [ + ":foo_a", + ":foo_b", + ":bar_a", + ], + filename_ends_with = ".a", + expected_count = 1, + tags = ["manual"], + ) + expected_count_failure_test( + name = "expected_count_failure_test", + target_under_test = ":expected_count_failure_subject", + ) + +# MARK: - Test Suite + +def filter_srcs_test_suite(): + _setup_src_file_targets() + _test_filename_ends_with() + _test_fail_if_no_criteria() + _test_expected_count_success() + _test_expected_count_failure() + + native.test_suite( + name = "filter_srcs_tests", + tests = [ + ":filename_ends_with_test", + ":fail_if_no_criteria_test", + ":expected_count_success_test", + ":expected_count_failure_test", + ], + ) diff --git a/tests/src_utils_tests.bzl b/tests/src_utils_tests.bzl new file mode 100644 index 00000000..24cfa561 --- /dev/null +++ b/tests/src_utils_tests.bzl @@ -0,0 +1,35 @@ +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//lib:src_utils.bzl", "src_utils") + +def _is_label_test(ctx): + env = unittest.begin(ctx) + + asserts.true(env, src_utils.is_label("//Sources/Foo")) + asserts.true(env, src_utils.is_label(":Foo")) + asserts.true(env, src_utils.is_label("//Sources/Foo:bar")) + asserts.false(env, src_utils.is_label("Bar.swift")) + asserts.false(env, src_utils.is_label("path/to/Bar.swift")) + + return unittest.end(env) + +is_label_test = unittest.make(_is_label_test) + +def _is_path_test(ctx): + env = unittest.begin(ctx) + + asserts.true(env, src_utils.is_path("Bar.swift")) + asserts.true(env, src_utils.is_path("path/to/Bar.swift")) + asserts.false(env, src_utils.is_path("//Sources/Foo")) + asserts.false(env, src_utils.is_path(":Foo")) + asserts.false(env, src_utils.is_path("//Sources/Foo:bar")) + + return unittest.end(env) + +is_path_test = unittest.make(_is_path_test) + +def src_utils_test_suite(): + return unittest.suite( + "src_utils_tests", + is_label_test, + is_path_test, + )