aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-08-06 03:02:07 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-08-06 03:02:07 +0000
commit74e2f30060dae33d5ca9f81e1d1614d06b4c1d06 (patch)
treec0a2a33d1d43e687f4fe1134ac14c09a9af5b8c0
parent971a80aefc3abdad1f8dcd6ff84eb6fb81147a45 (diff)
parente3e4f5325f3a9b46381531439b27dcfb294477fe (diff)
downloadbazel-skylib-android13-s1-release.tar.gz
Snap for 7615884 from e3e4f5325f3a9b46381531439b27dcfb294477fe to tm-releaseandroid-vts-13.0_r8android-vts-13.0_r7android-vts-13.0_r6android-vts-13.0_r5android-vts-13.0_r4android-vts-13.0_r3android-vts-13.0_r2android-vts-13.0_r1android-security-13.0.0_r9android-security-13.0.0_r8android-security-13.0.0_r7android-security-13.0.0_r6android-security-13.0.0_r5android-security-13.0.0_r4android-security-13.0.0_r3android-security-13.0.0_r2android-security-13.0.0_r17android-security-13.0.0_r16android-security-13.0.0_r15android-security-13.0.0_r14android-security-13.0.0_r13android-security-13.0.0_r12android-security-13.0.0_r11android-security-13.0.0_r10android-security-13.0.0_r1android-platform-13.0.0_r9android-platform-13.0.0_r8android-platform-13.0.0_r7android-platform-13.0.0_r6android-platform-13.0.0_r5android-platform-13.0.0_r4android-platform-13.0.0_r3android-platform-13.0.0_r2android-platform-13.0.0_r19android-platform-13.0.0_r18android-platform-13.0.0_r17android-platform-13.0.0_r16android-platform-13.0.0_r15android-platform-13.0.0_r14android-platform-13.0.0_r13android-platform-13.0.0_r12android-platform-13.0.0_r11android-platform-13.0.0_r10android-platform-13.0.0_r1android-cts-13.0_r8android-cts-13.0_r7android-cts-13.0_r6android-cts-13.0_r5android-cts-13.0_r4android-cts-13.0_r3android-cts-13.0_r2android-cts-13.0_r1android-13.0.0_r8android-13.0.0_r7android-13.0.0_r6android-13.0.0_r5android-13.0.0_r4android-13.0.0_r31android-13.0.0_r3android-13.0.0_r2android-13.0.0_r12android-13.0.0_r1android13-tests-releaseandroid13-security-releaseandroid13-s3-releaseandroid13-s2-releaseandroid13-s1-releaseandroid13-releaseandroid13-platform-releaseandroid13-gsi
Change-Id: Ib00b49264b55c948afe369739aac5cb111d9ef96
-rw-r--r--.bazelci/presubmit.yml97
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS9
-rw-r--r--BUILD86
-rw-r--r--CHANGELOG.md80
-rw-r--r--CODEOWNERS4
-rw-r--r--CONTRIBUTING.md27
-rw-r--r--CONTRIBUTORS20
-rw-r--r--LICENSE202
-rw-r--r--OWNERS6
-rw-r--r--README.md108
-rw-r--r--WORKSPACE78
-rw-r--r--bzl_library.bzl107
-rw-r--r--distribution/BUILD49
-rw-r--r--docs/BUILD137
-rwxr-xr-xdocs/analysis_test_doc.md61
-rwxr-xr-xdocs/build_test_doc.md67
-rwxr-xr-xdocs/collections_doc.md108
-rwxr-xr-xdocs/common_settings_doc.md296
-rwxr-xr-xdocs/copy_file_doc.md92
-rwxr-xr-xdocs/dicts_doc.md47
-rwxr-xr-xdocs/diff_test_doc.md59
-rwxr-xr-xdocs/native_binary_doc.md131
-rwxr-xr-xdocs/new_sets_doc.md519
-rwxr-xr-xdocs/partial_doc.md171
-rwxr-xr-xdocs/paths_doc.md311
-rwxr-xr-xdocs/regenerate_docs.sh27
-rwxr-xr-xdocs/run_binary_doc.md75
-rwxr-xr-xdocs/selects_doc.md168
-rwxr-xr-xdocs/shell_doc.md71
-rwxr-xr-xdocs/structs_doc.md29
-rwxr-xr-xdocs/types_doc.md261
-rwxr-xr-xdocs/unittest_doc.md869
-rwxr-xr-xdocs/versions_doc.md165
-rwxr-xr-xdocs/write_file_doc.md79
-rw-r--r--gazelle/bzl/BUILD53
-rw-r--r--gazelle/bzl/README.md13
-rw-r--r--gazelle/bzl/gazelle.go315
-rw-r--r--gazelle/bzl/gazelle_test.go143
-rw-r--r--gazelle/bzl/testdata/README.md56
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/BUILD.in0
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/BUILD.out8
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/foo.bzl7
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.in1
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.out8
-rw-r--r--gazelle/bzl/testdata/defaultvisibility/nested/dir/bar.bzl6
-rw-r--r--gazelle/bzl/testdata/empty/BUILD.in13
-rw-r--r--gazelle/bzl/testdata/empty/BUILD.out7
-rw-r--r--gazelle/bzl/testdata/empty/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/empty/foo.bzl0
-rw-r--r--gazelle/bzl/testdata/external/BUILD.in8
-rw-r--r--gazelle/bzl/testdata/external/BUILD.out15
-rw-r--r--gazelle/bzl/testdata/external/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/external/foo.bzl7
-rw-r--r--gazelle/bzl/testdata/import/BUILD.in0
-rw-r--r--gazelle/bzl/testdata/import/BUILD.out14
-rw-r--r--gazelle/bzl/testdata/import/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/import/bar.bzl6
-rw-r--r--gazelle/bzl/testdata/import/foo.bzl7
-rw-r--r--gazelle/bzl/testdata/multidir/BUILD.in0
-rw-r--r--gazelle/bzl/testdata/multidir/BUILD.out8
-rw-r--r--gazelle/bzl/testdata/multidir/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/multidir/foo.bzl7
-rw-r--r--gazelle/bzl/testdata/multidir/nested/dir/BUILD.out7
-rw-r--r--gazelle/bzl/testdata/multidir/nested/dir/bar.bzl6
-rw-r--r--gazelle/bzl/testdata/nobuildfiles/BUILD.out7
-rw-r--r--gazelle/bzl/testdata/nobuildfiles/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/nobuildfiles/foo.bzl0
-rw-r--r--gazelle/bzl/testdata/private/BUILD.in0
-rw-r--r--gazelle/bzl/testdata/private/BUILD.out8
-rw-r--r--gazelle/bzl/testdata/private/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/private/foo.bzl7
-rw-r--r--gazelle/bzl/testdata/private/private/BUILD.out6
-rw-r--r--gazelle/bzl/testdata/private/private/bar.bzl6
-rw-r--r--gazelle/bzl/testdata/simple/BUILD.in6
-rw-r--r--gazelle/bzl/testdata/simple/BUILD.out14
-rw-r--r--gazelle/bzl/testdata/simple/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/simple/foo.bzl0
-rw-r--r--gazelle/bzl/testdata/tests/BUILD.in6
-rw-r--r--gazelle/bzl/testdata/tests/BUILD.out14
-rw-r--r--gazelle/bzl/testdata/tests/WORKSPACE0
-rw-r--r--gazelle/bzl/testdata/tests/foo.bzl0
-rw-r--r--gazelle/bzl/testdata/tests/foo_tests.bzl0
-rw-r--r--internal_deps.bzl27
-rw-r--r--internal_setup.bzl18
-rw-r--r--lib.bzl20
-rw-r--r--lib/BUILD103
-rw-r--r--lib/collections.bzl72
-rw-r--r--lib/dicts.bzl43
-rw-r--r--lib/new_sets.bzl243
-rw-r--r--lib/old_sets.bzl17
-rw-r--r--lib/partial.bzl130
-rw-r--r--lib/paths.bzl242
-rw-r--r--lib/selects.bzl249
-rw-r--r--lib/sets.bzl19
-rw-r--r--lib/shell.bzl54
-rw-r--r--lib/structs.bzl35
-rw-r--r--lib/types.bzl152
-rw-r--r--lib/unittest.bzl516
-rw-r--r--lib/versions.bzl128
-rw-r--r--rules/BUILD93
-rw-r--r--rules/analysis_test.bzl56
-rw-r--r--rules/build_test.bzl90
-rw-r--r--rules/common_settings.bzl100
-rw-r--r--rules/copy_file.bzl29
-rw-r--r--rules/diff_test.bzl149
-rwxr-xr-xrules/empty_test.sh3
-rw-r--r--rules/native_binary.bzl116
-rw-r--r--rules/private/BUILD29
-rw-r--r--rules/private/copy_file_private.bzl142
-rw-r--r--rules/private/maprule_util.bzl139
-rw-r--r--rules/private/write_file_private.bzl113
-rw-r--r--rules/run_binary.bzl98
-rw-r--r--rules/select_file.bzl54
-rw-r--r--rules/write_file.bzl30
-rw-r--r--skylark_library.bzl23
-rw-r--r--tests/BUILD99
-rwxr-xr-xtests/analysis_test_test.sh154
-rw-r--r--tests/build_test_tests.bzl43
-rw-r--r--tests/collections_tests.bzl113
-rwxr-xr-xtests/common_settings_test.sh169
-rw-r--r--tests/copy_file/BUILD173
-rw-r--r--tests/copy_file/a.txt2
-rwxr-xr-xtests/copy_file/a_with_exec_bit.txt2
-rwxr-xr-xtests/copy_file/copy_file_tests.sh85
-rw-r--r--tests/dicts_tests.bzl91
-rw-r--r--tests/diff_test/BUILD46
-rw-r--r--tests/diff_test/a.txt1
-rw-r--r--tests/diff_test/aa.txt1
-rw-r--r--tests/diff_test/b.txt1
-rwxr-xr-xtests/diff_test/diff_test_tests.sh216
-rw-r--r--tests/native_binary/BUILD103
-rw-r--r--tests/native_binary/assertarg.cc53
-rw-r--r--tests/native_binary/assertdata.cc56
-rw-r--r--tests/native_binary/testdata.txt1
-rw-r--r--tests/new_sets_tests.bzl269
-rw-r--r--tests/partial_tests.bzl84
-rw-r--r--tests/paths_tests.bzl291
-rw-r--r--tests/run_binary/BUILD167
-rw-r--r--tests/run_binary/printargs.cc36
-rw-r--r--tests/select_file/BUILD36
-rw-r--r--tests/select_file/select_me.txt1
-rw-r--r--tests/select_file/subdir/inner.txt1
-rw-r--r--tests/selects_tests.bzl643
-rw-r--r--tests/shell_tests.bzl104
-rw-r--r--tests/structs_tests.bzl54
-rw-r--r--tests/types_tests.bzl248
-rwxr-xr-xtests/unittest.bash801
-rwxr-xr-xtests/unittest_test.sh136
-rw-r--r--tests/unittest_tests.bzl282
-rw-r--r--tests/versions_tests.bzl69
-rw-r--r--tests/write_file/BUILD166
-rwxr-xr-xtests/write_file/write_file_tests.sh66
-rw-r--r--toolchains/unittest/BUILD67
-rw-r--r--version.bzl16
-rw-r--r--workspace.bzl21
157 files changed, 13705 insertions, 0 deletions
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
new file mode 100644
index 0000000..d7e4845
--- /dev/null
+++ b/.bazelci/presubmit.yml
@@ -0,0 +1,97 @@
+---
+tasks:
+ ubuntu1804_latest:
+ name: "Latest Bazel"
+ platform: ubuntu1804
+ bazel: latest
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ ubuntu1604_latest:
+ name: "Latest Bazel"
+ platform: ubuntu1604
+ bazel: latest
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ macos_latest:
+ name: "Latest Bazel"
+ platform: macos
+ bazel: latest
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ windows_latest:
+ name: "Latest Bazel"
+ platform: windows
+ bazel: latest
+ build_targets:
+ - "//..."
+ test_targets:
+ - "--"
+ - "//..."
+ test_flags:
+ # TODO(laszlocsomor): remove "--test_env=LOCALAPPDATA" after
+ # https://github.com/bazelbuild/bazel/issues/7761 is fixed
+ - "--test_env=LOCALAPPDATA"
+
+ ubuntu1804_last_green:
+ name: "Last Green Bazel"
+ platform: ubuntu1804
+ bazel: last_green
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ ubuntu1604_last_green:
+ name: "Last Green Bazel"
+ platform: ubuntu1604
+ bazel: last_green
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ macos_last_green:
+ name: "Last Green Bazel"
+ platform: macos
+ bazel: last_green
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
+ test_flags:
+ - "--test_env=PATH"
+
+ windows_last_green:
+ name: "Last Green Bazel"
+ platform: windows
+ bazel: last_green
+ build_targets:
+ - "//..."
+ test_targets:
+ - "--"
+ - "//..."
+ test_flags:
+ # TODO(laszlocsomor): remove "--test_env=LOCALAPPDATA" after
+ # https://github.com/bazelbuild/bazel/issues/7761 is fixed
+ - "--test_env=LOCALAPPDATA"
+
+buildifier: latest
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a6ef824
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/bazel-*
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..11f2640
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,9 @@
+# This is the official list of Bazel authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+
+Google Inc.
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..bec32f5
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,86 @@
+load("//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+# gazelle:exclude internal_deps.bzl
+# gazelle:exclude internal_setup.bzl
+# buildifier: disable=skylark-comment
+# gazelle:exclude skylark_library.bzl
+
+exports_files(["LICENSE"])
+
+filegroup(
+ name = "test_deps",
+ testonly = True,
+ srcs = [
+ "BUILD",
+ "//lib:test_deps",
+ "//rules:test_deps",
+ "//toolchains/unittest:test_deps",
+ ] + glob(["*.bzl"]),
+)
+
+bzl_library(
+ name = "lib",
+ srcs = ["lib.bzl"],
+ deprecation = (
+ "lib.bzl will go away in the future, please directly depend on the" +
+ " module(s) needed as it is more efficient."
+ ),
+ deps = [
+ "//lib:collections",
+ "//lib:dicts",
+ "//lib:new_sets",
+ "//lib:partial",
+ "//lib:paths",
+ "//lib:selects",
+ "//lib:sets",
+ "//lib:shell",
+ "//lib:structs",
+ "//lib:types",
+ "//lib:unittest",
+ "//lib:versions",
+ ],
+)
+
+bzl_library(
+ name = "bzl_library",
+ srcs = ["bzl_library.bzl"],
+)
+
+bzl_library(
+ name = "version",
+ srcs = ["version.bzl"],
+)
+
+bzl_library(
+ name = "workspace",
+ srcs = ["workspace.bzl"],
+)
+
+# The files needed for distribution.
+# TODO(aiuto): We should strip this from the release, but there is no
+# capability now to generate BUILD.foo from BUILD and have it appear in the
+# tarball as BUILD.
+filegroup(
+ name = "distribution",
+ srcs = [
+ "LICENSE",
+ "BUILD",
+ "CODEOWNERS",
+ "CONTRIBUTORS",
+ "//lib:distribution",
+ "//rules:distribution",
+ "//rules/private:distribution",
+ "//toolchains/unittest:distribution",
+ ] + glob(["*.bzl"]),
+)
+
+filegroup(
+ name = "bins",
+ srcs = [
+ "//rules:bins",
+ ],
+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..dce01b4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,80 @@
+Release 1.0.3
+
+**Significant Changes**
+
+- Move Gazelle extension to //gazelle/bzl and change package name
+- Stop depending on rules_pkg through the federation. (#259)
+- copy_file: Add parameter to allow symlinks (#252)
+- Create Gazelle language for Starlark (#251)
+- Create a helper rule (`select_file`) for selecting a file from outputs of another rule (#233)
+
+
+**Incompatible Changes**
+- Remove links to maprules (#213)
+- Remove old_sets.bzl (#231)
+ It has been deprecated for a while, the code is not really compatible with Bazel depset-related changes.
+
+**Contributors**
+Andrew Z Allen, Bocete, Bor Kae Hwang, irengrig, Jay Conrod, Jonathan B Coe, Marc Plano-Lesay, Robbert van Ginkel, Thomas Van Lenten, Yannic
+
+
+Release 1.0.0
+
+**Incompatible Changes**
+
+- @bazel_skylib//:lib.bzl is removed. You now must specify specific modules
+ via @bazel_skylib//lib:<file>.bzl.
+- maprule.bzl is removed.
+
+**New Features**
+
+- Added types.is_set() to test whether an arbitrary object is a set as defined by sets.bzl.
+
+
+Release 0.9.0
+
+**New Features**
+
+- common_settings.bzl: Standard data types for user defined build
+ configuration. Common scalar build settings for rules to use so they don't
+ recreate them locally. This fulfills part of the SBC design doc:
+ https://docs.google.com/document/d/1vc8v-kXjvgZOdQdnxPTaV0rrLxtP2XwnD2tAZlYJOqw/edit#bookmark=id.iiumwic0jphr
+- selects.bzl: Add config_setting_group for config_setting AND/OR-chaining
+ Implements
+ https://github.com/bazelbuild/proposals/blob/master/designs/2018-11-09-config-setting-chaining.md.
+- Make sets.bzl point to new_sets.bzl instead of old_sets.bzl. new_sets.bzl
+ and old_sets.bzl should be removed in the following skylib release.
+
+- run_binary: runs an executable as an action
+
+ - This rule is an alternative for genrule(): it can run a binary with the
+ desired arguments, environment, inputs, and outputs, as a single build
+ action, without shelling out to Bash.
+ - Fixes https://github.com/bazelbuild/bazel-skylib/issues/149
+
+- New `native_binary()` and `native_test()` rules let you wrap a pre-built
+ binary in a binary and test rule respectively.
+
+ - native_binary() wraps a pre-built binary or script in a *_binary rule
+ interface. Rules like genrule can tool-depend on it, and it can be
+ executed with "bazel run". This rule can also augment the binary with
+ runfiles.
+ - native_test() is similar, but creates a testable rule instead of a
+ binary rule.
+ - Fixes https://github.com/bazelbuild/bazel-skylib/issues/148
+
+- diff_test: test rule compares two files and passes if the files match.
+
+ On Linux/macOS/non-Windows, the test compares files using 'diff'.
+
+ On Windows, the test compares files using 'fc.exe'. This utility is
+ available on all Windows versions I tried (Windows 2008 Server, Windows 2016
+ Datacenter Core).
+
+ See https://github.com/bazelbuild/bazel/issues/5508,
+ https://github.com/bazelbuild/bazel/issues/4319
+
+- maprule: move functionality to maprule_util.bzl. maprule_util.bzl will
+ benefit planned new rules (namely a genrule alternative).
+
+**This release is tested with Bazel 0.28**
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..07b74fa
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,4 @@
+* @c-parsons @laurentlb @jin @aiuto
+distribution/ @aiuto @fwe
+rules/ @juliexxia
+gazelle/ @achew22 @jayconrod
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9d3d1aa
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,27 @@
+Want to contribute? Great! First, read this page (including the small print at the end).
+
+### Before you contribute
+**Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1)
+(CLA)**, which you can do online.
+
+The CLA is necessary mainly because you own the copyright to your changes,
+even after your contribution becomes part of our codebase, so we need your
+permission to use and distribute your code. We also need to be sure of
+various other things — for instance that you'll tell us if you know that
+your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch
+with us first. Use the issue tracker to explain your idea so we can help and
+possibly guide you.
+
+### Code reviews and other contributions.
+**All submissions, including submissions by project members, require review.**
+Please follow the instructions in [the contributors documentation](http://bazel.io/contributing.html).
+
+### The small print
+Contributions made by corporations are covered by a different agreement than
+the one above, the
+[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..a3eeb5b
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,20 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+# Name <email address>
+
+Tony Allevato <allevato@google.com>
+Jon Brandvein <brandjon@google.com>
+bttk <bttk@google.com>
+Chris Parsons <cparsons@google.com>
+Dave MacLachlan <dmaclach@google.com>
+Dmitry Lomov <dslomov@google.com>
+Jingwen Chen <jingwen@google.com>
+Laurent Le Brun <laurentlb@google.com>
+Nathan Herring <nherring@google.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..9e6559f
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,6 @@
+include platform/build/bazel:/OWNERS
+
+# Kleaf
+# https://android.googlesource.com/kernel/build/+/refs/heads/master/kleaf/README.md
+elsk@google.com
+maennich@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4ab2420
--- /dev/null
+++ b/README.md
@@ -0,0 +1,108 @@
+# Skylib
+
+[![Build status](https://badge.buildkite.com/921dc61e2d3a350ec40efb291914360c0bfa9b6196fa357420.svg?branch=master)](https://buildkite.com/bazel/bazel-skylib)
+
+Skylib is a standard library that provides functions useful for manipulating
+collections, file paths, and other features that are useful when writing custom
+build rules in Bazel.
+
+> This library is currently under early development. Be aware that the APIs
+> in these modules may change during this time.
+
+Each of the `.bzl` files in the `lib` directory defines a "module"&mdash;a
+`struct` that contains a set of related functions and/or other symbols that can
+be loaded as a single unit, for convenience.
+
+Skylib also provides build rules under the `rules` directory.
+
+## Getting Started
+
+### `WORKSPACE` file
+
+See the **WORKSPACE setup** section [for the current release](https://github.com/bazelbuild/bazel-skylib/releases).
+
+If you want to use `lib/unittest.bzl` from Skylib versions released in or after
+December 2018, then you also should add to the `WORKSPACE` file:
+
+```python
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+
+bazel_skylib_workspace()
+```
+
+### `BUILD` and `*.bzl` files
+
+Then, in the `BUILD` and/or `*.bzl` files in your own workspace, you can load
+the modules (listed [below](#list-of-modules)) and access the symbols by
+dotting into those structs:
+
+```python
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load("@bazel_skylib//lib:shell.bzl", "shell")
+
+p = paths.basename("foo.bar")
+s = shell.quote(p)
+```
+
+## List of modules (in lib/)
+
+* [collections](docs/collections_doc.md)
+* [dicts](docs/dicts_doc.md)
+* [partial](docs/partial_doc.md)
+* [paths](docs/paths_doc.md)
+* [selects](docs/selects_doc.md)
+* [sets](lib/sets.bzl) - _deprecated_, use `new_sets`
+* [new_sets](docs/new_sets_doc.md)
+* [shell](docs/shell_doc.md)
+* [structs](docs/structs_doc.md)
+* [types](docs/types_doc.md)
+* [unittest](docs/unittest_doc.md)
+* [versions](docs/versions_doc.md)
+
+## List of rules (in rules/)
+
+* [analysis_test](docs/analysis_test_doc.md)
+* [build_test](docs/build_test_doc.md)
+
+## Writing a new module
+
+Steps to add a module to Skylib:
+
+1. Create a new `.bzl` file in the `lib` directory.
+
+1. Write the functions or other symbols (such as constants) in that file,
+ defining them privately (prefixed by an underscore).
+
+1. Create the exported module struct, mapping the public names of the symbols
+ to their implementations. For example, if your module was named `things` and
+ had a function named `manipulate`, your `things.bzl` file would look like
+ this:
+
+ ```python
+ def _manipulate():
+ ...
+
+ things = struct(
+ manipulate=_manipulate,
+ )
+ ```
+
+1. Add unit tests for your module in the `tests` directory.
+
+## `bzl_library`
+
+The `bzl_library.bzl` rule can be used to aggregate a set of
+Starlark files and its dependencies for use in test targets and
+documentation generation.
+
+## Troubleshooting
+
+If you try to use `unittest` and you get the following error:
+
+```
+ERROR: While resolving toolchains for target //foo:bar: no matching toolchains found for types @bazel_skylib//toolchains:toolchain_type
+ERROR: Analysis of target '//foo:bar' failed; build aborted: no matching toolchains found for types @bazel_skylib//toolchains:toolchain_type
+```
+
+then you probably forgot to load and call `bazel_skylib_workspace()` in your
+`WORKSPACE` file.
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..d78cfad
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,78 @@
+workspace(name = "bazel_skylib")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+http_archive(
+ name = "rules_pkg",
+ urls = [
+ "https://github.com/bazelbuild/rules_pkg/releases/download/0.2.5/rules_pkg-0.2.5.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.2.5/rules_pkg-0.2.5.tar.gz",
+ ],
+ sha256 = "352c090cc3d3f9a6b4e676cf42a6047c16824959b438895a76c2989c6d7c246a",
+)
+load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
+rules_pkg_dependencies()
+
+maybe(
+ name = "bazel_federation",
+ repo_rule = http_archive,
+ sha256 = "b10529fcf8a464591e845588348533981e948315b706183481e0d076afe2fa3c",
+ url = "https://github.com/bazelbuild/bazel-federation/releases/download/0.0.2/bazel_federation-0.0.2.tar.gz",
+)
+
+load("@bazel_federation//:repositories.bzl", "bazel_skylib_deps", "rules_go")
+
+bazel_skylib_deps()
+
+rules_go()
+
+load("@bazel_federation//setup:bazel_skylib.bzl", "bazel_skylib_setup")
+
+bazel_skylib_setup()
+
+load("@bazel_federation//setup:rules_go.bzl", "rules_go_setup")
+
+rules_go_setup()
+
+# Below this line is for documentation generation only,
+# and should thus not be included by dependencies on
+# bazel-skylib.
+
+load("//:internal_deps.bzl", "bazel_skylib_internal_deps")
+
+bazel_skylib_internal_deps()
+
+load("//:internal_setup.bzl", "bazel_skylib_internal_setup")
+
+bazel_skylib_internal_setup()
+
+maybe(
+ name = "rules_cc",
+ repo_rule = http_archive,
+ sha256 = "b4b2a2078bdb7b8328d843e8de07d7c13c80e6c89e86a09d6c4b424cfd1aaa19",
+ strip_prefix = "rules_cc-cb2dfba6746bfa3c3705185981f3109f0ae1b893",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
+ "https://github.com/bazelbuild/rules_cc/archive/cb2dfba6746bfa3c3705185981f3109f0ae1b893.zip",
+ ],
+)
+
+# Provide a repository hint for Gazelle to inform it that the go package
+# github.com/bazelbuild/rules_go is available from io_bazel_rules_go and it
+# doesn't need to duplicatively fetch it.
+# gazelle:repository go_repository name=io_bazel_rules_go importpath=github.com/bazelbuild/rules_go
+http_archive(
+ name = "bazel_gazelle",
+ sha256 = "bfd86b3cbe855d6c16c6fce60d76bd51f5c8dbc9cfcaef7a2bb5c1aafd0710e8",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.0/bazel-gazelle-v0.21.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.0/bazel-gazelle-v0.21.0.tar.gz",
+ ],
+)
+# Another Gazelle repository hint.
+# gazelle:repository go_repository name=bazel_gazelle importpath=github.com/bazelbuild/bazel-gazelle/testtools
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+
+gazelle_dependencies()
diff --git a/bzl_library.bzl b/bzl_library.bzl
new file mode 100644
index 0000000..1b59440
--- /dev/null
+++ b/bzl_library.bzl
@@ -0,0 +1,107 @@
+# Copyright 2017 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.
+
+"""Skylib module containing a library rule for aggregating rules files."""
+
+StarlarkLibraryInfo = provider(
+ "Information on contained Starlark rules.",
+ fields = {
+ "srcs": "Top level rules files.",
+ "transitive_srcs": "Transitive closure of rules files required for " +
+ "interpretation of the srcs",
+ },
+)
+
+def _bzl_library_impl(ctx):
+ deps_files = [x.files for x in ctx.attr.deps]
+ all_files = depset(ctx.files.srcs, order = "postorder", transitive = deps_files)
+ return [
+ # All dependent files should be listed in both `files` and in `runfiles`;
+ # this ensures that a `bzl_library` can be referenced as `data` from
+ # a separate program, or from `tools` of a genrule().
+ DefaultInfo(
+ files = all_files,
+ runfiles = ctx.runfiles(transitive_files = all_files),
+ ),
+
+ # We also define our own provider struct, for aggregation and testing.
+ StarlarkLibraryInfo(
+ srcs = ctx.files.srcs,
+ transitive_srcs = all_files,
+ ),
+ ]
+
+bzl_library = rule(
+ implementation = _bzl_library_impl,
+ attrs = {
+ "srcs": attr.label_list(
+ allow_files = [".bzl"],
+ doc = "List of `.bzl` files that are processed to create this target.",
+ ),
+ "deps": attr.label_list(
+ allow_files = [".bzl"],
+ providers = [
+ [StarlarkLibraryInfo],
+ ],
+ doc = """List of other `bzl_library` targets that are required by the
+Starlark files listed in `srcs`.""",
+ ),
+ },
+ doc = """Creates a logical collection of Starlark .bzl files.
+
+Example:
+ Suppose your project has the following structure:
+
+ ```
+ [workspace]/
+ WORKSPACE
+ BUILD
+ checkstyle/
+ BUILD
+ checkstyle.bzl
+ lua/
+ BUILD
+ lua.bzl
+ luarocks.bzl
+ ```
+
+ In this case, you can have `bzl_library` targets in `checkstyle/BUILD` and
+ `lua/BUILD`:
+
+ `checkstyle/BUILD`:
+
+ ```python
+ load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+ bzl_library(
+ name = "checkstyle-rules",
+ srcs = ["checkstyle.bzl"],
+ )
+ ```
+
+ `lua/BUILD`:
+
+ ```python
+ load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+ bzl_library(
+ name = "lua-rules",
+ srcs = [
+ "lua.bzl",
+ "luarocks.bzl",
+ ],
+ )
+ ```
+""",
+)
diff --git a/distribution/BUILD b/distribution/BUILD
new file mode 100644
index 0000000..ba903aa
--- /dev/null
+++ b/distribution/BUILD
@@ -0,0 +1,49 @@
+load("@bazel_skylib//:version.bzl", "version")
+load("@rules_pkg//:pkg.bzl", "pkg_tar")
+load("@rules_pkg//releasing:defs.bzl", "print_rel_notes")
+
+package(
+ default_visibility = ["//visibility:private"],
+)
+
+pkg_tar(
+ name = "srcs",
+ srcs = ["//:distribution"],
+ mode = "0444",
+ # Make it owned by root so it does not have the uid of the CI robot.
+ owner = "0.0",
+ package_dir = "",
+ strip_prefix = ".",
+)
+
+pkg_tar(
+ name = "bins",
+ srcs = ["//:bins"],
+ mode = "0555",
+ # Make it owned by root so it does not have the uid of the CI robot.
+ owner = "0.0",
+ package_dir = "",
+ strip_prefix = ".",
+)
+
+# Build the artifact to put on the github release page.
+pkg_tar(
+ name = "bazel-skylib-%s" % version,
+ extension = "tar.gz",
+ # Make it owned by root so it does not have the uid of the CI robot.
+ owner = "0.0",
+ strip_prefix = ".",
+ deps = [
+ ":bins.tar",
+ ":srcs.tar",
+ ],
+)
+
+print_rel_notes(
+ name = "relnotes",
+ outs = ["relnotes.txt"],
+ deps_method = "bazel_skylib_workspace",
+ repo = "bazel-skylib",
+ setup_file = ":workspace.bzl",
+ version = version,
+)
diff --git a/docs/BUILD b/docs/BUILD
new file mode 100644
index 0000000..1d5d0d8
--- /dev/null
+++ b/docs/BUILD
@@ -0,0 +1,137 @@
+load("@io_bazel_skydoc//stardoc:stardoc.bzl", "stardoc")
+
+licenses(["notice"])
+
+# Note that due to a bug in Bazel 0.22.0, these targets cannot be built without
+# the --incompatible_remap_main_repo flag.
+stardoc(
+ name = "build_test_docs",
+ out = "build_test_doc_gen.md",
+ input = "//rules:build_test.bzl",
+ deps = ["//rules:build_test"],
+)
+
+stardoc(
+ name = "analysis_test_docs",
+ out = "analysis_test_doc_gen.md",
+ input = "//rules:analysis_test.bzl",
+)
+
+stardoc(
+ name = "collections_docs",
+ out = "collections_doc_gen.md",
+ input = "//lib:collections.bzl",
+ deps = ["//lib:collections"],
+)
+
+stardoc(
+ name = "dicts_docs",
+ out = "dicts_doc_gen.md",
+ input = "//lib:dicts.bzl",
+ deps = ["//lib:dicts"],
+)
+
+stardoc(
+ name = "partial_docs",
+ out = "partial_doc_gen.md",
+ input = "//lib:partial.bzl",
+ deps = ["//lib:partial"],
+)
+
+stardoc(
+ name = "paths_docs",
+ out = "paths_doc_gen.md",
+ input = "//lib:paths.bzl",
+ deps = ["//lib:paths"],
+)
+
+stardoc(
+ name = "selects_docs",
+ out = "selects_doc_gen.md",
+ input = "//lib:selects.bzl",
+ deps = ["//lib:selects"],
+)
+
+stardoc(
+ name = "new_sets_docs",
+ out = "new_sets_doc_gen.md",
+ input = "//lib:new_sets.bzl",
+ deps = ["//lib:new_sets"],
+)
+
+stardoc(
+ name = "shell_docs",
+ out = "shell_doc_gen.md",
+ input = "//lib:shell.bzl",
+ deps = ["//lib:shell"],
+)
+
+stardoc(
+ name = "structs_docs",
+ out = "structs_doc_gen.md",
+ input = "//lib:structs.bzl",
+ deps = ["//lib:structs"],
+)
+
+stardoc(
+ name = "types_docs",
+ out = "types_doc_gen.md",
+ input = "//lib:types.bzl",
+ deps = ["//lib:types"],
+)
+
+stardoc(
+ name = "unittest_docs",
+ out = "unittest_doc_gen.md",
+ input = "//lib:unittest.bzl",
+ deps = ["//lib:unittest"],
+)
+
+stardoc(
+ name = "versions_docs",
+ out = "versions_doc_gen.md",
+ input = "//lib:versions.bzl",
+ deps = ["//lib:versions"],
+)
+
+stardoc(
+ name = "copy_file_docs",
+ out = "copy_file_doc_gen.md",
+ input = "//rules:copy_file.bzl",
+ deps = ["//rules:copy_file"],
+)
+
+stardoc(
+ name = "write_file_docs",
+ out = "write_file_doc_gen.md",
+ input = "//rules:write_file.bzl",
+ deps = ["//rules:write_file"],
+)
+
+stardoc(
+ name = "diff_test_docs",
+ out = "diff_test_doc_gen.md",
+ input = "//rules:diff_test.bzl",
+ deps = ["//rules:diff_test"],
+)
+
+stardoc(
+ name = "native_binary_docs",
+ out = "native_binary_doc_gen.md",
+ input = "//rules:native_binary.bzl",
+ deps = ["//rules:native_binary"],
+)
+
+stardoc(
+ name = "run_binary_docs",
+ out = "run_binary_doc_gen.md",
+ input = "//rules:run_binary.bzl",
+ deps = ["//rules:run_binary"],
+)
+
+stardoc(
+ name = "common_settings_docs",
+ out = "common_settings_doc_gen.md",
+ input = "//rules:common_settings.bzl",
+ deps = ["//rules:common_settings"],
+)
diff --git a/docs/analysis_test_doc.md b/docs/analysis_test_doc.md
new file mode 100755
index 0000000..817be66
--- /dev/null
+++ b/docs/analysis_test_doc.md
@@ -0,0 +1,61 @@
+<a name="#analysis_test"></a>
+## analysis_test
+
+<pre>
+analysis_test(<a href="#analysis_test-name">name</a>, <a href="#analysis_test-targets">targets</a>)
+</pre>
+
+Test rule checking that other targets can be successfully analyzed.
+
+ This rule essentially verifies that all targets under `targets` would
+ generate no errors when analyzed with `bazel build [targets] --nobuild`.
+ Action success/failure for the targets and their transitive dependencies
+ are not verified. An analysis test simply ensures that each target in the transitive
+ dependencies propagate providers appropriately and register actions for their outputs
+ appropriately.
+
+ NOTE: If the targets fail to analyze, instead of the analysis_test failing, the analysis_test
+ will fail to build. Ideally, it would instead result in a test failure. This is a current
+ infrastructure limitation that may be fixed in the future.
+
+ Typical usage:
+
+ load("@bazel_skylib//rules:analysis_test.bzl", "analysis_test")
+ analysis_test(
+ name = "my_analysis_test",
+ targets = [
+ "//some/package:rule",
+ ],
+ )
+
+ Args:
+ name: The name of the test rule.
+ targets: A list of targets to ensure build.
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysis_test-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ <tr id="analysis_test-targets">
+ <td><code>targets</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a>; required
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/build_test_doc.md b/docs/build_test_doc.md
new file mode 100755
index 0000000..6f3c597
--- /dev/null
+++ b/docs/build_test_doc.md
@@ -0,0 +1,67 @@
+## build_test
+
+<pre>
+build_test(<a href="#build_test-name">name</a>, <a href="#build_test-targets">targets</a>, <a href="#build_test-kwargs">kwargs</a>)
+</pre>
+
+Test rule checking that other targets build.
+
+This works not by an instance of this test failing, but instead by
+the targets it depends on failing to build, and hence failing
+the attempt to run this test.
+
+NOTE: At the moment, this won't work on Windows; but someone adding
+support would be welcomed.
+
+Typical usage:
+
+```
+ load("@bazel_skylib//rules:build_test.bzl", "build_test")
+ build_test(
+ name = "my_build_test",
+ targets = [
+ "//some/package:rule",
+ ],
+ )
+```
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="build_test-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ The name of the test rule.
+ </p>
+ </td>
+ </tr>
+ <tr id="build_test-targets">
+ <td><code>targets</code></td>
+ <td>
+ required.
+ <p>
+ A list of targets to ensure build.
+ </p>
+ </td>
+ </tr>
+ <tr id="build_test-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/collections_doc.md b/docs/collections_doc.md
new file mode 100755
index 0000000..64ffb5e
--- /dev/null
+++ b/docs/collections_doc.md
@@ -0,0 +1,108 @@
+## collections.after_each
+
+<pre>
+collections.after_each(<a href="#collections.after_each-separator">separator</a>, <a href="#collections.after_each-iterable">iterable</a>)
+</pre>
+
+Inserts `separator` after each item in `iterable`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="collections.after_each-separator">
+ <td><code>separator</code></td>
+ <td>
+ required.
+ <p>
+ The value to insert after each item in `iterable`.
+ </p>
+ </td>
+ </tr>
+ <tr id="collections.after_each-iterable">
+ <td><code>iterable</code></td>
+ <td>
+ required.
+ <p>
+ The list into which to intersperse the separator.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## collections.before_each
+
+<pre>
+collections.before_each(<a href="#collections.before_each-separator">separator</a>, <a href="#collections.before_each-iterable">iterable</a>)
+</pre>
+
+Inserts `separator` before each item in `iterable`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="collections.before_each-separator">
+ <td><code>separator</code></td>
+ <td>
+ required.
+ <p>
+ The value to insert before each item in `iterable`.
+ </p>
+ </td>
+ </tr>
+ <tr id="collections.before_each-iterable">
+ <td><code>iterable</code></td>
+ <td>
+ required.
+ <p>
+ The list into which to intersperse the separator.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## collections.uniq
+
+<pre>
+collections.uniq(<a href="#collections.uniq-iterable">iterable</a>)
+</pre>
+
+Returns a list of unique elements in `iterable`.
+
+Requires all the elements to be hashable.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="collections.uniq-iterable">
+ <td><code>iterable</code></td>
+ <td>
+ required.
+ <p>
+ An iterable to filter.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/common_settings_doc.md b/docs/common_settings_doc.md
new file mode 100755
index 0000000..676f070
--- /dev/null
+++ b/docs/common_settings_doc.md
@@ -0,0 +1,296 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+<a name="#bool_flag"></a>
+
+## bool_flag
+
+<pre>
+bool_flag(<a href="#bool_flag-name">name</a>)
+</pre>
+
+A bool-typed build setting that can be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="bool_flag-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#bool_setting"></a>
+
+## bool_setting
+
+<pre>
+bool_setting(<a href="#bool_setting-name">name</a>)
+</pre>
+
+A bool-typed build setting that cannot be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="bool_setting-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#int_flag"></a>
+
+## int_flag
+
+<pre>
+int_flag(<a href="#int_flag-name">name</a>)
+</pre>
+
+An int-typed build setting that can be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="int_flag-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#int_setting"></a>
+
+## int_setting
+
+<pre>
+int_setting(<a href="#int_setting-name">name</a>)
+</pre>
+
+An int-typed build setting that cannot be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="int_setting-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#string_flag"></a>
+
+## string_flag
+
+<pre>
+string_flag(<a href="#string_flag-name">name</a>, <a href="#string_flag-values">values</a>)
+</pre>
+
+A string-typed build setting that can be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="string_flag-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ <tr id="string_flag-values">
+ <td><code>values</code></td>
+ <td>
+ List of strings; optional
+ <p>
+ The list of allowed values for this setting. An error is raised if any other value is given.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#string_list_flag"></a>
+
+## string_list_flag
+
+<pre>
+string_list_flag(<a href="#string_list_flag-name">name</a>)
+</pre>
+
+A string list-typed build setting that can be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="string_list_flag-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#string_list_setting"></a>
+
+## string_list_setting
+
+<pre>
+string_list_setting(<a href="#string_list_setting-name">name</a>)
+</pre>
+
+A string list-typed build setting that cannot be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="string_list_setting-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#string_setting"></a>
+
+## string_setting
+
+<pre>
+string_setting(<a href="#string_setting-name">name</a>, <a href="#string_setting-values">values</a>)
+</pre>
+
+A string-typed build setting that cannot be set on the command line
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="string_setting-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ <tr id="string_setting-values">
+ <td><code>values</code></td>
+ <td>
+ List of strings; optional
+ <p>
+ The list of allowed values for this setting. An error is raised if any other value is given.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#BuildSettingInfo"></a>
+
+## BuildSettingInfo
+
+<pre>
+BuildSettingInfo(<a href="#BuildSettingInfo-value">value</a>)
+</pre>
+
+A singleton provider that contains the raw value of a build setting
+
+### Fields
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="BuildSettingInfo-value">
+ <td><code>value</code></td>
+ <td>
+ <p>The value of the build setting in the current configuration. This value may come from the command line or an upstream transition, or else it will be the build setting's default.</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/copy_file_doc.md b/docs/copy_file_doc.md
new file mode 100755
index 0000000..67148bc
--- /dev/null
+++ b/docs/copy_file_doc.md
@@ -0,0 +1,92 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+<a name="#copy_file"></a>
+
+## copy_file
+
+<pre>
+copy_file(<a href="#copy_file-name">name</a>, <a href="#copy_file-src">src</a>, <a href="#copy_file-out">out</a>, <a href="#copy_file-is_executable">is_executable</a>, <a href="#copy_file-allow_symlink">allow_symlink</a>, <a href="#copy_file-kwargs">kwargs</a>)
+</pre>
+
+Copies a file to another location.
+
+`native.genrule()` is sometimes used to copy files (often wishing to rename them). The 'copy_file' rule does this with a simpler interface than genrule.
+
+This rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command on Windows (no Bash is required).
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="copy_file-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ Name of the rule.
+ </p>
+ </td>
+ </tr>
+ <tr id="copy_file-src">
+ <td><code>src</code></td>
+ <td>
+ required.
+ <p>
+ A Label. The file to make a copy of. (Can also be the label of a rule
+ that generates a file.)
+ </p>
+ </td>
+ </tr>
+ <tr id="copy_file-out">
+ <td><code>out</code></td>
+ <td>
+ required.
+ <p>
+ Path of the output file, relative to this package.
+ </p>
+ </td>
+ </tr>
+ <tr id="copy_file-is_executable">
+ <td><code>is_executable</code></td>
+ <td>
+ optional. default is <code>False</code>
+ <p>
+ A boolean. Whether to make the output file executable. When
+ True, the rule's output can be executed using `bazel run` and can be
+ in the srcs of binary and test rules that require executable sources.
+ WARNING: If `allow_symlink` is True, `src` must also be executable.
+ </p>
+ </td>
+ </tr>
+ <tr id="copy_file-allow_symlink">
+ <td><code>allow_symlink</code></td>
+ <td>
+ optional. default is <code>False</code>
+ <p>
+ A boolean. Whether to allow symlinking instead of copying.
+ When False, the output is always a hard copy. When True, the output
+ *can* be a symlink, but there is no guarantee that a symlink is
+ created (i.e., at the time of writing, we don't create symlinks on
+ Windows). Set this to True if you need fast copying and your tools can
+ handle symlinks (which most UNIX tools can).
+ </p>
+ </td>
+ </tr>
+ <tr id="copy_file-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ further keyword arguments, e.g. `visibility`
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/dicts_doc.md b/docs/dicts_doc.md
new file mode 100755
index 0000000..2e95cf8
--- /dev/null
+++ b/docs/dicts_doc.md
@@ -0,0 +1,47 @@
+## dicts.add
+
+<pre>
+dicts.add(<a href="#dicts.add-dictionaries">dictionaries</a>, <a href="#dicts.add-kwargs">kwargs</a>)
+</pre>
+
+Returns a new `dict` that has all the entries of the given dictionaries.
+
+If the same key is present in more than one of the input dictionaries, the
+last of them in the argument list overrides any earlier ones.
+
+This function is designed to take zero or one arguments as well as multiple
+dictionaries, so that it follows arithmetic identities and callers can avoid
+special cases for their inputs: the sum of zero dictionaries is the empty
+dictionary, and the sum of a single dictionary is a copy of itself.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="dicts.add-dictionaries">
+ <td><code>dictionaries</code></td>
+ <td>
+ optional.
+ <p>
+ Zero or more dictionaries to be added.
+ </p>
+ </td>
+ </tr>
+ <tr id="dicts.add-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ Additional dictionary passed as keyword args.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/diff_test_doc.md b/docs/diff_test_doc.md
new file mode 100755
index 0000000..07ce8b9
--- /dev/null
+++ b/docs/diff_test_doc.md
@@ -0,0 +1,59 @@
+## diff_test
+
+<pre>
+diff_test(<a href="#diff_test-name">name</a>, <a href="#diff_test-file1">file1</a>, <a href="#diff_test-file2">file2</a>, <a href="#diff_test-kwargs">kwargs</a>)
+</pre>
+
+A test that compares two files.
+
+The test succeeds if the files' contents match.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="diff_test-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ The name of the test rule.
+ </p>
+ </td>
+ </tr>
+ <tr id="diff_test-file1">
+ <td><code>file1</code></td>
+ <td>
+ required.
+ <p>
+ Label of the file to compare to <code>file2</code>.
+ </p>
+ </td>
+ </tr>
+ <tr id="diff_test-file2">
+ <td><code>file2</code></td>
+ <td>
+ required.
+ <p>
+ Label of the file to compare to <code>file1</code>.
+ </p>
+ </td>
+ </tr>
+ <tr id="diff_test-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/native_binary_doc.md b/docs/native_binary_doc.md
new file mode 100755
index 0000000..9cf90f4
--- /dev/null
+++ b/docs/native_binary_doc.md
@@ -0,0 +1,131 @@
+## native_binary
+
+<pre>
+native_binary(<a href="#native_binary-name">name</a>, <a href="#native_binary-src">src</a>, <a href="#native_binary-out">out</a>, <a href="#native_binary-data">data</a>, <a href="#native_binary-kwargs">kwargs</a>)
+</pre>
+
+Wraps a pre-built binary or script with a binary rule.
+
+You can "bazel run" this rule like any other binary rule, and use it as a tool in genrule.tools for example. You can also augment the binary with runfiles.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="native_binary-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ </td>
+ </tr>
+ <tr id="native_binary-src">
+ <td><code>src</code></td>
+ <td>
+ required.
+ <p>
+ label; path of the pre-built executable
+ </p>
+ </td>
+ </tr>
+ <tr id="native_binary-out">
+ <td><code>out</code></td>
+ <td>
+ required.
+ <p>
+ output; an output name for the copy of the binary. (Bazel requires that this rule make a copy of 'src'.)
+ </p>
+ </td>
+ </tr>
+ <tr id="native_binary-data">
+ <td><code>data</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ list of labels; data dependencies
+ </p>
+ </td>
+ </tr>
+ <tr id="native_binary-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-binaries">common attributes for binaries</a>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## native_test
+
+<pre>
+native_test(<a href="#native_test-name">name</a>, <a href="#native_test-src">src</a>, <a href="#native_test-out">out</a>, <a href="#native_test-data">data</a>, <a href="#native_test-kwargs">kwargs</a>)
+</pre>
+
+Wraps a pre-built binary or script with a test rule.
+
+You can "bazel test" this rule like any other test rule. You can also augment the binary with
+runfiles.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="native_test-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ </td>
+ </tr>
+ <tr id="native_test-src">
+ <td><code>src</code></td>
+ <td>
+ required.
+ <p>
+ label; path of the pre-built executable
+ </p>
+ </td>
+ </tr>
+ <tr id="native_test-out">
+ <td><code>out</code></td>
+ <td>
+ required.
+ <p>
+ output; an output name for the copy of the binary. (Bazel requires that this rule make a copy of 'src'.)
+ </p>
+ </td>
+ </tr>
+ <tr id="native_test-data">
+ <td><code>data</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ list of labels; data dependencies
+ </p>
+ </td>
+ </tr>
+ <tr id="native_test-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/new_sets_doc.md b/docs/new_sets_doc.md
new file mode 100755
index 0000000..feec3b8
--- /dev/null
+++ b/docs/new_sets_doc.md
@@ -0,0 +1,519 @@
+## sets.make
+
+<pre>
+sets.make(<a href="#sets.make-elements">elements</a>)
+</pre>
+
+Creates a new set.
+
+All elements must be hashable.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.make-elements">
+ <td><code>elements</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ Optional sequence to construct the set out of.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.copy
+
+<pre>
+sets.copy(<a href="#sets.copy-s">s</a>)
+</pre>
+
+Creates a new set from another set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.copy-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.to_list
+
+<pre>
+sets.to_list(<a href="#sets.to_list-s">s</a>)
+</pre>
+
+Creates a list from the values in the set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.to_list-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.insert
+
+<pre>
+sets.insert(<a href="#sets.insert-s">s</a>, <a href="#sets.insert-e">e</a>)
+</pre>
+
+Inserts an element into the set.
+
+Element must be hashable. This mutates the original set.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.insert-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.insert-e">
+ <td><code>e</code></td>
+ <td>
+ required.
+ <p>
+ The element to be inserted.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.contains
+
+<pre>
+sets.contains(<a href="#sets.contains-a">a</a>, <a href="#sets.contains-e">e</a>)
+</pre>
+
+Checks for the existence of an element in a set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.contains-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.contains-e">
+ <td><code>e</code></td>
+ <td>
+ required.
+ <p>
+ The element to look for.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.is_equal
+
+<pre>
+sets.is_equal(<a href="#sets.is_equal-a">a</a>, <a href="#sets.is_equal-b">b</a>)
+</pre>
+
+Returns whether two sets are equal.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.is_equal-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.is_equal-b">
+ <td><code>b</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.is_subset
+
+<pre>
+sets.is_subset(<a href="#sets.is_subset-a">a</a>, <a href="#sets.is_subset-b">b</a>)
+</pre>
+
+Returns whether `a` is a subset of `b`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.is_subset-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.is_subset-b">
+ <td><code>b</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.disjoint
+
+<pre>
+sets.disjoint(<a href="#sets.disjoint-a">a</a>, <a href="#sets.disjoint-b">b</a>)
+</pre>
+
+Returns whether two sets are disjoint.
+
+Two sets are disjoint if they have no elements in common.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.disjoint-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.disjoint-b">
+ <td><code>b</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.intersection
+
+<pre>
+sets.intersection(<a href="#sets.intersection-a">a</a>, <a href="#sets.intersection-b">b</a>)
+</pre>
+
+Returns the intersection of two sets.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.intersection-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.intersection-b">
+ <td><code>b</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.union
+
+<pre>
+sets.union(<a href="#sets.union-args">args</a>)
+</pre>
+
+Returns the union of several sets.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.union-args">
+ <td><code>args</code></td>
+ <td>
+ optional.
+ <p>
+ An arbitrary number of sets or lists.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.difference
+
+<pre>
+sets.difference(<a href="#sets.difference-a">a</a>, <a href="#sets.difference-b">b</a>)
+</pre>
+
+Returns the elements in `a` that are not in `b`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.difference-a">
+ <td><code>a</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.difference-b">
+ <td><code>b</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.length
+
+<pre>
+sets.length(<a href="#sets.length-s">s</a>)
+</pre>
+
+Returns the number of elements in a set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.length-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.remove
+
+<pre>
+sets.remove(<a href="#sets.remove-s">s</a>, <a href="#sets.remove-e">e</a>)
+</pre>
+
+Removes an element from the set.
+
+Element must be hashable. This mutates the original set.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.remove-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ <tr id="sets.remove-e">
+ <td><code>e</code></td>
+ <td>
+ required.
+ <p>
+ The element to be removed.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.repr
+
+<pre>
+sets.repr(<a href="#sets.repr-s">s</a>)
+</pre>
+
+Returns a string value representing the set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.repr-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## sets.str
+
+<pre>
+sets.str(<a href="#sets.str-s">s</a>)
+</pre>
+
+Returns a string value representing the set.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="sets.str-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A set, as returned by `sets.make()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/partial_doc.md b/docs/partial_doc.md
new file mode 100755
index 0000000..4559ba3
--- /dev/null
+++ b/docs/partial_doc.md
@@ -0,0 +1,171 @@
+## partial.make
+
+<pre>
+partial.make(<a href="#partial.make-func">func</a>, <a href="#partial.make-args">args</a>, <a href="#partial.make-kwargs">kwargs</a>)
+</pre>
+
+Creates a partial that can be called using `call`.
+
+A partial can have args assigned to it at the make site, and can have args
+passed to it at the call sites.
+
+A partial 'function' can be defined with positional args and kwargs:
+
+ # function with no args
+ def function1():
+ ...
+
+ # function with 2 args
+ def function2(arg1, arg2):
+ ...
+
+ # function with 2 args and keyword args
+ def function3(arg1, arg2, x, y):
+ ...
+
+The positional args passed to the function are the args passed into make
+followed by any additional positional args given to call. The below example
+illustrates a function with two positional arguments where one is supplied by
+make and the other by call:
+
+ # function demonstrating 1 arg at make site, and 1 arg at call site
+ def _foo(make_arg1, func_arg1):
+ print(make_arg1 + " " + func_arg1 + "!")
+
+For example:
+
+ hi_func = partial.make(_foo, "Hello")
+ bye_func = partial.make(_foo, "Goodbye")
+ partial.call(hi_func, "Jennifer")
+ partial.call(hi_func, "Dave")
+ partial.call(bye_func, "Jennifer")
+ partial.call(bye_func, "Dave")
+
+prints:
+
+ "Hello, Jennifer!"
+ "Hello, Dave!"
+ "Goodbye, Jennifer!"
+ "Goodbye, Dave!"
+
+The keyword args given to the function are the kwargs passed into make
+unioned with the keyword args given to call. In case of a conflict, the
+keyword args given to call take precedence. This allows you to set a default
+value for keyword arguments and override it at the call site.
+
+Example with a make site arg, a call site arg, a make site kwarg and a
+call site kwarg:
+
+ def _foo(make_arg1, call_arg1, make_location, call_location):
+ print(make_arg1 + " is from " + make_location + " and " +
+ call_arg1 + " is from " + call_location + "!")
+
+ func = partial.make(_foo, "Ben", make_location="Hollywood")
+ partial.call(func, "Jennifer", call_location="Denver")
+
+Prints "Ben is from Hollywood and Jennifer is from Denver!".
+
+ partial.call(func, "Jennifer", make_location="LA", call_location="Denver")
+
+Prints "Ben is from LA and Jennifer is from Denver!".
+
+Note that keyword args may not overlap with positional args, regardless of
+whether they are given during the make or call step. For instance, you can't
+do:
+
+def foo(x):
+ pass
+
+func = partial.make(foo, 1)
+partial.call(func, x=2)
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="partial.make-func">
+ <td><code>func</code></td>
+ <td>
+ required.
+ <p>
+ The function to be called.
+ </p>
+ </td>
+ </tr>
+ <tr id="partial.make-args">
+ <td><code>args</code></td>
+ <td>
+ optional.
+ <p>
+ Positional arguments to be passed to function.
+ </p>
+ </td>
+ </tr>
+ <tr id="partial.make-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ Keyword arguments to be passed to function. Note that these can
+ be overridden at the call sites.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## partial.call
+
+<pre>
+partial.call(<a href="#partial.call-partial">partial</a>, <a href="#partial.call-args">args</a>, <a href="#partial.call-kwargs">kwargs</a>)
+</pre>
+
+Calls a partial created using `make`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="partial.call-partial">
+ <td><code>partial</code></td>
+ <td>
+ required.
+ <p>
+ The partial to be called.
+ </p>
+ </td>
+ </tr>
+ <tr id="partial.call-args">
+ <td><code>args</code></td>
+ <td>
+ optional.
+ <p>
+ Additional positional arguments to be appended to the ones given to
+ make.
+ </p>
+ </td>
+ </tr>
+ <tr id="partial.call-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ Additional keyword arguments to augment and override the ones
+ given to make.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/paths_doc.md b/docs/paths_doc.md
new file mode 100755
index 0000000..8797c01
--- /dev/null
+++ b/docs/paths_doc.md
@@ -0,0 +1,311 @@
+## paths.basename
+
+<pre>
+paths.basename(<a href="#paths.basename-p">p</a>)
+</pre>
+
+Returns the basename (i.e., the file portion) of a path.
+
+Note that if `p` ends with a slash, this function returns an empty string.
+This matches the behavior of Python's `os.path.basename`, but differs from
+the Unix `basename` command (which would return the path segment preceding
+the final slash).
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.basename-p">
+ <td><code>p</code></td>
+ <td>
+ required.
+ <p>
+ The path whose basename should be returned.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.dirname
+
+<pre>
+paths.dirname(<a href="#paths.dirname-p">p</a>)
+</pre>
+
+Returns the dirname of a path.
+
+The dirname is the portion of `p` up to but not including the file portion
+(i.e., the basename). Any slashes immediately preceding the basename are not
+included, unless omitting them would make the dirname empty.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.dirname-p">
+ <td><code>p</code></td>
+ <td>
+ required.
+ <p>
+ The path whose dirname should be returned.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.is_absolute
+
+<pre>
+paths.is_absolute(<a href="#paths.is_absolute-path">path</a>)
+</pre>
+
+Returns `True` if `path` is an absolute path.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.is_absolute-path">
+ <td><code>path</code></td>
+ <td>
+ required.
+ <p>
+ A path (which is a string).
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.join
+
+<pre>
+paths.join(<a href="#paths.join-path">path</a>, <a href="#paths.join-others">others</a>)
+</pre>
+
+Joins one or more path components intelligently.
+
+This function mimics the behavior of Python's `os.path.join` function on POSIX
+platform. It returns the concatenation of `path` and any members of `others`,
+inserting directory separators before each component except the first. The
+separator is not inserted if the path up until that point is either empty or
+already ends in a separator.
+
+If any component is an absolute path, all previous components are discarded.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.join-path">
+ <td><code>path</code></td>
+ <td>
+ required.
+ <p>
+ A path segment.
+ </p>
+ </td>
+ </tr>
+ <tr id="paths.join-others">
+ <td><code>others</code></td>
+ <td>
+ optional.
+ <p>
+ Additional path segments.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.normalize
+
+<pre>
+paths.normalize(<a href="#paths.normalize-path">path</a>)
+</pre>
+
+Normalizes a path, eliminating double slashes and other redundant segments.
+
+This function mimics the behavior of Python's `os.path.normpath` function on
+POSIX platforms; specifically:
+
+- If the entire path is empty, "." is returned.
+- All "." segments are removed, unless the path consists solely of a single
+ "." segment.
+- Trailing slashes are removed, unless the path consists solely of slashes.
+- ".." segments are removed as long as there are corresponding segments
+ earlier in the path to remove; otherwise, they are retained as leading ".."
+ segments.
+- Single and double leading slashes are preserved, but three or more leading
+ slashes are collapsed into a single leading slash.
+- Multiple adjacent internal slashes are collapsed into a single slash.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.normalize-path">
+ <td><code>path</code></td>
+ <td>
+ required.
+ <p>
+ A path.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.relativize
+
+<pre>
+paths.relativize(<a href="#paths.relativize-path">path</a>, <a href="#paths.relativize-start">start</a>)
+</pre>
+
+Returns the portion of `path` that is relative to `start`.
+
+Because we do not have access to the underlying file system, this
+implementation differs slightly from Python's `os.path.relpath` in that it
+will fail if `path` is not beneath `start` (rather than use parent segments to
+walk up to the common file system root).
+
+Relativizing paths that start with parent directory references only works if
+the path both start with the same initial parent references.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.relativize-path">
+ <td><code>path</code></td>
+ <td>
+ required.
+ <p>
+ The path to relativize.
+ </p>
+ </td>
+ </tr>
+ <tr id="paths.relativize-start">
+ <td><code>start</code></td>
+ <td>
+ required.
+ <p>
+ The ancestor path against which to relativize.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.replace_extension
+
+<pre>
+paths.replace_extension(<a href="#paths.replace_extension-p">p</a>, <a href="#paths.replace_extension-new_extension">new_extension</a>)
+</pre>
+
+Replaces the extension of the file at the end of a path.
+
+If the path has no extension, the new extension is added to it.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.replace_extension-p">
+ <td><code>p</code></td>
+ <td>
+ required.
+ <p>
+ The path whose extension should be replaced.
+ </p>
+ </td>
+ </tr>
+ <tr id="paths.replace_extension-new_extension">
+ <td><code>new_extension</code></td>
+ <td>
+ required.
+ <p>
+ The new extension for the file. The new extension should
+ begin with a dot if you want the new filename to have one.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## paths.split_extension
+
+<pre>
+paths.split_extension(<a href="#paths.split_extension-p">p</a>)
+</pre>
+
+Splits the path `p` into a tuple containing the root and extension.
+
+Leading periods on the basename are ignored, so
+`path.split_extension(".bashrc")` returns `(".bashrc", "")`.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="paths.split_extension-p">
+ <td><code>p</code></td>
+ <td>
+ required.
+ <p>
+ The path whose root and extension should be split.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/regenerate_docs.sh b/docs/regenerate_docs.sh
new file mode 100755
index 0000000..2ab9a08
--- /dev/null
+++ b/docs/regenerate_docs.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Copyright 2019 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.
+#
+# A script to manually regenerate the documentation contained in the docs/ directory.
+# Should be run from the WORKSPACE root.
+
+set -euo pipefail
+
+bazel build docs:all --experimental_remap_main_repo
+
+for filename in bazel-bin/docs/*_gen.md; do
+ target_filename="$(echo $filename | sed -En "s/bazel-bin\/(.*)_gen.md/\1/p").md"
+ cp $filename $target_filename
+done
diff --git a/docs/run_binary_doc.md b/docs/run_binary_doc.md
new file mode 100755
index 0000000..188e277
--- /dev/null
+++ b/docs/run_binary_doc.md
@@ -0,0 +1,75 @@
+<a name="#run_binary"></a>
+## run_binary
+
+<pre>
+run_binary(<a href="#run_binary-name">name</a>, <a href="#run_binary-args">args</a>, <a href="#run_binary-env">env</a>, <a href="#run_binary-outs">outs</a>, <a href="#run_binary-srcs">srcs</a>, <a href="#run_binary-tool">tool</a>)
+</pre>
+
+Runs a binary as a build action.<br/><br/>This rule does not require Bash (unlike <code>native.genrule</code>).
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="run_binary-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ <tr id="run_binary-args">
+ <td><code>args</code></td>
+ <td>
+ List of strings; optional
+ <p>
+ Command line arguments of the binary.<br/><br/>Subject to<code><a href="https://docs.bazel.build/versions/master/be/make-variables.html#location">$(location)</a></code> expansion.
+ </p>
+ </td>
+ </tr>
+ <tr id="run_binary-env">
+ <td><code>env</code></td>
+ <td>
+ <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a>; optional
+ <p>
+ Environment variables of the action.<br/><br/>Subject to <code><a href="https://docs.bazel.build/versions/master/be/make-variables.html#location">$(location)</a></code> expansion.
+ </p>
+ </td>
+ </tr>
+ <tr id="run_binary-outs">
+ <td><code>outs</code></td>
+ <td>
+ List of labels; required
+ <p>
+ Output files generated by the action.<br/><br/>These labels are available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
+ </p>
+ </td>
+ </tr>
+ <tr id="run_binary-srcs">
+ <td><code>srcs</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a>; optional
+ <p>
+ Additional inputs of the action.<br/><br/>These labels are available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
+ </p>
+ </td>
+ </tr>
+ <tr id="run_binary-tool">
+ <td><code>tool</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#labels">Label</a>; required
+ <p>
+ The tool to run in the action.<br/><br/>Must be the label of a *_binary rule, of a rule that generates an executable file, or of a file that can be executed as a subprocess (e.g. an .exe or .bat file on Windows or a binary with executable permission on Linux). This label is available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/selects_doc.md b/docs/selects_doc.md
new file mode 100755
index 0000000..55711c9
--- /dev/null
+++ b/docs/selects_doc.md
@@ -0,0 +1,168 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+<a name="#selects.with_or"></a>
+
+## selects.with_or
+
+<pre>
+selects.with_or(<a href="#selects.with_or-input_dict">input_dict</a>, <a href="#selects.with_or-no_match_error">no_match_error</a>)
+</pre>
+
+Drop-in replacement for `select()` that supports ORed keys.
+
+Example:
+
+ ```build
+ deps = selects.with_or({
+ "//configs:one": [":dep1"],
+ ("//configs:two", "//configs:three"): [":dep2or3"],
+ "//configs:four": [":dep4"],
+ "//conditions:default": [":default"]
+ })
+ ```
+
+ Key labels may appear at most once anywhere in the input.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="selects.with_or-input_dict">
+ <td><code>input_dict</code></td>
+ <td>
+ required.
+ <p>
+ The same dictionary `select()` takes, except keys may take
+ either the usual form `"//foo:config1"` or
+ `("//foo:config1", "//foo:config2", ...)` to signify
+ `//foo:config1` OR `//foo:config2` OR `...`.
+ </p>
+ </td>
+ </tr>
+ <tr id="selects.with_or-no_match_error">
+ <td><code>no_match_error</code></td>
+ <td>
+ optional. default is <code>""</code>
+ <p>
+ Optional custom error to report if no condition matches.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#selects.with_or_dict"></a>
+
+## selects.with_or_dict
+
+<pre>
+selects.with_or_dict(<a href="#selects.with_or_dict-input_dict">input_dict</a>)
+</pre>
+
+Variation of `with_or` that returns the dict of the `select()`.
+
+Unlike `select()`, the contents of the dict can be inspected by Starlark
+macros.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="selects.with_or_dict-input_dict">
+ <td><code>input_dict</code></td>
+ <td>
+ required.
+ <p>
+ Same as `with_or`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+<a name="#selects.config_setting_group"></a>
+
+## selects.config_setting_group
+
+<pre>
+selects.config_setting_group(<a href="#selects.config_setting_group-name">name</a>, <a href="#selects.config_setting_group-match_any">match_any</a>, <a href="#selects.config_setting_group-match_all">match_all</a>)
+</pre>
+
+Matches if all or any of its member `config_setting`s match.
+
+Example:
+
+ ```build
+ config_setting(name = "one", define_values = {"foo": "true"})
+ config_setting(name = "two", define_values = {"bar": "false"})
+ config_setting(name = "three", define_values = {"baz": "more_false"})
+
+ config_setting_group(
+ name = "one_two_three",
+ match_all = [":one", ":two", ":three"]
+ )
+
+ cc_binary(
+ name = "myapp",
+ srcs = ["myapp.cc"],
+ deps = select({
+ ":one_two_three": [":special_deps"],
+ "//conditions:default": [":default_deps"]
+ })
+ ```
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="selects.config_setting_group-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ The group's name. This is how `select()`s reference it.
+ </p>
+ </td>
+ </tr>
+ <tr id="selects.config_setting_group-match_any">
+ <td><code>match_any</code></td>
+ <td>
+ optional. default is <code>[]</code>
+ <p>
+ A list of `config_settings`. This group matches if *any* member
+ in the list matches. If this is set, `match_all` must not be set.
+ </p>
+ </td>
+ </tr>
+ <tr id="selects.config_setting_group-match_all">
+ <td><code>match_all</code></td>
+ <td>
+ optional. default is <code>[]</code>
+ <p>
+ A list of `config_settings`. This group matches if *every*
+ member in the list matches. If this is set, `match_any` must be not
+ set.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/shell_doc.md b/docs/shell_doc.md
new file mode 100755
index 0000000..6c655be
--- /dev/null
+++ b/docs/shell_doc.md
@@ -0,0 +1,71 @@
+## shell.array_literal
+
+<pre>
+shell.array_literal(<a href="#shell.array_literal-iterable">iterable</a>)
+</pre>
+
+Creates a string from a sequence that can be used as a shell array.
+
+For example, `shell.array_literal(["a", "b", "c"])` would return the string
+`("a" "b" "c")`, which can be used in a shell script wherever an array
+literal is needed.
+
+Note that all elements in the array are quoted (using `shell.quote`) for
+safety, even if they do not need to be.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="shell.array_literal-iterable">
+ <td><code>iterable</code></td>
+ <td>
+ required.
+ <p>
+ A sequence of elements. Elements that are not strings will be
+ converted to strings first, by calling `str()`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## shell.quote
+
+<pre>
+shell.quote(<a href="#shell.quote-s">s</a>)
+</pre>
+
+Quotes the given string for use in a shell command.
+
+This function quotes the given string (in case it contains spaces or other
+shell metacharacters.)
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="shell.quote-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ The string to quote.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/structs_doc.md b/docs/structs_doc.md
new file mode 100755
index 0000000..3b742cb
--- /dev/null
+++ b/docs/structs_doc.md
@@ -0,0 +1,29 @@
+## structs.to_dict
+
+<pre>
+structs.to_dict(<a href="#structs.to_dict-s">s</a>)
+</pre>
+
+Converts a `struct` to a `dict`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="structs.to_dict-s">
+ <td><code>s</code></td>
+ <td>
+ required.
+ <p>
+ A `struct`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/types_doc.md b/docs/types_doc.md
new file mode 100755
index 0000000..038ae0a
--- /dev/null
+++ b/docs/types_doc.md
@@ -0,0 +1,261 @@
+## types.is_list
+
+<pre>
+types.is_list(<a href="#types.is_list-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a list.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_list-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_string
+
+<pre>
+types.is_string(<a href="#types.is_string-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a string.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_string-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_bool
+
+<pre>
+types.is_bool(<a href="#types.is_bool-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a bool.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_bool-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_none
+
+<pre>
+types.is_none(<a href="#types.is_none-v">v</a>)
+</pre>
+
+Returns True if v has the type of None.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_none-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_int
+
+<pre>
+types.is_int(<a href="#types.is_int-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a signed integer.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_int-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_tuple
+
+<pre>
+types.is_tuple(<a href="#types.is_tuple-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a tuple.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_tuple-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_dict
+
+<pre>
+types.is_dict(<a href="#types.is_dict-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a dict.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_dict-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_function
+
+<pre>
+types.is_function(<a href="#types.is_function-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a function.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_function-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## types.is_depset
+
+<pre>
+types.is_depset(<a href="#types.is_depset-v">v</a>)
+</pre>
+
+Returns True if v is an instance of a `depset`.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="types.is_depset-v">
+ <td><code>v</code></td>
+ <td>
+ required.
+ <p>
+ The value whose type should be checked.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/unittest_doc.md b/docs/unittest_doc.md
new file mode 100755
index 0000000..4fbd96f
--- /dev/null
+++ b/docs/unittest_doc.md
@@ -0,0 +1,869 @@
+<a name="#unittest_toolchain"></a>
+## unittest_toolchain
+
+<pre>
+unittest_toolchain(<a href="#unittest_toolchain-name">name</a>, <a href="#unittest_toolchain-failure_templ">failure_templ</a>, <a href="#unittest_toolchain-file_ext">file_ext</a>, <a href="#unittest_toolchain-join_on">join_on</a>, <a href="#unittest_toolchain-success_templ">success_templ</a>)
+</pre>
+
+
+
+### Attributes
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest_toolchain-name">
+ <td><code>name</code></td>
+ <td>
+ <a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
+ <p>
+ A unique name for this target.
+ </p>
+ </td>
+ </tr>
+ <tr id="unittest_toolchain-failure_templ">
+ <td><code>failure_templ</code></td>
+ <td>
+ String; required
+ </td>
+ </tr>
+ <tr id="unittest_toolchain-file_ext">
+ <td><code>file_ext</code></td>
+ <td>
+ String; required
+ </td>
+ </tr>
+ <tr id="unittest_toolchain-join_on">
+ <td><code>join_on</code></td>
+ <td>
+ String; required
+ </td>
+ </tr>
+ <tr id="unittest_toolchain-success_templ">
+ <td><code>success_templ</code></td>
+ <td>
+ String; required
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.make
+
+<pre>
+analysistest.make(<a href="#analysistest.make-impl">impl</a>, <a href="#analysistest.make-expect_failure">expect_failure</a>, <a href="#analysistest.make-attrs">attrs</a>, <a href="#analysistest.make-config_settings">config_settings</a>)
+</pre>
+
+Creates an analysis test rule from its implementation function.
+
+An analysis test verifies the behavior of a "real" rule target by examining
+and asserting on the providers given by the real target.
+
+Each analysis test is defined in an implementation function that must then be
+associated with a rule so that a target can be built. This function handles
+the boilerplate to create and return a test rule and captures the
+implementation function's name so that it can be printed in test feedback.
+
+An example of an analysis test:
+
+```
+def _your_test(ctx):
+ env = analysistest.begin(ctx)
+
+ # Assert statements go here
+
+ return analysistest.end(env)
+
+your_test = analysistest.make(_your_test)
+```
+
+Recall that names of test rules must end in `_test`.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.make-impl">
+ <td><code>impl</code></td>
+ <td>
+ required.
+ <p>
+ The implementation function of the unit test.
+ </p>
+ </td>
+ </tr>
+ <tr id="analysistest.make-expect_failure">
+ <td><code>expect_failure</code></td>
+ <td>
+ optional. default is <code>False</code>
+ <p>
+ If true, the analysis test will expect the target_under_test
+ to fail. Assertions can be made on the underlying failure using asserts.expect_failure
+ </p>
+ </td>
+ </tr>
+ <tr id="analysistest.make-attrs">
+ <td><code>attrs</code></td>
+ <td>
+ optional. default is <code>{}</code>
+ <p>
+ An optional dictionary to supplement the attrs passed to the
+ unit test's `rule()` constructor.
+ </p>
+ </td>
+ </tr>
+ <tr id="analysistest.make-config_settings">
+ <td><code>config_settings</code></td>
+ <td>
+ optional. default is <code>{}</code>
+ <p>
+ A dictionary of configuration settings to change for the target under
+ test and its dependencies. This may be used to essentially change 'build flags' for
+ the target under test, and may thus be utilized to test multiple targets with different
+ flags in a single build
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.begin
+
+<pre>
+analysistest.begin(<a href="#analysistest.begin-ctx">ctx</a>)
+</pre>
+
+Begins a unit test.
+
+This should be the first function called in a unit test implementation
+function. It initializes a "test environment" that is used to collect
+assertion failures so that they can be reported and logged at the end of the
+test.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.begin-ctx">
+ <td><code>ctx</code></td>
+ <td>
+ required.
+ <p>
+ The Skylark context. Pass the implementation function's `ctx` argument
+ in verbatim.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.end
+
+<pre>
+analysistest.end(<a href="#analysistest.end-env">env</a>)
+</pre>
+
+Ends an analysis test and logs the results.
+
+This must be called and returned at the end of an analysis test implementation function so
+that the results are reported.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.end-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `analysistest.begin`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.fail
+
+<pre>
+analysistest.fail(<a href="#analysistest.fail-env">env</a>, <a href="#analysistest.fail-msg">msg</a>)
+</pre>
+
+Unconditionally causes the current test to fail.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.fail-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="analysistest.fail-msg">
+ <td><code>msg</code></td>
+ <td>
+ required.
+ <p>
+ The message to log describing the failure.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.target_actions
+
+<pre>
+analysistest.target_actions(<a href="#analysistest.target_actions-env">env</a>)
+</pre>
+
+Returns a list of actions registered by the target under test.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.target_actions-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `analysistest.begin`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## analysistest.target_under_test
+
+<pre>
+analysistest.target_under_test(<a href="#analysistest.target_under_test-env">env</a>)
+</pre>
+
+Returns the target under test.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="analysistest.target_under_test-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `analysistest.begin`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.expect_failure
+
+<pre>
+asserts.expect_failure(<a href="#asserts.expect_failure-env">env</a>, <a href="#asserts.expect_failure-expected_failure_msg">expected_failure_msg</a>)
+</pre>
+
+Asserts that the target under test has failed with a given error message.
+
+This requires that the analysis test is created with `analysistest.make()` and
+`expect_failures = True` is specified.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.expect_failure-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `analysistest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.expect_failure-expected_failure_msg">
+ <td><code>expected_failure_msg</code></td>
+ <td>
+ optional. default is <code>""</code>
+ <p>
+ The error message to expect as a result of analysis failures.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.equals
+
+<pre>
+asserts.equals(<a href="#asserts.equals-env">env</a>, <a href="#asserts.equals-expected">expected</a>, <a href="#asserts.equals-actual">actual</a>, <a href="#asserts.equals-msg">msg</a>)
+</pre>
+
+Asserts that the given `expected` and `actual` values are equal.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.equals-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.equals-expected">
+ <td><code>expected</code></td>
+ <td>
+ required.
+ <p>
+ The expected value of some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.equals-actual">
+ <td><code>actual</code></td>
+ <td>
+ required.
+ <p>
+ The actual value returned by some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.equals-msg">
+ <td><code>msg</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.false
+
+<pre>
+asserts.false(<a href="#asserts.false-env">env</a>, <a href="#asserts.false-condition">condition</a>, <a href="#asserts.false-msg">msg</a>)
+</pre>
+
+Asserts that the given `condition` is false.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.false-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.false-condition">
+ <td><code>condition</code></td>
+ <td>
+ required.
+ <p>
+ A value that will be evaluated in a Boolean context.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.false-msg">
+ <td><code>msg</code></td>
+ <td>
+ optional. default is <code>"Expected condition to be false, but was true."</code>
+ <p>
+ An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.set_equals
+
+<pre>
+asserts.set_equals(<a href="#asserts.set_equals-env">env</a>, <a href="#asserts.set_equals-expected">expected</a>, <a href="#asserts.set_equals-actual">actual</a>, <a href="#asserts.set_equals-msg">msg</a>)
+</pre>
+
+Asserts that the given `expected` and `actual` sets are equal.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.set_equals-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.set_equals-expected">
+ <td><code>expected</code></td>
+ <td>
+ required.
+ <p>
+ The expected set resulting from some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.set_equals-actual">
+ <td><code>actual</code></td>
+ <td>
+ required.
+ <p>
+ The actual set returned by some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.set_equals-msg">
+ <td><code>msg</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.new_set_equals
+
+<pre>
+asserts.new_set_equals(<a href="#asserts.new_set_equals-env">env</a>, <a href="#asserts.new_set_equals-expected">expected</a>, <a href="#asserts.new_set_equals-actual">actual</a>, <a href="#asserts.new_set_equals-msg">msg</a>)
+</pre>
+
+Asserts that the given `expected` and `actual` sets are equal.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.new_set_equals-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.new_set_equals-expected">
+ <td><code>expected</code></td>
+ <td>
+ required.
+ <p>
+ The expected set resulting from some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.new_set_equals-actual">
+ <td><code>actual</code></td>
+ <td>
+ required.
+ <p>
+ The actual set returned by some computation.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.new_set_equals-msg">
+ <td><code>msg</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## asserts.true
+
+<pre>
+asserts.true(<a href="#asserts.true-env">env</a>, <a href="#asserts.true-condition">condition</a>, <a href="#asserts.true-msg">msg</a>)
+</pre>
+
+Asserts that the given `condition` is true.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="asserts.true-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.true-condition">
+ <td><code>condition</code></td>
+ <td>
+ required.
+ <p>
+ A value that will be evaluated in a Boolean context.
+ </p>
+ </td>
+ </tr>
+ <tr id="asserts.true-msg">
+ <td><code>msg</code></td>
+ <td>
+ optional. default is <code>"Expected condition to be true, but was false."</code>
+ <p>
+ An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## register_unittest_toolchains
+
+<pre>
+register_unittest_toolchains()
+</pre>
+
+Registers the toolchains for unittest users.
+
+
+
+## unittest.make
+
+<pre>
+unittest.make(<a href="#unittest.make-impl">impl</a>, <a href="#unittest.make-attrs">attrs</a>)
+</pre>
+
+Creates a unit test rule from its implementation function.
+
+Each unit test is defined in an implementation function that must then be
+associated with a rule so that a target can be built. This function handles
+the boilerplate to create and return a test rule and captures the
+implementation function's name so that it can be printed in test feedback.
+
+The optional `attrs` argument can be used to define dependencies for this
+test, in order to form unit tests of rules.
+
+An example of a unit test:
+
+```
+def _your_test(ctx):
+ env = unittest.begin(ctx)
+
+ # Assert statements go here
+
+ return unittest.end(env)
+
+your_test = unittest.make(_your_test)
+```
+
+Recall that names of test rules must end in `_test`.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest.make-impl">
+ <td><code>impl</code></td>
+ <td>
+ required.
+ <p>
+ The implementation function of the unit test.
+ </p>
+ </td>
+ </tr>
+ <tr id="unittest.make-attrs">
+ <td><code>attrs</code></td>
+ <td>
+ optional. default is <code>{}</code>
+ <p>
+ An optional dictionary to supplement the attrs passed to the
+ unit test's `rule()` constructor.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## unittest.suite
+
+<pre>
+unittest.suite(<a href="#unittest.suite-name">name</a>, <a href="#unittest.suite-test_rules">test_rules</a>)
+</pre>
+
+Defines a `test_suite` target that contains multiple tests.
+
+After defining your test rules in a `.bzl` file, you need to create targets
+from those rules so that `blaze test` can execute them. Doing this manually
+in a BUILD file would consist of listing each test in your `load` statement
+and then creating each target one by one. To reduce duplication, we recommend
+writing a macro in your `.bzl` file to instantiate all targets, and calling
+that macro from your BUILD file so you only have to load one symbol.
+
+For the case where your unit tests do not take any (non-default) attributes --
+i.e., if your unit tests do not test rules -- you can use this function to
+create the targets and wrap them in a single test_suite target. In your
+`.bzl` file, write:
+
+```
+def your_test_suite():
+ unittest.suite(
+ "your_test_suite",
+ your_test,
+ your_other_test,
+ yet_another_test,
+ )
+```
+
+Then, in your `BUILD` file, simply load the macro and invoke it to have all
+of the targets created:
+
+```
+load("//path/to/your/package:tests.bzl", "your_test_suite")
+your_test_suite()
+```
+
+If you pass _N_ unit test rules to `unittest.suite`, _N_ + 1 targets will be
+created: a `test_suite` target named `${name}` (where `${name}` is the name
+argument passed in here) and targets named `${name}_test_${i}`, where `${i}`
+is the index of the test in the `test_rules` list, which is used to uniquely
+name each target.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest.suite-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ The name of the `test_suite` target, and the prefix of all the test
+ target names.
+ </p>
+ </td>
+ </tr>
+ <tr id="unittest.suite-test_rules">
+ <td><code>test_rules</code></td>
+ <td>
+ optional.
+ <p>
+ A list of test rules defines by `unittest.test`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## unittest.begin
+
+<pre>
+unittest.begin(<a href="#unittest.begin-ctx">ctx</a>)
+</pre>
+
+Begins a unit test.
+
+This should be the first function called in a unit test implementation
+function. It initializes a "test environment" that is used to collect
+assertion failures so that they can be reported and logged at the end of the
+test.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest.begin-ctx">
+ <td><code>ctx</code></td>
+ <td>
+ required.
+ <p>
+ The Skylark context. Pass the implementation function's `ctx` argument
+ in verbatim.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## unittest.end
+
+<pre>
+unittest.end(<a href="#unittest.end-env">env</a>)
+</pre>
+
+Ends a unit test and logs the results.
+
+This must be called and returned at the end of a unit test implementation function so
+that the results are reported.
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest.end-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## unittest.fail
+
+<pre>
+unittest.fail(<a href="#unittest.fail-env">env</a>, <a href="#unittest.fail-msg">msg</a>)
+</pre>
+
+Unconditionally causes the current test to fail.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="unittest.fail-env">
+ <td><code>env</code></td>
+ <td>
+ required.
+ <p>
+ The test environment returned by `unittest.begin`.
+ </p>
+ </td>
+ </tr>
+ <tr id="unittest.fail-msg">
+ <td><code>msg</code></td>
+ <td>
+ required.
+ <p>
+ The message to log describing the failure.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/versions_doc.md b/docs/versions_doc.md
new file mode 100755
index 0000000..d94076c
--- /dev/null
+++ b/docs/versions_doc.md
@@ -0,0 +1,165 @@
+## versions.get
+
+<pre>
+versions.get()
+</pre>
+
+Returns the current Bazel version
+
+
+
+## versions.parse
+
+<pre>
+versions.parse(<a href="#versions.parse-bazel_version">bazel_version</a>)
+</pre>
+
+Parses a version string into a 3-tuple of ints
+
+int tuples can be compared directly using binary operators (<, >).
+
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="versions.parse-bazel_version">
+ <td><code>bazel_version</code></td>
+ <td>
+ required.
+ <p>
+ the Bazel version string
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## versions.check
+
+<pre>
+versions.check(<a href="#versions.check-minimum_bazel_version">minimum_bazel_version</a>, <a href="#versions.check-maximum_bazel_version">maximum_bazel_version</a>, <a href="#versions.check-bazel_version">bazel_version</a>)
+</pre>
+
+Check that the version of Bazel is valid within the specified range.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="versions.check-minimum_bazel_version">
+ <td><code>minimum_bazel_version</code></td>
+ <td>
+ required.
+ <p>
+ minimum version of Bazel expected
+ </p>
+ </td>
+ </tr>
+ <tr id="versions.check-maximum_bazel_version">
+ <td><code>maximum_bazel_version</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ maximum version of Bazel expected
+ </p>
+ </td>
+ </tr>
+ <tr id="versions.check-bazel_version">
+ <td><code>bazel_version</code></td>
+ <td>
+ optional. default is <code>None</code>
+ <p>
+ the version of Bazel to check. Used for testing, defaults to native.bazel_version
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## versions.is_at_most
+
+<pre>
+versions.is_at_most(<a href="#versions.is_at_most-threshold">threshold</a>, <a href="#versions.is_at_most-version">version</a>)
+</pre>
+
+Check that a version is lower or equals to a threshold.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="versions.is_at_most-threshold">
+ <td><code>threshold</code></td>
+ <td>
+ required.
+ <p>
+ the maximum version string
+ </p>
+ </td>
+ </tr>
+ <tr id="versions.is_at_most-version">
+ <td><code>version</code></td>
+ <td>
+ required.
+ <p>
+ the version string to be compared to the threshold
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
+## versions.is_at_least
+
+<pre>
+versions.is_at_least(<a href="#versions.is_at_least-threshold">threshold</a>, <a href="#versions.is_at_least-version">version</a>)
+</pre>
+
+Check that a version is higher or equals to a threshold.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="versions.is_at_least-threshold">
+ <td><code>threshold</code></td>
+ <td>
+ required.
+ <p>
+ the minimum version string
+ </p>
+ </td>
+ </tr>
+ <tr id="versions.is_at_least-version">
+ <td><code>version</code></td>
+ <td>
+ required.
+ <p>
+ the version string to be compared to the threshold
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/docs/write_file_doc.md b/docs/write_file_doc.md
new file mode 100755
index 0000000..d8f098e
--- /dev/null
+++ b/docs/write_file_doc.md
@@ -0,0 +1,79 @@
+## write_file
+
+<pre>
+write_file(<a href="#write_file-name">name</a>, <a href="#write_file-out">out</a>, <a href="#write_file-content">content</a>, <a href="#write_file-is_executable">is_executable</a>, <a href="#write_file-newline">newline</a>, <a href="#write_file-kwargs">kwargs</a>)
+</pre>
+
+Creates a UTF-8 encoded text file.
+
+### Parameters
+
+<table class="params-table">
+ <colgroup>
+ <col class="col-param" />
+ <col class="col-description" />
+ </colgroup>
+ <tbody>
+ <tr id="write_file-name">
+ <td><code>name</code></td>
+ <td>
+ required.
+ <p>
+ Name of the rule.
+ </p>
+ </td>
+ </tr>
+ <tr id="write_file-out">
+ <td><code>out</code></td>
+ <td>
+ required.
+ <p>
+ Path of the output file, relative to this package.
+ </p>
+ </td>
+ </tr>
+ <tr id="write_file-content">
+ <td><code>content</code></td>
+ <td>
+ optional. default is <code>[]</code>
+ <p>
+ A list of strings. Lines of text, the contents of the file.
+ Newlines are added automatically after every line except the last one.
+ </p>
+ </td>
+ </tr>
+ <tr id="write_file-is_executable">
+ <td><code>is_executable</code></td>
+ <td>
+ optional. default is <code>False</code>
+ <p>
+ A boolean. Whether to make the output file executable.
+ When True, the rule's output can be executed using `bazel run` and can
+ be in the srcs of binary and test rules that require executable
+ sources.
+ </p>
+ </td>
+ </tr>
+ <tr id="write_file-newline">
+ <td><code>newline</code></td>
+ <td>
+ optional. default is <code>"auto"</code>
+ <p>
+ one of ["auto", "unix", "windows"]: line endings to use. "auto"
+ for platform-determined, "unix" for LF, and "windows" for CRLF.
+ </p>
+ </td>
+ </tr>
+ <tr id="write_file-kwargs">
+ <td><code>kwargs</code></td>
+ <td>
+ optional.
+ <p>
+ further keyword arguments, e.g. <code>visibility</code>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+
diff --git a/gazelle/bzl/BUILD b/gazelle/bzl/BUILD
new file mode 100644
index 0000000..fea1d51
--- /dev/null
+++ b/gazelle/bzl/BUILD
@@ -0,0 +1,53 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@bazel_gazelle//:def.bzl", "gazelle", "gazelle_binary")
+
+# gazelle:exclude testdata
+
+go_library(
+ name = "bzl",
+ srcs = ["gazelle.go"],
+ importpath = "github.com/bazelbuild/bazel-skylib/gazelle",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@bazel_gazelle//config:go_default_library",
+ "@bazel_gazelle//label:go_default_library",
+ "@bazel_gazelle//language:go_default_library",
+ "@bazel_gazelle//pathtools:go_default_library",
+ "@bazel_gazelle//repo:go_default_library",
+ "@bazel_gazelle//resolve:go_default_library",
+ "@bazel_gazelle//rule:go_default_library",
+ "@com_github_bazelbuild_buildtools//build:go_default_library",
+ ],
+)
+
+go_test(
+ name = "bzl_test",
+ srcs = ["gazelle_test.go"],
+ data = [
+ ":gazelle-skylib",
+ ] + glob([
+ "testdata/**",
+ ]),
+ embed = [":bzl"],
+ deps = [
+ "@bazel_gazelle//testtools:go_default_library",
+ "@io_bazel_rules_go//go/tools/bazel:go_default_library",
+ ],
+)
+
+# This gazelle binary is used exclusively for testing the gazelle language
+# extension and thus only has the skylib language installed.
+gazelle_binary(
+ name = "gazelle-skylib",
+ languages = [":bzl"],
+ visibility = [
+ # Also make the binary available in the root of the repo for use, but
+ # not externally.
+ "//:__pkg__",
+ ],
+)
+
+gazelle(
+ name = "gazelle",
+ gazelle = "//gazelle:gazelle-skylib",
+)
diff --git a/gazelle/bzl/README.md b/gazelle/bzl/README.md
new file mode 100644
index 0000000..8b7cb7b
--- /dev/null
+++ b/gazelle/bzl/README.md
@@ -0,0 +1,13 @@
+# Gazelle
+
+Gazelle is a `BUILD` file generator for Bazel. This directory contains a
+language extension for the Gazelle generator that allows it to automatically
+parse valid `bzl_library` targets for all `.bzl` files in a repo in which it
+runs. It will additionally include a `deps` entry tracking every `.bzl` that is
+`load`ed into the primary file.
+
+This can be used, for example, to generate
+[`stardoc`](https://github.com/bazelbuild/stardoc) documentation for your
+`.bzl` files, both simplify the task of and improve the quality of
+documentation.
+
diff --git a/gazelle/bzl/gazelle.go b/gazelle/bzl/gazelle.go
new file mode 100644
index 0000000..f948390
--- /dev/null
+++ b/gazelle/bzl/gazelle.go
@@ -0,0 +1,315 @@
+/* Copyright 2020 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.
+*/
+
+// Package bzl generates a `bzl_library` target for every `.bzl` file in
+// each package.
+//
+// The `bzl_library` rule is provided by
+// https://github.com/bazelbuild/bazel-skylib.
+//
+// This extension is experimental and subject to change. It is not included
+// in the default Gazelle binary.
+package bzl
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/label"
+ "github.com/bazelbuild/bazel-gazelle/language"
+ "github.com/bazelbuild/bazel-gazelle/pathtools"
+ "github.com/bazelbuild/bazel-gazelle/repo"
+ "github.com/bazelbuild/bazel-gazelle/resolve"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+
+ "github.com/bazelbuild/buildtools/build"
+)
+
+const languageName = "starlark"
+const fileType = ".bzl"
+
+var ignoreSuffix = suffixes{
+ "_tests.bzl",
+ "_test.bzl",
+}
+
+type suffixes []string
+
+func (s suffixes) Matches(test string) bool {
+ for _, v := range s {
+ if strings.HasSuffix(test, v) {
+ return true
+ }
+ }
+ return false
+}
+
+type bzlLibraryLang struct{}
+
+// NewLanguage is called by Gazelle to install this language extension in a binary.
+func NewLanguage() language.Language {
+ return &bzlLibraryLang{}
+}
+
+// Name returns the name of the language. This should be a prefix of the
+// kinds of rules generated by the language, e.g., "go" for the Go extension
+// since it generates "go_library" rules.
+func (*bzlLibraryLang) Name() string { return languageName }
+
+// The following methods are implemented to satisfy the
+// https://pkg.go.dev/github.com/bazelbuild/bazel-gazelle/resolve?tab=doc#Resolver
+// interface, but are otherwise unused.
+func (*bzlLibraryLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
+func (*bzlLibraryLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
+func (*bzlLibraryLang) KnownDirectives() []string { return nil }
+func (*bzlLibraryLang) Configure(c *config.Config, rel string, f *rule.File) {}
+
+// Kinds returns a map of maps rule names (kinds) and information on how to
+// match and merge attributes that may be found in rules of those kinds. All
+// kinds of rules generated for this language may be found here.
+func (*bzlLibraryLang) Kinds() map[string]rule.KindInfo {
+ return kinds
+}
+
+// Loads returns .bzl files and symbols they define. Every rule generated by
+// GenerateRules, now or in the past, should be loadable from one of these
+// files.
+func (*bzlLibraryLang) Loads() []rule.LoadInfo {
+ return []rule.LoadInfo{{
+ Name: "@bazel_skylib//:bzl_library.bzl",
+ Symbols: []string{"bzl_library"},
+ }}
+}
+
+// Fix repairs deprecated usage of language-specific rules in f. This is
+// called before the file is indexed. Unless c.ShouldFix is true, fixes
+// that delete or rename rules should not be performed.
+func (*bzlLibraryLang) Fix(c *config.Config, f *rule.File) {}
+
+// Imports returns a list of ImportSpecs that can be used to import the rule
+// r. This is used to populate RuleIndex.
+//
+// If nil is returned, the rule will not be indexed. If any non-nil slice is
+// returned, including an empty slice, the rule will be indexed.
+func (b *bzlLibraryLang) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
+ srcs := r.AttrStrings("srcs")
+ imports := make([]resolve.ImportSpec, len(srcs))
+
+ for _, src := range srcs {
+ spec := resolve.ImportSpec{
+ // Lang is the language in which the import string appears (this should
+ // match Resolver.Name).
+ Lang: languageName,
+ // Imp is an import string for the library.
+ Imp: fmt.Sprintf("//%s:%s", f.Pkg, src),
+ }
+
+ imports = append(imports, spec)
+ }
+
+ return imports
+}
+
+// Embeds returns a list of labels of rules that the given rule embeds. If
+// a rule is embedded by another importable rule of the same language, only
+// the embedding rule will be indexed. The embedding rule will inherit
+// the imports of the embedded rule.
+// Since SkyLark doesn't support embedding this should always return nil.
+func (*bzlLibraryLang) Embeds(r *rule.Rule, from label.Label) []label.Label { return nil }
+
+// Resolve translates imported libraries for a given rule into Bazel
+// dependencies. Information about imported libraries is returned for each
+// rule generated by language.GenerateRules in
+// language.GenerateResult.Imports. Resolve generates a "deps" attribute (or
+// the appropriate language-specific equivalent) for each import according to
+// language-specific rules and heuristics.
+func (*bzlLibraryLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, importsRaw interface{}, from label.Label) {
+ imports := importsRaw.([]string)
+
+ r.DelAttr("deps")
+
+ if len(imports) == 0 {
+ return
+ }
+
+ deps := make([]string, 0, len(imports))
+ for _, imp := range imports {
+ if strings.HasPrefix(imp, "@") || !c.IndexLibraries {
+ // This is a dependency that is external to the current repo, or indexing
+ // is disabled so take a guess at what hte target name should be.
+ deps = append(deps, strings.TrimSuffix(imp, fileType))
+ } else {
+ res := resolve.ImportSpec{
+ Lang: languageName,
+ Imp: imp,
+ }
+ matches := ix.FindRulesByImport(res, languageName)
+
+ if len(matches) == 0 {
+ log.Printf("%s: %q was not found in dependency index. Skipping. This may result in an incomplete deps section and require manual BUILD file intervention.\n", from.String(), imp)
+ }
+
+ for _, m := range matches {
+ deps = append(deps, m.Label.String())
+ }
+ }
+ }
+
+ sort.Strings(deps)
+ if len(deps) > 0 {
+ r.SetAttr("deps", deps)
+ }
+}
+
+var kinds = map[string]rule.KindInfo{
+ "bzl_library": {
+ NonEmptyAttrs: map[string]bool{"srcs": true, "deps": true},
+ MergeableAttrs: map[string]bool{"srcs": true},
+ },
+}
+
+// GenerateRules extracts build metadata from source files in a directory.
+// GenerateRules is called in each directory where an update is requested
+// in depth-first post-order.
+//
+// args contains the arguments for GenerateRules. This is passed as a
+// struct to avoid breaking implementations in the future when new
+// fields are added.
+//
+// A GenerateResult struct is returned. Optional fields may be added to this
+// type in the future.
+//
+// Any non-fatal errors this function encounters should be logged using
+// log.Print.
+func (*bzlLibraryLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
+ var rules []*rule.Rule
+ var imports []interface{}
+ for _, f := range append(args.RegularFiles, args.GenFiles...) {
+ if !isBzlSourceFile(f) {
+ continue
+ }
+ name := strings.TrimSuffix(f, fileType)
+ r := rule.NewRule("bzl_library", name)
+
+ r.SetAttr("srcs", []string{f})
+
+ if args.File == nil || !args.File.HasDefaultVisibility() {
+ inPrivateDir := pathtools.Index(args.Rel, "private") >= 0
+ if !inPrivateDir {
+ r.SetAttr("visibility", []string{"//visibility:public"})
+ }
+ }
+
+ fullPath := filepath.Join(args.Dir, f)
+ loads, err := getBzlFileLoads(fullPath)
+ if err != nil {
+ log.Printf("%s: contains syntax errors: %v", fullPath, err)
+ // Don't `continue` since it is reasonable to create a target even
+ // without deps.
+ }
+
+ rules = append(rules, r)
+ imports = append(imports, loads)
+ }
+
+ return language.GenerateResult{
+ Gen: rules,
+ Imports: imports,
+ Empty: generateEmpty(args),
+ }
+}
+
+func getBzlFileLoads(path string) ([]string, error) {
+ f, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
+ }
+ ast, err := build.ParseBuild(path, f)
+ if err != nil {
+ return nil, fmt.Errorf("build.Parse(%q) error: %v", f, err)
+ }
+
+ var loads []string
+ build.WalkOnce(ast, func(expr *build.Expr) {
+ n := *expr
+ if l, ok := n.(*build.LoadStmt); ok {
+ loads = append(loads, l.Module.Value)
+ }
+ })
+ sort.Strings(loads)
+
+ return loads, nil
+}
+
+func isBzlSourceFile(f string) bool {
+ return strings.HasSuffix(f, fileType) && !ignoreSuffix.Matches(f)
+}
+
+// generateEmpty generates the list of rules that don't need to exist in the
+// BUILD file any more.
+// For each bzl_library rule in args.File that only has srcs that aren't in
+// args.RegularFiles or args.GenFiles, add a bzl_library with no srcs or deps.
+// That will let Gazelle delete bzl_library rules after the corresponding .bzl
+// files are deleted.
+func generateEmpty(args language.GenerateArgs) []*rule.Rule {
+ var ret []*rule.Rule
+ if args.File == nil {
+ return ret
+ }
+ for _, r := range args.File.Rules {
+ if r.Kind() != "bzl_library" {
+ continue
+ }
+ name := r.AttrString("name")
+
+ exists := make(map[string]bool)
+ for _, f := range args.RegularFiles {
+ exists[f] = true
+ }
+ for _, f := range args.GenFiles {
+ exists[f] = true
+ }
+ for _, r := range args.File.Rules {
+ srcsExist := false
+ for _, f := range r.AttrStrings("srcs") {
+ if exists[f] {
+ srcsExist = true
+ break
+ }
+ }
+ if !srcsExist {
+ ret = append(ret, rule.NewRule("bzl_library", name))
+ }
+ }
+ }
+ return ret
+}
+
+type srcsList []string
+
+func (s srcsList) Contains(m string) bool {
+ for _, e := range s {
+ if e == m {
+ return true
+ }
+ }
+ return false
+}
diff --git a/gazelle/bzl/gazelle_test.go b/gazelle/bzl/gazelle_test.go
new file mode 100644
index 0000000..e4f59e8
--- /dev/null
+++ b/gazelle/bzl/gazelle_test.go
@@ -0,0 +1,143 @@
+package bzl
+
+/* Copyright 2020 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.
+*/
+
+import (
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/bazelbuild/bazel-gazelle/testtools"
+ "github.com/bazelbuild/rules_go/go/tools/bazel"
+)
+
+var gazellePath = findGazelle()
+
+const testDataPath = "gazelle/bzl/testdata/"
+
+// TestGazelleBinary runs a gazelle binary with starlib installed on each
+// directory in `testdata/*`. Please see `testdata/README.md` for more
+// information on each test.
+func TestGazelleBinary(t *testing.T) {
+ tests := map[string][]bazel.RunfileEntry{}
+
+ files, err := bazel.ListRunfiles()
+ if err != nil {
+ t.Fatalf("bazel.ListRunfiles() error: %v", err)
+ }
+ for _, f := range files {
+ if strings.HasPrefix(f.ShortPath, testDataPath) {
+ relativePath := strings.TrimPrefix(f.ShortPath, testDataPath)
+ parts := strings.SplitN(relativePath, "/", 2)
+ if len(parts) < 2 {
+ // This file is not a part of a testcase since it must be in a dir that
+ // is the test case and then have a path inside of that.
+ continue
+ }
+
+ tests[parts[0]] = append(tests[parts[0]], f)
+ }
+ }
+ if len(tests) == 0 {
+ t.Fatal("no tests found")
+ }
+
+ for testName, files := range tests {
+ testPath(t, testName, files)
+ }
+}
+
+func testPath(t *testing.T, name string, files []bazel.RunfileEntry) {
+ t.Run(name, func(t *testing.T) {
+ var inputs []testtools.FileSpec
+ var goldens []testtools.FileSpec
+
+ for _, f := range files {
+ path := f.Path
+ trim := testDataPath + name + "/"
+ shortPath := strings.TrimPrefix(f.ShortPath, trim)
+ info, err := os.Stat(path)
+ if err != nil {
+ t.Fatalf("os.Stat(%q) error: %v", path, err)
+ }
+
+ // Skip dirs.
+ if info.IsDir() {
+ continue
+ }
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ t.Errorf("ioutil.ReadFile(%q) error: %v", path, err)
+ }
+
+ // Now trim the common prefix off.
+ if strings.HasSuffix(shortPath, ".in") {
+ inputs = append(inputs, testtools.FileSpec{
+ Path: strings.TrimSuffix(shortPath, ".in"),
+ Content: string(content),
+ })
+ } else if strings.HasSuffix(shortPath, ".out") {
+ goldens = append(goldens, testtools.FileSpec{
+ Path: strings.TrimSuffix(shortPath, ".out"),
+ Content: string(content),
+ })
+ } else {
+ inputs = append(inputs, testtools.FileSpec{
+ Path: shortPath,
+ Content: string(content),
+ })
+ goldens = append(goldens, testtools.FileSpec{
+ Path: shortPath,
+ Content: string(content),
+ })
+ }
+ }
+
+ dir, cleanup := testtools.CreateFiles(t, inputs)
+ defer cleanup()
+
+ cmd := exec.Command(gazellePath, "-build_file_name=BUILD")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Dir = dir
+ if err := cmd.Run(); err != nil {
+ t.Fatal(err)
+ }
+
+ testtools.CheckFiles(t, dir, goldens)
+ if t.Failed() {
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ t.Logf("%q exists", path)
+ return nil
+ })
+ }
+ })
+}
+
+func findGazelle() string {
+ gazellePath, ok := bazel.FindBinary("gazelle/bzl", "gazelle-skylib")
+ if !ok {
+ panic("could not find gazelle binary")
+ }
+ return gazellePath
+}
diff --git a/gazelle/bzl/testdata/README.md b/gazelle/bzl/testdata/README.md
new file mode 100644
index 0000000..a6a3603
--- /dev/null
+++ b/gazelle/bzl/testdata/README.md
@@ -0,0 +1,56 @@
+# Gazelle test cases
+
+This directory contains a suite of test cases for the Skylark language plugin
+for Gazelle.
+
+Please note that there are no `BUILD` or `BUILD.bazel` files in subdirs, insted
+there are `BUILD.in` and `BUILD.out` describing what the `BUILD` should look
+like initially and what the `BUILD` file should look like after the run. These
+names are special because they are not recognized by Bazel as a proper `BUILD`
+file, and therefore are included in the data dependency by the recursive data
+glob in `//gazelle:go_default_test`. If you would like to include any extremely
+complicated tests that contain proper `BUILD` files you will need to manually
+add them to the `//gazelle:go_default_test` target's `data` section.
+
+## `simple`
+
+Simple is a base test case that was used to validate the parser.
+
+## `nobuildfiles`
+
+A test just like `simple` that has no `BUILD` files at the beginning.
+
+## `import`
+
+Import is a test case that imports a `.bzl` from the same directory.
+
+## `multidir`
+
+Multidir is a test that has a `.bzl` that imports from a different dirrectory.
+
+## `tests`
+
+Using the skylib as an example, this test has `.bzl` files that end in
+`_tests.bzl` which are `load`ed into `BUILD` files and never imported by
+another repo.
+
+## `private`
+
+Using the skylib as an example, this test has `.bzl` files that live in a
+directory called `private` which is used to indicate that they are repo private.
+Note that this is distict from the expectations of go's `internal` where the
+relative position in the hierarchy determines the visibility.
+
+## `defaultvisibility`
+
+If the package declares a `default_visibility` the generated `bzl_library`
+should not set its own `visibility`.
+
+## `external`
+
+This test demonstrates that if you load from another repo, it is able to
+generate a `deps` entry for the dependency.
+
+## `empty`
+
+Gazelle has the ability to remove old and unused targets. Test that.
diff --git a/gazelle/bzl/testdata/defaultvisibility/BUILD.in b/gazelle/bzl/testdata/defaultvisibility/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/BUILD.in
diff --git a/gazelle/bzl/testdata/defaultvisibility/BUILD.out b/gazelle/bzl/testdata/defaultvisibility/BUILD.out
new file mode 100644
index 0000000..44c1495
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/BUILD.out
@@ -0,0 +1,8 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//nested/dir:bar"],
+)
diff --git a/gazelle/bzl/testdata/defaultvisibility/WORKSPACE b/gazelle/bzl/testdata/defaultvisibility/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/WORKSPACE
diff --git a/gazelle/bzl/testdata/defaultvisibility/foo.bzl b/gazelle/bzl/testdata/defaultvisibility/foo.bzl
new file mode 100644
index 0000000..1191303
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/foo.bzl
@@ -0,0 +1,7 @@
+"""
+Doc string
+"""
+
+load("//nested/dir:bar.bzl", "func")
+
+func()
diff --git a/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.in b/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.in
new file mode 100644
index 0000000..830932a
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.in
@@ -0,0 +1 @@
+package(default_visibility = ["//:__pkg__"])
diff --git a/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.out b/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.out
new file mode 100644
index 0000000..8394b9c
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/nested/dir/BUILD.out
@@ -0,0 +1,8 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+package(default_visibility = ["//:__pkg__"])
+
+bzl_library(
+ name = "bar",
+ srcs = ["bar.bzl"],
+)
diff --git a/gazelle/bzl/testdata/defaultvisibility/nested/dir/bar.bzl b/gazelle/bzl/testdata/defaultvisibility/nested/dir/bar.bzl
new file mode 100644
index 0000000..8ab1fcb
--- /dev/null
+++ b/gazelle/bzl/testdata/defaultvisibility/nested/dir/bar.bzl
@@ -0,0 +1,6 @@
+"""
+Doc string
+"""
+
+def asdf():
+ pass
diff --git a/gazelle/bzl/testdata/empty/BUILD.in b/gazelle/bzl/testdata/empty/BUILD.in
new file mode 100644
index 0000000..e1e154c
--- /dev/null
+++ b/gazelle/bzl/testdata/empty/BUILD.in
@@ -0,0 +1,13 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "weirdly_named_target_that_will_be_removed",
+ srcs = ["nonexistant.bzl"],
+ visibility = ["//visibility:public"],
+)
+
+bzl_library(
+ name = "weirdly_named_target_that_will_be_renamed",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/empty/BUILD.out b/gazelle/bzl/testdata/empty/BUILD.out
new file mode 100644
index 0000000..ac0e990
--- /dev/null
+++ b/gazelle/bzl/testdata/empty/BUILD.out
@@ -0,0 +1,7 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/empty/WORKSPACE b/gazelle/bzl/testdata/empty/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/empty/WORKSPACE
diff --git a/gazelle/bzl/testdata/empty/foo.bzl b/gazelle/bzl/testdata/empty/foo.bzl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/empty/foo.bzl
diff --git a/gazelle/bzl/testdata/external/BUILD.in b/gazelle/bzl/testdata/external/BUILD.in
new file mode 100644
index 0000000..e7bc836
--- /dev/null
+++ b/gazelle/bzl/testdata/external/BUILD.in
@@ -0,0 +1,8 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
diff --git a/gazelle/bzl/testdata/external/BUILD.out b/gazelle/bzl/testdata/external/BUILD.out
new file mode 100644
index 0000000..c70685f
--- /dev/null
+++ b/gazelle/bzl/testdata/external/BUILD.out
@@ -0,0 +1,15 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["@external_repo//path/to:file"],
+)
diff --git a/gazelle/bzl/testdata/external/WORKSPACE b/gazelle/bzl/testdata/external/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/external/WORKSPACE
diff --git a/gazelle/bzl/testdata/external/foo.bzl b/gazelle/bzl/testdata/external/foo.bzl
new file mode 100644
index 0000000..ad548a5
--- /dev/null
+++ b/gazelle/bzl/testdata/external/foo.bzl
@@ -0,0 +1,7 @@
+"""
+Test sample code.
+"""
+
+load("@external_repo//path/to:file.bzl", "func")
+
+func()
diff --git a/gazelle/bzl/testdata/import/BUILD.in b/gazelle/bzl/testdata/import/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/import/BUILD.in
diff --git a/gazelle/bzl/testdata/import/BUILD.out b/gazelle/bzl/testdata/import/BUILD.out
new file mode 100644
index 0000000..a19a11a
--- /dev/null
+++ b/gazelle/bzl/testdata/import/BUILD.out
@@ -0,0 +1,14 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "bar",
+ srcs = ["bar.bzl"],
+ visibility = ["//visibility:public"],
+)
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//:bar"],
+)
diff --git a/gazelle/bzl/testdata/import/WORKSPACE b/gazelle/bzl/testdata/import/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/import/WORKSPACE
diff --git a/gazelle/bzl/testdata/import/bar.bzl b/gazelle/bzl/testdata/import/bar.bzl
new file mode 100644
index 0000000..7ffea51
--- /dev/null
+++ b/gazelle/bzl/testdata/import/bar.bzl
@@ -0,0 +1,6 @@
+"""
+Doc string
+"""
+
+def func():
+ pass
diff --git a/gazelle/bzl/testdata/import/foo.bzl b/gazelle/bzl/testdata/import/foo.bzl
new file mode 100644
index 0000000..eb8d33c
--- /dev/null
+++ b/gazelle/bzl/testdata/import/foo.bzl
@@ -0,0 +1,7 @@
+"""
+Doc string
+"""
+
+load("//:bar.bzl", "func")
+
+func()
diff --git a/gazelle/bzl/testdata/multidir/BUILD.in b/gazelle/bzl/testdata/multidir/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/BUILD.in
diff --git a/gazelle/bzl/testdata/multidir/BUILD.out b/gazelle/bzl/testdata/multidir/BUILD.out
new file mode 100644
index 0000000..44c1495
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/BUILD.out
@@ -0,0 +1,8 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//nested/dir:bar"],
+)
diff --git a/gazelle/bzl/testdata/multidir/WORKSPACE b/gazelle/bzl/testdata/multidir/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/WORKSPACE
diff --git a/gazelle/bzl/testdata/multidir/foo.bzl b/gazelle/bzl/testdata/multidir/foo.bzl
new file mode 100644
index 0000000..1191303
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/foo.bzl
@@ -0,0 +1,7 @@
+"""
+Doc string
+"""
+
+load("//nested/dir:bar.bzl", "func")
+
+func()
diff --git a/gazelle/bzl/testdata/multidir/nested/dir/BUILD.out b/gazelle/bzl/testdata/multidir/nested/dir/BUILD.out
new file mode 100644
index 0000000..07ec8ce
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/nested/dir/BUILD.out
@@ -0,0 +1,7 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "bar",
+ srcs = ["bar.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/multidir/nested/dir/bar.bzl b/gazelle/bzl/testdata/multidir/nested/dir/bar.bzl
new file mode 100644
index 0000000..7ffea51
--- /dev/null
+++ b/gazelle/bzl/testdata/multidir/nested/dir/bar.bzl
@@ -0,0 +1,6 @@
+"""
+Doc string
+"""
+
+def func():
+ pass
diff --git a/gazelle/bzl/testdata/nobuildfiles/BUILD.out b/gazelle/bzl/testdata/nobuildfiles/BUILD.out
new file mode 100644
index 0000000..ac0e990
--- /dev/null
+++ b/gazelle/bzl/testdata/nobuildfiles/BUILD.out
@@ -0,0 +1,7 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/nobuildfiles/WORKSPACE b/gazelle/bzl/testdata/nobuildfiles/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/nobuildfiles/WORKSPACE
diff --git a/gazelle/bzl/testdata/nobuildfiles/foo.bzl b/gazelle/bzl/testdata/nobuildfiles/foo.bzl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/nobuildfiles/foo.bzl
diff --git a/gazelle/bzl/testdata/private/BUILD.in b/gazelle/bzl/testdata/private/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/private/BUILD.in
diff --git a/gazelle/bzl/testdata/private/BUILD.out b/gazelle/bzl/testdata/private/BUILD.out
new file mode 100644
index 0000000..e65b00f
--- /dev/null
+++ b/gazelle/bzl/testdata/private/BUILD.out
@@ -0,0 +1,8 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//private:bar"],
+)
diff --git a/gazelle/bzl/testdata/private/WORKSPACE b/gazelle/bzl/testdata/private/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/private/WORKSPACE
diff --git a/gazelle/bzl/testdata/private/foo.bzl b/gazelle/bzl/testdata/private/foo.bzl
new file mode 100644
index 0000000..7e1dcd1
--- /dev/null
+++ b/gazelle/bzl/testdata/private/foo.bzl
@@ -0,0 +1,7 @@
+"""
+Test sample code.
+"""
+
+load("//private:bar.bzl", "func")
+
+func()
diff --git a/gazelle/bzl/testdata/private/private/BUILD.out b/gazelle/bzl/testdata/private/private/BUILD.out
new file mode 100644
index 0000000..eb2e935
--- /dev/null
+++ b/gazelle/bzl/testdata/private/private/BUILD.out
@@ -0,0 +1,6 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "bar",
+ srcs = ["bar.bzl"],
+)
diff --git a/gazelle/bzl/testdata/private/private/bar.bzl b/gazelle/bzl/testdata/private/private/bar.bzl
new file mode 100644
index 0000000..c8a4871
--- /dev/null
+++ b/gazelle/bzl/testdata/private/private/bar.bzl
@@ -0,0 +1,6 @@
+"""
+Test sample code.
+"""
+
+def func():
+ pass
diff --git a/gazelle/bzl/testdata/simple/BUILD.in b/gazelle/bzl/testdata/simple/BUILD.in
new file mode 100644
index 0000000..e267b5a
--- /dev/null
+++ b/gazelle/bzl/testdata/simple/BUILD.in
@@ -0,0 +1,6 @@
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
diff --git a/gazelle/bzl/testdata/simple/BUILD.out b/gazelle/bzl/testdata/simple/BUILD.out
new file mode 100644
index 0000000..3c71d56
--- /dev/null
+++ b/gazelle/bzl/testdata/simple/BUILD.out
@@ -0,0 +1,14 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/simple/WORKSPACE b/gazelle/bzl/testdata/simple/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/simple/WORKSPACE
diff --git a/gazelle/bzl/testdata/simple/foo.bzl b/gazelle/bzl/testdata/simple/foo.bzl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/simple/foo.bzl
diff --git a/gazelle/bzl/testdata/tests/BUILD.in b/gazelle/bzl/testdata/tests/BUILD.in
new file mode 100644
index 0000000..e267b5a
--- /dev/null
+++ b/gazelle/bzl/testdata/tests/BUILD.in
@@ -0,0 +1,6 @@
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
diff --git a/gazelle/bzl/testdata/tests/BUILD.out b/gazelle/bzl/testdata/tests/BUILD.out
new file mode 100644
index 0000000..3c71d56
--- /dev/null
+++ b/gazelle/bzl/testdata/tests/BUILD.out
@@ -0,0 +1,14 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+# Some comment to be preserved
+
+filegroup(
+ name = "allfiles",
+ srcs = glob(["**"]),
+)
+
+bzl_library(
+ name = "foo",
+ srcs = ["foo.bzl"],
+ visibility = ["//visibility:public"],
+)
diff --git a/gazelle/bzl/testdata/tests/WORKSPACE b/gazelle/bzl/testdata/tests/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/tests/WORKSPACE
diff --git a/gazelle/bzl/testdata/tests/foo.bzl b/gazelle/bzl/testdata/tests/foo.bzl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/tests/foo.bzl
diff --git a/gazelle/bzl/testdata/tests/foo_tests.bzl b/gazelle/bzl/testdata/tests/foo_tests.bzl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/bzl/testdata/tests/foo_tests.bzl
diff --git a/internal_deps.bzl b/internal_deps.bzl
new file mode 100644
index 0000000..c7dacd8
--- /dev/null
+++ b/internal_deps.bzl
@@ -0,0 +1,27 @@
+# Copyright 2019 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.
+
+"""Dependencies that are needed for running skylib tests."""
+
+load(
+ "@bazel_federation//:repositories.bzl",
+ "bazel",
+ "bazel_stardoc",
+ "rules_pkg",
+)
+
+def bazel_skylib_internal_deps():
+ bazel()
+ bazel_stardoc()
+ rules_pkg()
diff --git a/internal_setup.bzl b/internal_setup.bzl
new file mode 100644
index 0000000..e3189ed
--- /dev/null
+++ b/internal_setup.bzl
@@ -0,0 +1,18 @@
+# Copyright 2019 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.
+
+"""Setup function that must be invoked before running skylib tests."""
+
+def bazel_skylib_internal_setup():
+ pass # placeholder function for the federation
diff --git a/lib.bzl b/lib.bzl
new file mode 100644
index 0000000..268cd6b
--- /dev/null
+++ b/lib.bzl
@@ -0,0 +1,20 @@
+# Copyright 2017 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.
+
+"""Index from which multiple modules can be loaded."""
+
+fail(
+ "lib.bzl has been removed, please directly load the bzl file(s) of the" +
+ " module(s) needed as it is more efficient.",
+)
diff --git a/lib/BUILD b/lib/BUILD
new file mode 100644
index 0000000..26a6120
--- /dev/null
+++ b/lib/BUILD
@@ -0,0 +1,103 @@
+load("//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+# export bzl files for the documentation
+exports_files(
+ glob(["*.bzl"]),
+ visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+ name = "collections",
+ srcs = ["collections.bzl"],
+)
+
+bzl_library(
+ name = "dicts",
+ srcs = ["dicts.bzl"],
+)
+
+bzl_library(
+ name = "partial",
+ srcs = ["partial.bzl"],
+)
+
+bzl_library(
+ name = "paths",
+ srcs = ["paths.bzl"],
+)
+
+bzl_library(
+ name = "selects",
+ srcs = ["selects.bzl"],
+)
+
+bzl_library(
+ name = "sets",
+ srcs = ["sets.bzl"],
+ deps = [
+ ":new_sets",
+ ],
+)
+
+bzl_library(
+ name = "new_sets",
+ srcs = ["new_sets.bzl"],
+ deps = [
+ ":dicts",
+ ],
+)
+
+bzl_library(
+ name = "shell",
+ srcs = ["shell.bzl"],
+)
+
+bzl_library(
+ name = "structs",
+ srcs = ["structs.bzl"],
+)
+
+bzl_library(
+ name = "types",
+ srcs = ["types.bzl"],
+)
+
+bzl_library(
+ name = "unittest",
+ srcs = ["unittest.bzl"],
+ deps = [
+ ":new_sets",
+ ":sets",
+ ":types",
+ ],
+)
+
+bzl_library(
+ name = "versions",
+ srcs = ["versions.bzl"],
+)
+
+filegroup(
+ name = "test_deps",
+ testonly = True,
+ srcs = ["BUILD"] + glob(["*.bzl"]),
+)
+
+# The files needed for distribution
+filegroup(
+ name = "distribution",
+ srcs = glob(["*"]),
+ visibility = [
+ "//:__pkg__",
+ "//distribution:__pkg__",
+ ],
+)
+
+bzl_library(
+ name = "old_sets",
+ srcs = ["old_sets.bzl"],
+)
diff --git a/lib/collections.bzl b/lib/collections.bzl
new file mode 100644
index 0000000..f41eea2
--- /dev/null
+++ b/lib/collections.bzl
@@ -0,0 +1,72 @@
+# Copyright 2017 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.
+
+"""Skylib module containing functions that operate on collections."""
+
+def _after_each(separator, iterable):
+ """Inserts `separator` after each item in `iterable`.
+
+ Args:
+ separator: The value to insert after each item in `iterable`.
+ iterable: The list into which to intersperse the separator.
+
+ Returns:
+ A new list with `separator` after each item in `iterable`.
+ """
+ result = []
+ for x in iterable:
+ result.append(x)
+ result.append(separator)
+
+ return result
+
+def _before_each(separator, iterable):
+ """Inserts `separator` before each item in `iterable`.
+
+ Args:
+ separator: The value to insert before each item in `iterable`.
+ iterable: The list into which to intersperse the separator.
+
+ Returns:
+ A new list with `separator` before each item in `iterable`.
+ """
+ result = []
+ for x in iterable:
+ result.append(separator)
+ result.append(x)
+
+ return result
+
+def _uniq(iterable):
+ """Returns a list of unique elements in `iterable`.
+
+ Requires all the elements to be hashable.
+
+ Args:
+ iterable: An iterable to filter.
+
+ Returns:
+ A new list with all unique elements from `iterable`.
+ """
+ unique_elements = {element: None for element in iterable}
+
+ # list() used here for python3 compatibility.
+ # TODO(bazel-team): Remove when testing frameworks no longer require python compatibility.
+ return list(unique_elements.keys())
+
+collections = struct(
+ after_each = _after_each,
+ before_each = _before_each,
+ uniq = _uniq,
+)
diff --git a/lib/dicts.bzl b/lib/dicts.bzl
new file mode 100644
index 0000000..3f8e661
--- /dev/null
+++ b/lib/dicts.bzl
@@ -0,0 +1,43 @@
+# Copyright 2017 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.
+
+"""Skylib module containing functions that operate on dictionaries."""
+
+def _add(*dictionaries, **kwargs):
+ """Returns a new `dict` that has all the entries of the given dictionaries.
+
+ If the same key is present in more than one of the input dictionaries, the
+ last of them in the argument list overrides any earlier ones.
+
+ This function is designed to take zero or one arguments as well as multiple
+ dictionaries, so that it follows arithmetic identities and callers can avoid
+ special cases for their inputs: the sum of zero dictionaries is the empty
+ dictionary, and the sum of a single dictionary is a copy of itself.
+
+ Args:
+ *dictionaries: Zero or more dictionaries to be added.
+ **kwargs: Additional dictionary passed as keyword args.
+
+ Returns:
+ A new `dict` that has all the entries of the given dictionaries.
+ """
+ result = {}
+ for d in dictionaries:
+ result.update(d)
+ result.update(kwargs)
+ return result
+
+dicts = struct(
+ add = _add,
+)
diff --git a/lib/new_sets.bzl b/lib/new_sets.bzl
new file mode 100644
index 0000000..cd90a30
--- /dev/null
+++ b/lib/new_sets.bzl
@@ -0,0 +1,243 @@
+# Copyright 2018 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.
+
+"""Skylib module containing common hash-set algorithms.
+
+ An empty set can be created using: `sets.make()`, or it can be created with some starting values
+ if you pass it an sequence: `sets.make([1, 2, 3])`. This returns a struct containing all of the
+ values as keys in a dictionary - this means that all passed in values must be hashable. The
+ values in the set can be retrieved using `sets.to_list(my_set)`.
+
+ An arbitrary object can be tested whether it is a set generated by `sets.make()` or not with the
+ `types.is_set()` method in types.bzl.
+"""
+
+load(":dicts.bzl", "dicts")
+
+def _make(elements = None):
+ """Creates a new set.
+
+ All elements must be hashable.
+
+ Args:
+ elements: Optional sequence to construct the set out of.
+
+ Returns:
+ A set containing the passed in values.
+ """
+
+ # If you change the structure of a set, you need to also update the _is_set method
+ # in types.bzl.
+ elements = elements if elements else []
+ return struct(_values = {e: None for e in elements})
+
+def _copy(s):
+ """Creates a new set from another set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+
+ Returns:
+ A new set containing the same elements as `s`.
+ """
+ return struct(_values = dict(s._values))
+
+def _to_list(s):
+ """Creates a list from the values in the set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+
+ Returns:
+ A list of values inserted into the set.
+ """
+ return s._values.keys()
+
+def _insert(s, e):
+ """Inserts an element into the set.
+
+ Element must be hashable. This mutates the original set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+ e: The element to be inserted.
+
+ Returns:
+ The set `s` with `e` included.
+ """
+ s._values[e] = None
+ return s
+
+def _remove(s, e):
+ """Removes an element from the set.
+
+ Element must be hashable. This mutates the original set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+ e: The element to be removed.
+
+ Returns:
+ The set `s` with `e` removed.
+ """
+ s._values.pop(e)
+ return s
+
+def _contains(a, e):
+ """Checks for the existence of an element in a set.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ e: The element to look for.
+
+ Returns:
+ True if the element exists in the set, False if the element does not.
+ """
+ return e in a._values
+
+def _get_shorter_and_longer(a, b):
+ """Returns two sets in the order of shortest and longest.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ `a`, `b` if `a` is shorter than `b` - or `b`, `a` if `b` is shorter than `a`.
+ """
+ if _length(a) < _length(b):
+ return a, b
+ return b, a
+
+def _is_equal(a, b):
+ """Returns whether two sets are equal.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ True if `a` is equal to `b`, False otherwise.
+ """
+ return a._values == b._values
+
+def _is_subset(a, b):
+ """Returns whether `a` is a subset of `b`.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ True if `a` is a subset of `b`, False otherwise.
+ """
+ for e in a._values.keys():
+ if e not in b._values:
+ return False
+ return True
+
+def _disjoint(a, b):
+ """Returns whether two sets are disjoint.
+
+ Two sets are disjoint if they have no elements in common.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ True if `a` and `b` are disjoint, False otherwise.
+ """
+ shorter, longer = _get_shorter_and_longer(a, b)
+ for e in shorter._values.keys():
+ if e in longer._values:
+ return False
+ return True
+
+def _intersection(a, b):
+ """Returns the intersection of two sets.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ A set containing the elements that are in both `a` and `b`.
+ """
+ shorter, longer = _get_shorter_and_longer(a, b)
+ return struct(_values = {e: None for e in shorter._values.keys() if e in longer._values})
+
+def _union(*args):
+ """Returns the union of several sets.
+
+ Args:
+ *args: An arbitrary number of sets.
+
+ Returns:
+ The set union of all sets in `*args`.
+ """
+ return struct(_values = dicts.add(*[s._values for s in args]))
+
+def _difference(a, b):
+ """Returns the elements in `a` that are not in `b`.
+
+ Args:
+ a: A set, as returned by `sets.make()`.
+ b: A set, as returned by `sets.make()`.
+
+ Returns:
+ A set containing the elements that are in `a` but not in `b`.
+ """
+ return struct(_values = {e: None for e in a._values.keys() if e not in b._values})
+
+def _length(s):
+ """Returns the number of elements in a set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+
+ Returns:
+ An integer representing the number of elements in the set.
+ """
+ return len(s._values)
+
+def _repr(s):
+ """Returns a string value representing the set.
+
+ Args:
+ s: A set, as returned by `sets.make()`.
+
+ Returns:
+ A string representing the set.
+ """
+ return repr(s._values.keys())
+
+sets = struct(
+ make = _make,
+ copy = _copy,
+ to_list = _to_list,
+ insert = _insert,
+ contains = _contains,
+ is_equal = _is_equal,
+ is_subset = _is_subset,
+ disjoint = _disjoint,
+ intersection = _intersection,
+ union = _union,
+ difference = _difference,
+ length = _length,
+ remove = _remove,
+ repr = _repr,
+ str = _repr,
+ # is_set is declared in types.bzl
+)
diff --git a/lib/old_sets.bzl b/lib/old_sets.bzl
new file mode 100644
index 0000000..20245b2
--- /dev/null
+++ b/lib/old_sets.bzl
@@ -0,0 +1,17 @@
+# Copyright 2017 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.
+
+"""Obsolete file, see sets.bzl instead."""
+
+fail("old_sets.bzl has been removed, please use sets.bzl instead")
diff --git a/lib/partial.bzl b/lib/partial.bzl
new file mode 100644
index 0000000..e2f24b7
--- /dev/null
+++ b/lib/partial.bzl
@@ -0,0 +1,130 @@
+# Copyright 2018 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.
+
+"""Starlark module for working with partial function objects.
+
+Partial function objects allow some parameters are bound before the call.
+
+Similar to https://docs.python.org/3/library/functools.html#functools.partial.
+"""
+
+def _call(partial, *args, **kwargs):
+ """Calls a partial created using `make`.
+
+ Args:
+ partial: The partial to be called.
+ *args: Additional positional arguments to be appended to the ones given to
+ make.
+ **kwargs: Additional keyword arguments to augment and override the ones
+ given to make.
+
+ Returns:
+ Whatever the function in the partial returns.
+ """
+ function_args = partial.args + args
+ function_kwargs = dict(partial.kwargs)
+ function_kwargs.update(kwargs)
+ return partial.function(*function_args, **function_kwargs)
+
+def _make(func, *args, **kwargs):
+ """Creates a partial that can be called using `call`.
+
+ A partial can have args assigned to it at the make site, and can have args
+ passed to it at the call sites.
+
+ A partial 'function' can be defined with positional args and kwargs:
+
+ # function with no args
+ def function1():
+ ...
+
+ # function with 2 args
+ def function2(arg1, arg2):
+ ...
+
+ # function with 2 args and keyword args
+ def function3(arg1, arg2, x, y):
+ ...
+
+ The positional args passed to the function are the args passed into make
+ followed by any additional positional args given to call. The below example
+ illustrates a function with two positional arguments where one is supplied by
+ make and the other by call:
+
+ # function demonstrating 1 arg at make site, and 1 arg at call site
+ def _foo(make_arg1, func_arg1):
+ print(make_arg1 + " " + func_arg1 + "!")
+
+ For example:
+
+ hi_func = partial.make(_foo, "Hello")
+ bye_func = partial.make(_foo, "Goodbye")
+ partial.call(hi_func, "Jennifer")
+ partial.call(hi_func, "Dave")
+ partial.call(bye_func, "Jennifer")
+ partial.call(bye_func, "Dave")
+
+ prints:
+
+ "Hello, Jennifer!"
+ "Hello, Dave!"
+ "Goodbye, Jennifer!"
+ "Goodbye, Dave!"
+
+ The keyword args given to the function are the kwargs passed into make
+ unioned with the keyword args given to call. In case of a conflict, the
+ keyword args given to call take precedence. This allows you to set a default
+ value for keyword arguments and override it at the call site.
+
+ Example with a make site arg, a call site arg, a make site kwarg and a
+ call site kwarg:
+
+ def _foo(make_arg1, call_arg1, make_location, call_location):
+ print(make_arg1 + " is from " + make_location + " and " +
+ call_arg1 + " is from " + call_location + "!")
+
+ func = partial.make(_foo, "Ben", make_location="Hollywood")
+ partial.call(func, "Jennifer", call_location="Denver")
+
+ Prints "Ben is from Hollywood and Jennifer is from Denver!".
+
+ partial.call(func, "Jennifer", make_location="LA", call_location="Denver")
+
+ Prints "Ben is from LA and Jennifer is from Denver!".
+
+ Note that keyword args may not overlap with positional args, regardless of
+ whether they are given during the make or call step. For instance, you can't
+ do:
+
+ def foo(x):
+ pass
+
+ func = partial.make(foo, 1)
+ partial.call(func, x=2)
+
+ Args:
+ func: The function to be called.
+ *args: Positional arguments to be passed to function.
+ **kwargs: Keyword arguments to be passed to function. Note that these can
+ be overridden at the call sites.
+
+ Returns:
+ A new `partial` that can be called using `call`
+ """
+ return struct(function = func, args = args, kwargs = kwargs)
+
+partial = struct(
+ make = _make,
+ call = _call,
+)
diff --git a/lib/paths.bzl b/lib/paths.bzl
new file mode 100644
index 0000000..8e3fcae
--- /dev/null
+++ b/lib/paths.bzl
@@ -0,0 +1,242 @@
+# Copyright 2017 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.
+
+"""Skylib module containing file path manipulation functions.
+
+NOTE: The functions in this module currently only support paths with Unix-style
+path separators (forward slash, "/"); they do not handle Windows-style paths
+with backslash separators or drive letters.
+"""
+
+def _basename(p):
+ """Returns the basename (i.e., the file portion) of a path.
+
+ Note that if `p` ends with a slash, this function returns an empty string.
+ This matches the behavior of Python's `os.path.basename`, but differs from
+ the Unix `basename` command (which would return the path segment preceding
+ the final slash).
+
+ Args:
+ p: The path whose basename should be returned.
+
+ Returns:
+ The basename of the path, which includes the extension.
+ """
+ return p.rpartition("/")[-1]
+
+def _dirname(p):
+ """Returns the dirname of a path.
+
+ The dirname is the portion of `p` up to but not including the file portion
+ (i.e., the basename). Any slashes immediately preceding the basename are not
+ included, unless omitting them would make the dirname empty.
+
+ Args:
+ p: The path whose dirname should be returned.
+
+ Returns:
+ The dirname of the path.
+ """
+ prefix, sep, _ = p.rpartition("/")
+ if not prefix:
+ return sep
+ else:
+ # If there are multiple consecutive slashes, strip them all out as Python's
+ # os.path.dirname does.
+ return prefix.rstrip("/")
+
+def _is_absolute(path):
+ """Returns `True` if `path` is an absolute path.
+
+ Args:
+ path: A path (which is a string).
+
+ Returns:
+ `True` if `path` is an absolute path.
+ """
+ return path.startswith("/") or (len(path) > 2 and path[1] == ":")
+
+def _join(path, *others):
+ """Joins one or more path components intelligently.
+
+ This function mimics the behavior of Python's `os.path.join` function on POSIX
+ platform. It returns the concatenation of `path` and any members of `others`,
+ inserting directory separators before each component except the first. The
+ separator is not inserted if the path up until that point is either empty or
+ already ends in a separator.
+
+ If any component is an absolute path, all previous components are discarded.
+
+ Args:
+ path: A path segment.
+ *others: Additional path segments.
+
+ Returns:
+ A string containing the joined paths.
+ """
+ result = path
+
+ for p in others:
+ if _is_absolute(p):
+ result = p
+ elif not result or result.endswith("/"):
+ result += p
+ else:
+ result += "/" + p
+
+ return result
+
+def _normalize(path):
+ """Normalizes a path, eliminating double slashes and other redundant segments.
+
+ This function mimics the behavior of Python's `os.path.normpath` function on
+ POSIX platforms; specifically:
+
+ - If the entire path is empty, "." is returned.
+ - All "." segments are removed, unless the path consists solely of a single
+ "." segment.
+ - Trailing slashes are removed, unless the path consists solely of slashes.
+ - ".." segments are removed as long as there are corresponding segments
+ earlier in the path to remove; otherwise, they are retained as leading ".."
+ segments.
+ - Single and double leading slashes are preserved, but three or more leading
+ slashes are collapsed into a single leading slash.
+ - Multiple adjacent internal slashes are collapsed into a single slash.
+
+ Args:
+ path: A path.
+
+ Returns:
+ The normalized path.
+ """
+ if not path:
+ return "."
+
+ if path.startswith("//") and not path.startswith("///"):
+ initial_slashes = 2
+ elif path.startswith("/"):
+ initial_slashes = 1
+ else:
+ initial_slashes = 0
+ is_relative = (initial_slashes == 0)
+
+ components = path.split("/")
+ new_components = []
+
+ for component in components:
+ if component in ("", "."):
+ continue
+ if component == "..":
+ if new_components and new_components[-1] != "..":
+ # Only pop the last segment if it isn't another "..".
+ new_components.pop()
+ elif is_relative:
+ # Preserve leading ".." segments for relative paths.
+ new_components.append(component)
+ else:
+ new_components.append(component)
+
+ path = "/".join(new_components)
+ if not is_relative:
+ path = ("/" * initial_slashes) + path
+
+ return path or "."
+
+def _relativize(path, start):
+ """Returns the portion of `path` that is relative to `start`.
+
+ Because we do not have access to the underlying file system, this
+ implementation differs slightly from Python's `os.path.relpath` in that it
+ will fail if `path` is not beneath `start` (rather than use parent segments to
+ walk up to the common file system root).
+
+ Relativizing paths that start with parent directory references only works if
+ the path both start with the same initial parent references.
+
+ Args:
+ path: The path to relativize.
+ start: The ancestor path against which to relativize.
+
+ Returns:
+ The portion of `path` that is relative to `start`.
+ """
+ segments = _normalize(path).split("/")
+ start_segments = _normalize(start).split("/")
+ if start_segments == ["."]:
+ start_segments = []
+ start_length = len(start_segments)
+
+ if (path.startswith("/") != start.startswith("/") or
+ len(segments) < start_length):
+ fail("Path '%s' is not beneath '%s'" % (path, start))
+
+ for ancestor_segment, segment in zip(start_segments, segments):
+ if ancestor_segment != segment:
+ fail("Path '%s' is not beneath '%s'" % (path, start))
+
+ length = len(segments) - start_length
+ result_segments = segments[-length:]
+ return "/".join(result_segments)
+
+def _replace_extension(p, new_extension):
+ """Replaces the extension of the file at the end of a path.
+
+ If the path has no extension, the new extension is added to it.
+
+ Args:
+ p: The path whose extension should be replaced.
+ new_extension: The new extension for the file. The new extension should
+ begin with a dot if you want the new filename to have one.
+
+ Returns:
+ The path with the extension replaced (or added, if it did not have one).
+ """
+ return _split_extension(p)[0] + new_extension
+
+def _split_extension(p):
+ """Splits the path `p` into a tuple containing the root and extension.
+
+ Leading periods on the basename are ignored, so
+ `path.split_extension(".bashrc")` returns `(".bashrc", "")`.
+
+ Args:
+ p: The path whose root and extension should be split.
+
+ Returns:
+ A tuple `(root, ext)` such that the root is the path without the file
+ extension, and `ext` is the file extension (which, if non-empty, contains
+ the leading dot). The returned tuple always satisfies the relationship
+ `root + ext == p`.
+ """
+ b = _basename(p)
+ last_dot_in_basename = b.rfind(".")
+
+ # If there is no dot or the only dot in the basename is at the front, then
+ # there is no extension.
+ if last_dot_in_basename <= 0:
+ return (p, "")
+
+ dot_distance_from_end = len(b) - last_dot_in_basename
+ return (p[:-dot_distance_from_end], p[-dot_distance_from_end:])
+
+paths = struct(
+ basename = _basename,
+ dirname = _dirname,
+ is_absolute = _is_absolute,
+ join = _join,
+ normalize = _normalize,
+ relativize = _relativize,
+ replace_extension = _replace_extension,
+ split_extension = _split_extension,
+)
diff --git a/lib/selects.bzl b/lib/selects.bzl
new file mode 100644
index 0000000..c511fe5
--- /dev/null
+++ b/lib/selects.bzl
@@ -0,0 +1,249 @@
+# Copyright 2017 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.
+
+"""Skylib module containing convenience interfaces for select()."""
+
+def _with_or(input_dict, no_match_error = ""):
+ """Drop-in replacement for `select()` that supports ORed keys.
+
+ Example:
+
+ ```build
+ deps = selects.with_or({
+ "//configs:one": [":dep1"],
+ ("//configs:two", "//configs:three"): [":dep2or3"],
+ "//configs:four": [":dep4"],
+ "//conditions:default": [":default"]
+ })
+ ```
+
+ Key labels may appear at most once anywhere in the input.
+
+ Args:
+ input_dict: The same dictionary `select()` takes, except keys may take
+ either the usual form `"//foo:config1"` or
+ `("//foo:config1", "//foo:config2", ...)` to signify
+ `//foo:config1` OR `//foo:config2` OR `...`.
+ no_match_error: Optional custom error to report if no condition matches.
+
+ Returns:
+ A native `select()` that expands
+
+ `("//configs:two", "//configs:three"): [":dep2or3"]`
+
+ to
+
+ ```build
+ "//configs:two": [":dep2or3"],
+ "//configs:three": [":dep2or3"],
+ ```
+ """
+ return select(_with_or_dict(input_dict), no_match_error = no_match_error)
+
+def _with_or_dict(input_dict):
+ """Variation of `with_or` that returns the dict of the `select()`.
+
+ Unlike `select()`, the contents of the dict can be inspected by Starlark
+ macros.
+
+ Args:
+ input_dict: Same as `with_or`.
+
+ Returns:
+ A dictionary usable by a native `select()`.
+ """
+ output_dict = {}
+ for (key, value) in input_dict.items():
+ if type(key) == type(()):
+ for config_setting in key:
+ if config_setting in output_dict.keys():
+ fail("key %s appears multiple times" % config_setting)
+ output_dict[config_setting] = value
+ else:
+ if key in output_dict.keys():
+ fail("key %s appears multiple times" % key)
+ output_dict[key] = value
+ return output_dict
+
+def _config_setting_group(name, match_any = [], match_all = [], visibility = None):
+ """Matches if all or any of its member `config_setting`s match.
+
+ Example:
+
+ ```build
+ config_setting(name = "one", define_values = {"foo": "true"})
+ config_setting(name = "two", define_values = {"bar": "false"})
+ config_setting(name = "three", define_values = {"baz": "more_false"})
+
+ config_setting_group(
+ name = "one_two_three",
+ match_all = [":one", ":two", ":three"]
+ )
+
+ cc_binary(
+ name = "myapp",
+ srcs = ["myapp.cc"],
+ deps = select({
+ ":one_two_three": [":special_deps"],
+ "//conditions:default": [":default_deps"]
+ })
+ ```
+
+ Args:
+ name: The group's name. This is how `select()`s reference it.
+ match_any: A list of `config_settings`. This group matches if *any* member
+ in the list matches. If this is set, `match_all` must not be set.
+ match_all: A list of `config_settings`. This group matches if *every*
+ member in the list matches. If this is set, `match_any` must be not
+ set.
+ visibility: Visibility of the config_setting_group.
+ """
+ empty1 = not bool(len(match_any))
+ empty2 = not bool(len(match_all))
+ if (empty1 and empty2) or (not empty1 and not empty2):
+ fail('Either "match_any" or "match_all" must be set, but not both.')
+ _check_duplicates(match_any)
+ _check_duplicates(match_all)
+
+ if ((len(match_any) == 1 and match_any[0] == "//conditions:default") or
+ (len(match_all) == 1 and match_all[0] == "//conditions:default")):
+ # If the only entry is "//conditions:default", the condition is
+ # automatically true.
+ _config_setting_always_true(name, visibility)
+ elif not empty1:
+ _config_setting_or_group(name, match_any, visibility)
+ else:
+ _config_setting_and_group(name, match_all, visibility)
+
+def _check_duplicates(settings):
+ """Fails if any entry in settings appears more than once."""
+ seen = {}
+ for setting in settings:
+ if setting in seen:
+ fail(setting + " appears more than once. Duplicates not allowed.")
+ seen[setting] = True
+
+def _remove_default_condition(settings):
+ """Returns settings with "//conditions:default" entries filtered out."""
+ new_settings = []
+ for setting in settings:
+ if settings != "//conditions:default":
+ new_settings.append(setting)
+ return new_settings
+
+def _config_setting_or_group(name, settings, visibility):
+ """ORs multiple config_settings together (inclusively).
+
+ The core idea is to create a sequential chain of alias targets where each is
+ select-resolved as follows: If alias n matches config_setting n, the chain
+ is true so it resolves to config_setting n. Else it resolves to alias n+1
+ (which checks config_setting n+1, and so on). If none of the config_settings
+ match, the final alias resolves to one of them arbitrarily, which by
+ definition doesn't match.
+ """
+
+ # "//conditions:default" is present, the whole chain is automatically true.
+ if len(_remove_default_condition(settings)) < len(settings):
+ _config_setting_always_true(name, visibility)
+ return
+
+ elif len(settings) == 1: # One entry? Just alias directly to it.
+ native.alias(
+ name = name,
+ actual = settings[0],
+ visibility = visibility,
+ )
+ return
+
+ # We need n-1 aliases for n settings. The first alias has no extension. The
+ # second alias is named name + "_2", and so on. For the first n-2 aliases,
+ # if they don't match they reference the next alias over. If the n-1st alias
+ # doesn't match, it references the final setting (which is then evaluated
+ # directly to determine the final value of the AND chain).
+ actual = [name + "_" + str(i) for i in range(2, len(settings))]
+ actual.append(settings[-1])
+
+ for i in range(1, len(settings)):
+ native.alias(
+ name = name if i == 1 else name + "_" + str(i),
+ actual = select({
+ settings[i - 1]: settings[i - 1],
+ "//conditions:default": actual[i - 1],
+ }),
+ visibility = visibility if i == 1 else ["//visibility:private"],
+ )
+
+def _config_setting_and_group(name, settings, visibility):
+ """ANDs multiple config_settings together.
+
+ The core idea is to create a sequential chain of alias targets where each is
+ select-resolved as follows: If alias n matches config_setting n, it resolves to
+ alias n+1 (which evaluates config_setting n+1, and so on). Else it resolves to
+ config_setting n, which doesn't match by definition. The only way to get a
+ matching final result is if all config_settings match.
+ """
+
+ # "//conditions:default" is automatically true so doesn't need checking.
+ settings = _remove_default_condition(settings)
+
+ # One config_setting input? Just alias directly to it.
+ if len(settings) == 1:
+ native.alias(
+ name = name,
+ actual = settings[0],
+ visibility = visibility,
+ )
+ return
+
+ # We need n-1 aliases for n settings. The first alias has no extension. The
+ # second alias is named name + "_2", and so on. For the first n-2 aliases,
+ # if they match they reference the next alias over. If the n-1st alias matches,
+ # it references the final setting (which is then evaluated directly to determine
+ # the final value of the AND chain).
+ actual = [name + "_" + str(i) for i in range(2, len(settings))]
+ actual.append(settings[-1])
+
+ for i in range(1, len(settings)):
+ native.alias(
+ name = name if i == 1 else name + "_" + str(i),
+ actual = select({
+ settings[i - 1]: actual[i - 1],
+ "//conditions:default": settings[i - 1],
+ }),
+ visibility = visibility if i == 1 else ["//visibility:private"],
+ )
+
+def _config_setting_always_true(name, visibility):
+ """Returns a config_setting with the given name that's always true.
+
+ This is achieved by constructing a two-entry OR chain where each
+ config_setting takes opposite values of a boolean flag.
+ """
+ name_on = name + "_stamp_binary_on_check"
+ name_off = name + "_stamp_binary_off_check"
+ native.config_setting(
+ name = name_on,
+ values = {"stamp": "1"},
+ )
+ native.config_setting(
+ name = name_off,
+ values = {"stamp": "0"},
+ )
+ return _config_setting_or_group(name, [":" + name_on, ":" + name_off], visibility)
+
+selects = struct(
+ with_or = _with_or,
+ with_or_dict = _with_or_dict,
+ config_setting_group = _config_setting_group,
+)
diff --git a/lib/sets.bzl b/lib/sets.bzl
new file mode 100644
index 0000000..448c498
--- /dev/null
+++ b/lib/sets.bzl
@@ -0,0 +1,19 @@
+# Copyright 2018 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.
+
+"""Skylib module reexporting deprecated set algorithms."""
+
+load(":new_sets.bzl", _sets = "sets")
+
+sets = _sets
diff --git a/lib/shell.bzl b/lib/shell.bzl
new file mode 100644
index 0000000..4a851ca
--- /dev/null
+++ b/lib/shell.bzl
@@ -0,0 +1,54 @@
+# Copyright 2017 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.
+
+"""Skylib module containing shell utility functions."""
+
+def _array_literal(iterable):
+ """Creates a string from a sequence that can be used as a shell array.
+
+ For example, `shell.array_literal(["a", "b", "c"])` would return the string
+ `("a" "b" "c")`, which can be used in a shell script wherever an array
+ literal is needed.
+
+ Note that all elements in the array are quoted (using `shell.quote`) for
+ safety, even if they do not need to be.
+
+ Args:
+ iterable: A sequence of elements. Elements that are not strings will be
+ converted to strings first, by calling `str()`.
+
+ Returns:
+ A string that represents the sequence as a shell array; that is,
+ parentheses containing the quoted elements.
+ """
+ return "(" + " ".join([_quote(str(i)) for i in iterable]) + ")"
+
+def _quote(s):
+ """Quotes the given string for use in a shell command.
+
+ This function quotes the given string (in case it contains spaces or other
+ shell metacharacters.)
+
+ Args:
+ s: The string to quote.
+
+ Returns:
+ A quoted version of the string that can be passed to a shell command.
+ """
+ return "'" + s.replace("'", "'\\''") + "'"
+
+shell = struct(
+ array_literal = _array_literal,
+ quote = _quote,
+)
diff --git a/lib/structs.bzl b/lib/structs.bzl
new file mode 100644
index 0000000..f291152
--- /dev/null
+++ b/lib/structs.bzl
@@ -0,0 +1,35 @@
+# Copyright 2017 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.
+
+"""Skylib module containing functions that operate on structs."""
+
+def _to_dict(s):
+ """Converts a `struct` to a `dict`.
+
+ Args:
+ s: A `struct`.
+
+ Returns:
+ A `dict` whose keys and values are the same as the fields in `s`. The
+ transformation is only applied to the struct's fields and not to any
+ nested values.
+ """
+ attributes = dir(s)
+ attributes.remove("to_json")
+ attributes.remove("to_proto")
+ return {key: getattr(s, key) for key in attributes}
+
+structs = struct(
+ to_dict = _to_dict,
+)
diff --git a/lib/types.bzl b/lib/types.bzl
new file mode 100644
index 0000000..db1ca18
--- /dev/null
+++ b/lib/types.bzl
@@ -0,0 +1,152 @@
+# Copyright 2018 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.
+"""Skylib module containing functions checking types."""
+
+# create instance singletons to avoid unnecessary allocations
+_a_bool_type = type(True)
+_a_dict_type = type({})
+_a_list_type = type([])
+_a_string_type = type("")
+_a_tuple_type = type(())
+_an_int_type = type(1)
+_a_depset_type = type(depset())
+_a_struct_type = type(struct())
+
+def _a_function():
+ pass
+
+_a_function_type = type(_a_function)
+
+def _is_list(v):
+ """Returns True if v is an instance of a list.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a list, False otherwise.
+ """
+ return type(v) == _a_list_type
+
+def _is_string(v):
+ """Returns True if v is an instance of a string.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a string, False otherwise.
+ """
+ return type(v) == _a_string_type
+
+def _is_bool(v):
+ """Returns True if v is an instance of a bool.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a bool, False otherwise.
+ """
+ return type(v) == _a_bool_type
+
+def _is_none(v):
+ """Returns True if v has the type of None.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is None, False otherwise.
+ """
+ return type(v) == type(None)
+
+def _is_int(v):
+ """Returns True if v is an instance of a signed integer.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a signed integer, False otherwise.
+ """
+ return type(v) == _an_int_type
+
+def _is_tuple(v):
+ """Returns True if v is an instance of a tuple.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a tuple, False otherwise.
+ """
+ return type(v) == _a_tuple_type
+
+def _is_dict(v):
+ """Returns True if v is an instance of a dict.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a dict, False otherwise.
+ """
+ return type(v) == _a_dict_type
+
+def _is_function(v):
+ """Returns True if v is an instance of a function.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a function, False otherwise.
+ """
+ return type(v) == _a_function_type
+
+def _is_depset(v):
+ """Returns True if v is an instance of a `depset`.
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v is an instance of a `depset`, False otherwise.
+ """
+ return type(v) == _a_depset_type
+
+def _is_set(v):
+ """Returns True if v is a set created by sets.make().
+
+ Args:
+ v: The value whose type should be checked.
+
+ Returns:
+ True if v was created by sets.make(), False otherwise.
+ """
+ return type(v) == _a_struct_type and hasattr(v, "_values") and _is_dict(v._values)
+
+types = struct(
+ is_list = _is_list,
+ is_string = _is_string,
+ is_bool = _is_bool,
+ is_none = _is_none,
+ is_int = _is_int,
+ is_tuple = _is_tuple,
+ is_dict = _is_dict,
+ is_function = _is_function,
+ is_depset = _is_depset,
+ is_set = _is_set,
+)
diff --git a/lib/unittest.bzl b/lib/unittest.bzl
new file mode 100644
index 0000000..0cdbd94
--- /dev/null
+++ b/lib/unittest.bzl
@@ -0,0 +1,516 @@
+# Copyright 2017 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.
+
+"""Unit testing support.
+
+Unlike most Skylib files, this exports two modules: `unittest` which contains
+functions to declare and define unit tests, and `asserts` which contains the
+assertions used to within tests.
+"""
+
+load(":new_sets.bzl", new_sets = "sets")
+load(":types.bzl", "types")
+
+# The following function should only be called from WORKSPACE files and workspace macros.
+# buildifier: disable=unnamed-macro
+def register_unittest_toolchains():
+ """Registers the toolchains for unittest users."""
+ native.register_toolchains(
+ "@bazel_skylib//toolchains/unittest:cmd_toolchain",
+ "@bazel_skylib//toolchains/unittest:bash_toolchain",
+ )
+
+TOOLCHAIN_TYPE = "@bazel_skylib//toolchains/unittest:toolchain_type"
+
+_UnittestToolchainInfo = provider(
+ doc = "Execution platform information for rules in the bazel_skylib repository.",
+ fields = ["file_ext", "success_templ", "failure_templ", "join_on"],
+)
+
+def _unittest_toolchain_impl(ctx):
+ return [
+ platform_common.ToolchainInfo(
+ unittest_toolchain_info = _UnittestToolchainInfo(
+ file_ext = ctx.attr.file_ext,
+ success_templ = ctx.attr.success_templ,
+ failure_templ = ctx.attr.failure_templ,
+ join_on = ctx.attr.join_on,
+ ),
+ ),
+ ]
+
+unittest_toolchain = rule(
+ implementation = _unittest_toolchain_impl,
+ attrs = {
+ "failure_templ": attr.string(mandatory = True),
+ "file_ext": attr.string(mandatory = True),
+ "join_on": attr.string(mandatory = True),
+ "success_templ": attr.string(mandatory = True),
+ },
+)
+
+def _impl_function_name(impl):
+ """Derives the name of the given rule implementation function.
+
+ This can be used for better test feedback.
+
+ Args:
+ impl: the rule implementation function
+
+ Returns:
+ The name of the given function
+ """
+
+ # Starlark currently stringifies a function as "<function NAME>", so we use
+ # that knowledge to parse the "NAME" portion out. If this behavior ever
+ # changes, we'll need to update this.
+ # TODO(bazel-team): Expose a ._name field on functions to avoid this.
+ impl_name = str(impl)
+ impl_name = impl_name.partition("<function ")[-1]
+ return impl_name.rpartition(">")[0]
+
+def _make(impl, attrs = {}):
+ """Creates a unit test rule from its implementation function.
+
+ Each unit test is defined in an implementation function that must then be
+ associated with a rule so that a target can be built. This function handles
+ the boilerplate to create and return a test rule and captures the
+ implementation function's name so that it can be printed in test feedback.
+
+ The optional `attrs` argument can be used to define dependencies for this
+ test, in order to form unit tests of rules.
+
+ An example of a unit test:
+
+ ```
+ def _your_test(ctx):
+ env = unittest.begin(ctx)
+
+ # Assert statements go here
+
+ return unittest.end(env)
+
+ your_test = unittest.make(_your_test)
+ ```
+
+ Recall that names of test rules must end in `_test`.
+
+ Args:
+ impl: The implementation function of the unit test.
+ attrs: An optional dictionary to supplement the attrs passed to the
+ unit test's `rule()` constructor.
+
+ Returns:
+ A rule definition that should be stored in a global whose name ends in
+ `_test`.
+ """
+ attrs = dict(attrs)
+ attrs["_impl_name"] = attr.string(default = _impl_function_name(impl))
+
+ return rule(
+ impl,
+ attrs = attrs,
+ _skylark_testable = True,
+ test = True,
+ toolchains = [TOOLCHAIN_TYPE],
+ )
+
+_ActionInfo = provider(fields = ["actions", "bin_path"])
+
+def _action_retrieving_aspect_impl(target, ctx):
+ return [
+ _ActionInfo(
+ actions = target.actions,
+ bin_path = ctx.bin_dir.path,
+ ),
+ ]
+
+_action_retrieving_aspect = aspect(
+ attr_aspects = [],
+ implementation = _action_retrieving_aspect_impl,
+)
+
+# TODO(cparsons): Provide more full documentation on analysis testing in README.
+def _make_analysis_test(
+ impl,
+ expect_failure = False,
+ attrs = {},
+ fragments = [],
+ config_settings = {}):
+ """Creates an analysis test rule from its implementation function.
+
+ An analysis test verifies the behavior of a "real" rule target by examining
+ and asserting on the providers given by the real target.
+
+ Each analysis test is defined in an implementation function that must then be
+ associated with a rule so that a target can be built. This function handles
+ the boilerplate to create and return a test rule and captures the
+ implementation function's name so that it can be printed in test feedback.
+
+ An example of an analysis test:
+
+ ```
+ def _your_test(ctx):
+ env = analysistest.begin(ctx)
+
+ # Assert statements go here
+
+ return analysistest.end(env)
+
+ your_test = analysistest.make(_your_test)
+ ```
+
+ Recall that names of test rules must end in `_test`.
+
+ Args:
+ impl: The implementation function of the unit test.
+ expect_failure: If true, the analysis test will expect the target_under_test
+ to fail. Assertions can be made on the underlying failure using asserts.expect_failure
+ attrs: An optional dictionary to supplement the attrs passed to the
+ unit test's `rule()` constructor.
+ fragments: An optional list of fragment names that can be used to give rules access to
+ language-specific parts of configuration.
+ config_settings: A dictionary of configuration settings to change for the target under
+ test and its dependencies. This may be used to essentially change 'build flags' for
+ the target under test, and may thus be utilized to test multiple targets with different
+ flags in a single build
+
+ Returns:
+ A rule definition that should be stored in a global whose name ends in
+ `_test`.
+ """
+ attrs = dict(attrs)
+ attrs["_impl_name"] = attr.string(default = _impl_function_name(impl))
+
+ changed_settings = dict(config_settings)
+ if expect_failure:
+ changed_settings["//command_line_option:allow_analysis_failures"] = "True"
+
+ target_attr_kwargs = {}
+ if changed_settings:
+ test_transition = analysis_test_transition(
+ settings = changed_settings,
+ )
+ target_attr_kwargs["cfg"] = test_transition
+
+ attrs["target_under_test"] = attr.label(
+ aspects = [_action_retrieving_aspect],
+ mandatory = True,
+ **target_attr_kwargs
+ )
+
+ return rule(
+ impl,
+ attrs = attrs,
+ fragments = fragments,
+ test = True,
+ toolchains = [TOOLCHAIN_TYPE],
+ analysis_test = True,
+ )
+
+def _suite(name, *test_rules):
+ """Defines a `test_suite` target that contains multiple tests.
+
+ After defining your test rules in a `.bzl` file, you need to create targets
+ from those rules so that `blaze test` can execute them. Doing this manually
+ in a BUILD file would consist of listing each test in your `load` statement
+ and then creating each target one by one. To reduce duplication, we recommend
+ writing a macro in your `.bzl` file to instantiate all targets, and calling
+ that macro from your BUILD file so you only have to load one symbol.
+
+ For the case where your unit tests do not take any (non-default) attributes --
+ i.e., if your unit tests do not test rules -- you can use this function to
+ create the targets and wrap them in a single test_suite target. In your
+ `.bzl` file, write:
+
+ ```
+ def your_test_suite():
+ unittest.suite(
+ "your_test_suite",
+ your_test,
+ your_other_test,
+ yet_another_test,
+ )
+ ```
+
+ Then, in your `BUILD` file, simply load the macro and invoke it to have all
+ of the targets created:
+
+ ```
+ load("//path/to/your/package:tests.bzl", "your_test_suite")
+ your_test_suite()
+ ```
+
+ If you pass _N_ unit test rules to `unittest.suite`, _N_ + 1 targets will be
+ created: a `test_suite` target named `${name}` (where `${name}` is the name
+ argument passed in here) and targets named `${name}_test_${i}`, where `${i}`
+ is the index of the test in the `test_rules` list, which is used to uniquely
+ name each target.
+
+ Args:
+ name: The name of the `test_suite` target, and the prefix of all the test
+ target names.
+ *test_rules: A list of test rules defines by `unittest.test`.
+ """
+ test_names = []
+ for index, test_rule in enumerate(test_rules):
+ test_name = "%s_test_%d" % (name, index)
+ test_rule(name = test_name)
+ test_names.append(test_name)
+
+ native.test_suite(
+ name = name,
+ tests = [":%s" % t for t in test_names],
+ )
+
+def _begin(ctx):
+ """Begins a unit test.
+
+ This should be the first function called in a unit test implementation
+ function. It initializes a "test environment" that is used to collect
+ assertion failures so that they can be reported and logged at the end of the
+ test.
+
+ Args:
+ ctx: The Starlark context. Pass the implementation function's `ctx` argument
+ in verbatim.
+
+ Returns:
+ A test environment struct that must be passed to assertions and finally to
+ `unittest.end`. Do not rely on internal details about the fields in this
+ struct as it may change.
+ """
+ return struct(ctx = ctx, failures = [])
+
+def _end_analysis_test(env):
+ """Ends an analysis test and logs the results.
+
+ This must be called and returned at the end of an analysis test implementation function so
+ that the results are reported.
+
+ Args:
+ env: The test environment returned by `analysistest.begin`.
+
+ Returns:
+ A list of providers needed to automatically register the analysis test result.
+ """
+ return [AnalysisTestResultInfo(
+ success = (len(env.failures) == 0),
+ message = "\n".join(env.failures),
+ )]
+
+def _end(env):
+ """Ends a unit test and logs the results.
+
+ This must be called and returned at the end of a unit test implementation function so
+ that the results are reported.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+
+ Returns:
+ A list of providers needed to automatically register the test result.
+ """
+
+ tc = env.ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info
+ testbin = env.ctx.actions.declare_file(env.ctx.label.name + tc.file_ext)
+ if env.failures:
+ cmd = tc.failure_templ % tc.join_on.join(env.failures)
+ else:
+ cmd = tc.success_templ
+
+ env.ctx.actions.write(
+ output = testbin,
+ content = cmd,
+ is_executable = True,
+ )
+ return [DefaultInfo(executable = testbin)]
+
+def _fail(env, msg):
+ """Unconditionally causes the current test to fail.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+ msg: The message to log describing the failure.
+ """
+ full_msg = "In test %s: %s" % (env.ctx.attr._impl_name, msg)
+
+ # There isn't a better way to output the message in Starlark, so use print.
+ # buildifier: disable=print
+ print(full_msg)
+ env.failures.append(full_msg)
+
+def _assert_true(
+ env,
+ condition,
+ msg = "Expected condition to be true, but was false."):
+ """Asserts that the given `condition` is true.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+ condition: A value that will be evaluated in a Boolean context.
+ msg: An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ """
+ if not condition:
+ _fail(env, msg)
+
+def _assert_false(
+ env,
+ condition,
+ msg = "Expected condition to be false, but was true."):
+ """Asserts that the given `condition` is false.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+ condition: A value that will be evaluated in a Boolean context.
+ msg: An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ """
+ if condition:
+ _fail(env, msg)
+
+def _assert_equals(env, expected, actual, msg = None):
+ """Asserts that the given `expected` and `actual` values are equal.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+ expected: The expected value of some computation.
+ actual: The actual value returned by some computation.
+ msg: An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ """
+ if expected != actual:
+ expectation_msg = 'Expected "%s", but got "%s"' % (expected, actual)
+ if msg:
+ full_msg = "%s (%s)" % (msg, expectation_msg)
+ else:
+ full_msg = expectation_msg
+ _fail(env, full_msg)
+
+def _assert_set_equals(env, expected, actual, msg = None):
+ """Asserts that the given `expected` and `actual` sets are equal.
+
+ Args:
+ env: The test environment returned by `unittest.begin`.
+ expected: The expected set resulting from some computation.
+ actual: The actual set returned by some computation.
+ msg: An optional message that will be printed that describes the failure.
+ If omitted, a default will be used.
+ """
+ if not new_sets.is_equal(expected, actual):
+ missing = new_sets.difference(expected, actual)
+ unexpected = new_sets.difference(actual, expected)
+ expectation_msg = "Expected %s, but got %s" % (new_sets.str(expected), new_sets.str(actual))
+ if new_sets.length(missing) > 0:
+ expectation_msg += ", missing are %s" % (new_sets.str(missing))
+ if new_sets.length(unexpected) > 0:
+ expectation_msg += ", unexpected are %s" % (new_sets.str(unexpected))
+ if msg:
+ full_msg = "%s (%s)" % (msg, expectation_msg)
+ else:
+ full_msg = expectation_msg
+ _fail(env, full_msg)
+
+_assert_new_set_equals = _assert_set_equals
+
+def _expect_failure(env, expected_failure_msg = ""):
+ """Asserts that the target under test has failed with a given error message.
+
+ This requires that the analysis test is created with `analysistest.make()` and
+ `expect_failures = True` is specified.
+
+ Args:
+ env: The test environment returned by `analysistest.begin`.
+ expected_failure_msg: The error message to expect as a result of analysis failures.
+ """
+ dep = _target_under_test(env)
+ if AnalysisFailureInfo in dep:
+ actual_errors = ""
+ for cause in dep[AnalysisFailureInfo].causes.to_list():
+ actual_errors += cause.message + "\n"
+ if actual_errors.find(expected_failure_msg) < 0:
+ expectation_msg = "Expected errors to contain '%s' but did not. " % expected_failure_msg
+ expectation_msg += "Actual errors:%s" % actual_errors
+ _fail(env, expectation_msg)
+ else:
+ _fail(env, "Expected failure of target_under_test, but found success")
+
+def _target_actions(env):
+ """Returns a list of actions registered by the target under test.
+
+ Args:
+ env: The test environment returned by `analysistest.begin`.
+
+ Returns:
+ A list of actions registered by the target under test
+ """
+
+ # Validate?
+ return _target_under_test(env)[_ActionInfo].actions
+
+def _target_bin_dir_path(env):
+ """Returns ctx.bin_dir.path for the target under test.
+
+ Args:
+ env: The test environment returned by `analysistest.begin`.
+
+ Returns:
+ Output bin dir path string.
+ """
+ return _target_under_test(env)[_ActionInfo].bin_path
+
+def _target_under_test(env):
+ """Returns the target under test.
+
+ Args:
+ env: The test environment returned by `analysistest.begin`.
+
+ Returns:
+ The target under test.
+ """
+ result = getattr(env.ctx.attr, "target_under_test")
+ if types.is_list(result):
+ if result:
+ return result[0]
+ else:
+ fail("test rule does not have a target_under_test")
+ return result
+
+asserts = struct(
+ expect_failure = _expect_failure,
+ equals = _assert_equals,
+ false = _assert_false,
+ set_equals = _assert_set_equals,
+ new_set_equals = _assert_new_set_equals,
+ true = _assert_true,
+)
+
+unittest = struct(
+ make = _make,
+ suite = _suite,
+ begin = _begin,
+ end = _end,
+ fail = _fail,
+)
+
+analysistest = struct(
+ make = _make_analysis_test,
+ begin = _begin,
+ end = _end_analysis_test,
+ fail = _fail,
+ target_actions = _target_actions,
+ target_bin_dir_path = _target_bin_dir_path,
+ target_under_test = _target_under_test,
+)
diff --git a/lib/versions.bzl b/lib/versions.bzl
new file mode 100644
index 0000000..0209a6f
--- /dev/null
+++ b/lib/versions.bzl
@@ -0,0 +1,128 @@
+# Copyright 2018 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.
+
+"""Skylib module containing functions for checking Bazel versions."""
+
+def _get_bazel_version():
+ """Returns the current Bazel version"""
+
+ return native.bazel_version
+
+def _extract_version_number(bazel_version):
+ """Extracts the semantic version number from a version string
+
+ Args:
+ bazel_version: the version string that begins with the semantic version
+ e.g. "1.2.3rc1 abc1234" where "abc1234" is a commit hash.
+
+ Returns:
+ The semantic version string, like "1.2.3".
+ """
+ for i in range(len(bazel_version)):
+ c = bazel_version[i]
+ if not (c.isdigit() or c == "."):
+ return bazel_version[:i]
+ return bazel_version
+
+# Parse the bazel version string from `native.bazel_version`.
+# e.g.
+# "0.10.0rc1 abc123d" => (0, 10, 0)
+# "0.3.0" => (0, 3, 0)
+def _parse_bazel_version(bazel_version):
+ """Parses a version string into a 3-tuple of ints
+
+ int tuples can be compared directly using binary operators (<, >).
+
+ Args:
+ bazel_version: the Bazel version string
+
+ Returns:
+ An int 3-tuple of a (major, minor, patch) version.
+ """
+
+ version = _extract_version_number(bazel_version)
+ return tuple([int(n) for n in version.split(".")])
+
+def _is_at_most(threshold, version):
+ """Check that a version is lower or equals to a threshold.
+
+ Args:
+ threshold: the maximum version string
+ version: the version string to be compared to the threshold
+
+ Returns:
+ True if version <= threshold.
+ """
+ return _parse_bazel_version(version) <= _parse_bazel_version(threshold)
+
+def _is_at_least(threshold, version):
+ """Check that a version is higher or equals to a threshold.
+
+ Args:
+ threshold: the minimum version string
+ version: the version string to be compared to the threshold
+
+ Returns:
+ True if version >= threshold.
+ """
+
+ return _parse_bazel_version(version) >= _parse_bazel_version(threshold)
+
+def _check_bazel_version(minimum_bazel_version, maximum_bazel_version = None, bazel_version = None):
+ """Check that the version of Bazel is valid within the specified range.
+
+ Args:
+ minimum_bazel_version: minimum version of Bazel expected
+ maximum_bazel_version: maximum version of Bazel expected
+ bazel_version: the version of Bazel to check. Used for testing, defaults to native.bazel_version
+ """
+ if not bazel_version:
+ if "bazel_version" not in dir(native):
+ fail("Current Bazel version is lower than 0.2.1; expected at least {}".format(
+ minimum_bazel_version,
+ ))
+ elif not native.bazel_version:
+ # Using a non-release version, assume it is good.
+ return
+ else:
+ bazel_version = native.bazel_version
+
+ if not _is_at_least(
+ threshold = minimum_bazel_version,
+ version = bazel_version,
+ ):
+ fail("Current Bazel version is {}; expected at least {}".format(
+ bazel_version,
+ minimum_bazel_version,
+ ))
+
+ if maximum_bazel_version:
+ if not _is_at_most(
+ threshold = maximum_bazel_version,
+ version = bazel_version,
+ ):
+ fail("Current Bazel version is {}; expected at most {}".format(
+ bazel_version,
+ maximum_bazel_version,
+ ))
+
+ pass
+
+versions = struct(
+ get = _get_bazel_version,
+ parse = _parse_bazel_version,
+ check = _check_bazel_version,
+ is_at_most = _is_at_most,
+ is_at_least = _is_at_least,
+)
diff --git a/rules/BUILD b/rules/BUILD
new file mode 100644
index 0000000..26cda0c
--- /dev/null
+++ b/rules/BUILD
@@ -0,0 +1,93 @@
+load("//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+bzl_library(
+ name = "analysis_test",
+ srcs = ["analysis_test.bzl"],
+)
+
+bzl_library(
+ name = "build_test",
+ srcs = ["build_test.bzl"],
+ deps = ["//lib:new_sets"],
+)
+
+bzl_library(
+ name = "copy_file",
+ srcs = ["copy_file.bzl"],
+ deps = ["//rules/private:copy_file_private"],
+)
+
+bzl_library(
+ name = "write_file",
+ srcs = ["write_file.bzl"],
+ deps = ["//rules/private:write_file_private"],
+)
+
+bzl_library(
+ name = "diff_test",
+ srcs = ["diff_test.bzl"],
+)
+
+bzl_library(
+ name = "native_binary",
+ srcs = ["native_binary.bzl"],
+ deps = ["//rules/private:copy_file_private"],
+)
+
+bzl_library(
+ name = "run_binary",
+ srcs = ["run_binary.bzl"],
+ deps = ["//lib:dicts"],
+)
+
+bzl_library(
+ name = "common_settings",
+ srcs = ["common_settings.bzl"],
+)
+
+# Exported for build_test.bzl to make sure of, it is an implementation detail
+# of the rule and should not be directly used by anything else.
+exports_files(["empty_test.sh"])
+
+filegroup(
+ name = "test_deps",
+ testonly = True,
+ srcs = [
+ "BUILD",
+ "empty_test.sh",
+ ] + glob(["*.bzl"]),
+)
+
+# The files needed for distribution
+filegroup(
+ name = "distribution",
+ srcs = [
+ "BUILD",
+ ] + glob(["*.bzl"]),
+ visibility = [
+ "//:__pkg__",
+ ],
+)
+
+filegroup(
+ name = "bins",
+ srcs = glob(["*.sh"]),
+ visibility = [
+ "//:__pkg__",
+ ],
+)
+
+# export bzl files for the documentation
+exports_files(
+ glob(["*.bzl"]),
+ visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+ name = "select_file",
+ srcs = ["select_file.bzl"],
+)
diff --git a/rules/analysis_test.bzl b/rules/analysis_test.bzl
new file mode 100644
index 0000000..46d32dd
--- /dev/null
+++ b/rules/analysis_test.bzl
@@ -0,0 +1,56 @@
+# Copyright 2019 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.
+
+"""A test verifying other targets can be successfully analyzed as part of a `bazel test`"""
+
+def _analysis_test_impl(ctx):
+ """Implementation function for analysis_test. """
+ _ignore = [ctx]
+ return [AnalysisTestResultInfo(
+ success = True,
+ message = "All targets succeeded analysis",
+ )]
+
+analysis_test = rule(
+ _analysis_test_impl,
+ attrs = {"targets": attr.label_list(mandatory = True)},
+ test = True,
+ analysis_test = True,
+ doc = """Test rule checking that other targets can be successfully analyzed.
+
+ This rule essentially verifies that all targets under `targets` would
+ generate no errors when analyzed with `bazel build [targets] --nobuild`.
+ Action success/failure for the targets and their transitive dependencies
+ are not verified. An analysis test simply ensures that each target in the transitive
+ dependencies propagate providers appropriately and register actions for their outputs
+ appropriately.
+
+ NOTE: If the targets fail to analyze, instead of the analysis_test failing, the analysis_test
+ will fail to build. Ideally, it would instead result in a test failure. This is a current
+ infrastructure limitation that may be fixed in the future.
+
+ Typical usage:
+
+ load("@bazel_skylib//rules:analysis_test.bzl", "analysis_test")
+ analysis_test(
+ name = "my_analysis_test",
+ targets = [
+ "//some/package:rule",
+ ],
+ )
+
+ Args:
+ name: The name of the test rule.
+ targets: A list of targets to ensure build.""",
+)
diff --git a/rules/build_test.bzl b/rules/build_test.bzl
new file mode 100644
index 0000000..edcb50a
--- /dev/null
+++ b/rules/build_test.bzl
@@ -0,0 +1,90 @@
+# Copyright 2019 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.
+
+"""A test verifying other targets build as part of a `bazel test`"""
+
+load("//lib:new_sets.bzl", "sets")
+
+def build_test(name, targets, **kwargs):
+ """Test rule checking that other targets build.
+
+ This works not by an instance of this test failing, but instead by
+ the targets it depends on failing to build, and hence failing
+ the attempt to run this test.
+
+ NOTE: At the moment, this won't work on Windows; but someone adding
+ support would be welcomed.
+
+ Typical usage:
+
+ ```
+ load("@bazel_skylib//rules:build_test.bzl", "build_test")
+ build_test(
+ name = "my_build_test",
+ targets = [
+ "//some/package:rule",
+ ],
+ )
+ ```
+
+ Args:
+ name: The name of the test rule.
+ targets: A list of targets to ensure build.
+ **kwargs: The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ """
+ if len(targets) == 0:
+ fail("targets must be non-empty", "targets")
+ if kwargs.get("data", None):
+ fail("data is not supported on a build_test()", "data")
+
+ # Remove any duplicate test targets.
+ targets = sets.to_list(sets.make(targets))
+
+ # Use a genrule to ensure the targets are built (works because it forces
+ # the outputs of the other rules on as data for the genrule)
+
+ # Split into batches to hopefully avoid things becoming so large they are
+ # too much for a remote execution set up.
+ batch_size = max(1, len(targets) // 100)
+
+ # Pull a few args over from the test to the genrule.
+ args_to_reuse = ["compatible_with", "restricted_to", "tags"]
+ genrule_args = {k: kwargs.get(k) for k in args_to_reuse if k in kwargs}
+
+ # Pass an output from the genrules as data to a shell test to bundle
+ # it all up in a test.
+ test_data = []
+
+ for idx, batch in enumerate([targets[i:i + batch_size] for i in range(0, len(targets), batch_size)]):
+ full_name = "{name}_{idx}__deps".format(name = name, idx = idx)
+ test_data.append(full_name)
+ native.genrule(
+ name = full_name,
+ srcs = batch,
+ outs = [full_name + ".out"],
+ testonly = 1,
+ visibility = ["//visibility:private"],
+ # TODO: Does this need something else for Windows?
+ cmd = "touch $@",
+ **genrule_args
+ )
+
+ native.sh_test(
+ name = name,
+ # TODO: Does this need something else for Windows?
+ srcs = ["@bazel_skylib//rules:empty_test.sh"],
+ data = test_data,
+ size = kwargs.pop("size", "small"), # Default to small for test size
+ **kwargs
+ )
diff --git a/rules/common_settings.bzl b/rules/common_settings.bzl
new file mode 100644
index 0000000..e80ab09
--- /dev/null
+++ b/rules/common_settings.bzl
@@ -0,0 +1,100 @@
+# Copyright 2019 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.
+
+"""Common build setting rules
+
+These rules return a BuildSettingInfo with the value of the build setting.
+For label-typed settings, use the native label_flag and label_setting rules.
+
+More documentation on how to use build settings at
+https://docs.bazel.build/versions/master/skylark/config.html#user-defined-build-settings
+"""
+
+BuildSettingInfo = provider(
+ doc = "A singleton provider that contains the raw value of a build setting",
+ fields = {
+ "value": "The value of the build setting in the current configuration. " +
+ "This value may come from the command line or an upstream transition, " +
+ "or else it will be the build setting's default.",
+ },
+)
+
+def _impl(ctx):
+ return BuildSettingInfo(value = ctx.build_setting_value)
+
+int_flag = rule(
+ implementation = _impl,
+ build_setting = config.int(flag = True),
+ doc = "An int-typed build setting that can be set on the command line",
+)
+
+int_setting = rule(
+ implementation = _impl,
+ build_setting = config.int(),
+ doc = "An int-typed build setting that cannot be set on the command line",
+)
+
+bool_flag = rule(
+ implementation = _impl,
+ build_setting = config.bool(flag = True),
+ doc = "A bool-typed build setting that can be set on the command line",
+)
+
+bool_setting = rule(
+ implementation = _impl,
+ build_setting = config.bool(),
+ doc = "A bool-typed build setting that cannot be set on the command line",
+)
+
+string_list_flag = rule(
+ implementation = _impl,
+ build_setting = config.string_list(flag = True),
+ doc = "A string list-typed build setting that can be set on the command line",
+)
+
+string_list_setting = rule(
+ implementation = _impl,
+ build_setting = config.string_list(),
+ doc = "A string list-typed build setting that cannot be set on the command line",
+)
+
+def _string_impl(ctx):
+ allowed_values = ctx.attr.values
+ value = ctx.build_setting_value
+ if len(allowed_values) == 0 or value in ctx.attr.values:
+ return BuildSettingInfo(value = value)
+ else:
+ fail("Error setting " + str(ctx.label) + ": invalid value '" + value + "'. Allowed values are " + str(allowed_values))
+
+string_flag = rule(
+ implementation = _string_impl,
+ build_setting = config.string(flag = True),
+ attrs = {
+ "values": attr.string_list(
+ doc = "The list of allowed values for this setting. An error is raised if any other value is given.",
+ ),
+ },
+ doc = "A string-typed build setting that can be set on the command line",
+)
+
+string_setting = rule(
+ implementation = _string_impl,
+ build_setting = config.string(),
+ attrs = {
+ "values": attr.string_list(
+ doc = "The list of allowed values for this setting. An error is raised if any other value is given.",
+ ),
+ },
+ doc = "A string-typed build setting that cannot be set on the command line",
+)
diff --git a/rules/copy_file.bzl b/rules/copy_file.bzl
new file mode 100644
index 0000000..c3bc5e6
--- /dev/null
+++ b/rules/copy_file.bzl
@@ -0,0 +1,29 @@
+# Copyright 2019 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.
+
+"""A rule that copies a file to another place.
+
+native.genrule() is sometimes used to copy files (often wishing to rename them).
+The 'copy_file' rule does this with a simpler interface than genrule.
+
+The rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command
+on Windows (no Bash is required).
+"""
+
+load(
+ "//rules/private:copy_file_private.bzl",
+ _copy_file = "copy_file",
+)
+
+copy_file = _copy_file
diff --git a/rules/diff_test.bzl b/rules/diff_test.bzl
new file mode 100644
index 0000000..93cf363
--- /dev/null
+++ b/rules/diff_test.bzl
@@ -0,0 +1,149 @@
+# Copyright 2019 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.
+
+"""A test rule that compares two binary files.
+
+The rule uses a Bash command (diff) on Linux/macOS/non-Windows, and a cmd.exe
+command (fc.exe) on Windows (no Bash is required).
+"""
+
+def _runfiles_path(f):
+ if f.root.path:
+ return f.path[len(f.root.path) + 1:] # generated file
+ else:
+ return f.path # source file
+
+def _diff_test_impl(ctx):
+ if ctx.attr.is_windows:
+ test_bin = ctx.actions.declare_file(ctx.label.name + "-test.bat")
+ ctx.actions.write(
+ output = test_bin,
+ content = """@rem Generated by diff_test.bzl, do not edit.
+@echo off
+SETLOCAL ENABLEEXTENSIONS
+SETLOCAL ENABLEDELAYEDEXPANSION
+set MF=%RUNFILES_MANIFEST_FILE:/=\\%
+set PATH=%SYSTEMROOT%\\system32
+set F1={file1}
+set F2={file2}
+if "!F1:~0,9!" equ "external/" (set F1=!F1:~9!) else (set F1=!TEST_WORKSPACE!/!F1!)
+if "!F2:~0,9!" equ "external/" (set F2=!F2:~9!) else (set F2=!TEST_WORKSPACE!/!F2!)
+for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F1! " "%MF%"`) do (
+ set RF1=%%i
+ set RF1=!RF1:/=\\!
+)
+if "!RF1!" equ "" (
+ echo>&2 ERROR: !F1! not found
+ exit /b 1
+)
+for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F2! " "%MF%"`) do (
+ set RF2=%%i
+ set RF2=!RF2:/=\\!
+)
+if "!RF2!" equ "" (
+ echo>&2 ERROR: !F2! not found
+ exit /b 1
+)
+fc.exe 2>NUL 1>NUL /B "!RF1!" "!RF2!"
+if %ERRORLEVEL% neq 0 (
+ if %ERRORLEVEL% equ 1 (
+ echo>&2 FAIL: files "{file1}" and "{file2}" differ
+ exit /b 1
+ ) else (
+ fc.exe /B "!RF1!" "!RF2!"
+ exit /b %errorlevel%
+ )
+)
+""".format(
+ file1 = _runfiles_path(ctx.file.file1),
+ file2 = _runfiles_path(ctx.file.file2),
+ ),
+ is_executable = True,
+ )
+ else:
+ test_bin = ctx.actions.declare_file(ctx.label.name + "-test.sh")
+ ctx.actions.write(
+ output = test_bin,
+ content = r"""#!/bin/bash
+set -euo pipefail
+F1="{file1}"
+F2="{file2}"
+[[ "$F1" =~ ^external/* ]] && F1="${{F1#external/}}" || F1="$TEST_WORKSPACE/$F1"
+[[ "$F2" =~ ^external/* ]] && F2="${{F2#external/}}" || F2="$TEST_WORKSPACE/$F2"
+if [[ -d "${{RUNFILES_DIR:-/dev/null}}" && "${{RUNFILES_MANIFEST_ONLY:-}}" != 1 ]]; then
+ RF1="$RUNFILES_DIR/$F1"
+ RF2="$RUNFILES_DIR/$F2"
+elif [[ -f "${{RUNFILES_MANIFEST_FILE:-/dev/null}}" ]]; then
+ RF1="$(grep -F -m1 "$F1 " "$RUNFILES_MANIFEST_FILE" | sed 's/^[^ ]* //')"
+ RF2="$(grep -F -m1 "$F2 " "$RUNFILES_MANIFEST_FILE" | sed 's/^[^ ]* //')"
+elif [[ -f "$TEST_SRCDIR/$F1" && -f "$TEST_SRCDIR/$F2" ]]; then
+ RF1="$TEST_SRCDIR/$F1"
+ RF2="$TEST_SRCDIR/$F2"
+else
+ echo >&2 "ERROR: could not find \"{file1}\" and \"{file2}\""
+ exit 1
+fi
+if ! diff "$RF1" "$RF2"; then
+ echo >&2 "FAIL: files \"{file1}\" and \"{file2}\" differ"
+ exit 1
+fi
+""".format(
+ file1 = _runfiles_path(ctx.file.file1),
+ file2 = _runfiles_path(ctx.file.file2),
+ ),
+ is_executable = True,
+ )
+ return DefaultInfo(
+ executable = test_bin,
+ files = depset(direct = [test_bin]),
+ runfiles = ctx.runfiles(files = [test_bin, ctx.file.file1, ctx.file.file2]),
+ )
+
+_diff_test = rule(
+ attrs = {
+ "file1": attr.label(
+ allow_single_file = True,
+ mandatory = True,
+ ),
+ "file2": attr.label(
+ allow_single_file = True,
+ mandatory = True,
+ ),
+ "is_windows": attr.bool(mandatory = True),
+ },
+ test = True,
+ implementation = _diff_test_impl,
+)
+
+def diff_test(name, file1, file2, **kwargs):
+ """A test that compares two files.
+
+ The test succeeds if the files' contents match.
+
+ Args:
+ name: The name of the test rule.
+ file1: Label of the file to compare to <code>file2</code>.
+ file2: Label of the file to compare to <code>file1</code>.
+ **kwargs: The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ """
+ _diff_test(
+ name = name,
+ file1 = file1,
+ file2 = file2,
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ **kwargs
+ )
diff --git a/rules/empty_test.sh b/rules/empty_test.sh
new file mode 100755
index 0000000..fde58b3
--- /dev/null
+++ b/rules/empty_test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+# This is a support file for build_test.bzl, it shouldn't be used by anything else.
+exit 0
diff --git a/rules/native_binary.bzl b/rules/native_binary.bzl
new file mode 100644
index 0000000..7d885a0
--- /dev/null
+++ b/rules/native_binary.bzl
@@ -0,0 +1,116 @@
+# Copyright 2019 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.
+
+"""native_binary() and native_test() rule implementations.
+
+These rules let you wrap a pre-built binary or script in a conventional binary
+and test rule respectively. They fulfill the same goal as sh_binary and sh_test
+do, but they run the wrapped binary directly, instead of through Bash, so they
+don't depend on Bash and work with --shell_exectuable="".
+"""
+
+load("//rules/private:copy_file_private.bzl", "copy_bash", "copy_cmd")
+
+def _impl_rule(ctx, is_windows):
+ out = ctx.actions.declare_file(ctx.attr.out)
+ if is_windows:
+ copy_cmd(ctx, ctx.file.src, out)
+ else:
+ copy_bash(ctx, ctx.file.src, out)
+ return DefaultInfo(
+ executable = out,
+ files = depset([out]),
+ runfiles = ctx.runfiles(
+ files = [out],
+ collect_data = True,
+ collect_default = True,
+ ),
+ )
+
+def _impl(ctx):
+ return _impl_rule(ctx, ctx.attr.is_windows)
+
+_ATTRS = {
+ "src": attr.label(
+ executable = True,
+ allow_single_file = True,
+ mandatory = True,
+ cfg = "host",
+ ),
+ "data": attr.label_list(allow_files = True),
+ # "out" is attr.string instead of attr.output, so that it is select()'able.
+ "out": attr.string(mandatory = True),
+ "is_windows": attr.bool(mandatory = True),
+}
+
+_native_binary = rule(
+ implementation = _impl,
+ attrs = _ATTRS,
+ executable = True,
+)
+
+_native_test = rule(
+ implementation = _impl,
+ attrs = _ATTRS,
+ test = True,
+)
+
+def native_binary(name, src, out, data = None, **kwargs):
+ """Wraps a pre-built binary or script with a binary rule.
+
+ You can "bazel run" this rule like any other binary rule, and use it as a tool in genrule.tools for example. You can also augment the binary with runfiles.
+
+ Args:
+ name: The name of the test rule.
+ src: label; path of the pre-built executable
+ out: output; an output name for the copy of the binary. (Bazel requires that this rule make a copy of 'src'.)
+ data: list of labels; data dependencies
+ **kwargs: The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-binaries">common attributes for binaries</a>.
+ """
+ _native_binary(
+ name = name,
+ src = src,
+ out = out,
+ data = data,
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ **kwargs
+ )
+
+def native_test(name, src, out, data = None, **kwargs):
+ """Wraps a pre-built binary or script with a test rule.
+
+ You can "bazel test" this rule like any other test rule. You can also augment the binary with
+ runfiles.
+
+ Args:
+ name: The name of the test rule.
+ src: label; path of the pre-built executable
+ out: output; an output name for the copy of the binary. (Bazel requires that this rule make a copy of 'src'.)
+ data: list of labels; data dependencies
+ **kwargs: The <a href="https://docs.bazel.build/versions/master/be/common-definitions.html#common-attributes-tests">common attributes for tests</a>.
+ """
+ _native_test(
+ name = name,
+ src = src,
+ out = out,
+ data = data,
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ **kwargs
+ )
diff --git a/rules/private/BUILD b/rules/private/BUILD
new file mode 100644
index 0000000..f835fb4
--- /dev/null
+++ b/rules/private/BUILD
@@ -0,0 +1,29 @@
+load("//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+bzl_library(
+ name = "copy_file_private",
+ srcs = ["copy_file_private.bzl"],
+ visibility = ["//rules:__pkg__"],
+)
+
+bzl_library(
+ name = "write_file_private",
+ srcs = ["write_file_private.bzl"],
+ visibility = ["//rules:__pkg__"],
+)
+
+bzl_library(
+ name = "maprule_util",
+ srcs = ["maprule_util.bzl"],
+)
+
+# The files needed for distribution
+filegroup(
+ name = "distribution",
+ srcs = glob(["*"]),
+ visibility = [
+ "//:__subpackages__",
+ ],
+)
diff --git a/rules/private/copy_file_private.bzl b/rules/private/copy_file_private.bzl
new file mode 100644
index 0000000..75cb027
--- /dev/null
+++ b/rules/private/copy_file_private.bzl
@@ -0,0 +1,142 @@
+# Copyright 2019 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.
+
+"""Implementation of copy_file macro and underlying rules.
+
+These rules copy a file to another location using Bash (on Linux/macOS) or
+cmd.exe (on Windows). '_copy_xfile' marks the resulting file executable,
+'_copy_file' does not.
+"""
+
+def copy_cmd(ctx, src, dst):
+ # Most Windows binaries built with MSVC use a certain argument quoting
+ # scheme. Bazel uses that scheme too to quote arguments. However,
+ # cmd.exe uses different semantics, so Bazel's quoting is wrong here.
+ # To fix that we write the command to a .bat file so no command line
+ # quoting or escaping is required.
+ bat = ctx.actions.declare_file(ctx.label.name + "-cmd.bat")
+ ctx.actions.write(
+ output = bat,
+ # Do not use lib/shell.bzl's shell.quote() method, because that uses
+ # Bash quoting syntax, which is different from cmd.exe's syntax.
+ content = "@copy /Y \"%s\" \"%s\" >NUL" % (
+ src.path.replace("/", "\\"),
+ dst.path.replace("/", "\\"),
+ ),
+ is_executable = True,
+ )
+ ctx.actions.run(
+ inputs = [src],
+ tools = [bat],
+ outputs = [dst],
+ executable = "cmd.exe",
+ arguments = ["/C", bat.path.replace("/", "\\")],
+ mnemonic = "CopyFile",
+ progress_message = "Copying files",
+ use_default_shell_env = True,
+ )
+
+def copy_bash(ctx, src, dst):
+ ctx.actions.run_shell(
+ tools = [src],
+ outputs = [dst],
+ command = "cp -f \"$1\" \"$2\"",
+ arguments = [src.path, dst.path],
+ mnemonic = "CopyFile",
+ progress_message = "Copying files",
+ use_default_shell_env = True,
+ )
+
+def _copy_file_impl(ctx):
+ if ctx.attr.allow_symlink:
+ ctx.actions.symlink(
+ output = ctx.outputs.out,
+ target_file = ctx.file.src,
+ is_executable = ctx.attr.is_executable,
+ )
+ else:
+ if ctx.attr.is_windows:
+ copy_cmd(ctx, ctx.file.src, ctx.outputs.out)
+ else:
+ copy_bash(ctx, ctx.file.src, ctx.outputs.out)
+
+ files = depset(direct = [ctx.outputs.out])
+ runfiles = ctx.runfiles(files = [ctx.outputs.out])
+ if ctx.attr.is_executable:
+ return [DefaultInfo(files = files, runfiles = runfiles, executable = ctx.outputs.out)]
+ else:
+ return [DefaultInfo(files = files, runfiles = runfiles)]
+
+_ATTRS = {
+ "src": attr.label(mandatory = True, allow_single_file = True),
+ "out": attr.output(mandatory = True),
+ "is_windows": attr.bool(mandatory = True),
+ "is_executable": attr.bool(mandatory = True),
+ "allow_symlink": attr.bool(mandatory = True),
+}
+
+_copy_file = rule(
+ implementation = _copy_file_impl,
+ provides = [DefaultInfo],
+ attrs = _ATTRS,
+)
+
+_copy_xfile = rule(
+ implementation = _copy_file_impl,
+ executable = True,
+ provides = [DefaultInfo],
+ attrs = _ATTRS,
+)
+
+def copy_file(name, src, out, is_executable = False, allow_symlink = False, **kwargs):
+ """Copies a file to another location.
+
+ `native.genrule()` is sometimes used to copy files (often wishing to rename them). The 'copy_file' rule does this with a simpler interface than genrule.
+
+ This rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command on Windows (no Bash is required).
+
+ Args:
+ name: Name of the rule.
+ src: A Label. The file to make a copy of. (Can also be the label of a rule
+ that generates a file.)
+ out: Path of the output file, relative to this package.
+ is_executable: A boolean. Whether to make the output file executable. When
+ True, the rule's output can be executed using `bazel run` and can be
+ in the srcs of binary and test rules that require executable sources.
+ WARNING: If `allow_symlink` is True, `src` must also be executable.
+ allow_symlink: A boolean. Whether to allow symlinking instead of copying.
+ When False, the output is always a hard copy. When True, the output
+ *can* be a symlink, but there is no guarantee that a symlink is
+ created (i.e., at the time of writing, we don't create symlinks on
+ Windows). Set this to True if you need fast copying and your tools can
+ handle symlinks (which most UNIX tools can).
+ **kwargs: further keyword arguments, e.g. `visibility`
+ """
+
+ copy_file_impl = _copy_file
+ if is_executable:
+ copy_file_impl = _copy_xfile
+
+ copy_file_impl(
+ name = name,
+ src = src,
+ out = out,
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ is_executable = is_executable,
+ allow_symlink = allow_symlink,
+ **kwargs
+ )
diff --git a/rules/private/maprule_util.bzl b/rules/private/maprule_util.bzl
new file mode 100644
index 0000000..dab65a5
--- /dev/null
+++ b/rules/private/maprule_util.bzl
@@ -0,0 +1,139 @@
+# Copyright 2019 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.
+
+"""Utilities for maprule."""
+
+def resolve_locations(ctx, strategy, d):
+ """Resolve $(location) references in the values of a dictionary.
+
+ Args:
+ ctx: the 'ctx' argument of the rule implementation function
+ strategy: a struct with an 'as_path(string) -> string' function
+ d: {string: string} dictionary; values may contain $(location) references
+ for labels declared in the rule's 'srcs' and 'tools' attributes
+
+ Returns:
+ {string: string} dict, same as 'd' except "$(location)" references are
+ resolved.
+ """
+ location_expressions = []
+ parts = {}
+ was_anything_to_resolve = False
+ for k, v in d.items():
+ # Look for "$(location ...)" or "$(locations ...)", resolve if found.
+ # _validate_attributes already ensured that there's at most one $(location/s ...) in "v".
+ if "$(location" in v:
+ tokens = v.split("$(location")
+ was_anything_to_resolve = True
+ closing_paren = tokens[1].find(")")
+ location_expressions.append("$(location" + tokens[1][:closing_paren + 1])
+ parts[k] = (tokens[0], tokens[1][closing_paren + 1:])
+ else:
+ location_expressions.append("")
+
+ resolved = {}
+ if was_anything_to_resolve:
+ # Resolve all $(location) expressions in one go. Should be faster than resolving them
+ # one-by-one.
+ all_location_expressions = "<split_here>".join(location_expressions)
+ all_resolved_locations = ctx.expand_location(all_location_expressions)
+ resolved_locations = strategy.as_path(all_resolved_locations).split("<split_here>")
+
+ i = 0
+
+ # Starlark dictionaries have a deterministic order of iteration, so the element order in
+ # "resolved_locations" matches the order in "location_expressions", i.e. the previous
+ # iteration order of "d".
+ for k, v in d.items():
+ if location_expressions[i]:
+ head, tail = parts[k]
+ resolved[k] = head + resolved_locations[i] + tail
+ else:
+ resolved[k] = v
+ i += 1
+ else:
+ resolved = d
+
+ return resolved
+
+def fail_if_errors(errors):
+ """Reports errors and fails the rule.
+
+ Args:
+ errors: list of strings; the errors to report. At most 10 are reported.
+ """
+ if errors:
+ # Don't overwhelm the user; report up to ten errors.
+ fail("\n".join(errors[:10]))
+
+def _as_windows_path(s):
+ """Returns the input path as a Windows path (replaces all of "/" with "\")."""
+ return s.replace("/", "\\")
+
+def _unchanged_path(s):
+ """Returns the input string (path) unchanged."""
+ return s
+
+def _create_cmd_action(
+ ctx,
+ outputs,
+ command,
+ inputs = None,
+ env = None,
+ progress_message = None,
+ mnemonic = None,
+ manifests_from_tools = None):
+ """Create one action using cmd.exe."""
+ ctx.actions.run(
+ inputs = inputs or [],
+ outputs = outputs,
+ executable = "cmd.exe",
+ env = env,
+ arguments = ["/C", command],
+ progress_message = progress_message or "Running cmd.exe command",
+ mnemonic = mnemonic or "CmdExeCommand",
+ input_manifests = manifests_from_tools,
+ )
+
+def _create_bash_action(
+ ctx,
+ outputs,
+ command,
+ inputs = None,
+ env = None,
+ progress_message = None,
+ mnemonic = None,
+ manifests_from_tools = None):
+ """Create one action using Bash."""
+ ctx.actions.run_shell(
+ inputs = inputs or [],
+ outputs = outputs,
+ env = env,
+ command = command,
+ progress_message = progress_message or "Running Bash command",
+ mnemonic = mnemonic or "BashCommand",
+ input_manifests = manifests_from_tools,
+ )
+
+# Action creation utilities for cmd.exe actions.
+CMD_STRATEGY = struct(
+ as_path = _as_windows_path,
+ create_action = _create_cmd_action,
+)
+
+# Action creation utilities for Bash actions.
+BASH_STRATEGY = struct(
+ as_path = _unchanged_path,
+ create_action = _create_bash_action,
+)
diff --git a/rules/private/write_file_private.bzl b/rules/private/write_file_private.bzl
new file mode 100644
index 0000000..ac8ce9c
--- /dev/null
+++ b/rules/private/write_file_private.bzl
@@ -0,0 +1,113 @@
+# Copyright 2019 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.
+
+"""Implementation of write_file macro and underlying rules.
+
+These rules write a UTF-8 encoded text file, using Bazel's FileWriteAction.
+'_write_xfile' marks the resulting file executable, '_write_file' does not.
+"""
+
+def _common_impl(ctx, is_windows, is_executable):
+ if ctx.attr.newline == "auto":
+ newline = "\r\n" if is_windows else "\n"
+ elif ctx.attr.newline == "windows":
+ newline = "\r\n"
+ else:
+ newline = "\n"
+
+ # ctx.actions.write creates a FileWriteAction which uses UTF-8 encoding.
+ ctx.actions.write(
+ output = ctx.outputs.out,
+ content = newline.join(ctx.attr.content) if ctx.attr.content else "",
+ is_executable = is_executable,
+ )
+ files = depset(direct = [ctx.outputs.out])
+ runfiles = ctx.runfiles(files = [ctx.outputs.out])
+ if is_executable:
+ return [DefaultInfo(files = files, runfiles = runfiles, executable = ctx.outputs.out)]
+ else:
+ return [DefaultInfo(files = files, runfiles = runfiles)]
+
+def _impl(ctx):
+ return _common_impl(ctx, ctx.attr.is_windows, False)
+
+def _ximpl(ctx):
+ return _common_impl(ctx, ctx.attr.is_windows, True)
+
+_ATTRS = {
+ "out": attr.output(mandatory = True),
+ "content": attr.string_list(mandatory = False, allow_empty = True),
+ "newline": attr.string(values = ["unix", "windows", "auto"], default = "auto"),
+ "is_windows": attr.bool(mandatory = True),
+}
+
+_write_file = rule(
+ implementation = _impl,
+ provides = [DefaultInfo],
+ attrs = _ATTRS,
+)
+
+_write_xfile = rule(
+ implementation = _ximpl,
+ executable = True,
+ provides = [DefaultInfo],
+ attrs = _ATTRS,
+)
+
+def write_file(
+ name,
+ out,
+ content = [],
+ is_executable = False,
+ newline = "auto",
+ **kwargs):
+ """Creates a UTF-8 encoded text file.
+
+ Args:
+ name: Name of the rule.
+ out: Path of the output file, relative to this package.
+ content: A list of strings. Lines of text, the contents of the file.
+ Newlines are added automatically after every line except the last one.
+ is_executable: A boolean. Whether to make the output file executable.
+ When True, the rule's output can be executed using `bazel run` and can
+ be in the srcs of binary and test rules that require executable
+ sources.
+ newline: one of ["auto", "unix", "windows"]: line endings to use. "auto"
+ for platform-determined, "unix" for LF, and "windows" for CRLF.
+ **kwargs: further keyword arguments, e.g. <code>visibility</code>
+ """
+ if is_executable:
+ _write_xfile(
+ name = name,
+ content = content,
+ out = out,
+ newline = newline or "auto",
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ **kwargs
+ )
+ else:
+ _write_file(
+ name = name,
+ content = content,
+ out = out,
+ newline = newline or "auto",
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ **kwargs
+ )
diff --git a/rules/run_binary.bzl b/rules/run_binary.bzl
new file mode 100644
index 0000000..10f1ab5
--- /dev/null
+++ b/rules/run_binary.bzl
@@ -0,0 +1,98 @@
+# Copyright 2019 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.
+
+"""
+run_binary() build rule implementation.
+
+Runs a binary as a build action. This rule does not require Bash (unlike native.genrule()).
+"""
+
+load("//lib:dicts.bzl", "dicts")
+
+def _impl(ctx):
+ tool_as_list = [ctx.attr.tool]
+ tool_inputs, tool_input_mfs = ctx.resolve_tools(tools = tool_as_list)
+ args = [
+ # Expand $(location) / $(locations) in args.
+ #
+ # To keep the rule simple, do not expand Make Variables (like *_binary.args usually would).
+ # (We can add this feature later if users ask for it.)
+ #
+ # Also for simple implementation and usage, do not Bash-tokenize the arguments. Without
+ # tokenization the user can write args=["a b"] to pass (a b) as one argument, but with
+ # tokenization they would have to write args=["'a b'"] or args=["a\\ b"]. There's no
+ # documented tokenization function anyway (as of 2019-05-21 ctx.tokenize exists but is
+ # undocumented, see https://github.com/bazelbuild/bazel/issues/8389).
+ ctx.expand_location(a, tool_as_list) if "$(location" in a else a
+ for a in ctx.attr.args
+ ]
+ envs = {
+ # Expand $(location) / $(locations) in the values.
+ k: ctx.expand_location(v, tool_as_list) if "$(location" in v else v
+ for k, v in ctx.attr.env.items()
+ }
+ ctx.actions.run(
+ outputs = ctx.outputs.outs,
+ inputs = ctx.files.srcs,
+ tools = tool_inputs,
+ executable = ctx.executable.tool,
+ arguments = args,
+ mnemonic = "RunBinary",
+ use_default_shell_env = False,
+ env = dicts.add(ctx.configuration.default_shell_env, envs),
+ input_manifests = tool_input_mfs,
+ )
+ return DefaultInfo(
+ files = depset(ctx.outputs.outs),
+ runfiles = ctx.runfiles(files = ctx.outputs.outs),
+ )
+
+run_binary = rule(
+ implementation = _impl,
+ doc = "Runs a binary as a build action.<br/><br/>This rule does not require Bash (unlike" +
+ " <code>native.genrule</code>).",
+ attrs = {
+ "tool": attr.label(
+ doc = "The tool to run in the action.<br/><br/>Must be the label of a *_binary rule," +
+ " of a rule that generates an executable file, or of a file that can be" +
+ " executed as a subprocess (e.g. an .exe or .bat file on Windows or a binary" +
+ " with executable permission on Linux). This label is available for" +
+ " <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
+ executable = True,
+ allow_files = True,
+ mandatory = True,
+ cfg = "host",
+ ),
+ "env": attr.string_dict(
+ doc = "Environment variables of the action.<br/><br/>Subject to " +
+ " <code><a href=\"https://docs.bazel.build/versions/master/be/make-variables.html#location\">$(location)</a></code>" +
+ " expansion.",
+ ),
+ "srcs": attr.label_list(
+ allow_files = True,
+ doc = "Additional inputs of the action.<br/><br/>These labels are available for" +
+ " <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
+ ),
+ "outs": attr.output_list(
+ mandatory = True,
+ doc = "Output files generated by the action.<br/><br/>These labels are available for" +
+ " <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
+ ),
+ "args": attr.string_list(
+ doc = "Command line arguments of the binary.<br/><br/>Subject to" +
+ "<code><a href=\"https://docs.bazel.build/versions/master/be/make-variables.html#location\">$(location)</a></code>" +
+ " expansion.",
+ ),
+ },
+)
diff --git a/rules/select_file.bzl b/rules/select_file.bzl
new file mode 100644
index 0000000..f359e43
--- /dev/null
+++ b/rules/select_file.bzl
@@ -0,0 +1,54 @@
+# Copyright 2020 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.
+
+"""
+select_file() build rule implementation.
+
+Selects a single file from the outputs of some target by given relative path.
+"""
+
+def _impl(ctx):
+ if ctx.attr.subpath and len(ctx.attr.subpath) == 0:
+ fail("Subpath can not be empty.")
+
+ out = None
+ canonical = ctx.attr.subpath.replace("\\", "/")
+ for file_ in ctx.attr.srcs.files.to_list():
+ if file_.path.replace("\\", "/").endswith(canonical):
+ out = file_
+ break
+ if not out:
+ files_str = ",\n".join([
+ str(f.path)
+ for f in ctx.attr.srcs.files.to_list()
+ ])
+ fail("Can not find specified file in [%s]" % files_str)
+ return [DefaultInfo(files = depset([out]))]
+
+select_file = rule(
+ implementation = _impl,
+ doc = "Selects a single file from the outputs of some target \
+by given relative path",
+ attrs = {
+ "srcs": attr.label(
+ allow_files = True,
+ mandatory = True,
+ doc = "The target producing the file among other outputs",
+ ),
+ "subpath": attr.string(
+ mandatory = True,
+ doc = "Relative path to the file",
+ ),
+ },
+)
diff --git a/rules/write_file.bzl b/rules/write_file.bzl
new file mode 100644
index 0000000..908bbc6
--- /dev/null
+++ b/rules/write_file.bzl
@@ -0,0 +1,30 @@
+# Copyright 2019 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.
+
+"""A rule that writes a UTF-8 encoded text file from user-specified contents.
+
+native.genrule() is sometimes used to create a text file. The 'write_file' and
+macro does this with a simpler interface than genrule.
+
+The rules generated by the macro do not use Bash or any other shell to write the
+file. Instead they use Starlark's built-in file writing action
+(ctx.actions.write).
+"""
+
+load(
+ "//rules/private:write_file_private.bzl",
+ _write_file = "write_file",
+)
+
+write_file = _write_file
diff --git a/skylark_library.bzl b/skylark_library.bzl
new file mode 100644
index 0000000..40e8bba
--- /dev/null
+++ b/skylark_library.bzl
@@ -0,0 +1,23 @@
+# Copyright 2018 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.
+
+"""Skylib module containing a library rule for aggregating rules files.
+
+Removed:
+ Use bzl_library in bzl_library.bzl instead.
+"""
+
+fail(
+ "skylark_library.bzl has been removed, please use bzl_library.bzl instead.",
+)
diff --git a/tests/BUILD b/tests/BUILD
new file mode 100644
index 0000000..c2a4223
--- /dev/null
+++ b/tests/BUILD
@@ -0,0 +1,99 @@
+load("//:bzl_library.bzl", "bzl_library")
+load(":build_test_tests.bzl", "build_test_test_suite")
+load(":collections_tests.bzl", "collections_test_suite")
+load(":dicts_tests.bzl", "dicts_test_suite")
+load(":new_sets_tests.bzl", "new_sets_test_suite")
+load(":partial_tests.bzl", "partial_test_suite")
+load(":paths_tests.bzl", "paths_test_suite")
+load(":selects_tests.bzl", "selects_test_suite")
+load(":shell_tests.bzl", "shell_args_test_gen", "shell_test_suite")
+load(":structs_tests.bzl", "structs_test_suite")
+load(":types_tests.bzl", "types_test_suite")
+load(":unittest_tests.bzl", "unittest_passing_tests_suite")
+load(":versions_tests.bzl", "versions_test_suite")
+
+licenses(["notice"])
+
+exports_files(
+ ["unittest.bash"],
+ visibility = ["//tests:__subpackages__"],
+)
+
+build_test_test_suite()
+
+collections_test_suite()
+
+dicts_test_suite()
+
+new_sets_test_suite()
+
+partial_test_suite()
+
+paths_test_suite()
+
+selects_test_suite()
+
+shell_test_suite()
+
+structs_test_suite()
+
+types_test_suite()
+
+unittest_passing_tests_suite()
+
+versions_test_suite()
+
+bzl_library(
+ name = "unittest_tests_bzl",
+ srcs = ["unittest_tests.bzl"],
+ visibility = ["//visibility:private"],
+ deps = ["//lib:unittest"],
+)
+
+sh_test(
+ name = "unittest_e2e_test",
+ srcs = ["unittest_test.sh"],
+ data = [
+ ":unittest.bash",
+ ":unittest_tests_bzl",
+ "//lib:dicts",
+ "//lib:new_sets",
+ "//lib:sets",
+ "//lib:types",
+ "//lib:unittest",
+ "//toolchains/unittest:test_deps",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+ tags = ["local"],
+)
+
+sh_test(
+ name = "analysis_test_e2e_test",
+ srcs = ["analysis_test_test.sh"],
+ data = [
+ ":unittest.bash",
+ "//rules:analysis_test.bzl",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+ tags = ["local"],
+)
+
+sh_test(
+ name = "common_settings_e2e_test",
+ srcs = ["common_settings_test.sh"],
+ data = [
+ ":unittest.bash",
+ "//rules:common_settings.bzl",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+ tags = ["local"],
+)
+
+shell_args_test_gen(
+ name = "shell_spawn_e2e_test_src",
+)
+
+sh_test(
+ name = "shell_spawn_e2e_test",
+ srcs = [":shell_spawn_e2e_test_src"],
+)
diff --git a/tests/analysis_test_test.sh b/tests/analysis_test_test.sh
new file mode 100755
index 0000000..db9bbac
--- /dev/null
+++ b/tests/analysis_test_test.sh
@@ -0,0 +1,154 @@
+#!/bin/bash
+
+# Copyright 2019 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.
+#
+# End to end tests for analysis_test.bzl.
+#
+# End to end tests of analysis_test.bzl cover verification that
+# analysis_test tests fail when their underlying test targets fail analysis.
+
+# --- begin runfiles.bash initialization ---
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function create_pkg() {
+ local -r pkg="$1"
+ mkdir -p "$pkg"
+ cd "$pkg"
+
+ cat > WORKSPACE <<EOF
+workspace(name = 'bazel_skylib')
+EOF
+
+ mkdir -p rules
+ cat > rules/BUILD <<EOF
+exports_files(["*.bzl"])
+EOF
+
+ ln -sf "$(rlocation bazel_skylib/rules/analysis_test.bzl)" rules/analysis_test.bzl
+
+ mkdir -p fakerules
+ cat > fakerules/rules.bzl <<EOF
+load("//rules:analysis_test.bzl", "analysis_test")
+
+def _fake_rule_impl(ctx):
+ fail("This rule should never work")
+
+fake_rule = rule(
+ implementation = _fake_rule_impl,
+)
+
+def _fake_depending_rule_impl(ctx):
+ return []
+
+fake_depending_rule = rule(
+ implementation = _fake_depending_rule_impl,
+ attrs = {"deps" : attr.label_list()},
+)
+EOF
+
+ cat > fakerules/BUILD <<EOF
+exports_files(["*.bzl"])
+EOF
+
+ mkdir -p testdir
+ cat > testdir/dummy.cc <<EOF
+int dummy() { return 0; }
+EOF
+
+ cat > testdir/BUILD <<EOF
+load("//rules:analysis_test.bzl", "analysis_test")
+load("//fakerules:rules.bzl", "fake_rule", "fake_depending_rule")
+
+fake_rule(name = "target_fails")
+
+fake_depending_rule(name = "dep_fails",
+ deps = [":target_fails"])
+
+analysis_test(
+ name = "direct_target_fails",
+ targets = [":target_fails"],
+)
+
+analysis_test(
+ name = "transitive_target_fails",
+ targets = [":dep_fails"],
+)
+
+# Use it in a non-test target
+cc_library(
+ name = "dummy_cc_library",
+ srcs = ["dummy.cc"],
+)
+
+analysis_test(
+ name = "target_succeeds",
+ targets = [":dummy_cc_library"],
+)
+EOF
+}
+
+function test_target_succeeds() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:target_succeeds >"$TEST_log" 2>&1 || fail "Expected test to pass"
+
+ expect_log "PASSED"
+}
+
+function test_direct_target_fails() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:direct_target_fails --test_output=all --verbose_failures \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "This rule should never work"
+}
+
+function test_transitive_target_fails() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:transitive_target_fails --test_output=all --verbose_failures \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "This rule should never work"
+}
+
+cd "$TEST_TMPDIR"
+run_suite "analysis_test test suite"
diff --git a/tests/build_test_tests.bzl b/tests/build_test_tests.bzl
new file mode 100644
index 0000000..5bcbbd3
--- /dev/null
+++ b/tests/build_test_tests.bzl
@@ -0,0 +1,43 @@
+# Copyright 2019 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.
+
+"""Unit tests for build_test.bzl."""
+
+load("//rules:build_test.bzl", "build_test")
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+# buildifier: disable=unnamed-macro
+def build_test_test_suite():
+ # Since the rules doesn't do anything really, it just makes some targets
+ # to get Bazel to build other targets via a `bazel test`, just make some
+ # targets to exercise the rule.
+
+ # Make a source file
+ native.genrule(
+ name = "build_test__make_src",
+ outs = ["build_test__src.cc"],
+ cmd = "echo 'int dummy() { return 0; }' > $@",
+ )
+
+ # Use it in a non-test target
+ cc_library(
+ name = "build_test__build_target",
+ srcs = [":build_test__make_src"],
+ )
+
+ # Wrap a build test around the target.
+ build_test(
+ name = "build_test__test",
+ targets = [":build_test__build_target"],
+ )
diff --git a/tests/collections_tests.bzl b/tests/collections_tests.bzl
new file mode 100644
index 0000000..9e86672
--- /dev/null
+++ b/tests/collections_tests.bzl
@@ -0,0 +1,113 @@
+# Copyright 2017 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.
+
+"""Unit tests for collections.bzl."""
+
+load("//lib:collections.bzl", "collections")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _after_each_test(ctx):
+ """Unit tests for collections.after_each."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, [], collections.after_each("1", []))
+ asserts.equals(env, ["a", "1"], collections.after_each("1", ["a"]))
+ asserts.equals(
+ env,
+ ["a", "1", "b", "1"],
+ collections.after_each("1", ["a", "b"]),
+ )
+
+ # We don't care what type the separator is, we just put it there; so None
+ # should be just as valid as anything else.
+ asserts.equals(
+ env,
+ ["a", None, "b", None],
+ collections.after_each(None, ["a", "b"]),
+ )
+
+ return unittest.end(env)
+
+after_each_test = unittest.make(_after_each_test)
+
+def _before_each_test(ctx):
+ """Unit tests for collections.before_each."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, [], collections.before_each("1", []))
+ asserts.equals(env, ["1", "a"], collections.before_each("1", ["a"]))
+ asserts.equals(
+ env,
+ ["1", "a", "1", "b"],
+ collections.before_each("1", ["a", "b"]),
+ )
+
+ # We don't care what type the separator is, we just put it there; so None
+ # should be just as valid as anything else.
+ asserts.equals(
+ env,
+ [None, "a", None, "b"],
+ collections.before_each(None, ["a", "b"]),
+ )
+
+ return unittest.end(env)
+
+before_each_test = unittest.make(_before_each_test)
+
+def _uniq_test(ctx):
+ env = unittest.begin(ctx)
+ asserts.equals(env, collections.uniq([0, 1, 2, 3]), [0, 1, 2, 3])
+ asserts.equals(env, collections.uniq([]), [])
+ asserts.equals(env, collections.uniq([1, 1, 1, 1, 1]), [1])
+ asserts.equals(
+ env,
+ collections.uniq([
+ True,
+ 5,
+ "foo",
+ 5,
+ False,
+ struct(a = 1),
+ True,
+ struct(b = 2),
+ "bar",
+ (1,),
+ "foo",
+ struct(a = 1),
+ (1,),
+ ]),
+ [
+ True,
+ 5,
+ "foo",
+ False,
+ struct(a = 1),
+ struct(b = 2),
+ "bar",
+ (1,),
+ ],
+ )
+
+ return unittest.end(env)
+
+uniq_test = unittest.make(_uniq_test)
+
+def collections_test_suite():
+ """Creates the test targets and test suite for collections.bzl tests."""
+ unittest.suite(
+ "collections_tests",
+ after_each_test,
+ before_each_test,
+ uniq_test,
+ )
diff --git a/tests/common_settings_test.sh b/tests/common_settings_test.sh
new file mode 100755
index 0000000..531e830
--- /dev/null
+++ b/tests/common_settings_test.sh
@@ -0,0 +1,169 @@
+#!/bin/bash
+
+# Copyright 2019 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.
+#
+# End to end tests for common_settings.bzl
+
+# --- begin runfiles.bash initialization ---
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function create_volcano_pkg() {
+ local -r pkg="$1"
+ mkdir -p "$pkg"
+ cd "$pkg"
+
+ cat > WORKSPACE <<EOF
+workspace(name = 'bazel_skylib')
+EOF
+
+ mkdir -p rules
+ cat > rules/BUILD <<EOF
+exports_files(["*.bzl"])
+EOF
+
+ ln -sf "$(rlocation bazel_skylib/rules/common_settings.bzl)" rules/common_settings.bzl
+
+ mkdir -p volcano
+ cat > volcano/rules.bzl <<EOF
+load("//rules:common_settings.bzl", "BuildSettingInfo")
+
+def _volcano_impl(ctx):
+ description = struct(
+ height = ctx.attr.height[BuildSettingInfo].value,
+ active = ctx.attr.active[BuildSettingInfo].value,
+ namer = ctx.attr.namer[BuildSettingInfo].value,
+ nicknames = ctx.attr.nicknames[BuildSettingInfo].value
+ )
+ print(description)
+
+volcano = rule(
+ implementation = _volcano_impl,
+ attrs = {
+ "height" : attr.label(),
+ "active" : attr.label(),
+ "namer": attr.label(),
+ "nicknames": attr.label(),
+ }
+)
+EOF
+
+ cat > volcano/BUILD <<EOF
+load(
+ "//rules:common_settings.bzl",
+ "int_flag",
+ "int_setting",
+ "bool_flag",
+ "string_flag",
+ "string_list_flag",
+)
+load("//volcano:rules.bzl", "volcano")
+
+int_flag(
+ name = "height-flag",
+ build_setting_default = 9677 # pre-1980 explosion
+)
+
+bool_flag(
+ name = "active-flag",
+ build_setting_default = True
+)
+
+string_flag(
+ name = "namer-flag",
+ build_setting_default = "cpt-george-vancouver",
+ values = ["cpt-george-vancouver", "puyallup-tribe"]
+)
+
+string_list_flag(
+ name = "nicknames-flag",
+ build_setting_default = ["loowit", "loowitiatkla", "lavelatla"]
+)
+
+int_setting(
+ name = "height-setting",
+ build_setting_default = 9677
+)
+
+volcano(
+ name = "mt-st-helens",
+ height = ":height-flag",
+ active = ":active-flag",
+ namer = ":namer-flag",
+ nicknames = ":nicknames-flag",
+)
+EOF
+
+}
+
+function test_can_set_flags() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_volcano_pkg "$pkg"
+
+ bazel build volcano:mt-st-helens --//volcano:height-flag=8366 \
+ --//volcano:active-flag=False --//volcano:namer-flag=puyallup-tribe \
+ --//volcano:nicknames-flag=volcano-mc-volcanoface \
+ >"$TEST_log" 2>&1 || fail "Expected test to pass"
+
+ expect_log "active = False"
+ expect_log "height = 8366"
+ expect_log "namer = \"puyallup-tribe\""
+ expect_log "nicknames = \[\"volcano-mc-volcanoface\"\]"
+}
+
+function test_cannot_set_settings() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_volcano_pkg "$pkg"
+
+ bazel build volcano:mt-st-helens --//volcano:height-setting=8366 \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "Unrecognized option: //volcano:height-setting"
+}
+
+function test_not_allowed_value() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_volcano_pkg "$pkg"
+
+ bazel build volcano:mt-st-helens --//volcano:namer-flag=me \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "Error setting //volcano:namer-flag: invalid value 'me'. Allowed values are"
+}
+
+
+cd "$TEST_TMPDIR"
+run_suite "common_settings test suite"
diff --git a/tests/copy_file/BUILD b/tests/copy_file/BUILD
new file mode 100644
index 0000000..79ab7ba
--- /dev/null
+++ b/tests/copy_file/BUILD
@@ -0,0 +1,173 @@
+# This package aids testing the 'copy_file' rule.
+#
+# The package contains 4 copy_file rules:
+# - 'copy_src' and 'copy_gen' copy a source file and a generated file
+# respectively
+# - 'copy_xsrc' and 'copy_xgen' copy a source file and a generated file
+# respectively (both are shell scripts), and mark their output as executable
+#
+# The generated file is the output of the 'gen' genrule.
+#
+# The 'bin_src' and 'bin_gen' rules are sh_binary rules. They use the
+# 'copy_xsrc' and 'copy_xgen' rules respectively. The sh_binary rule requires
+# its source to be executable, so building these two rules successfully means
+# that 'copy_file' managed to make its output executable.
+#
+# The 'run_executables' genrule runs the 'bin_src' and 'bin_gen' binaries,
+# partly to ensure they can be run, and partly so we can observe their output
+# and assert the contents in the 'copy_file_tests' test.
+#
+# The 'file_deps' filegroup depends on 'copy_src'. The filegroup rule uses the
+# DefaultInfo.files field from its dependencies. When we data-depend on the
+# filegroup from 'copy_file_tests', we transitively data-depend on the
+# DefaultInfo.files of the 'copy_src' rule.
+#
+# The 'copy_file_tests' test is the actual integration test. It data-depends
+# on:
+# - the 'run_executables' rule, to get the outputs of 'bin_src' and 'bin_gen'
+# - the 'file_deps' rule, and by nature of using a filegroup, we get the files
+# from the DefaultInfo.files of the 'copy_file' rule, and thereby assert that
+# that field contains the output file of the rule
+# - the 'copy_nonempty_text' rule, and thereby on the DefaultInfo.runfiles field
+# of it, so we assert that that field contains the output file of the rule
+
+load("//rules:copy_file.bzl", "copy_file")
+
+licenses(["notice"])
+
+package(default_testonly = 1)
+
+sh_test(
+ name = "copy_file_tests",
+ srcs = ["copy_file_tests.sh"],
+ data = [
+ ":run_executables",
+ # Use DefaultInfo.files from 'copy_src' (via 'file_deps').
+ ":file_deps",
+ # Use DefaultInfo.runfiles from 'copy_gen'.
+ ":copy_gen",
+ ":copy_gen_symlink",
+ "//tests:unittest.bash",
+ ],
+ deps = ["@bazel_tools//tools/bash/runfiles"],
+)
+
+filegroup(
+ name = "file_deps",
+ # Use DefaultInfo.files from 'copy_src'.
+ srcs = [
+ ":copy_src",
+ ":copy_src_symlink",
+ ],
+)
+
+# If 'run_executables' is built, then 'bin_gen' and 'bin_src' are
+# executable, asserting that copy_file makes the output executable.
+genrule(
+ name = "run_executables",
+ outs = [
+ "xsrc-out-symlink.txt",
+ "xgen-out-symlink.txt",
+ "xsrc-out.txt",
+ "xgen-out.txt",
+ ],
+ cmd = " && ".join([
+ "$(location :bin_src_symlink) > $(location xsrc-out-symlink.txt)",
+ "$(location :bin_gen_symlink) > $(location xgen-out-symlink.txt)",
+ "$(location :bin_src) > $(location xsrc-out.txt)",
+ "$(location :bin_gen) > $(location xgen-out.txt)",
+ ]),
+ output_to_bindir = 1,
+ tools = [
+ ":bin_gen",
+ ":bin_src",
+ ":bin_gen_symlink",
+ ":bin_src_symlink",
+ ],
+)
+
+# If 'bin_src' is built, then 'copy_xsrc' made its output executable.
+sh_binary(
+ name = "bin_src",
+ srcs = [":copy_xsrc"],
+)
+
+# If 'bin_src' is built, then 'copy_xsrc' made its output executable.
+sh_binary(
+ name = "bin_src_symlink",
+ srcs = [":copy_xsrc_symlink"],
+)
+
+# If 'bin_gen' is built, then 'copy_xgen' made its output executable.
+sh_binary(
+ name = "bin_gen",
+ srcs = [":copy_xgen"],
+)
+
+# If 'bin_gen' is built, then 'copy_xgen' made its output executable.
+sh_binary(
+ name = "bin_gen_symlink",
+ srcs = [":copy_xgen_symlink"],
+)
+
+copy_file(
+ name = "copy_src",
+ src = "a.txt",
+ out = "out/a-out.txt",
+)
+
+copy_file(
+ name = "copy_src_symlink",
+ src = "a.txt",
+ out = "out/a-out-symlink.txt",
+ allow_symlink = True,
+)
+
+copy_file(
+ name = "copy_gen",
+ src = ":gen",
+ out = "out/gen-out.txt",
+ allow_symlink = True,
+)
+
+copy_file(
+ name = "copy_gen_symlink",
+ src = ":gen",
+ out = "out/gen-out-symlink.txt",
+)
+
+copy_file(
+ name = "copy_xsrc",
+ src = "a.txt",
+ out = "xout/a-out.sh",
+ is_executable = True,
+)
+
+copy_file(
+ name = "copy_xsrc_symlink",
+ src = "a_with_exec_bit.txt",
+ out = "xout/a-out-symlink.sh",
+ is_executable = True,
+ allow_symlink = True,
+)
+
+copy_file(
+ name = "copy_xgen",
+ src = ":gen",
+ out = "xout/gen-out.sh",
+ is_executable = True,
+)
+
+copy_file(
+ name = "copy_xgen_symlink",
+ src = ":gen",
+ out = "xout/gen-out-symlink.sh",
+ is_executable = True,
+ allow_symlink = True,
+)
+
+genrule(
+ name = "gen",
+ outs = ["b.txt"],
+ cmd = "echo -e '#!/bin/bash\necho potato' > $@",
+)
diff --git a/tests/copy_file/a.txt b/tests/copy_file/a.txt
new file mode 100644
index 0000000..acd332a
--- /dev/null
+++ b/tests/copy_file/a.txt
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo aaa
diff --git a/tests/copy_file/a_with_exec_bit.txt b/tests/copy_file/a_with_exec_bit.txt
new file mode 100755
index 0000000..acd332a
--- /dev/null
+++ b/tests/copy_file/a_with_exec_bit.txt
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo aaa
diff --git a/tests/copy_file/copy_file_tests.sh b/tests/copy_file/copy_file_tests.sh
new file mode 100755
index 0000000..dfee635
--- /dev/null
+++ b/tests/copy_file/copy_file_tests.sh
@@ -0,0 +1,85 @@
+# Copyright 2019 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.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function test_copy_src() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/out/a-out.txt)" >"$TEST_log"
+ expect_log '^#!/bin/bash$'
+ expect_log '^echo aaa$'
+}
+
+function test_copy_src_symlink() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/out/a-out-symlink.txt)" >"$TEST_log"
+ expect_log '^#!/bin/bash$'
+ expect_log '^echo aaa$'
+}
+
+function test_copy_gen() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/out/gen-out.txt)" >"$TEST_log"
+ expect_log '^#!/bin/bash$'
+ expect_log '^echo potato$'
+}
+
+function test_copy_gen_symlink() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/out/gen-out-symlink.txt)" >"$TEST_log"
+ expect_log '^#!/bin/bash$'
+ expect_log '^echo potato$'
+}
+
+function test_copy_xsrc() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/xsrc-out.txt)" >"$TEST_log"
+ expect_log '^aaa$'
+}
+
+function test_copy_xsrc_symlink() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/xsrc-out-symlink.txt)" >"$TEST_log"
+ expect_log '^aaa$'
+}
+
+function test_copy_xgen() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/xgen-out.txt)" >"$TEST_log"
+ expect_log '^potato$'
+}
+
+function test_copy_xgen_symlink() {
+ cat "$(rlocation bazel_skylib/tests/copy_file/xgen-out-symlink.txt)" >"$TEST_log"
+ expect_log '^potato$'
+}
+
+run_suite "copy_file_tests test suite"
diff --git a/tests/dicts_tests.bzl b/tests/dicts_tests.bzl
new file mode 100644
index 0000000..2fc407d
--- /dev/null
+++ b/tests/dicts_tests.bzl
@@ -0,0 +1,91 @@
+# Copyright 2017 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.
+
+"""Unit tests for dicts.bzl."""
+
+load("//lib:dicts.bzl", "dicts")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _add_test(ctx):
+ """Unit tests for dicts.add."""
+ env = unittest.begin(ctx)
+
+ # Test zero- and one-argument behavior.
+ asserts.equals(env, {}, dicts.add())
+ asserts.equals(env, {"a": 1}, dicts.add({"a": 1}))
+ asserts.equals(env, {"a": 1}, dicts.add(a = 1))
+ asserts.equals(env, {"a": 1, "b": 2}, dicts.add({"a": 1}, b = 2))
+
+ # Test simple two-argument behavior.
+ asserts.equals(env, {"a": 1, "b": 2}, dicts.add({"a": 1}, {"b": 2}))
+ asserts.equals(env, {"a": 1, "b": 2, "c": 3}, dicts.add({"a": 1}, {"b": 2}, c = 3))
+
+ # Test simple more-than-two-argument behavior.
+ asserts.equals(
+ env,
+ {"a": 1, "b": 2, "c": 3, "d": 4},
+ dicts.add({"a": 1}, {"b": 2}, {"c": 3}, {"d": 4}),
+ )
+ asserts.equals(
+ env,
+ {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5},
+ dicts.add({"a": 1}, {"b": 2}, {"c": 3}, {"d": 4}, e = 5),
+ )
+
+ # Test same-key overriding.
+ asserts.equals(env, {"a": 100}, dicts.add({"a": 1}, {"a": 100}))
+ asserts.equals(env, {"a": 100}, dicts.add({"a": 1}, a = 100))
+ asserts.equals(env, {"a": 10}, dicts.add({"a": 1}, {"a": 100}, {"a": 10}))
+ asserts.equals(env, {"a": 10}, dicts.add({"a": 1}, {"a": 100}, a = 10))
+ asserts.equals(
+ env,
+ {"a": 100, "b": 10},
+ dicts.add({"a": 1}, {"a": 100}, {"b": 10}),
+ )
+ asserts.equals(env, {"a": 10}, dicts.add({"a": 1}, {}, {"a": 10}))
+ asserts.equals(env, {"a": 10}, dicts.add({"a": 1}, {}, a = 10))
+ asserts.equals(
+ env,
+ {"a": 10, "b": 5},
+ dicts.add({"a": 1}, {"a": 10, "b": 5}),
+ )
+ asserts.equals(
+ env,
+ {"a": 10, "b": 5},
+ dicts.add({"a": 1}, a = 10, b = 5),
+ )
+
+ # Test some other boundary cases.
+ asserts.equals(env, {"a": 1}, dicts.add({"a": 1}, {}))
+
+ # Since dictionaries are passed around by reference, make sure that the
+ # result of dicts.add is always a *copy* by modifying it afterwards and
+ # ensuring that the original argument doesn't also reflect the change. We do
+ # this to protect against someone who might attempt to optimize the function
+ # by returning the argument itself in the one-argument case.
+ original = {"a": 1}
+ result = dicts.add(original)
+ result["a"] = 2
+ asserts.equals(env, 1, original["a"])
+
+ return unittest.end(env)
+
+add_test = unittest.make(_add_test)
+
+def dicts_test_suite():
+ """Creates the test targets and test suite for dicts.bzl tests."""
+ unittest.suite(
+ "dicts_tests",
+ add_test,
+ )
diff --git a/tests/diff_test/BUILD b/tests/diff_test/BUILD
new file mode 100644
index 0000000..aa381d8
--- /dev/null
+++ b/tests/diff_test/BUILD
@@ -0,0 +1,46 @@
+# This package aids testing the 'diff_test' rule.
+
+load("//rules:diff_test.bzl", "diff_test")
+
+licenses(["notice"])
+
+package(default_testonly = 1)
+
+sh_test(
+ name = "diff_test_tests",
+ srcs = ["diff_test_tests.sh"],
+ data = [
+ "//rules:diff_test",
+ "//tests:unittest.bash",
+ ],
+ # Test marked local because it uses bazel.
+ tags = ["local"],
+ deps = ["@bazel_tools//tools/bash/runfiles"],
+)
+
+diff_test(
+ name = "same_src_src",
+ file1 = "a.txt",
+ file2 = "aa.txt",
+)
+
+diff_test(
+ name = "same_src_gen",
+ file1 = "a.txt",
+ file2 = "a-gen.txt",
+)
+
+diff_test(
+ name = "same_gen_gen",
+ file1 = "a-gen.txt",
+ file2 = "aa-gen.txt",
+)
+
+genrule(
+ name = "gen",
+ outs = [
+ "a-gen.txt",
+ "aa-gen.txt",
+ ],
+ cmd = "echo -n 'potato' > $(location a-gen.txt) && echo -n 'potato' > $(location aa-gen.txt)",
+)
diff --git a/tests/diff_test/a.txt b/tests/diff_test/a.txt
new file mode 100644
index 0000000..86ba009
--- /dev/null
+++ b/tests/diff_test/a.txt
@@ -0,0 +1 @@
+potato \ No newline at end of file
diff --git a/tests/diff_test/aa.txt b/tests/diff_test/aa.txt
new file mode 100644
index 0000000..86ba009
--- /dev/null
+++ b/tests/diff_test/aa.txt
@@ -0,0 +1 @@
+potato \ No newline at end of file
diff --git a/tests/diff_test/b.txt b/tests/diff_test/b.txt
new file mode 100644
index 0000000..1910281
--- /dev/null
+++ b/tests/diff_test/b.txt
@@ -0,0 +1 @@
+foo \ No newline at end of file
diff --git a/tests/diff_test/diff_test_tests.sh b/tests/diff_test/diff_test_tests.sh
new file mode 100755
index 0000000..ed894c2
--- /dev/null
+++ b/tests/diff_test/diff_test_tests.sh
@@ -0,0 +1,216 @@
+# Copyright 2019 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.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function import_diff_test() {
+ local -r repo="$1"
+ mkdir -p "${repo}/rules"
+ touch "${repo}/WORKSPACE"
+ ln -sf "$(rlocation bazel_skylib/rules/diff_test.bzl)" \
+ "${repo}/rules/diff_test.bzl"
+ echo "exports_files(['diff_test.bzl'])" > "${repo}/rules/BUILD"
+}
+
+function assert_simple_diff_test() {
+ local -r flag="$1"
+ local -r ws="${TEST_TMPDIR}/$2"
+ local -r subdir="$3"
+
+ import_diff_test "$ws"
+ touch "$ws/WORKSPACE"
+ mkdir -p "$ws/$subdir"
+ cat >"$ws/${subdir}BUILD" <<'eof'
+load("//rules:diff_test.bzl", "diff_test")
+
+diff_test(
+ name = "same",
+ file1 = "a.txt",
+ file2 = "a.txt",
+)
+
+diff_test(
+ name = "different",
+ file1 = "a.txt",
+ file2 = "b.txt",
+)
+eof
+ echo foo > "$ws/$subdir/a.txt"
+ echo bar > "$ws/$subdir/b.txt"
+
+ (cd "$ws" && \
+ bazel test "$flag" "//${subdir%/}:same" --test_output=errors 1>"$TEST_log" 2>&1 \
+ || fail "expected success")
+
+ (cd "$ws" && \
+ bazel test "$flag" "//${subdir%/}:different" --test_output=errors 1>"$TEST_log" 2>&1 \
+ && fail "expected failure" || true)
+ expect_log "FAIL: files \"${subdir}a.txt\" and \"${subdir}b.txt\" differ"
+}
+
+function assert_from_ext_repo() {
+ local -r flag="$1"
+ local -r ws="${TEST_TMPDIR}/$2"
+
+ # Import the rule to an external repository.
+ import_diff_test "$ws/bzl"
+ mkdir -p "$ws/ext1/foo" "$ws/main/ext1/foo" "$ws/ext2/foo" "$ws/main/ext2/foo"
+ cat >"$ws/main/WORKSPACE" <<'eof'
+local_repository(
+ name = "bzl",
+ path = "../bzl",
+)
+
+local_repository(
+ name = "ext1",
+ path = "../ext1",
+)
+
+local_repository(
+ name = "ext2",
+ path = "../ext2",
+)
+eof
+
+ # @ext1 has source files
+ touch "$ws/ext1/WORKSPACE"
+ echo 'exports_files(["foo.txt"])' >"$ws/ext1/foo/BUILD"
+ echo 'foo' > "$ws/ext1/foo/foo.txt"
+
+ # @//ext1/foo has different files than @ext1//foo
+ echo 'exports_files(["foo.txt"])' >"$ws/main/ext1/foo/BUILD"
+ echo 'not foo' > "$ws/main/ext1/foo/foo.txt"
+
+ # @ext2 has generated files
+ touch "$ws/ext2/WORKSPACE"
+ cat >"$ws/ext2/foo/BUILD" <<'eof'
+genrule(
+ name = "gen",
+ outs = [
+ "foo.txt",
+ "bar.txt",
+ ],
+ cmd = "echo 'foo' > $(location foo.txt) && echo 'bar' > $(location bar.txt)",
+ visibility = ["//visibility:public"],
+)
+eof
+
+ # @//ext2/foo has different files than @ext2//foo
+ cat >"$ws/main/ext2/foo/BUILD" <<'eof'
+genrule(
+ name = "gen",
+ outs = ["foo.txt"],
+ cmd = "echo 'not foo' > $@",
+ visibility = ["//visibility:public"],
+)
+eof
+
+ cat >"$ws/main/BUILD" <<'eof'
+load("@bzl//rules:diff_test.bzl", "diff_test")
+
+diff_test(
+ name = "same",
+ file1 = "@ext1//foo:foo.txt",
+ file2 = "@ext2//foo:foo.txt",
+)
+
+diff_test(
+ name = "different1",
+ file1 = "@ext1//foo:foo.txt",
+ file2 = "@ext2//foo:bar.txt",
+)
+
+diff_test(
+ name = "different2",
+ file1 = "@ext1//foo:foo.txt",
+ file2 = "//ext1/foo:foo.txt",
+)
+
+diff_test(
+ name = "different3",
+ file1 = "//ext2/foo:foo.txt",
+ file2 = "@ext2//foo:foo.txt",
+)
+eof
+
+ (cd "$ws/main" && \
+ bazel test "$flag" //:same --test_output=errors 1>"$TEST_log" 2>&1 \
+ || fail "expected success")
+
+ (cd "$ws/main" && \
+ bazel test "$flag" //:different1 --test_output=errors 1>"$TEST_log" 2>&1 \
+ && fail "expected failure" || true)
+ expect_log 'FAIL: files "external/ext1/foo/foo.txt" and "external/ext2/foo/bar.txt" differ'
+
+ (cd "$ws/main" && \
+ bazel test "$flag" //:different2 --test_output=errors 1>"$TEST_log" 2>&1 \
+ && fail "expected failure" || true)
+ expect_log 'FAIL: files "external/ext1/foo/foo.txt" and "ext1/foo/foo.txt" differ'
+
+ (cd "$ws/main" && \
+ bazel test "$flag" //:different3 --test_output=errors 1>"$TEST_log" 2>&1 \
+ && fail "expected failure" || true)
+ expect_log 'FAIL: files "ext2/foo/foo.txt" and "external/ext2/foo/foo.txt" differ'
+}
+
+function test_simple_diff_test_with_legacy_external_runfiles() {
+ assert_simple_diff_test "--legacy_external_runfiles" "${FUNCNAME[0]}" ""
+}
+
+function test_simple_diff_test_without_legacy_external_runfiles() {
+ assert_simple_diff_test "--nolegacy_external_runfiles" "${FUNCNAME[0]}" ""
+}
+
+function test_directory_named_external_with_legacy_external_runfiles() {
+ assert_simple_diff_test "--legacy_external_runfiles" "${FUNCNAME[0]}" "path/to/direcotry/external/in/name/"
+}
+
+function test_directory_named_external_without_legacy_external_runfiles() {
+ assert_simple_diff_test "--nolegacy_external_runfiles" "${FUNCNAME[0]}" "path/to/direcotry/external/in/name/"
+}
+
+function test_from_ext_repo_with_legacy_external_runfiles() {
+ assert_from_ext_repo "--legacy_external_runfiles" "${FUNCNAME[0]}"
+}
+
+function test_from_ext_repo_without_legacy_external_runfiles() {
+ assert_from_ext_repo "--nolegacy_external_runfiles" "${FUNCNAME[0]}"
+}
+
+cd "$TEST_TMPDIR"
+run_suite "diff_test_tests test suite"
diff --git a/tests/native_binary/BUILD b/tests/native_binary/BUILD
new file mode 100644
index 0000000..852d607
--- /dev/null
+++ b/tests/native_binary/BUILD
@@ -0,0 +1,103 @@
+load("//rules:copy_file.bzl", "copy_file")
+load("//rules:native_binary.bzl", "native_binary", "native_test")
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//visibility:private"],
+)
+
+cc_binary(
+ name = "assertarg",
+ srcs = ["assertarg.cc"],
+)
+
+cc_binary(
+ name = "assertdata",
+ srcs = ["assertdata.cc"],
+ deps = ["@bazel_tools//tools/cpp/runfiles"],
+ # Depends on the runfiles library but doesn't have data-dependencies, on
+ # purpose. We supplement the runfiles in the native_binary / native_test
+ # rule.
+)
+
+# A rule that copies "assertarg"'s output as an opaque executable, simulating a
+# binary that's not built from source and needs to be wrapped in native_binary.
+copy_file(
+ name = "copy_assertarg_exe",
+ src = ":assertarg",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "assertarg_copy.exe",
+ is_executable = True,
+)
+
+# A rule that copies "assertdata"'s output as an opaque executable, simulating a
+# binary that's not built from source and needs to be wrapped in native_binary.
+copy_file(
+ name = "copy_assertdata_exe",
+ src = ":assertdata",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "assertdata_copy.exe",
+ is_executable = True,
+)
+
+_ARGS = [
+ "'a b'",
+ "c\\ d",
+ "$(location testdata.txt) $$(location testdata.txt) $(location testdata.txt)",
+ "'$(location testdata.txt) $$(location testdata.txt) $(location testdata.txt)'",
+ "$$TEST_SRCDIR",
+
+ # TODO(laszlocsomor): uncomment this (and its counterpart in assertarg.cc)
+ # after https://github.com/bazelbuild/bazel/issues/6622 is resolved and the
+ # Bash-less test wrapper is the default on Windows.
+ # "$${TEST_SRCDIR}",
+]
+
+native_binary(
+ name = "args_bin",
+ src = ":copy_assertarg_exe",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "args_bin.exe",
+ args = _ARGS,
+ # We only need the data-dependency for $(location) expansion.
+ data = ["testdata.txt"],
+)
+
+native_test(
+ name = "args_test",
+ src = ":copy_assertarg_exe",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "args_test.exe",
+ args = _ARGS,
+ # We only need the data-dependency for $(location) expansion.
+ data = ["testdata.txt"],
+)
+
+native_binary(
+ name = "data_bin",
+ src = ":copy_assertdata_exe",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "data_bin.exe",
+ data = ["testdata.txt"],
+)
+
+native_test(
+ name = "data_test",
+ src = ":copy_assertdata_exe",
+ # On Windows we need the ".exe" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".exe" on every platform.
+ out = "data_test.exe",
+ data = ["testdata.txt"],
+)
diff --git a/tests/native_binary/assertarg.cc b/tests/native_binary/assertarg.cc
new file mode 100644
index 0000000..02cb0fd
--- /dev/null
+++ b/tests/native_binary/assertarg.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 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.
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char** argv) {
+ static const char* kExpected[] = {
+ "a b",
+ "c d",
+ "tests/native_binary/testdata.txt",
+ "$(location",
+ "testdata.txt)",
+ "tests/native_binary/testdata.txt",
+ "tests/native_binary/testdata.txt $(location testdata.txt) tests/native_binary/testdata.txt",
+ "$TEST_SRCDIR",
+
+ // TODO(laszlocsomor): uncomment this (and its counterpart in the BUILD
+ // file) after https://github.com/bazelbuild/bazel/issues/6622 is resolved
+ // and the Bash-less test wrapper is the default on Windows.
+ // "${TEST_SRCDIR}",
+
+ "",
+ };
+
+ for (int i = 1; i < argc; ++i) {
+ if (!kExpected[i - 1][0]) {
+ fprintf(stderr, "too many arguments, expected only %d\n", i);
+ return 1;
+ }
+ if (argc < i) {
+ fprintf(stderr, "expected more than %d arguments\n", i);
+ return 1;
+ }
+ if (strcmp(argv[i], kExpected[i - 1]) != 0) {
+ fprintf(stderr, "argv[%d]=(%s), expected (%s)\n", i, argv[i],
+ kExpected[i - 1]);
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/tests/native_binary/assertdata.cc b/tests/native_binary/assertdata.cc
new file mode 100644
index 0000000..111aaa2
--- /dev/null
+++ b/tests/native_binary/assertdata.cc
@@ -0,0 +1,56 @@
+// Copyright 2019 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.
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include "tools/cpp/runfiles/runfiles.h"
+
+using bazel::tools::cpp::runfiles::Runfiles;
+
+int main(int argc, char** argv) {
+ std::string error;
+ std::unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
+ if (runfiles == nullptr) {
+ fprintf(stderr, "ERROR(" __FILE__ ":%d): Could not init runfiles\n",
+ __LINE__);
+ return 1;
+ }
+
+ // Even though this cc_binary rule has no data-dependencies, the
+ // native_binary that wraps a copy of this binary does, so we have some
+ // runfiles.
+ std::string path =
+ runfiles->Rlocation("bazel_skylib/tests/native_binary/testdata.txt");
+ if (path.empty()) {
+ fprintf(stderr, "ERROR(" __FILE__ ":%d): Could not find runfile\n",
+ __LINE__);
+ }
+
+ FILE* f = fopen(path.c_str(), "rt");
+ char buf[6];
+ size_t s = fread(buf, 1, 5, f);
+ fclose(f);
+ buf[5] = 0;
+ if (s != 5 || std::string("hello") != std::string(buf, 5)) {
+ fprintf(stderr, "ERROR(" __FILE__ ":%d): bad runfile contents (%s)\n",
+ __LINE__, buf);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/tests/native_binary/testdata.txt b/tests/native_binary/testdata.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/tests/native_binary/testdata.txt
@@ -0,0 +1 @@
+hello
diff --git a/tests/new_sets_tests.bzl b/tests/new_sets_tests.bzl
new file mode 100644
index 0000000..e73b7d4
--- /dev/null
+++ b/tests/new_sets_tests.bzl
@@ -0,0 +1,269 @@
+# Copyright 2018 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.
+
+"""Unit tests for new_sets.bzl."""
+
+load("//lib:new_sets.bzl", "sets")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _is_equal_test(ctx):
+ """Unit tests for sets.is_equal."""
+
+ # Note that if this test fails, the results for the other `sets` tests will
+ # be inconclusive because they use `asserts.new_set_equals`, which in turn
+ # calls `sets.is_equal`.
+ env = unittest.begin(ctx)
+
+ asserts.true(env, sets.is_equal(sets.make(), sets.make()))
+ asserts.false(env, sets.is_equal(sets.make(), sets.make([1])))
+ asserts.false(env, sets.is_equal(sets.make([1]), sets.make()))
+ asserts.true(env, sets.is_equal(sets.make([1]), sets.make([1])))
+ asserts.false(env, sets.is_equal(sets.make([1]), sets.make([1, 2])))
+ asserts.false(env, sets.is_equal(sets.make([1]), sets.make([2])))
+ asserts.false(env, sets.is_equal(sets.make([1]), sets.make([1, 2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.true(env, sets.is_equal(sets.make([1, 1]), sets.make([1])))
+
+ return unittest.end(env)
+
+is_equal_test = unittest.make(_is_equal_test)
+
+def _is_subset_test(ctx):
+ """Unit tests for sets.is_subset."""
+ env = unittest.begin(ctx)
+
+ asserts.true(env, sets.is_subset(sets.make(), sets.make()))
+ asserts.true(env, sets.is_subset(sets.make(), sets.make([1])))
+ asserts.false(env, sets.is_subset(sets.make([1]), sets.make()))
+ asserts.true(env, sets.is_subset(sets.make([1]), sets.make([1])))
+ asserts.true(env, sets.is_subset(sets.make([1]), sets.make([1, 2])))
+ asserts.false(env, sets.is_subset(sets.make([1]), sets.make([2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.true(env, sets.is_subset(sets.make([1, 1]), sets.make([1, 2])))
+
+ return unittest.end(env)
+
+is_subset_test = unittest.make(_is_subset_test)
+
+def _disjoint_test(ctx):
+ """Unit tests for sets.disjoint."""
+ env = unittest.begin(ctx)
+
+ asserts.true(env, sets.disjoint(sets.make(), sets.make()))
+ asserts.true(env, sets.disjoint(sets.make(), sets.make([1])))
+ asserts.true(env, sets.disjoint(sets.make([1]), sets.make()))
+ asserts.false(env, sets.disjoint(sets.make([1]), sets.make([1])))
+ asserts.false(env, sets.disjoint(sets.make([1]), sets.make([1, 2])))
+ asserts.true(env, sets.disjoint(sets.make([1]), sets.make([2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.false(env, sets.disjoint(sets.make([1, 1]), sets.make([1, 2])))
+
+ return unittest.end(env)
+
+disjoint_test = unittest.make(_disjoint_test)
+
+def _intersection_test(ctx):
+ """Unit tests for sets.intersection."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.make(), sets.intersection(sets.make(), sets.make()))
+ asserts.new_set_equals(env, sets.make(), sets.intersection(sets.make(), sets.make([1])))
+ asserts.new_set_equals(env, sets.make(), sets.intersection(sets.make([1]), sets.make()))
+ asserts.new_set_equals(env, sets.make([1]), sets.intersection(sets.make([1]), sets.make([1])))
+ asserts.new_set_equals(env, sets.make([1]), sets.intersection(sets.make([1]), sets.make([1, 2])))
+ asserts.new_set_equals(env, sets.make(), sets.intersection(sets.make([1]), sets.make([2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.new_set_equals(env, sets.make([1]), sets.intersection(sets.make([1, 1]), sets.make([1, 2])))
+
+ return unittest.end(env)
+
+intersection_test = unittest.make(_intersection_test)
+
+def _union_test(ctx):
+ """Unit tests for sets.union."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.make(), sets.union())
+ asserts.new_set_equals(env, sets.make([1]), sets.union(sets.make([1])))
+ asserts.new_set_equals(env, sets.make(), sets.union(sets.make(), sets.make()))
+ asserts.new_set_equals(env, sets.make([1]), sets.union(sets.make(), sets.make([1])))
+ asserts.new_set_equals(env, sets.make([1]), sets.union(sets.make([1]), sets.make()))
+ asserts.new_set_equals(env, sets.make([1]), sets.union(sets.make([1]), sets.make([1])))
+ asserts.new_set_equals(env, sets.make([1, 2]), sets.union(sets.make([1]), sets.make([1, 2])))
+ asserts.new_set_equals(env, sets.make([1, 2]), sets.union(sets.make([1]), sets.make([2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.new_set_equals(env, sets.make([1, 2]), sets.union(sets.make([1, 1]), sets.make([1, 2])))
+
+ return unittest.end(env)
+
+union_test = unittest.make(_union_test)
+
+def _difference_test(ctx):
+ """Unit tests for sets.difference."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.make(), sets.difference(sets.make(), sets.make()))
+ asserts.new_set_equals(env, sets.make(), sets.difference(sets.make(), sets.make([1])))
+ asserts.new_set_equals(env, sets.make([1]), sets.difference(sets.make([1]), sets.make()))
+ asserts.new_set_equals(env, sets.make(), sets.difference(sets.make([1]), sets.make([1])))
+ asserts.new_set_equals(env, sets.make(), sets.difference(sets.make([1]), sets.make([1, 2])))
+ asserts.new_set_equals(env, sets.make([1]), sets.difference(sets.make([1]), sets.make([2])))
+
+ # If passing a list, verify that duplicate elements are ignored.
+ asserts.new_set_equals(env, sets.make([2]), sets.difference(sets.make([1, 2]), sets.make([1, 1])))
+
+ return unittest.end(env)
+
+difference_test = unittest.make(_difference_test)
+
+def _to_list_test(ctx):
+ """Unit tests for sets.to_list."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, [], sets.to_list(sets.make()))
+ asserts.equals(env, [1], sets.to_list(sets.make([1, 1, 1])))
+ asserts.equals(env, [1, 2, 3], sets.to_list(sets.make([1, 2, 3])))
+
+ return unittest.end(env)
+
+to_list_test = unittest.make(_to_list_test)
+
+def _make_test(ctx):
+ """Unit tests for sets.make."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, {}, sets.make()._values)
+ asserts.equals(env, {x: None for x in [1, 2, 3]}, sets.make([1, 1, 2, 2, 3, 3])._values)
+
+ return unittest.end(env)
+
+make_test = unittest.make(_make_test)
+
+def _copy_test(ctx):
+ """Unit tests for sets.copy."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.copy(sets.make()), sets.make())
+ asserts.new_set_equals(env, sets.copy(sets.make([1, 2, 3])), sets.make([1, 2, 3]))
+
+ # Ensure mutating the copy does not mutate the original
+ original = sets.make([1, 2, 3])
+ copy = sets.copy(original)
+ copy._values[5] = None
+ asserts.false(env, sets.is_equal(original, copy))
+
+ return unittest.end(env)
+
+copy_test = unittest.make(_copy_test)
+
+def _insert_test(ctx):
+ """Unit tests for sets.insert."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.make([1, 2, 3]), sets.insert(sets.make([1, 2]), 3))
+
+ # Ensure mutating the inserted set does mutate the original set.
+ original = sets.make([1, 2, 3])
+ after_insert = sets.insert(original, 4)
+ asserts.new_set_equals(
+ env,
+ original,
+ after_insert,
+ msg = "Insert creates a new set which is an O(n) operation, insert should be O(1).",
+ )
+
+ return unittest.end(env)
+
+insert_test = unittest.make(_insert_test)
+
+def _contains_test(ctx):
+ """Unit tests for sets.contains."""
+ env = unittest.begin(ctx)
+
+ asserts.false(env, sets.contains(sets.make(), 1))
+ asserts.true(env, sets.contains(sets.make([1]), 1))
+ asserts.true(env, sets.contains(sets.make([1, 2]), 1))
+ asserts.false(env, sets.contains(sets.make([2, 3]), 1))
+
+ return unittest.end(env)
+
+contains_test = unittest.make(_contains_test)
+
+def _length_test(ctx):
+ """Unit test for sets.length."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, 0, sets.length(sets.make()))
+ asserts.equals(env, 1, sets.length(sets.make([1])))
+ asserts.equals(env, 2, sets.length(sets.make([1, 2])))
+
+ return unittest.end(env)
+
+length_test = unittest.make(_length_test)
+
+def _remove_test(ctx):
+ """Unit test for sets.remove."""
+ env = unittest.begin(ctx)
+
+ asserts.new_set_equals(env, sets.make([1, 2]), sets.remove(sets.make([1, 2, 3]), 3))
+
+ # Ensure mutating the inserted set does mutate the original set.
+ original = sets.make([1, 2, 3])
+ after_removal = sets.remove(original, 3)
+ asserts.new_set_equals(env, original, after_removal)
+
+ return unittest.end(env)
+
+remove_test = unittest.make(_remove_test)
+
+def _repr_str_test(ctx):
+ """Unit test for sets.repr and sets.str."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, "[]", sets.repr(sets.make()))
+ asserts.equals(env, "[1]", sets.repr(sets.make([1])))
+ asserts.equals(env, "[1, 2]", sets.repr(sets.make([1, 2])))
+
+ asserts.equals(env, "[]", sets.str(sets.make()))
+ asserts.equals(env, "[1]", sets.str(sets.make([1])))
+ asserts.equals(env, "[1, 2]", sets.str(sets.make([1, 2])))
+
+ return unittest.end(env)
+
+repr_str_test = unittest.make(_repr_str_test)
+
+def new_sets_test_suite():
+ """Creates the test targets and test suite for new_sets.bzl tests."""
+ unittest.suite(
+ "new_sets_tests",
+ disjoint_test,
+ intersection_test,
+ is_equal_test,
+ is_subset_test,
+ difference_test,
+ union_test,
+ to_list_test,
+ make_test,
+ copy_test,
+ insert_test,
+ contains_test,
+ length_test,
+ remove_test,
+ repr_str_test,
+ )
diff --git a/tests/partial_tests.bzl b/tests/partial_tests.bzl
new file mode 100644
index 0000000..6d778c3
--- /dev/null
+++ b/tests/partial_tests.bzl
@@ -0,0 +1,84 @@
+# Copyright 2018 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.
+
+"""Unit tests for partial.bzl."""
+
+load("//lib:partial.bzl", "partial")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _make_noargs_nokwargs():
+ """Test utility for no args no kwargs case"""
+ return 1
+
+def _make_args_nokwargs(arg1, arg2, arg3):
+ """Test utility for args no kwargs case"""
+ return arg1 + arg2 + arg3
+
+def _make_args_kwargs(arg1, arg2, arg3, **kwargs):
+ """Test utility for args and kwargs case"""
+ return arg1 + arg2 + arg3 + kwargs["x"] + kwargs["y"]
+
+def _call_noargs_nokwargs(call_arg1):
+ """Test utility no args no kwargs case where values passed from call site"""
+ return call_arg1
+
+def _call_args_nokwargs(func_arg1, call_arg1):
+ """Test utility for args no kwargs case where values passed from call site"""
+ return func_arg1 + call_arg1
+
+def _call_args_kwargs(func_arg1, call_arg1, func_mult, call_mult):
+ """Test utility for args and kwargs case where values passed from call site"""
+ return (func_arg1 + call_arg1) * func_mult * call_mult
+
+def _make_call_test(ctx):
+ """Unit tests for partial.make and partial.call."""
+ env = unittest.begin(ctx)
+
+ # Test cases where there are no args (or kwargs) at the make site, only
+ # at the call site.
+ foo = partial.make(_make_noargs_nokwargs)
+ asserts.equals(env, 1, partial.call(foo))
+
+ foo = partial.make(_make_args_nokwargs)
+ asserts.equals(env, 6, partial.call(foo, 1, 2, 3))
+
+ foo = partial.make(_make_args_kwargs)
+ asserts.equals(env, 15, partial.call(foo, 1, 2, 3, x = 4, y = 5))
+
+ # Test cases where there are args (and/or kwargs) at the make site and the
+ # call site.
+ foo = partial.make(_call_noargs_nokwargs, 100)
+ asserts.equals(env, 100, partial.call(foo))
+
+ foo = partial.make(_call_args_nokwargs, 100)
+ asserts.equals(env, 112, partial.call(foo, 12))
+
+ foo = partial.make(_call_args_kwargs, 100, func_mult = 10)
+ asserts.equals(env, 2240, partial.call(foo, 12, call_mult = 2))
+
+ # Test case where there are args and kwargs ath the make site, and the call
+ # site overrides some make site args.
+ foo = partial.make(_call_args_kwargs, 100, func_mult = 10)
+ asserts.equals(env, 1120, partial.call(foo, 12, func_mult = 5, call_mult = 2))
+
+ return unittest.end(env)
+
+make_call_test = unittest.make(_make_call_test)
+
+def partial_test_suite():
+ """Creates the test targets and test suite for partial.bzl tests."""
+ unittest.suite(
+ "partial_tests",
+ make_call_test,
+ )
diff --git a/tests/paths_tests.bzl b/tests/paths_tests.bzl
new file mode 100644
index 0000000..42253d8
--- /dev/null
+++ b/tests/paths_tests.bzl
@@ -0,0 +1,291 @@
+# Copyright 2017 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.
+
+"""Unit tests for paths.bzl."""
+
+load("//lib:paths.bzl", "paths")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _basename_test(ctx):
+ """Unit tests for paths.basename."""
+ env = unittest.begin(ctx)
+
+ # Verify some degenerate cases.
+ asserts.equals(env, "", paths.basename(""))
+ asserts.equals(env, "", paths.basename("/"))
+ asserts.equals(env, "bar", paths.basename("foo///bar"))
+
+ # Verify some realistic cases.
+ asserts.equals(env, "foo", paths.basename("foo"))
+ asserts.equals(env, "foo", paths.basename("/foo"))
+ asserts.equals(env, "foo", paths.basename("bar/foo"))
+ asserts.equals(env, "foo", paths.basename("/bar/foo"))
+
+ # Verify that we correctly duplicate Python's os.path.basename behavior,
+ # where a trailing slash means the basename is empty.
+ asserts.equals(env, "", paths.basename("foo/"))
+ asserts.equals(env, "", paths.basename("/foo/"))
+
+ return unittest.end(env)
+
+basename_test = unittest.make(_basename_test)
+
+def _dirname_test(ctx):
+ """Unit tests for paths.dirname."""
+ env = unittest.begin(ctx)
+
+ # Verify some degenerate cases.
+ asserts.equals(env, "", paths.dirname(""))
+ asserts.equals(env, "/", paths.dirname("/"))
+ asserts.equals(env, "foo", paths.dirname("foo///bar"))
+
+ # Verify some realistic cases.
+ asserts.equals(env, "", paths.dirname("foo"))
+ asserts.equals(env, "/", paths.dirname("/foo"))
+ asserts.equals(env, "bar", paths.dirname("bar/foo"))
+ asserts.equals(env, "/bar", paths.dirname("/bar/foo"))
+
+ # Verify that we correctly duplicate Python's os.path.dirname behavior,
+ # where a trailing slash means the dirname is the same as the original
+ # path (without the trailing slash).
+ asserts.equals(env, "foo", paths.dirname("foo/"))
+ asserts.equals(env, "/foo", paths.dirname("/foo/"))
+
+ return unittest.end(env)
+
+dirname_test = unittest.make(_dirname_test)
+
+def _is_absolute_test(ctx):
+ """Unit tests for paths.is_absolute."""
+ env = unittest.begin(ctx)
+
+ # Try a degenerate case.
+ asserts.false(env, paths.is_absolute(""))
+
+ # Try some relative paths.
+ asserts.false(env, paths.is_absolute("foo"))
+ asserts.false(env, paths.is_absolute("foo/"))
+ asserts.false(env, paths.is_absolute("foo/bar"))
+
+ # Try some Linux absolute paths.
+ asserts.true(env, paths.is_absolute("/"))
+ asserts.true(env, paths.is_absolute("/foo"))
+ asserts.true(env, paths.is_absolute("/foo/"))
+ asserts.true(env, paths.is_absolute("/foo/bar"))
+
+ # Try some Windows absolute paths.
+ asserts.true(env, paths.is_absolute("D:\\"))
+ asserts.true(env, paths.is_absolute("C:\\"))
+ asserts.true(env, paths.is_absolute("C:\\foo"))
+ asserts.true(env, paths.is_absolute("C:\\foo\\bar"))
+
+ return unittest.end(env)
+
+is_absolute_test = unittest.make(_is_absolute_test)
+
+def _join_test(ctx):
+ """Unit tests for paths.join."""
+ env = unittest.begin(ctx)
+
+ # Try a degenerate case.
+ asserts.equals(env, "", paths.join(""))
+
+ # Try some basic paths.
+ asserts.equals(env, "foo", paths.join("foo"))
+ asserts.equals(env, "foo/bar", paths.join("foo", "bar"))
+ asserts.equals(env, "foo/bar/baz", paths.join("foo", "bar", "baz"))
+
+ # Make sure an initially absolute path stays absolute.
+ asserts.equals(env, "/foo", paths.join("/foo"))
+ asserts.equals(env, "/foo/bar", paths.join("/foo", "bar"))
+
+ # Make sure an absolute path later in the list resets the result.
+ asserts.equals(env, "/baz", paths.join("foo", "bar", "/baz"))
+ asserts.equals(env, "/baz", paths.join("foo", "/bar", "/baz"))
+ asserts.equals(env, "/bar/baz", paths.join("foo", "/bar", "baz"))
+ asserts.equals(env, "/bar", paths.join("/foo", "/bar"))
+
+ # Make sure a leading empty segment doesn't make it absolute.
+ asserts.equals(env, "foo", paths.join("", "foo"))
+
+ # Try some trailing slash scenarios.
+ asserts.equals(env, "foo/", paths.join("foo", ""))
+ asserts.equals(env, "foo/", paths.join("foo/"))
+ asserts.equals(env, "foo/", paths.join("foo/", ""))
+ asserts.equals(env, "foo//", paths.join("foo//", ""))
+ asserts.equals(env, "foo//", paths.join("foo//"))
+ asserts.equals(env, "foo/bar/baz/", paths.join("foo/", "bar/", "baz", ""))
+ asserts.equals(env, "foo/bar/baz/", paths.join("foo/", "bar/", "baz/"))
+ asserts.equals(env, "foo/bar/baz/", paths.join("foo/", "bar/", "baz/", ""))
+
+ # Make sure that adjacent empty segments don't add extra path separators.
+ asserts.equals(env, "foo/", paths.join("foo", "", ""))
+ asserts.equals(env, "foo", paths.join("", "", "foo"))
+ asserts.equals(env, "foo/bar", paths.join("foo", "", "", "bar"))
+
+ return unittest.end(env)
+
+join_test = unittest.make(_join_test)
+
+def _normalize_test(ctx):
+ """Unit tests for paths.normalize."""
+ env = unittest.begin(ctx)
+
+ # Try the most basic case.
+ asserts.equals(env, ".", paths.normalize(""))
+
+ # Try some basic adjacent-slash removal.
+ asserts.equals(env, "foo/bar", paths.normalize("foo//bar"))
+ asserts.equals(env, "foo/bar", paths.normalize("foo////bar"))
+
+ # Try some "." removal.
+ asserts.equals(env, "foo/bar", paths.normalize("foo/./bar"))
+ asserts.equals(env, "foo/bar", paths.normalize("./foo/bar"))
+ asserts.equals(env, "foo/bar", paths.normalize("foo/bar/."))
+ asserts.equals(env, "/", paths.normalize("/."))
+
+ # Try some ".." removal.
+ asserts.equals(env, "bar", paths.normalize("foo/../bar"))
+ asserts.equals(env, "foo", paths.normalize("foo/bar/.."))
+ asserts.equals(env, ".", paths.normalize("foo/.."))
+ asserts.equals(env, ".", paths.normalize("foo/bar/../.."))
+ asserts.equals(env, "..", paths.normalize("foo/../.."))
+ asserts.equals(env, "/", paths.normalize("/foo/../.."))
+ asserts.equals(env, "../../c", paths.normalize("a/b/../../../../c/d/.."))
+
+ # Make sure one or two initial slashes are preserved, but three or more are
+ # collapsed to a single slash.
+ asserts.equals(env, "/foo", paths.normalize("/foo"))
+ asserts.equals(env, "//foo", paths.normalize("//foo"))
+ asserts.equals(env, "/foo", paths.normalize("///foo"))
+
+ # Trailing slashes should be removed unless the entire path is a trailing
+ # slash.
+ asserts.equals(env, "/", paths.normalize("/"))
+ asserts.equals(env, "foo", paths.normalize("foo/"))
+ asserts.equals(env, "foo/bar", paths.normalize("foo/bar/"))
+
+ return unittest.end(env)
+
+normalize_test = unittest.make(_normalize_test)
+
+def _relativize_test(ctx):
+ """Unit tests for paths.relativize."""
+ env = unittest.begin(ctx)
+
+ # Make sure that relative-to-current-directory works in all forms.
+ asserts.equals(env, "foo", paths.relativize("foo", ""))
+ asserts.equals(env, "foo", paths.relativize("foo", "."))
+
+ # Try some regular cases.
+ asserts.equals(env, "bar", paths.relativize("foo/bar", "foo"))
+ asserts.equals(env, "baz", paths.relativize("foo/bar/baz", "foo/bar"))
+ asserts.equals(env, "bar/baz", paths.relativize("foo/bar/baz", "foo"))
+
+ # Try a case where a parent directory is normalized away.
+ asserts.equals(env, "baz", paths.relativize("foo/bar/../baz", "foo"))
+
+ # Relative paths work, as long as they share a common start.
+ asserts.equals(env, "file", paths.relativize("../foo/bar/baz/file", "../foo/bar/baz"))
+ asserts.equals(env, "baz/file", paths.relativize("../foo/bar/baz/file", "../foo/bar"))
+
+ # TODO(allevato): Test failure cases, once that is possible.
+
+ return unittest.end(env)
+
+relativize_test = unittest.make(_relativize_test)
+
+def _replace_extension_test(ctx):
+ """Unit tests for paths.replace_extension."""
+ env = unittest.begin(ctx)
+
+ # Try some degenerate cases.
+ asserts.equals(env, ".foo", paths.replace_extension("", ".foo"))
+ asserts.equals(env, "/.foo", paths.replace_extension("/", ".foo"))
+ asserts.equals(env, "foo.bar", paths.replace_extension("foo", ".bar"))
+
+ # Try a directory with an extension and basename that doesn't have one.
+ asserts.equals(
+ env,
+ "foo.bar/baz.quux",
+ paths.replace_extension("foo.bar/baz", ".quux"),
+ )
+
+ # Now try some things with legit extensions.
+ asserts.equals(env, "a.z", paths.replace_extension("a.b", ".z"))
+ asserts.equals(env, "a.b.z", paths.replace_extension("a.b.c", ".z"))
+ asserts.equals(env, "a/b.z", paths.replace_extension("a/b.c", ".z"))
+ asserts.equals(env, "a.b/c.z", paths.replace_extension("a.b/c.d", ".z"))
+ asserts.equals(env, ".a/b.z", paths.replace_extension(".a/b.c", ".z"))
+ asserts.equals(env, ".a.z", paths.replace_extension(".a.b", ".z"))
+
+ # Verify that we don't insert a period on the extension if none is provided.
+ asserts.equals(env, "foobaz", paths.replace_extension("foo.bar", "baz"))
+
+ return unittest.end(env)
+
+replace_extension_test = unittest.make(_replace_extension_test)
+
+def _split_extension_test(ctx):
+ """Unit tests for paths.split_extension."""
+ env = unittest.begin(ctx)
+
+ # Try some degenerate cases.
+ asserts.equals(env, ("", ""), paths.split_extension(""))
+ asserts.equals(env, ("/", ""), paths.split_extension("/"))
+ asserts.equals(env, ("foo", ""), paths.split_extension("foo"))
+
+ # Try some paths whose basenames start with ".".
+ asserts.equals(env, (".", ""), paths.split_extension("."))
+ asserts.equals(env, (".bashrc", ""), paths.split_extension(".bashrc"))
+ asserts.equals(env, ("foo/.bashrc", ""), paths.split_extension("foo/.bashrc"))
+ asserts.equals(
+ env,
+ (".foo/.bashrc", ""),
+ paths.split_extension(".foo/.bashrc"),
+ )
+
+ # Try some directories with extensions with basenames that don't have one.
+ asserts.equals(env, ("foo.bar/baz", ""), paths.split_extension("foo.bar/baz"))
+ asserts.equals(
+ env,
+ ("foo.bar/.bashrc", ""),
+ paths.split_extension("foo.bar/.bashrc"),
+ )
+
+ # Now try some things that will actually get split.
+ asserts.equals(env, ("a", ".b"), paths.split_extension("a.b"))
+ asserts.equals(env, ("a.b", ".c"), paths.split_extension("a.b.c"))
+ asserts.equals(env, ("a/b", ".c"), paths.split_extension("a/b.c"))
+ asserts.equals(env, ("a.b/c", ".d"), paths.split_extension("a.b/c.d"))
+ asserts.equals(env, (".a/b", ".c"), paths.split_extension(".a/b.c"))
+ asserts.equals(env, (".a", ".b"), paths.split_extension(".a.b"))
+
+ return unittest.end(env)
+
+split_extension_test = unittest.make(_split_extension_test)
+
+def paths_test_suite():
+ """Creates the test targets and test suite for paths.bzl tests."""
+ unittest.suite(
+ "paths_tests",
+ basename_test,
+ dirname_test,
+ is_absolute_test,
+ join_test,
+ normalize_test,
+ relativize_test,
+ replace_extension_test,
+ split_extension_test,
+ )
diff --git a/tests/run_binary/BUILD b/tests/run_binary/BUILD
new file mode 100644
index 0000000..f511c03
--- /dev/null
+++ b/tests/run_binary/BUILD
@@ -0,0 +1,167 @@
+load("//rules:diff_test.bzl", "diff_test")
+load("//rules:run_binary.bzl", "run_binary")
+load("//rules:write_file.bzl", "write_file")
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//visibility:private"],
+)
+
+diff_test(
+ name = "run_script_test",
+ file1 = ":run_script.out",
+ file2 = ":run_script_expected",
+)
+
+# Generate this file with write_file instead of checking it in to the source
+# tree. This ensures line endings are consistent across "run_script.expected"
+# and "run_script.out".
+write_file(
+ name = "run_script_expected",
+ out = "run_script.expected",
+ content = [
+ "arg1=(foo)",
+ "arg2=(bar)",
+ "ENV_LOCATION=(a tests/run_binary/BUILD)",
+ "ENV_LOCATIONS=(b\\ tests/run_binary/BUILD tests/run_binary/printargs.cc)",
+ "ENV_COMPLEX=(xx/yy \\\"zz)",
+ "ENV_PATH_BASH=($PATH)",
+ "ENV_PATH_CMD=(%PATH%)",
+ # Can't prevent "echo" from adding a newline on Windows, so let's add
+ # one to the expected output too.
+ "",
+ ],
+)
+
+run_binary(
+ name = "run_script",
+ srcs = [
+ "BUILD",
+ ":dummy_srcs",
+ ],
+ outs = ["run_script.out"],
+ # Not testing any complex arguments here, because Windows .bat file argument
+ # escaping is different from most MSVC-built Windows binaries. We test
+ # argument escaping in "run_bin".
+ args = [
+ "foo",
+ "bar",
+ ],
+ # Test complex environment variables. They are location-expanded but not
+ # Bash-tokenized, and should appear the same for Windows .bat files and Bash
+ # .sh scripts.
+ env = {
+ # Testing $(location) expansion only on source files so the result is
+ # predictable. The path of generated files depends on the target
+ # platform.
+ "ENV_LOCATION": "a $(location BUILD)",
+ "ENV_LOCATIONS": "b\\ $(locations :dummy_srcs)",
+ "ENV_COMPLEX": "xx/yy \\\"zz",
+ "ENV_PATH_BASH": "$PATH",
+ "ENV_PATH_CMD": "%PATH%",
+ "OUT": "$(location run_script.out)",
+ },
+ tool = ":script",
+)
+
+write_file(
+ name = "script",
+ # On Windows we need the ".bat" extension.
+ # On other platforms the extension doesn't matter.
+ # Therefore we can use ".bat" on every platform.
+ out = "script.bat",
+ content = select({
+ "@bazel_tools//src/conditions:host_windows": [
+ "@echo>%OUT% arg1=(%1)",
+ "@echo>>%OUT% arg2=(%2)",
+ "@echo>>%OUT% ENV_LOCATION=(%ENV_LOCATION%)",
+ "@echo>>%OUT% ENV_LOCATIONS=(%ENV_LOCATIONS%)",
+ "@echo>>%OUT% ENV_COMPLEX=(%ENV_COMPLEX%)",
+ "@echo>>%OUT% ENV_PATH_BASH=(%ENV_PATH_BASH%)",
+ "@echo>>%OUT% ENV_PATH_CMD=(%ENV_PATH_CMD%)",
+ ],
+ "//conditions:default": [
+ "#!/bin/bash",
+ "echo > \"$OUT\" \"arg1=($1)\"",
+ "echo >> \"$OUT\" \"arg2=($2)\"",
+ "echo >> \"$OUT\" \"ENV_LOCATION=($ENV_LOCATION)\"",
+ "echo >> \"$OUT\" \"ENV_LOCATIONS=($ENV_LOCATIONS)\"",
+ "echo >> \"$OUT\" \"ENV_COMPLEX=($ENV_COMPLEX)\"",
+ "echo >> \"$OUT\" \"ENV_PATH_BASH=($ENV_PATH_BASH)\"",
+ "echo >> \"$OUT\" \"ENV_PATH_CMD=($ENV_PATH_CMD)\"",
+ ],
+ }),
+ is_executable = True,
+)
+
+diff_test(
+ name = "run_bin_test",
+ file1 = ":run_bin.out",
+ file2 = ":run_bin_expected",
+)
+
+# Generate this file with write_file instead of checking it in to the source
+# tree. This ensures line endings are consistent across "run_bin.expected"
+# and "run_bin.out".
+write_file(
+ name = "run_bin_expected",
+ out = "run_bin.expected",
+ content = [
+ "arg1=(a b)",
+ "arg2=(\"c d\")",
+ "arg3=(e\\ f)",
+ "arg4=(xx/yy\\ \\\"zz)",
+ "arg5=(tests/run_binary/BUILD)",
+ "arg6=(tests/run_binary/BUILD tests/run_binary/printargs.cc)",
+ "arg7=('tests/run_binary/BUILD $tests/run_binary/BUILD')",
+ "arg8=($PATH)",
+ "arg9=($$PATH)",
+ "arg10=(${PATH})",
+ # Add trailing newline, as printed by printargs.
+ "",
+ ],
+)
+
+run_binary(
+ name = "run_bin",
+ srcs = [
+ "BUILD",
+ ":dummy_srcs",
+ ],
+ outs = ["run_bin.out"],
+ # Test complex arguments here. They are location-expanded but not
+ # Bash-tokenized, and should appear the same on every platform.
+ args = [
+ "a b",
+ "\"c d\"",
+ "e\\ f",
+ "xx/yy\\ \\\"zz",
+ # Testing $(location) expansion only on source files so the result is
+ # predictable. The path of generated files depends on the target
+ # platform.
+ "$(location BUILD)",
+ "$(locations :dummy_srcs)",
+ "'$(location BUILD) $$(location BUILD)'",
+ "$PATH",
+ "$$PATH",
+ "${PATH}",
+ ],
+ # Not testing any complex envvars here, because we already did in
+ # "run_script".
+ env = {"OUT": "$(location run_bin.out)"},
+ tool = ":printargs",
+)
+
+filegroup(
+ name = "dummy_srcs",
+ srcs = [
+ "BUILD",
+ "printargs.cc",
+ ],
+)
+
+cc_binary(
+ name = "printargs",
+ srcs = ["printargs.cc"],
+)
diff --git a/tests/run_binary/printargs.cc b/tests/run_binary/printargs.cc
new file mode 100644
index 0000000..64eedf2
--- /dev/null
+++ b/tests/run_binary/printargs.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char** argv) {
+ char* out_path = getenv("OUT");
+ if (!out_path || !*out_path) {
+ fprintf(stderr, "ERROR(" __FILE__ ":%d): envvar OUT is undefined\n",
+ __LINE__);
+ return 1;
+ }
+ FILE* f = fopen(out_path, "wt");
+ if (!f) {
+ fprintf(stderr, "ERROR(" __FILE__ ":%d): could not open output file '%s'\n",
+ __LINE__, out_path);
+ return 1;
+ }
+ for (int i = 1; i < argc; ++i) {
+ fprintf(f, "arg%d=(%s)\n", i, argv[i]);
+ }
+ fclose(f);
+ return 0;
+}
diff --git a/tests/select_file/BUILD b/tests/select_file/BUILD
new file mode 100644
index 0000000..a498a1e
--- /dev/null
+++ b/tests/select_file/BUILD
@@ -0,0 +1,36 @@
+load("//rules:select_file.bzl", "select_file")
+load("//rules:diff_test.bzl", "diff_test")
+
+licenses(["notice"])
+
+filegroup(
+ name = "fg",
+ srcs = [
+ "subdir/inner.txt",
+ ":select_me.txt",
+ ],
+)
+
+select_file(
+ name = "select_me",
+ srcs = ":fg",
+ subpath = "select_me.txt",
+)
+
+select_file(
+ name = "select_inner",
+ srcs = ":fg",
+ subpath = "subdir/inner.txt",
+)
+
+diff_test(
+ name = "selected_me",
+ file1 = ":select_me",
+ file2 = ":select_me.txt",
+)
+
+diff_test(
+ name = "selected_inner",
+ file1 = ":select_inner",
+ file2 = "subdir/inner.txt",
+)
diff --git a/tests/select_file/select_me.txt b/tests/select_file/select_me.txt
new file mode 100644
index 0000000..4221a1e
--- /dev/null
+++ b/tests/select_file/select_me.txt
@@ -0,0 +1 @@
+Outer
diff --git a/tests/select_file/subdir/inner.txt b/tests/select_file/subdir/inner.txt
new file mode 100644
index 0000000..d87e45a
--- /dev/null
+++ b/tests/select_file/subdir/inner.txt
@@ -0,0 +1 @@
+Inner
diff --git a/tests/selects_tests.bzl b/tests/selects_tests.bzl
new file mode 100644
index 0000000..28f3ac4
--- /dev/null
+++ b/tests/selects_tests.bzl
@@ -0,0 +1,643 @@
+# Copyright 2017 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.
+
+"""Unit tests for selects.bzl."""
+
+load("//lib:selects.bzl", "selects")
+load("//lib:unittest.bzl", "analysistest", "asserts", "unittest")
+
+###################################################
+# with_or_test
+###################################################
+def _with_or_test(ctx):
+ """Unit tests for with_or."""
+ env = unittest.begin(ctx)
+
+ # We actually test on with_or_dict because Starlark can't get the
+ # dictionary from a select().
+
+ # Test select()-compatible input syntax.
+ input_dict = {":foo": ":d1", "//conditions:default": ":d1"}
+ asserts.equals(env, input_dict, selects.with_or_dict(input_dict))
+
+ # Test OR syntax.
+ or_dict = {(":foo", ":bar"): ":d1"}
+ asserts.equals(
+ env,
+ {":bar": ":d1", ":foo": ":d1"},
+ selects.with_or_dict(or_dict),
+ )
+
+ # Test mixed syntax.
+ mixed_dict = {
+ ":foo": ":d1",
+ (":bar", ":baz"): ":d2",
+ "//conditions:default": ":d3",
+ }
+ asserts.equals(
+ env,
+ {
+ ":bar": ":d2",
+ ":baz": ":d2",
+ ":foo": ":d1",
+ "//conditions:default": ":d3",
+ },
+ selects.with_or_dict(mixed_dict),
+ )
+
+ return unittest.end(env)
+
+with_or_test = unittest.make(_with_or_test)
+
+###################################################
+# BUILD declarations for config_setting_group tests
+###################################################
+
+# TODO: redefine these config_settings with Starlark build flags when
+# they're available non-experimentally.
+def _create_config_settings():
+ native.config_setting(
+ name = "condition1",
+ values = {"cpu": "ppc"},
+ )
+ native.config_setting(
+ name = "condition2",
+ values = {"compilation_mode": "opt"},
+ )
+ native.config_setting(
+ name = "condition3",
+ values = {"features": "myfeature"},
+ )
+
+def _create_config_setting_groups():
+ selects.config_setting_group(
+ name = "1_and_2_and_3",
+ match_all = [":condition1", ":condition2", ":condition3"],
+ )
+ selects.config_setting_group(
+ name = "1_and_nothing_else",
+ match_all = [":condition1"],
+ )
+ selects.config_setting_group(
+ name = "1_or_2_or_3",
+ match_any = [":condition1", ":condition2", ":condition3"],
+ )
+ selects.config_setting_group(
+ name = "1_or_nothing_else",
+ match_any = [":condition1"],
+ )
+
+###################################################
+# Support code for config_setting_group tests
+###################################################
+
+def _set_conditions(condition_list):
+ """Returns an argument for config_settings that sets specific options.
+
+ Args:
+ condition_list: a list of three booleans
+
+ Returns:
+ a dictionary parameter for config_settings such that ":conditionN" is True
+ iff condition_list[N + 1] is True
+ """
+ if len(condition_list) != 3:
+ fail("condition_list must be a list of 3 booleans")
+ ans = {}
+ if condition_list[0]:
+ ans["//command_line_option:cpu"] = "ppc"
+ else:
+ ans["//command_line_option:cpu"] = "k8"
+ if condition_list[1]:
+ ans["//command_line_option:compilation_mode"] = "opt"
+ else:
+ ans["//command_line_option:compilation_mode"] = "dbg"
+ if condition_list[2]:
+ ans["//command_line_option:features"] = ["myfeature"]
+ else:
+ ans["//command_line_option:features"] = ["notmyfeature"]
+ return ans
+
+_BooleanInfo = provider()
+
+def _boolean_attr_impl(ctx):
+ return [_BooleanInfo(value = ctx.attr.myboolean)]
+
+boolean_attr_rule = rule(
+ implementation = _boolean_attr_impl,
+ attrs = {"myboolean": attr.bool()},
+)
+
+def _expect_matches(ctx):
+ """Generic test implementation expecting myboolean == True."""
+ env = analysistest.begin(ctx)
+ attrval = analysistest.target_under_test(env)[_BooleanInfo].value
+ asserts.equals(env, True, attrval)
+ return analysistest.end(env)
+
+def _expect_doesnt_match(ctx):
+ """Generic test implementation expecting myboolean == False."""
+ env = analysistest.begin(ctx)
+ attrval = analysistest.target_under_test(env)[_BooleanInfo].value
+ asserts.equals(env, False, attrval)
+ return analysistest.end(env)
+
+def _config_setting_group_test(name, config_settings):
+ return analysistest.make()
+
+###################################################
+# and_config_setting_group_matches_test
+###################################################
+and_config_setting_group_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([True, True, True]),
+)
+
+def _and_config_setting_group_matches_test():
+ """Test verifying match on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_matches_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_matches_test(
+ name = "and_config_setting_group_matches_test",
+ target_under_test = ":and_config_setting_group_matches_rule",
+ )
+
+###################################################
+# and_config_setting_group_first_match_fails_test
+###################################################
+and_config_setting_group_first_match_fails_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([False, True, True]),
+)
+
+def _and_config_setting_group_first_match_fails_test():
+ """Test verifying first condition mismatch on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_first_match_fails_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_first_match_fails_test(
+ name = "and_config_setting_group_first_match_fails_test",
+ target_under_test = ":and_config_setting_group_first_match_fails_rule",
+ )
+
+###################################################
+# and_config_setting_group_middle_match_fails_test
+###################################################
+and_config_setting_group_middle_match_fails_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([True, False, True]),
+)
+
+def _and_config_setting_group_middle_match_fails_test():
+ """Test verifying middle condition mismatch on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_middle_match_fails_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_middle_match_fails_test(
+ name = "and_config_setting_group_middle_match_fails_test",
+ target_under_test = ":and_config_setting_group_middle_match_fails_rule",
+ )
+
+###################################################
+# and_config_setting_group_last_match_fails_test
+###################################################
+and_config_setting_group_last_match_fails_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([True, True, False]),
+)
+
+def _and_config_setting_group_last_match_fails_test():
+ """Test verifying last condition mismatch on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_last_match_fails_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_last_match_fails_test(
+ name = "and_config_setting_group_last_match_fails_test",
+ target_under_test = ":and_config_setting_group_last_match_fails_rule",
+ )
+
+###################################################
+# and_config_setting_group_multiple_matches_fail_test
+###################################################
+and_config_setting_group_multiple_matches_fail_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([True, False, False]),
+)
+
+def _and_config_setting_group_multiple_matches_fail_test():
+ """Test verifying multple conditions mismatch on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_multiple_matches_fail_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_multiple_matches_fail_test(
+ name = "and_config_setting_group_multiple_matches_fail_test",
+ target_under_test = ":and_config_setting_group_multiple_matches_fail_rule",
+ )
+
+###################################################
+# and_config_setting_group_all_matches_fail_test
+###################################################
+and_config_setting_group_all_matches_fail_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([False, False, False]),
+)
+
+def _and_config_setting_group_all_matches_fail_test():
+ """Test verifying all conditions mismatch on an ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_all_matches_fail_rule",
+ myboolean = select(
+ {
+ ":1_and_2_and_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_all_matches_fail_test(
+ name = "and_config_setting_group_all_matches_fail_test",
+ target_under_test = ":and_config_setting_group_all_matches_fail_rule",
+ )
+
+###################################################
+# and_config_setting_group_single_setting_matches_test
+###################################################
+and_config_setting_group_single_setting_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = {"//command_line_option:cpu": "ppc"},
+)
+
+def _and_config_setting_group_single_setting_matches_test():
+ """Test verifying match on single-entry ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_single_setting_matches_rule",
+ myboolean = select(
+ {
+ ":1_and_nothing_else": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_single_setting_matches_test(
+ name = "and_config_setting_group_single_setting_matches_test",
+ target_under_test = ":and_config_setting_group_single_setting_matches_rule",
+ )
+
+###################################################
+# and_config_setting_group_single_setting_fails_test
+###################################################
+and_config_setting_group_single_setting_fails_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = {"//command_line_option:cpu": "x86"},
+)
+
+def _and_config_setting_group_single_setting_fails_test():
+ """Test verifying no match on single-entry ANDing config_setting_group."""
+ boolean_attr_rule(
+ name = "and_config_setting_group_single_setting_fails_rule",
+ myboolean = select(
+ {
+ ":1_and_nothing_else": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ and_config_setting_group_single_setting_fails_test(
+ name = "and_config_setting_group_single_setting_fails_test",
+ target_under_test = ":and_config_setting_group_single_setting_fails_rule",
+ )
+
+###################################################
+# or_config_setting_group_no_match_test
+###################################################
+or_config_setting_group_no_matches_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = _set_conditions([False, False, False]),
+)
+
+def _or_config_setting_group_no_matches_test():
+ """Test verifying no matches on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_no_matches_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_no_matches_test(
+ name = "or_config_setting_group_no_matches_test",
+ target_under_test = ":or_config_setting_group_no_matches_rule",
+ )
+
+###################################################
+# or_config_setting_group_first_cond_matches_test
+###################################################
+or_config_setting_group_first_cond_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([True, False, False]),
+)
+
+def _or_config_setting_group_first_cond_matches_test():
+ """Test verifying first condition matching on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_first_cond_matches_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_first_cond_matches_test(
+ name = "or_config_setting_group_first_cond_matches_test",
+ target_under_test = ":or_config_setting_group_first_cond_matches_rule",
+ )
+
+###################################################
+# or_config_setting_group_middle_cond_matches_test
+###################################################
+or_config_setting_group_middle_cond_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([False, True, False]),
+)
+
+def _or_config_setting_group_middle_cond_matches_test():
+ """Test verifying middle condition matching on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_middle_cond_matches_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_middle_cond_matches_test(
+ name = "or_config_setting_group_middle_cond_matches_test",
+ target_under_test = ":or_config_setting_group_middle_cond_matches_rule",
+ )
+
+###################################################
+# or_config_setting_group_last_cond_matches_test
+###################################################
+or_config_setting_group_last_cond_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([False, False, True]),
+)
+
+def _or_config_setting_group_last_cond_matches_test():
+ """Test verifying last condition matching on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_last_cond_matches_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_last_cond_matches_test(
+ name = "or_config_setting_group_last_cond_matches_test",
+ target_under_test = ":or_config_setting_group_last_cond_matches_rule",
+ )
+
+###################################################
+# or_config_setting_group_multiple_conds_match_test
+###################################################
+or_config_setting_group_multiple_conds_match_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([False, True, True]),
+)
+
+def _or_config_setting_group_multiple_conds_match_test():
+ """Test verifying multple conditions matching on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_multiple_conds_match_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_multiple_conds_match_test(
+ name = "or_config_setting_group_multiple_conds_match_test",
+ target_under_test = ":or_config_setting_group_multiple_conds_match_rule",
+ )
+
+###################################################
+# or_config_setting_group_all_conds_match_test
+###################################################
+or_config_setting_group_all_conds_match_test = analysistest.make(
+ _expect_matches,
+ config_settings = _set_conditions([False, True, True]),
+)
+
+def _or_config_setting_group_all_conds_match_test():
+ """Test verifying all conditions matching on an ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_all_conds_match_rule",
+ myboolean = select(
+ {
+ ":1_or_2_or_3": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_all_conds_match_test(
+ name = "or_config_setting_group_all_conds_match_test",
+ target_under_test = ":or_config_setting_group_all_conds_match_rule",
+ )
+
+###################################################
+# or_config_setting_group_single_setting_matches_test
+###################################################
+or_config_setting_group_single_setting_matches_test = analysistest.make(
+ _expect_matches,
+ config_settings = {"//command_line_option:cpu": "ppc"},
+)
+
+def _or_config_setting_group_single_setting_matches_test():
+ """Test verifying match on single-entry ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_single_setting_matches_rule",
+ myboolean = select(
+ {
+ ":1_or_nothing_else": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_single_setting_matches_test(
+ name = "or_config_setting_group_single_setting_matches_test",
+ target_under_test = ":or_config_setting_group_single_setting_matches_rule",
+ )
+
+###################################################
+# or_config_setting_group_single_setting_fails_test
+###################################################
+or_config_setting_group_single_setting_fails_test = analysistest.make(
+ _expect_doesnt_match,
+ config_settings = {"//command_line_option:cpu": "x86"},
+)
+
+def _or_config_setting_group_single_setting_fails_test():
+ """Test verifying no match on single-entry ORing config_setting_group."""
+ boolean_attr_rule(
+ name = "or_config_setting_group_single_setting_fails_rule",
+ myboolean = select(
+ {
+ ":1_or_nothing_else": True,
+ "//conditions:default": False,
+ },
+ ),
+ )
+ or_config_setting_group_single_setting_fails_test(
+ name = "or_config_setting_group_single_setting_fails_test",
+ target_under_test = ":or_config_setting_group_single_setting_fails_rule",
+ )
+
+###################################################
+# always_true_match_all_test
+###################################################
+always_true_match_all_test = analysistest.make(_expect_matches)
+
+def _always_true_match_all_test():
+ """Tests that "match_all=['//conditions:default']" always matches."""
+ selects.config_setting_group(
+ name = "all_always_match",
+ match_all = ["//conditions:default"],
+ )
+ boolean_attr_rule(
+ name = "match_always_true_rule",
+ myboolean = select(
+ {
+ ":all_always_match": True,
+ },
+ ),
+ )
+ always_true_match_all_test(
+ name = "always_true_match_all_test",
+ target_under_test = ":match_always_true_rule",
+ )
+
+###################################################
+# always_true_match_any_test
+###################################################
+always_true_match_any_test = analysistest.make(_expect_matches)
+
+def _always_true_match_any_test():
+ """Tests that "match_any=['//conditions:default']" always matches."""
+ selects.config_setting_group(
+ name = "any_always_match",
+ match_any = ["//conditions:default"],
+ )
+ boolean_attr_rule(
+ name = "match_any_always_true_rule",
+ myboolean = select(
+ {
+ ":any_always_match": True,
+ },
+ ),
+ )
+ always_true_match_any_test(
+ name = "always_true_match_any_test",
+ target_under_test = ":match_any_always_true_rule",
+ )
+
+###################################################
+# empty_config_setting_group_not_allowed_test
+###################################################
+
+# config_setting_group with no parameters triggers a failure.
+# TODO: how do we test this? This requires catching macro
+# evaluation failure.
+
+###################################################
+# and_and_or_not_allowed_together_test
+###################################################
+
+# config_setting_group: setting both match_any and match_or
+# triggers a failure.
+# TODO: how do we test this? This requires catching macro
+# evaluation failure.
+
+###################################################
+
+# buildifier: disable=unnamed-macro
+def selects_test_suite():
+ """Creates the test targets and test suite for selects.bzl tests."""
+ unittest.suite(
+ "selects_tests",
+ with_or_test,
+ )
+
+ _create_config_settings()
+ _create_config_setting_groups()
+
+ _and_config_setting_group_matches_test()
+ _and_config_setting_group_first_match_fails_test()
+ _and_config_setting_group_middle_match_fails_test()
+ _and_config_setting_group_last_match_fails_test()
+ _and_config_setting_group_multiple_matches_fail_test()
+ _and_config_setting_group_all_matches_fail_test()
+ _and_config_setting_group_single_setting_matches_test()
+ _and_config_setting_group_single_setting_fails_test()
+
+ _or_config_setting_group_no_matches_test()
+ _or_config_setting_group_first_cond_matches_test()
+ _or_config_setting_group_middle_cond_matches_test()
+ _or_config_setting_group_last_cond_matches_test()
+ _or_config_setting_group_multiple_conds_match_test()
+ _or_config_setting_group_all_conds_match_test()
+ _or_config_setting_group_single_setting_matches_test()
+ _or_config_setting_group_single_setting_fails_test()
+
+ _always_true_match_all_test()
+ _always_true_match_any_test()
+
+ # _empty_config_setting_group_not_allowed_test()
+ # _and_and_or_not_allowed_together_test()
diff --git a/tests/shell_tests.bzl b/tests/shell_tests.bzl
new file mode 100644
index 0000000..32d517f
--- /dev/null
+++ b/tests/shell_tests.bzl
@@ -0,0 +1,104 @@
+# Copyright 2017 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.
+
+"""Unit tests for shell.bzl."""
+
+load("//lib:shell.bzl", "shell")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _shell_array_literal_test(ctx):
+ """Unit tests for shell.array_literal."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, "()", shell.array_literal([]))
+ asserts.equals(env, "('1')", shell.array_literal([1]))
+ asserts.equals(env, "('1' '2' '3')", shell.array_literal([1, 2, 3]))
+ asserts.equals(env, "('$foo')", shell.array_literal(["$foo"]))
+ asserts.equals(env, "('qu\"o\"te')", shell.array_literal(['qu"o"te']))
+
+ return unittest.end(env)
+
+shell_array_literal_test = unittest.make(_shell_array_literal_test)
+
+def _shell_quote_test(ctx):
+ """Unit tests for shell.quote."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, "'foo'", shell.quote("foo"))
+ asserts.equals(env, "'foo bar'", shell.quote("foo bar"))
+ asserts.equals(env, "'three spaces'", shell.quote("three spaces"))
+ asserts.equals(env, "' leading'", shell.quote(" leading"))
+ asserts.equals(env, "'trailing '", shell.quote("trailing "))
+ asserts.equals(env, "'new\nline'", shell.quote("new\nline"))
+ asserts.equals(env, "'tab\tcharacter'", shell.quote("tab\tcharacter"))
+ asserts.equals(env, "'$foo'", shell.quote("$foo"))
+ asserts.equals(env, "'qu\"o\"te'", shell.quote('qu"o"te'))
+ asserts.equals(env, "'it'\\''s'", shell.quote("it's"))
+ asserts.equals(env, "'foo\\bar'", shell.quote("foo\\bar"))
+ asserts.equals(env, "'back`echo q`uote'", shell.quote("back`echo q`uote"))
+
+ return unittest.end(env)
+
+shell_quote_test = unittest.make(_shell_quote_test)
+
+def _shell_args_test_gen_impl(ctx):
+ """Test argument escaping: this rule writes a script for a sh_test."""
+ args = [
+ "foo",
+ "foo bar",
+ "three spaces",
+ " leading",
+ "trailing ",
+ "new\nline",
+ "tab\tcharacter",
+ "$foo",
+ 'qu"o"te',
+ "it's",
+ "foo\\bar",
+ "back`echo q`uote",
+ ]
+ script_content = "\n".join([
+ "#!/bin/bash",
+ "myarray=" + shell.array_literal(args),
+ 'output=$(echo "${myarray[@]}")',
+ # For logging:
+ 'echo "DEBUG: output=[${output}]" >&2',
+ # The following is a shell representation of what the echo of the quoted
+ # array will look like. It looks a bit confusing considering it's shell
+ # quoted into Python. Shell using single quotes to minimize shell
+ # escaping, so only the single quote needs to be escaped as '\'', all
+ # others are essentially kept literally.
+ "expected='foo foo bar three spaces leading trailing new",
+ "line tab\tcharacter $foo qu\"o\"te it'\\''s foo\\bar back`echo q`uote'",
+ '[[ "${output}" == "${expected}" ]]',
+ ])
+ out = ctx.actions.declare_file(ctx.label.name + ".sh")
+ ctx.actions.write(
+ output = out,
+ content = script_content,
+ is_executable = True,
+ )
+ return [DefaultInfo(files = depset([out]))]
+
+shell_args_test_gen = rule(
+ implementation = _shell_args_test_gen_impl,
+)
+
+def shell_test_suite():
+ """Creates the test targets and test suite for shell.bzl tests."""
+ unittest.suite(
+ "shell_tests",
+ shell_array_literal_test,
+ shell_quote_test,
+ )
diff --git a/tests/structs_tests.bzl b/tests/structs_tests.bzl
new file mode 100644
index 0000000..79de7ad
--- /dev/null
+++ b/tests/structs_tests.bzl
@@ -0,0 +1,54 @@
+# Copyright 2017 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.
+
+"""Unit tests for structs.bzl."""
+
+load("//lib:structs.bzl", "structs")
+load("//lib:unittest.bzl", "asserts", "unittest")
+
+def _add_test(ctx):
+ """Unit tests for dicts.add."""
+ env = unittest.begin(ctx)
+
+ # Test zero- and one-argument behavior.
+ asserts.equals(env, {}, structs.to_dict(struct()))
+ asserts.equals(env, {"a": 1}, structs.to_dict(struct(a = 1)))
+
+ # Test simple two-argument behavior.
+ asserts.equals(env, {"a": 1, "b": 2}, structs.to_dict(struct(a = 1, b = 2)))
+
+ # Test simple more-than-two-argument behavior.
+ asserts.equals(
+ env,
+ {"a": 1, "b": 2, "c": 3, "d": 4},
+ structs.to_dict(struct(a = 1, b = 2, c = 3, d = 4)),
+ )
+
+ # Test transformation is not applied transitively.
+ asserts.equals(
+ env,
+ {"a": 1, "b": struct(bb = 1)},
+ structs.to_dict(struct(a = 1, b = struct(bb = 1))),
+ )
+
+ return unittest.end(env)
+
+add_test = unittest.make(_add_test)
+
+def structs_test_suite():
+ """Creates the test targets and test suite for structs.bzl tests."""
+ unittest.suite(
+ "structs_tests",
+ add_test,
+ )
diff --git a/tests/types_tests.bzl b/tests/types_tests.bzl
new file mode 100644
index 0000000..aaf7cab
--- /dev/null
+++ b/tests/types_tests.bzl
@@ -0,0 +1,248 @@
+# Copyright 2018 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.
+"""Unit tests for types.bzl."""
+
+load("//lib:types.bzl", "types")
+load("//lib:unittest.bzl", "asserts", "unittest")
+load("//lib:new_sets.bzl", "sets")
+
+def _a_function():
+ """A dummy function for testing."""
+ pass
+
+def _is_string_test(ctx):
+ """Unit tests for types.is_string."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_string(""))
+ asserts.true(env, types.is_string("string"))
+
+ asserts.false(env, types.is_string(4))
+ asserts.false(env, types.is_string([1]))
+ asserts.false(env, types.is_string({}))
+ asserts.false(env, types.is_string(()))
+ asserts.false(env, types.is_string(True))
+ asserts.false(env, types.is_string(None))
+ asserts.false(env, types.is_string(_a_function))
+ asserts.false(env, types.is_string(depset()))
+
+ return unittest.end(env)
+
+is_string_test = unittest.make(_is_string_test)
+
+def _is_bool_test(ctx):
+ """Unit tests for types.is_bool."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_bool(True))
+ asserts.true(env, types.is_bool(False))
+
+ asserts.false(env, types.is_bool(4))
+ asserts.false(env, types.is_bool([1]))
+ asserts.false(env, types.is_bool({}))
+ asserts.false(env, types.is_bool(()))
+ asserts.false(env, types.is_bool(""))
+ asserts.false(env, types.is_bool(None))
+ asserts.false(env, types.is_bool(_a_function))
+ asserts.false(env, types.is_bool(depset()))
+
+ return unittest.end(env)
+
+is_bool_test = unittest.make(_is_bool_test)
+
+def _is_list_test(ctx):
+ """Unit tests for types.is_list."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_list([]))
+ asserts.true(env, types.is_list([1]))
+
+ asserts.false(env, types.is_list(4))
+ asserts.false(env, types.is_list("s"))
+ asserts.false(env, types.is_list({}))
+ asserts.false(env, types.is_list(()))
+ asserts.false(env, types.is_list(True))
+ asserts.false(env, types.is_list(None))
+ asserts.false(env, types.is_list(_a_function))
+ asserts.false(env, types.is_list(depset()))
+
+ return unittest.end(env)
+
+is_list_test = unittest.make(_is_list_test)
+
+def _is_none_test(ctx):
+ """Unit tests for types.is_none."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_none(None))
+
+ asserts.false(env, types.is_none(4))
+ asserts.false(env, types.is_none("s"))
+ asserts.false(env, types.is_none({}))
+ asserts.false(env, types.is_none(()))
+ asserts.false(env, types.is_none(True))
+ asserts.false(env, types.is_none([]))
+ asserts.false(env, types.is_none([1]))
+ asserts.false(env, types.is_none(_a_function))
+ asserts.false(env, types.is_none(depset()))
+
+ return unittest.end(env)
+
+is_none_test = unittest.make(_is_none_test)
+
+def _is_int_test(ctx):
+ """Unit tests for types.is_int."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_int(1))
+ asserts.true(env, types.is_int(-1))
+
+ asserts.false(env, types.is_int("s"))
+ asserts.false(env, types.is_int({}))
+ asserts.false(env, types.is_int(()))
+ asserts.false(env, types.is_int(True))
+ asserts.false(env, types.is_int([]))
+ asserts.false(env, types.is_int([1]))
+ asserts.false(env, types.is_int(None))
+ asserts.false(env, types.is_int(_a_function))
+ asserts.false(env, types.is_int(depset()))
+
+ return unittest.end(env)
+
+is_int_test = unittest.make(_is_int_test)
+
+def _is_tuple_test(ctx):
+ """Unit tests for types.is_tuple."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_tuple(()))
+ asserts.true(env, types.is_tuple((1,)))
+
+ asserts.false(env, types.is_tuple(1))
+ asserts.false(env, types.is_tuple("s"))
+ asserts.false(env, types.is_tuple({}))
+ asserts.false(env, types.is_tuple(True))
+ asserts.false(env, types.is_tuple([]))
+ asserts.false(env, types.is_tuple([1]))
+ asserts.false(env, types.is_tuple(None))
+ asserts.false(env, types.is_tuple(_a_function))
+ asserts.false(env, types.is_tuple(depset()))
+
+ return unittest.end(env)
+
+is_tuple_test = unittest.make(_is_tuple_test)
+
+def _is_dict_test(ctx):
+ """Unit tests for types.is_dict."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_dict({}))
+ asserts.true(env, types.is_dict({"key": "value"}))
+
+ asserts.false(env, types.is_dict(1))
+ asserts.false(env, types.is_dict("s"))
+ asserts.false(env, types.is_dict(()))
+ asserts.false(env, types.is_dict(True))
+ asserts.false(env, types.is_dict([]))
+ asserts.false(env, types.is_dict([1]))
+ asserts.false(env, types.is_dict(None))
+ asserts.false(env, types.is_dict(_a_function))
+ asserts.false(env, types.is_dict(depset()))
+
+ return unittest.end(env)
+
+is_dict_test = unittest.make(_is_dict_test)
+
+def _is_function_test(ctx):
+ """Unit tests for types.is_function."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_function(_a_function))
+
+ asserts.false(env, types.is_function({}))
+ asserts.false(env, types.is_function(1))
+ asserts.false(env, types.is_function("s"))
+ asserts.false(env, types.is_function(()))
+ asserts.false(env, types.is_function(True))
+ asserts.false(env, types.is_function([]))
+ asserts.false(env, types.is_function([1]))
+ asserts.false(env, types.is_function(None))
+ asserts.false(env, types.is_function(depset()))
+
+ return unittest.end(env)
+
+is_function_test = unittest.make(_is_function_test)
+
+def _is_depset_test(ctx):
+ """Unit tests for types.is_depset."""
+
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_depset(depset()))
+ asserts.true(env, types.is_depset(depset(["foo"])))
+ asserts.true(env, types.is_depset(
+ depset(["foo"], transitive = [depset(["bar", "baz"])]),
+ ))
+
+ asserts.false(env, types.is_depset({}))
+ asserts.false(env, types.is_depset(1))
+ asserts.false(env, types.is_depset("s"))
+ asserts.false(env, types.is_depset(()))
+ asserts.false(env, types.is_depset(True))
+ asserts.false(env, types.is_depset([]))
+ asserts.false(env, types.is_depset([1]))
+ asserts.false(env, types.is_depset(None))
+
+ return unittest.end(env)
+
+is_depset_test = unittest.make(_is_depset_test)
+
+def _is_set_test(ctx):
+ """Unit test for types.is_set."""
+ env = unittest.begin(ctx)
+
+ asserts.true(env, types.is_set(sets.make()))
+ asserts.true(env, types.is_set(sets.make([1])))
+ asserts.false(env, types.is_set(None))
+ asserts.false(env, types.is_set({}))
+ asserts.false(env, types.is_set(struct(foo = 1)))
+ asserts.false(env, types.is_set(struct(_values = "not really values")))
+
+ return unittest.end(env)
+
+is_set_test = unittest.make(_is_set_test)
+
+def types_test_suite():
+ """Creates the test targets and test suite for types.bzl tests."""
+ unittest.suite(
+ "types_tests",
+ is_list_test,
+ is_string_test,
+ is_bool_test,
+ is_none_test,
+ is_int_test,
+ is_tuple_test,
+ is_dict_test,
+ is_function_test,
+ is_depset_test,
+ is_set_test,
+ )
diff --git a/tests/unittest.bash b/tests/unittest.bash
new file mode 100755
index 0000000..3bd07c7
--- /dev/null
+++ b/tests/unittest.bash
@@ -0,0 +1,801 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+#
+# Common utility file for Bazel shell tests
+#
+# unittest.bash: a unit test framework in Bash.
+#
+# A typical test suite looks like so:
+#
+# ------------------------------------------------------------------------
+# #!/bin/bash
+#
+# source path/to/unittest.bash || exit 1
+#
+# # Test that foo works.
+# function test_foo() {
+# foo >$TEST_log || fail "foo failed";
+# expect_log "blah" "Expected to see 'blah' in output of 'foo'."
+# }
+#
+# # Test that bar works.
+# function test_bar() {
+# bar 2>$TEST_log || fail "bar failed";
+# expect_not_log "ERROR" "Unexpected error from 'bar'."
+# ...
+# assert_equals $x $y
+# }
+#
+# run_suite "Test suite for blah"
+# ------------------------------------------------------------------------
+#
+# Each test function is considered to pass iff fail() is not called
+# while it is active. fail() may be called directly, or indirectly
+# via other assertions such as expect_log(). run_suite must be called
+# at the very end.
+#
+# A test function may redefine functions "set_up" and/or "tear_down";
+# these functions are executed before and after each test function,
+# respectively. Similarly, "cleanup" and "timeout" may be redefined,
+# and these function are called upon exit (of any kind) or a timeout.
+#
+# The user can pass --test_arg to bazel test to select specific tests
+# to run. Specifying --test_arg multiple times allows to select several
+# tests to be run in the given order. Additionally the user may define
+# TESTS=(test_foo test_bar ...) to specify a subset of test functions to
+# execute, for example, a working set during debugging. By default, all
+# functions called test_* will be executed.
+#
+# This file provides utilities for assertions over the output of a
+# command. The output of the command under test is directed to the
+# file $TEST_log, and then the expect_log* assertions can be used to
+# test for the presence of certain regular expressions in that file.
+#
+# The test framework is responsible for restoring the original working
+# directory before each test.
+#
+# The order in which test functions are run is not defined, so it is
+# important that tests clean up after themselves.
+#
+# Each test will be run in a new subshell.
+#
+# Functions named __* are not intended for use by clients.
+#
+# This framework implements the "test sharding protocol".
+#
+
+[ -n "$BASH_VERSION" ] ||
+ { echo "unittest.bash only works with bash!" >&2; exit 1; }
+
+DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+
+#### Configuration variables (may be overridden by testenv.sh or the suite):
+
+# This function may be called by testenv.sh or a test suite to enable errexit
+# in a way that enables us to print pretty stack traces when something fails.
+function enable_errexit() {
+ set -o errtrace
+ set -eu
+ trap __test_terminated_err ERR
+}
+
+function disable_errexit() {
+ set +o errtrace
+ set +eu
+ trap - ERR
+}
+
+#### Set up the test environment, branched from the old shell/testenv.sh
+
+# Enable errexit with pretty stack traces.
+enable_errexit
+
+# Print message in "$1" then exit with status "$2"
+die () {
+ # second argument is optional, defaulting to 1
+ local status_code=${2:-1}
+ # Stop capturing stdout/stderr, and dump captured output
+ if [ "$CAPTURED_STD_ERR" -ne 0 -o "$CAPTURED_STD_OUT" -ne 0 ]; then
+ restore_outputs
+ if [ "$CAPTURED_STD_OUT" -ne 0 ]; then
+ cat "${TEST_TMPDIR}/captured.out"
+ CAPTURED_STD_OUT=0
+ fi
+ if [ "$CAPTURED_STD_ERR" -ne 0 ]; then
+ cat "${TEST_TMPDIR}/captured.err" 1>&2
+ CAPTURED_STD_ERR=0
+ fi
+ fi
+
+ if [ -n "${1-}" ] ; then
+ echo "$1" 1>&2
+ fi
+ if [ -n "${BASH-}" ]; then
+ local caller_n=0
+ while [ $caller_n -lt 4 ] && caller_out=$(caller $caller_n 2>/dev/null); do
+ test $caller_n -eq 0 && echo "CALLER stack (max 4):"
+ echo " $caller_out"
+ let caller_n=caller_n+1
+ done 1>&2
+ fi
+ if [ x"$status_code" != x -a x"$status_code" != x"0" ]; then
+ exit "$status_code"
+ else
+ exit 1
+ fi
+}
+
+# Print message in "$1" then record that a non-fatal error occurred in ERROR_COUNT
+ERROR_COUNT="${ERROR_COUNT:-0}"
+error () {
+ if [ -n "$1" ] ; then
+ echo "$1" 1>&2
+ fi
+ ERROR_COUNT=$(($ERROR_COUNT + 1))
+}
+
+# Die if "$1" != "$2", print $3 as death reason
+check_eq () {
+ [ "$1" = "$2" ] || die "Check failed: '$1' == '$2' ${3:+ ($3)}"
+}
+
+# Die if "$1" == "$2", print $3 as death reason
+check_ne () {
+ [ "$1" != "$2" ] || die "Check failed: '$1' != '$2' ${3:+ ($3)}"
+}
+
+# The structure of the following if statements is such that if '[' fails
+# (e.g., a non-number was passed in) then the check will fail.
+
+# Die if "$1" > "$2", print $3 as death reason
+check_le () {
+ [ "$1" -gt "$2" ] || die "Check failed: '$1' <= '$2' ${3:+ ($3)}"
+}
+
+# Die if "$1" >= "$2", print $3 as death reason
+check_lt () {
+ [ "$1" -lt "$2" ] || die "Check failed: '$1' < '$2' ${3:+ ($3)}"
+}
+
+# Die if "$1" < "$2", print $3 as death reason
+check_ge () {
+ [ "$1" -ge "$2" ] || die "Check failed: '$1' >= '$2' ${3:+ ($3)}"
+}
+
+# Die if "$1" <= "$2", print $3 as death reason
+check_gt () {
+ [ "$1" -gt "$2" ] || die "Check failed: '$1' > '$2' ${3:+ ($3)}"
+}
+
+# Die if $2 !~ $1; print $3 as death reason
+check_match ()
+{
+ expr match "$2" "$1" >/dev/null || \
+ die "Check failed: '$2' does not match regex '$1' ${3:+ ($3)}"
+}
+
+# Run command "$1" at exit. Like "trap" but multiple atexits don't
+# overwrite each other. Will break if someone does call trap
+# directly. So, don't do that.
+ATEXIT="${ATEXIT-}"
+atexit () {
+ if [ -z "$ATEXIT" ]; then
+ ATEXIT="$1"
+ else
+ ATEXIT="$1 ; $ATEXIT"
+ fi
+ trap "$ATEXIT" EXIT
+}
+
+## TEST_TMPDIR
+if [ -z "${TEST_TMPDIR:-}" ]; then
+ export TEST_TMPDIR="$(mktemp -d ${TMPDIR:-/tmp}/bazel-test.XXXXXXXX)"
+fi
+if [ ! -e "${TEST_TMPDIR}" ]; then
+ mkdir -p -m 0700 "${TEST_TMPDIR}"
+ # Clean TEST_TMPDIR on exit
+ atexit "rm -fr ${TEST_TMPDIR}"
+fi
+
+# Functions to compare the actual output of a test to the expected
+# (golden) output.
+#
+# Usage:
+# capture_test_stdout
+# ... do something ...
+# diff_test_stdout "$TEST_SRCDIR/path/to/golden.out"
+
+# Redirect a file descriptor to a file.
+CAPTURED_STD_OUT="${CAPTURED_STD_OUT:-0}"
+CAPTURED_STD_ERR="${CAPTURED_STD_ERR:-0}"
+
+capture_test_stdout () {
+ exec 3>&1 # Save stdout as fd 3
+ exec 4>"${TEST_TMPDIR}/captured.out"
+ exec 1>&4
+ CAPTURED_STD_OUT=1
+}
+
+capture_test_stderr () {
+ exec 6>&2 # Save stderr as fd 6
+ exec 7>"${TEST_TMPDIR}/captured.err"
+ exec 2>&7
+ CAPTURED_STD_ERR=1
+}
+
+# Force XML_OUTPUT_FILE to an existing path
+if [ -z "${XML_OUTPUT_FILE:-}" ]; then
+ XML_OUTPUT_FILE=${TEST_TMPDIR}/ouput.xml
+fi
+
+#### Global variables:
+
+TEST_name="" # The name of the current test.
+
+TEST_log=$TEST_TMPDIR/log # The log file over which the
+ # expect_log* assertions work. Must
+ # be absolute to be robust against
+ # tests invoking 'cd'!
+
+TEST_passed="true" # The result of the current test;
+ # failed assertions cause this to
+ # become false.
+
+# These variables may be overridden by the test suite:
+
+TESTS=() # A subset or "working set" of test
+ # functions that should be run. By
+ # default, all tests called test_* are
+ # run.
+if [ $# -gt 0 ]; then
+ # Legacy behavior is to ignore missing regexp, but with errexit
+ # the following line fails without || true.
+ # TODO(dmarting): maybe we should revisit the way of selecting
+ # test with that framework (use Bazel's environment variable instead).
+ TESTS=($(for i in $@; do echo $i; done | grep ^test_ || true))
+ if (( ${#TESTS[@]} == 0 )); then
+ echo "WARNING: Arguments do not specifies tests!" >&2
+ fi
+fi
+
+TEST_verbose="true" # Whether or not to be verbose. A
+ # command; "true" or "false" are
+ # acceptable. The default is: true.
+
+TEST_script="$(pwd)/$0" # Full path to test script
+
+#### Internal functions
+
+function __show_log() {
+ echo "-- Test log: -----------------------------------------------------------"
+ [[ -e $TEST_log ]] && cat $TEST_log || echo "(Log file did not exist.)"
+ echo "------------------------------------------------------------------------"
+}
+
+# Usage: __pad <title> <pad-char>
+# Print $title padded to 80 columns with $pad_char.
+function __pad() {
+ local title=$1
+ local pad=$2
+ {
+ echo -n "$pad$pad $title "
+ printf "%80s" " " | tr ' ' "$pad"
+ } | head -c 80
+ echo
+}
+
+#### Exported functions
+
+# Usage: init_test ...
+# Deprecated. Has no effect.
+function init_test() {
+ :
+}
+
+
+# Usage: set_up
+# Called before every test function. May be redefined by the test suite.
+function set_up() {
+ :
+}
+
+# Usage: tear_down
+# Called after every test function. May be redefined by the test suite.
+function tear_down() {
+ :
+}
+
+# Usage: cleanup
+# Called upon eventual exit of the test suite. May be redefined by
+# the test suite.
+function cleanup() {
+ :
+}
+
+# Usage: timeout
+# Called upon early exit from a test due to timeout.
+function timeout() {
+ :
+}
+
+# Usage: fail <message> [<message> ...]
+# Print failure message with context information, and mark the test as
+# a failure. The context includes a stacktrace including the longest sequence
+# of calls outside this module. (We exclude the top and bottom portions of
+# the stack because they just add noise.) Also prints the contents of
+# $TEST_log.
+function fail() {
+ __show_log >&2
+ echo "$TEST_name FAILED:" "$@" "." >&2
+ echo "$@" >$TEST_TMPDIR/__fail
+ TEST_passed="false"
+ __show_stack
+ # Cleanup as we are leaving the subshell now
+ tear_down
+ exit 1
+}
+
+# Usage: warn <message>
+# Print a test warning with context information.
+# The context includes a stacktrace including the longest sequence
+# of calls outside this module. (We exclude the top and bottom portions of
+# the stack because they just add noise.)
+function warn() {
+ __show_log >&2
+ echo "$TEST_name WARNING: $1." >&2
+ __show_stack
+
+ if [ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]; then
+ echo "$TEST_name WARNING: $1." >> "$TEST_WARNINGS_OUTPUT_FILE"
+ fi
+}
+
+# Usage: show_stack
+# Prints the portion of the stack that does not belong to this module,
+# i.e. the user's code that called a failing assertion. Stack may not
+# be available if Bash is reading commands from stdin; an error is
+# printed in that case.
+__show_stack() {
+ local i=0
+ local trace_found=0
+
+ # Skip over active calls within this module:
+ while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} == ${BASH_SOURCE[0]} ]]; do
+ (( ++i ))
+ done
+
+ # Show all calls until the next one within this module (typically run_suite):
+ while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} != ${BASH_SOURCE[0]} ]]; do
+ # Read online docs for BASH_LINENO to understand the strange offset.
+ # Undefined can occur in the BASH_SOURCE stack apparently when one exits from a subshell
+ echo "${BASH_SOURCE[i]:-"Unknown"}:${BASH_LINENO[i - 1]:-"Unknown"}: in call to ${FUNCNAME[i]:-"Unknown"}" >&2
+ (( ++i ))
+ trace_found=1
+ done
+
+ [ $trace_found = 1 ] || echo "[Stack trace not available]" >&2
+}
+
+# Usage: expect_log <regexp> [error-message]
+# Asserts that $TEST_log matches regexp. Prints the contents of
+# $TEST_log and the specified (optional) error message otherwise, and
+# returns non-zero.
+function expect_log() {
+ local pattern=$1
+ local message=${2:-Expected regexp "$pattern" not found}
+ grep -sq -- "$pattern" $TEST_log && return 0
+
+ fail "$message"
+ return 1
+}
+
+# Usage: expect_log_warn <regexp> [error-message]
+# Warns if $TEST_log does not match regexp. Prints the contents of
+# $TEST_log and the specified (optional) error message on mismatch.
+function expect_log_warn() {
+ local pattern=$1
+ local message=${2:-Expected regexp "$pattern" not found}
+ grep -sq -- "$pattern" $TEST_log && return 0
+
+ warn "$message"
+ return 1
+}
+
+# Usage: expect_log_once <regexp> [error-message]
+# Asserts that $TEST_log contains one line matching <regexp>.
+# Prints the contents of $TEST_log and the specified (optional)
+# error message otherwise, and returns non-zero.
+function expect_log_once() {
+ local pattern=$1
+ local message=${2:-Expected regexp "$pattern" not found exactly once}
+ expect_log_n "$pattern" 1 "$message"
+}
+
+# Usage: expect_log_n <regexp> <count> [error-message]
+# Asserts that $TEST_log contains <count> lines matching <regexp>.
+# Prints the contents of $TEST_log and the specified (optional)
+# error message otherwise, and returns non-zero.
+function expect_log_n() {
+ local pattern=$1
+ local expectednum=${2:-1}
+ local message=${3:-Expected regexp "$pattern" not found exactly $expectednum times}
+ local count=$(grep -sc -- "$pattern" $TEST_log)
+ [[ $count = $expectednum ]] && return 0
+ fail "$message"
+ return 1
+}
+
+# Usage: expect_not_log <regexp> [error-message]
+# Asserts that $TEST_log does not match regexp. Prints the contents
+# of $TEST_log and the specified (optional) error message otherwise, and
+# returns non-zero.
+function expect_not_log() {
+ local pattern=$1
+ local message=${2:-Unexpected regexp "$pattern" found}
+ grep -sq -- "$pattern" $TEST_log || return 0
+
+ fail "$message"
+ return 1
+}
+
+# Usage: expect_log_with_timeout <regexp> <timeout> [error-message]
+# Waits for the given regexp in the $TEST_log for up to timeout seconds.
+# Prints the contents of $TEST_log and the specified (optional)
+# error message otherwise, and returns non-zero.
+function expect_log_with_timeout() {
+ local pattern=$1
+ local timeout=$2
+ local message=${3:-Regexp "$pattern" not found in "$timeout" seconds}
+ local count=0
+ while [ $count -lt $timeout ]; do
+ grep -sq -- "$pattern" $TEST_log && return 0
+ let count=count+1
+ sleep 1
+ done
+
+ grep -sq -- "$pattern" $TEST_log && return 0
+ fail "$message"
+ return 1
+}
+
+# Usage: expect_cmd_with_timeout <expected> <cmd> [timeout]
+# Repeats the command once a second for up to timeout seconds (10s by default),
+# until the output matches the expected value. Fails and returns 1 if
+# the command does not return the expected value in the end.
+function expect_cmd_with_timeout() {
+ local expected="$1"
+ local cmd="$2"
+ local timeout=${3:-10}
+ local count=0
+ while [ $count -lt $timeout ]; do
+ local actual="$($cmd)"
+ [ "$expected" = "$actual" ] && return 0
+ let count=count+1
+ sleep 1
+ done
+
+ [ "$expected" = "$actual" ] && return 0
+ fail "Expected '$expected' within ${timeout}s, was '$actual'"
+ return 1
+}
+
+# Usage: assert_one_of <expected_list>... <actual>
+# Asserts that actual is one of the items in expected_list
+# Example: assert_one_of ( "foo", "bar", "baz" ) actualval
+function assert_one_of() {
+ local args=("$@")
+ local last_arg_index=$((${#args[@]} - 1))
+ local actual=${args[last_arg_index]}
+ unset args[last_arg_index]
+ for expected_item in "${args[@]}"; do
+ [ "$expected_item" = "$actual" ] && return 0
+ done;
+
+ fail "Expected one of '${args[@]}', was '$actual'"
+ return 1
+}
+
+# Usage: assert_equals <expected> <actual>
+# Asserts [ expected = actual ].
+function assert_equals() {
+ local expected=$1 actual=$2
+ [ "$expected" = "$actual" ] && return 0
+
+ fail "Expected '$expected', was '$actual'"
+ return 1
+}
+
+# Usage: assert_not_equals <unexpected> <actual>
+# Asserts [ unexpected != actual ].
+function assert_not_equals() {
+ local unexpected=$1 actual=$2
+ [ "$unexpected" != "$actual" ] && return 0;
+
+ fail "Expected not '$unexpected', was '$actual'"
+ return 1
+}
+
+# Usage: assert_contains <regexp> <file> [error-message]
+# Asserts that file matches regexp. Prints the contents of
+# file and the specified (optional) error message otherwise, and
+# returns non-zero.
+function assert_contains() {
+ local pattern=$1
+ local file=$2
+ local message=${3:-Expected regexp "$pattern" not found in "$file"}
+ grep -sq -- "$pattern" "$file" && return 0
+
+ cat "$file" >&2
+ fail "$message"
+ return 1
+}
+
+# Usage: assert_not_contains <regexp> <file> [error-message]
+# Asserts that file does not match regexp. Prints the contents of
+# file and the specified (optional) error message otherwise, and
+# returns non-zero.
+function assert_not_contains() {
+ local pattern=$1
+ local file=$2
+ local message=${3:-Expected regexp "$pattern" found in "$file"}
+ grep -sq -- "$pattern" "$file" || return 0
+
+ cat "$file" >&2
+ fail "$message"
+ return 1
+}
+
+# Updates the global variables TESTS if
+# sharding is enabled, i.e. ($TEST_TOTAL_SHARDS > 0).
+function __update_shards() {
+ [ -z "${TEST_TOTAL_SHARDS-}" ] && return 0
+
+ [ "$TEST_TOTAL_SHARDS" -gt 0 ] ||
+ { echo "Invalid total shards $TEST_TOTAL_SHARDS" >&2; exit 1; }
+
+ [ "$TEST_SHARD_INDEX" -lt 0 -o "$TEST_SHARD_INDEX" -ge "$TEST_TOTAL_SHARDS" ] &&
+ { echo "Invalid shard $shard_index" >&2; exit 1; }
+
+ TESTS=$(for test in "${TESTS[@]}"; do echo "$test"; done |
+ awk "NR % $TEST_TOTAL_SHARDS == $TEST_SHARD_INDEX")
+
+ [ -z "${TEST_SHARD_STATUS_FILE-}" ] || touch "$TEST_SHARD_STATUS_FILE"
+}
+
+# Usage: __test_terminated <signal-number>
+# Handler that is called when the test terminated unexpectedly
+function __test_terminated() {
+ __show_log >&2
+ echo "$TEST_name FAILED: terminated by signal $1." >&2
+ TEST_passed="false"
+ __show_stack
+ timeout
+ exit 1
+}
+
+# Usage: __test_terminated_err
+# Handler that is called when the test terminated unexpectedly due to "errexit".
+function __test_terminated_err() {
+ # When a subshell exits due to signal ERR, its parent shell also exits,
+ # thus the signal handler is called recursively and we print out the
+ # error message and stack trace multiple times. We're only interested
+ # in the first one though, as it contains the most information, so ignore
+ # all following.
+ if [[ -f $TEST_TMPDIR/__err_handled ]]; then
+ exit 1
+ fi
+ __show_log >&2
+ if [[ ! -z "$TEST_name" ]]; then
+ echo -n "$TEST_name "
+ fi
+ echo "FAILED: terminated because this command returned a non-zero status:" >&2
+ touch $TEST_TMPDIR/__err_handled
+ TEST_passed="false"
+ __show_stack
+ # If $TEST_name is still empty, the test suite failed before we even started
+ # to run tests, so we shouldn't call tear_down.
+ if [[ ! -z "$TEST_name" ]]; then
+ tear_down
+ fi
+ exit 1
+}
+
+# Usage: __trap_with_arg <handler> <signals ...>
+# Helper to install a trap handler for several signals preserving the signal
+# number, so that the signal number is available to the trap handler.
+function __trap_with_arg() {
+ func="$1" ; shift
+ for sig ; do
+ trap "$func $sig" "$sig"
+ done
+}
+
+# Usage: <node> <block>
+# Adds the block to the given node in the report file. Quotes in the in
+# arguments need to be escaped.
+function __log_to_test_report() {
+ local node="$1"
+ local block="$2"
+ if [[ ! -e "$XML_OUTPUT_FILE" ]]; then
+ local xml_header='<?xml version="1.0" encoding="UTF-8"?>'
+ echo "$xml_header<testsuites></testsuites>" > $XML_OUTPUT_FILE
+ fi
+
+ # replace match on node with block and match
+ # replacement expression only needs escaping for quotes
+ perl -e "\
+\$input = @ARGV[0]; \
+\$/=undef; \
+open FILE, '+<$XML_OUTPUT_FILE'; \
+\$content = <FILE>; \
+if (\$content =~ /($node.*)\$/) { \
+ seek FILE, 0, 0; \
+ print FILE \$\` . \$input . \$1; \
+}; \
+close FILE" "$block"
+}
+
+# Usage: <total> <passed>
+# Adds the test summaries to the xml nodes.
+function __finish_test_report() {
+ local total=$1
+ local passed=$2
+ local failed=$((total - passed))
+
+ cat $XML_OUTPUT_FILE | \
+ sed \
+ "s/<testsuites>/<testsuites tests=\"$total\" failures=\"0\" errors=\"$failed\">/" | \
+ sed \
+ "s/<testsuite>/<testsuite tests=\"$total\" failures=\"0\" errors=\"$failed\">/" \
+ > $XML_OUTPUT_FILE.bak
+
+ rm -f $XML_OUTPUT_FILE
+ mv $XML_OUTPUT_FILE.bak $XML_OUTPUT_FILE
+}
+
+# Multi-platform timestamp function
+UNAME=$(uname -s | tr 'A-Z' 'a-z')
+if [ "$UNAME" = "linux" ] || [[ "$UNAME" =~ msys_nt* ]]; then
+ function timestamp() {
+ echo $(($(date +%s%N)/1000000))
+ }
+else
+ function timestamp() {
+ # OS X and FreeBSD do not have %N so python is the best we can do
+ python -c 'import time; print int(round(time.time() * 1000))'
+ }
+fi
+
+function get_run_time() {
+ local ts_start=$1
+ local ts_end=$2
+ run_time_ms=$((${ts_end}-${ts_start}))
+ echo $(($run_time_ms/1000)).${run_time_ms: -3}
+}
+
+# Usage: run_tests <suite-comment>
+# Must be called from the end of the user's test suite.
+# Calls exit with zero on success, non-zero otherwise.
+function run_suite() {
+ echo >&2
+ echo "$1" >&2
+ echo >&2
+
+ __log_to_test_report "<\/testsuites>" "<testsuite></testsuite>"
+
+ local total=0
+ local passed=0
+
+ atexit "cleanup"
+
+ # If the user didn't specify an explicit list of tests (e.g. a
+ # working set), use them all.
+ if [ ${#TESTS[@]} = 0 ]; then
+ TESTS=$(declare -F | awk '{print $3}' | grep ^test_)
+ elif [ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]; then
+ if grep -q "TESTS=" "$TEST_script" ; then
+ echo "TESTS variable overridden in Bazel sh_test. Please remove before submitting" \
+ >> "$TEST_WARNINGS_OUTPUT_FILE"
+ fi
+ fi
+
+ __update_shards
+
+ for TEST_name in ${TESTS[@]}; do
+ >$TEST_log # Reset the log.
+ TEST_passed="true"
+
+ total=$(($total + 1))
+ if [[ "$TEST_verbose" == "true" ]]; then
+ __pad $TEST_name '*' >&2
+ fi
+
+ local run_time="0.0"
+ rm -f $TEST_TMPDIR/{__ts_start,__ts_end}
+
+ if [ "$(type -t $TEST_name)" = function ]; then
+ # Save exit handlers eventually set.
+ local SAVED_ATEXIT="$ATEXIT";
+ ATEXIT=
+
+ # Run test in a subshell.
+ rm -f $TEST_TMPDIR/__err_handled
+ __trap_with_arg __test_terminated INT KILL PIPE TERM ABRT FPE ILL QUIT SEGV
+ (
+ timestamp >$TEST_TMPDIR/__ts_start
+ set_up
+ eval $TEST_name
+ tear_down
+ timestamp >$TEST_TMPDIR/__ts_end
+ test $TEST_passed == "true"
+ ) 2>&1 | tee $TEST_TMPDIR/__log
+ # Note that tee will prevent the control flow continuing if the test
+ # spawned any processes which are still running and have not closed
+ # their stdout.
+
+ test_subshell_status=${PIPESTATUS[0]}
+ if [ "$test_subshell_status" != 0 ]; then
+ TEST_passed="false"
+ # Ensure that an end time is recorded in case the test subshell
+ # terminated prematurely.
+ [ -f $TEST_TMPDIR/__ts_end ] || timestamp >$TEST_TMPDIR/__ts_end
+ fi
+
+ # Calculate run time for the testcase.
+ local ts_start=$(cat $TEST_TMPDIR/__ts_start)
+ local ts_end=$(cat $TEST_TMPDIR/__ts_end)
+ run_time=$(get_run_time $ts_start $ts_end)
+
+ # Eventually restore exit handlers.
+ if [ -n "$SAVED_ATEXIT" ]; then
+ ATEXIT="$SAVED_ATEXIT"
+ trap "$ATEXIT" EXIT
+ fi
+ else # Bad test explicitly specified in $TESTS.
+ fail "Not a function: '$TEST_name'"
+ fi
+
+ local testcase_tag=""
+
+ if [[ "$TEST_passed" == "true" ]]; then
+ if [[ "$TEST_verbose" == "true" ]]; then
+ echo "PASSED: $TEST_name" >&2
+ fi
+ passed=$(($passed + 1))
+ testcase_tag="<testcase name=\"$TEST_name\" status=\"run\" time=\"$run_time\" classname=\"\"></testcase>"
+ else
+ echo "FAILED: $TEST_name" >&2
+ # end marker in CDATA cannot be escaped, we need to split the CDATA sections
+ log=$(cat $TEST_TMPDIR/__log | sed 's/]]>/]]>]]&gt;<![CDATA[/g')
+ fail_msg=$(cat $TEST_TMPDIR/__fail 2> /dev/null || echo "No failure message")
+ testcase_tag="<testcase name=\"$TEST_name\" status=\"run\" time=\"$run_time\" classname=\"\"><error message=\"$fail_msg\"><![CDATA[$log]]></error></testcase>"
+ fi
+
+ if [[ "$TEST_verbose" == "true" ]]; then
+ echo >&2
+ fi
+ __log_to_test_report "<\/testsuite>" "$testcase_tag"
+ done
+
+ __finish_test_report $total $passed
+ __pad "$passed / $total tests passed." '*' >&2
+ [ $total = $passed ] || {
+ __pad "There were errors." '*'
+ exit 1
+ } >&2
+
+ exit 0
+}
diff --git a/tests/unittest_test.sh b/tests/unittest_test.sh
new file mode 100755
index 0000000..baed490
--- /dev/null
+++ b/tests/unittest_test.sh
@@ -0,0 +1,136 @@
+#!/bin/bash
+
+# Copyright 2019 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.
+#
+# End to end tests for unittest.bzl.
+#
+# Specifically, end to end tests of unittest.bzl cover verification that
+# analysis-phase tests written with unittest.bzl appropriately
+# cause test failures in cases where violated assertions are made.
+
+# --- begin runfiles.bash initialization ---
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function create_pkg() {
+ local -r pkg="$1"
+ mkdir -p "$pkg"
+ cd "$pkg"
+
+ cat > WORKSPACE <<EOF
+workspace(name = 'bazel_skylib')
+
+load("//lib:unittest.bzl", "register_unittest_toolchains")
+
+register_unittest_toolchains()
+EOF
+
+ # Copy relevant skylib sources into the current workspace.
+ mkdir -p tests
+ touch tests/BUILD
+ cat > tests/BUILD <<EOF
+exports_files(["*.bzl"])
+EOF
+ ln -sf "$(rlocation bazel_skylib/tests/unittest_tests.bzl)" tests/unittest_tests.bzl
+
+ mkdir -p lib
+ touch lib/BUILD
+ cat > lib/BUILD <<EOF
+exports_files(["*.bzl"])
+EOF
+ ln -sf "$(rlocation bazel_skylib/lib/dicts.bzl)" lib/dicts.bzl
+ ln -sf "$(rlocation bazel_skylib/lib/new_sets.bzl)" lib/new_sets.bzl
+ ln -sf "$(rlocation bazel_skylib/lib/sets.bzl)" lib/sets.bzl
+ ln -sf "$(rlocation bazel_skylib/lib/types.bzl)" lib/types.bzl
+ ln -sf "$(rlocation bazel_skylib/lib/unittest.bzl)" lib/unittest.bzl
+
+ mkdir -p toolchains/unittest
+ ln -sf "$(rlocation bazel_skylib/toolchains/unittest/BUILD)" toolchains/unittest/BUILD
+
+ # Create test files.
+ mkdir -p testdir
+ cat > testdir/BUILD <<EOF
+load("//tests:unittest_tests.bzl",
+ "basic_passing_test",
+ "basic_failing_test",
+ "fail_unexpected_passing_test",
+ "fail_unexpected_passing_fake_rule")
+
+basic_passing_test(name = "basic_passing_test")
+
+basic_failing_test(name = "basic_failing_test")
+
+fail_unexpected_passing_test(
+ name = "fail_unexpected_passing_test",
+ target_under_test = ":fail_unexpected_passing_fake_target",
+)
+fail_unexpected_passing_fake_rule(
+ name = "fail_unexpected_passing_fake_target",
+ tags = ["manual"])
+EOF
+}
+
+function test_basic_passing_test() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:basic_passing_test >"$TEST_log" 2>&1 || fail "Expected test to pass"
+
+ expect_log "PASSED"
+}
+
+function test_basic_failing_test() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:basic_failing_test --test_output=all --verbose_failures \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "In test _basic_failing_test from //tests:unittest_tests.bzl: Expected \"1\", but got \"2\""
+}
+
+function test_fail_unexpected_passing_test() {
+ local -r pkg="${FUNCNAME[0]}"
+ create_pkg "$pkg"
+
+ bazel test testdir:fail_unexpected_passing_test --test_output=all --verbose_failures \
+ >"$TEST_log" 2>&1 && fail "Expected test to fail" || true
+
+ expect_log "Expected failure of target_under_test, but found success"
+}
+
+cd "$TEST_TMPDIR"
+run_suite "unittest test suite"
diff --git a/tests/unittest_tests.bzl b/tests/unittest_tests.bzl
new file mode 100644
index 0000000..3d5a198
--- /dev/null
+++ b/tests/unittest_tests.bzl
@@ -0,0 +1,282 @@
+# Copyright 2019 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.
+
+"""Unit tests for unittest.bzl."""
+
+load("//lib:unittest.bzl", "analysistest", "asserts", "unittest")
+
+###################################
+####### fail_basic_test ###########
+###################################
+def _basic_failing_test(ctx):
+ """Unit tests for a basic library verification test that fails."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, 1, 2)
+
+ return unittest.end(env)
+
+basic_failing_test = unittest.make(_basic_failing_test)
+
+###################################
+####### basic_passing_test ########
+###################################
+def _basic_passing_test(ctx):
+ """Unit tests for a basic library verification test."""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, 1, 1)
+
+ return unittest.end(env)
+
+basic_passing_test = unittest.make(_basic_passing_test)
+
+###################################
+####### change_setting_test #######
+###################################
+def _change_setting_test(ctx):
+ """Test to verify that an analysis test may change configuration."""
+ env = analysistest.begin(ctx)
+
+ dep_min_os_version = analysistest.target_under_test(env)[_ChangeSettingInfo].min_os_version
+ asserts.equals(env, "1234.5678", dep_min_os_version)
+
+ return analysistest.end(env)
+
+_ChangeSettingInfo = provider()
+
+def _change_setting_fake_rule(ctx):
+ return [_ChangeSettingInfo(min_os_version = ctx.fragments.cpp.minimum_os_version())]
+
+change_setting_fake_rule = rule(
+ implementation = _change_setting_fake_rule,
+ fragments = ["cpp"],
+)
+
+change_setting_test = analysistest.make(
+ _change_setting_test,
+ config_settings = {
+ "//command_line_option:minimum_os_version": "1234.5678",
+ },
+)
+
+####################################
+####### failure_testing_test #######
+####################################
+def _failure_testing_test(ctx):
+ """Test to verify that an analysis test may verify a rule fails with fail()."""
+ env = analysistest.begin(ctx)
+
+ asserts.expect_failure(env, "This rule should never work")
+
+ return analysistest.end(env)
+
+def _failure_testing_fake_rule(ctx):
+ ignore = [ctx]
+ fail("This rule should never work")
+
+failure_testing_fake_rule = rule(
+ implementation = _failure_testing_fake_rule,
+)
+
+failure_testing_test = analysistest.make(
+ _failure_testing_test,
+ expect_failure = True,
+)
+
+############################################
+####### fail_unexpected_passing_test #######
+############################################
+def _fail_unexpected_passing_test(ctx):
+ """Test that fails by expecting an error that never occurs."""
+ env = analysistest.begin(ctx)
+
+ asserts.expect_failure(env, "Oh no, going to fail")
+
+ return analysistest.end(env)
+
+def _fail_unexpected_passing_fake_rule(ctx):
+ _ignore = [ctx]
+ return []
+
+fail_unexpected_passing_fake_rule = rule(
+ implementation = _fail_unexpected_passing_fake_rule,
+)
+
+fail_unexpected_passing_test = analysistest.make(
+ _fail_unexpected_passing_test,
+ expect_failure = True,
+)
+
+################################################
+####### change_setting_with_failure_test #######
+################################################
+def _change_setting_with_failure_test(ctx):
+ """Test verifying failure while changing configuration."""
+ env = analysistest.begin(ctx)
+
+ asserts.expect_failure(env, "unexpected minimum_os_version!!!")
+
+ return analysistest.end(env)
+
+def _change_setting_with_failure_fake_rule(ctx):
+ if ctx.fragments.cpp.minimum_os_version() == "error_error":
+ fail("unexpected minimum_os_version!!!")
+ return []
+
+change_setting_with_failure_fake_rule = rule(
+ implementation = _change_setting_with_failure_fake_rule,
+ fragments = ["cpp"],
+)
+
+change_setting_with_failure_test = analysistest.make(
+ _change_setting_with_failure_test,
+ expect_failure = True,
+ config_settings = {
+ "//command_line_option:minimum_os_version": "error_error",
+ },
+)
+
+####################################
+####### inspect_actions_test #######
+####################################
+def _inspect_actions_test(ctx):
+ """Test verifying actions registered by a target."""
+ env = analysistest.begin(ctx)
+
+ actions = analysistest.target_actions(env)
+ asserts.equals(env, 1, len(actions))
+ action_output = actions[0].outputs.to_list()[0]
+ asserts.equals(env, "out.txt", action_output.basename)
+ return analysistest.end(env)
+
+def _inspect_actions_fake_rule(ctx):
+ out_file = ctx.actions.declare_file("out.txt")
+ ctx.actions.run_shell(
+ command = "echo 'hello' > %s" % out_file.basename,
+ outputs = [out_file],
+ )
+ return [DefaultInfo(files = depset([out_file]))]
+
+inspect_actions_fake_rule = rule(
+ implementation = _inspect_actions_fake_rule,
+)
+
+inspect_actions_test = analysistest.make(
+ _inspect_actions_test,
+)
+
+########################################
+####### inspect_output_dirs_test #######
+########################################
+_OutputDirInfo = provider(fields = ["bin_path"])
+
+def _inspect_output_dirs_test(ctx):
+ """Test verifying output directories used by a test."""
+ env = analysistest.begin(ctx)
+
+ # Assert that the output bin dir observed by the aspect added by analysistest
+ # is the same as those observed by the rule directly, even when that's
+ # under a config transition and therefore not the same as the bin dir
+ # used by the test rule.
+ bin_path = analysistest.target_bin_dir_path(env)
+ target_under_test = analysistest.target_under_test(env)
+ asserts.false(env, not bin_path, "bin dir path not found.")
+ asserts.false(
+ env,
+ bin_path == ctx.bin_dir.path,
+ "test bin dir (%s) expected to differ with target_under_test bin dir (%s)." % (bin_path, ctx.bin_dir.path),
+ )
+ asserts.equals(env, bin_path, target_under_test[_OutputDirInfo].bin_path)
+ return analysistest.end(env)
+
+def _inspect_output_dirs_fake_rule(ctx):
+ return [
+ _OutputDirInfo(
+ bin_path = ctx.bin_dir.path,
+ ),
+ ]
+
+inspect_output_dirs_fake_rule = rule(
+ implementation = _inspect_output_dirs_fake_rule,
+)
+
+inspect_output_dirs_test = analysistest.make(
+ _inspect_output_dirs_test,
+ # The output directories differ between the test and target under test when
+ # the target under test is under a config transition.
+ config_settings = {
+ "//command_line_option:minimum_os_version": "1234.5678",
+ },
+)
+
+#########################################
+
+# buildifier: disable=unnamed-macro
+def unittest_passing_tests_suite():
+ """Creates the test targets and test suite for passing unittest.bzl tests.
+
+ Not all tests are included. Some unittest.bzl tests verify a test fails
+ when assertions are not met. Such tests must be run in an e2e shell test.
+ This suite only includes tests which verify success tests.
+ """
+ unittest.suite(
+ "unittest_tests",
+ basic_passing_test,
+ )
+
+ change_setting_test(
+ name = "change_setting_test",
+ target_under_test = ":change_setting_fake_target",
+ )
+ change_setting_fake_rule(
+ name = "change_setting_fake_target",
+ tags = ["manual"],
+ )
+
+ failure_testing_test(
+ name = "failure_testing_test",
+ target_under_test = ":failure_testing_fake_target",
+ )
+ failure_testing_fake_rule(
+ name = "failure_testing_fake_target",
+ tags = ["manual"],
+ )
+
+ change_setting_with_failure_test(
+ name = "change_setting_with_failure_test",
+ target_under_test = ":change_setting_with_failure_fake_target",
+ )
+ change_setting_with_failure_fake_rule(
+ name = "change_setting_with_failure_fake_target",
+ tags = ["manual"],
+ )
+
+ inspect_actions_test(
+ name = "inspect_actions_test",
+ target_under_test = ":inspect_actions_fake_target",
+ )
+ inspect_actions_fake_rule(
+ name = "inspect_actions_fake_target",
+ tags = ["manual"],
+ )
+
+ inspect_output_dirs_test(
+ name = "inspect_output_dirs_test",
+ target_under_test = ":inspect_output_dirs_fake_target",
+ )
+ inspect_output_dirs_fake_rule(
+ name = "inspect_output_dirs_fake_target",
+ tags = ["manual"],
+ )
diff --git a/tests/versions_tests.bzl b/tests/versions_tests.bzl
new file mode 100644
index 0000000..422cb41
--- /dev/null
+++ b/tests/versions_tests.bzl
@@ -0,0 +1,69 @@
+# Copyright 2018 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.
+
+"""Unit tests for versions.bzl."""
+
+load("//lib:unittest.bzl", "asserts", "unittest")
+load("//lib:versions.bzl", "versions")
+
+def _parse_test(ctx):
+ """Unit tests for versions.parse"""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, (0, 10, 0), versions.parse("0.10.0rc1 abcd123"))
+ asserts.equals(env, (0, 4, 0), versions.parse("0.4.0 abcd123"))
+ asserts.equals(env, (0, 4, 0), versions.parse("0.4.0"))
+ asserts.equals(env, (0, 4, 0), versions.parse("0.4.0rc"))
+
+ return unittest.end(env)
+
+def _version_comparison_test(ctx):
+ """Unit tests for versions.is_at_least and is_at_most"""
+ env = unittest.begin(ctx)
+
+ asserts.false(env, versions.is_at_least("0.11.0 123abcd", "0.10.0rc1 abcd123"))
+ asserts.true(env, versions.is_at_least("0.9.0", "0.10.0rc2"))
+ asserts.true(env, versions.is_at_least("0.9.0", "0.9.0rc3"))
+ asserts.true(env, versions.is_at_least("0.9.0", "1.2.3"))
+
+ asserts.false(env, versions.is_at_most("0.4.0 123abcd", "0.10.0rc1 abcd123"))
+ asserts.true(env, versions.is_at_most("0.4.0", "0.3.0rc2"))
+ asserts.true(env, versions.is_at_most("0.4.0", "0.4.0rc3"))
+ asserts.true(env, versions.is_at_most("1.4.0", "0.4.0rc3"))
+
+ return unittest.end(env)
+
+def _check_test(ctx):
+ """Unit tests for versions.check"""
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, None, versions.check("0.4.5 abcdef", bazel_version = "0.10.0rc1 abcd123"))
+ asserts.equals(env, None, versions.check("0.4.5", bazel_version = "0.4.5"))
+ asserts.equals(env, None, versions.check("0.4.5", bazel_version = "0.10.0rc1 abcd123"))
+ asserts.equals(env, None, versions.check("0.4.5", maximum_bazel_version = "1.0.0", bazel_version = "0.10.0rc1 abcd123"))
+
+ return unittest.end(env)
+
+parse_test = unittest.make(_parse_test)
+version_comparison_test = unittest.make(_version_comparison_test)
+check_test = unittest.make(_check_test)
+
+def versions_test_suite():
+ """Creates the test targets and test suite for versions.bzl tests."""
+ unittest.suite(
+ "versions_tests",
+ parse_test,
+ version_comparison_test,
+ check_test,
+ )
diff --git a/tests/write_file/BUILD b/tests/write_file/BUILD
new file mode 100644
index 0000000..9ea3609
--- /dev/null
+++ b/tests/write_file/BUILD
@@ -0,0 +1,166 @@
+# This package aids testing the 'write_file' rule.
+#
+# The package contains 4 write_file rules:
+# - 'write_empty_text' and 'write_empty_bin' write an empty text and an empty
+# executable file respectively
+# - 'write_nonempty_text' and 'write_nonempty_bin' write a non-empty text and
+# a non-empty executable file (a shell script) respectively
+#
+# The 'bin_empty' and 'bin_nonempty' rules are sh_binary rules. They use
+# the 'write_empty_bin' and 'write_nonempty_bin' rules respectively. The
+# sh_binary rule requires its source to be executable, so building these two
+# rules successfully means that 'write_file' managed to make its output
+# executable.
+#
+# The 'run_executables' genrule runs the 'bin_empty' and 'bin_nonempty'
+# binaries, partly to ensure they can be run, and partly so we can observe their
+# output and assert the contents in the 'write_file_tests' test.
+#
+# The 'file_deps' filegroup depends on 'write_empty_text'. The filegroup rule
+# uses the DefaultInfo.files field from its dependencies. When we data-depend on
+# the filegroup from 'write_file_tests', we transitively data-depend on the
+# DefaultInfo.files of the 'write_empty_text' rule.
+#
+# The 'write_file_tests' test is the actual integration test. It data-depends
+# on:
+# - the 'run_executables' rule, to get the outputs of 'bin_empty' and
+# 'bin_nonempty'
+# - the 'file_deps' rule, and by nature of using a filegroup, we get the files
+# from the DefaultInfo.files of the 'write_file' rule, and thereby assert that
+# that field contains the output file of the rule
+# - the 'write_nonempty_text' rule, and thereby on the DefaultInfo.runfiles
+# field of it, so we assert that that field contains the output file of the
+# rule
+
+load("//rules:diff_test.bzl", "diff_test")
+load("//rules:write_file.bzl", "write_file")
+
+licenses(["notice"])
+
+package(default_testonly = 1)
+
+sh_test(
+ name = "write_file_tests",
+ srcs = ["write_file_tests.sh"],
+ data = [
+ ":run_executables",
+ # Use DefaultInfo.files from 'write_empty_text' (via 'file_deps').
+ ":file_deps",
+ # Use DefaultInfo.runfiles from 'write_nonempty_text'.
+ ":write_nonempty_text",
+ "//tests:unittest.bash",
+ ],
+ deps = ["@bazel_tools//tools/bash/runfiles"],
+)
+
+filegroup(
+ name = "file_deps",
+ # Use DefaultInfo.files from 'write_empty_text'.
+ srcs = [":write_empty_text"],
+)
+
+# If 'run_executables' is built, then 'bin_nonempty' and 'bin_empty' are
+# executable, asserting that write_file makes the output executable.
+genrule(
+ name = "run_executables",
+ outs = [
+ "empty-bin-out.txt",
+ "nonempty-bin-out.txt",
+ ],
+ cmd = ("$(location :bin_empty) > $(location empty-bin-out.txt) && " +
+ "$(location :bin_nonempty) > $(location nonempty-bin-out.txt)"),
+ output_to_bindir = 1,
+ tools = [
+ ":bin_empty",
+ ":bin_nonempty",
+ ],
+)
+
+# If 'bin_empty' is built, then 'write_empty_bin' made its output executable.
+sh_binary(
+ name = "bin_empty",
+ srcs = [":write_empty_bin"],
+)
+
+# If 'bin_nonempty' is built, then 'write_nonempty_bin' made its output
+# executable.
+sh_binary(
+ name = "bin_nonempty",
+ srcs = [":write_nonempty_bin"],
+)
+
+write_file(
+ name = "write_empty_text",
+ out = "out/empty.txt",
+)
+
+write_file(
+ name = "write_nonempty_text",
+ out = "out/nonempty.txt",
+ content = [
+ "aaa",
+ "bbb",
+ ],
+)
+
+write_file(
+ name = "write_empty_bin",
+ out = "out/empty.sh",
+ is_executable = True,
+)
+
+write_file(
+ name = "write_nonempty_bin",
+ out = "out/nonempty.sh",
+ content = [
+ "#!/bin/bash",
+ "echo potato",
+ ],
+ is_executable = True,
+)
+
+write_file(
+ name = "newline_unix_actual",
+ out = "out/newline_unix_actual.txt",
+ content = [
+ "ab",
+ "cd",
+ "ef",
+ ],
+ newline = "unix",
+)
+
+write_file(
+ name = "newline_unix_exp",
+ out = "out/newline_unix_exp.txt",
+ content = ["ab\ncd\nef"],
+)
+
+diff_test(
+ name = "unix_line_ending_test",
+ file1 = ":newline_unix_actual",
+ file2 = ":newline_unix_exp",
+)
+
+write_file(
+ name = "newline_win_actual",
+ out = "out/newline_win_actual.txt",
+ content = [
+ "ab",
+ "cd",
+ "ef",
+ ],
+ newline = "windows",
+)
+
+write_file(
+ name = "newline_win_exp",
+ out = "out/newline_win_exp.txt",
+ content = ["ab\r\ncd\r\nef"],
+)
+
+diff_test(
+ name = "win_line_ending_test",
+ file1 = ":newline_win_actual",
+ file2 = ":newline_win_exp",
+)
diff --git a/tests/write_file/write_file_tests.sh b/tests/write_file/write_file_tests.sh
new file mode 100755
index 0000000..2464230
--- /dev/null
+++ b/tests/write_file/write_file_tests.sh
@@ -0,0 +1,66 @@
+# Copyright 2019 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.
+
+# --- begin runfiles.bash initialization ---
+# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ if [[ -f "$0.runfiles_manifest" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+ elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+ export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+ elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ export RUNFILES_DIR="$0.runfiles"
+ fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+ source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+ source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+ echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+ exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+function assert_empty_file() {
+ local -r path="$1"
+ # Not using 'du' to check the file is empty, because it doesn't work on CI.
+ [[ "$(echo -n "($(cat "$path"))")" = "()" ]]
+}
+
+function test_write_empty_text() {
+ assert_empty_file "$(rlocation bazel_skylib/tests/write_file/out/empty.txt)"
+}
+
+function test_write_nonempty_text() {
+ cat "$(rlocation bazel_skylib/tests/write_file/out/nonempty.txt)" >"$TEST_log"
+ expect_log '^aaa$'
+ expect_log '^bbb$'
+}
+
+function test_write_empty_bin() {
+ assert_empty_file "$(rlocation bazel_skylib/tests/write_file/empty-bin-out.txt)"
+}
+
+function test_write_nonempty_bin() {
+ cat "$(rlocation bazel_skylib/tests/write_file/nonempty-bin-out.txt)" >"$TEST_log"
+ expect_log '^potato$'
+}
+
+run_suite "write_file_tests test suite"
diff --git a/toolchains/unittest/BUILD b/toolchains/unittest/BUILD
new file mode 100644
index 0000000..03ceff4
--- /dev/null
+++ b/toolchains/unittest/BUILD
@@ -0,0 +1,67 @@
+load("//lib:unittest.bzl", "TOOLCHAIN_TYPE", "unittest_toolchain")
+
+licenses(["notice"])
+
+toolchain_type(
+ name = "toolchain_type",
+ visibility = ["//visibility:public"],
+)
+
+unittest_toolchain(
+ name = "cmd",
+ failure_templ = """@echo off
+echo %s
+exit /b 1
+""",
+ file_ext = ".bat",
+ join_on = "\necho ",
+ success_templ = "@exit /b 0",
+ visibility = ["//visibility:public"],
+)
+
+toolchain(
+ name = "cmd_toolchain",
+ exec_compatible_with = [
+ "@platforms//os:windows",
+ ],
+ toolchain = ":cmd",
+ toolchain_type = TOOLCHAIN_TYPE,
+)
+
+unittest_toolchain(
+ name = "bash",
+ failure_templ = """#!/bin/sh
+cat <<'EOF'
+%s
+EOF
+exit 1
+""",
+ file_ext = ".sh",
+ join_on = "\n",
+ success_templ = "#!/bin/sh\nexit 0",
+ visibility = ["//visibility:public"],
+)
+
+toolchain(
+ name = "bash_toolchain",
+ toolchain = ":bash",
+ toolchain_type = TOOLCHAIN_TYPE,
+)
+
+filegroup(
+ name = "test_deps",
+ testonly = True,
+ srcs = [
+ "BUILD",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+# The files needed for distribution
+filegroup(
+ name = "distribution",
+ srcs = ["BUILD"],
+ visibility = [
+ "//:__pkg__",
+ ],
+)
diff --git a/version.bzl b/version.bzl
new file mode 100644
index 0000000..3906779
--- /dev/null
+++ b/version.bzl
@@ -0,0 +1,16 @@
+# Copyright 2019 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.
+"""The version of bazel-skylib."""
+
+version = "1.0.3"
diff --git a/workspace.bzl b/workspace.bzl
new file mode 100644
index 0000000..29fd1e3
--- /dev/null
+++ b/workspace.bzl
@@ -0,0 +1,21 @@
+# Copyright 2019 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.
+
+"""Dependency registration helpers for repositories which need to load bazel-skylib."""
+
+load("@bazel_skylib//lib:unittest.bzl", "register_unittest_toolchains")
+
+def bazel_skylib_workspace():
+ """Registers toolchains and declares repository dependencies of the bazel_skylib repository."""
+ register_unittest_toolchains()