feat: add some paths and utils (#1)

* feat: add some paths and utils

* test: exercise relative to_label

* chore: cleanup naming and rctx helper
This commit is contained in:
Alex Eagle 2021-11-09 15:13:30 -08:00 committed by GitHub
parent 1c5ab3dad8
commit cb8c2bad22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 529 additions and 7 deletions

View File

@ -10,6 +10,8 @@ load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
_DOCS = {
"//lib:expand_make_vars.bzl": "expand_make_vars.md",
"//lib:params_file.bzl": "params_file.md",
"//lib:utils.bzl": "utils.md",
"//lib:paths.bzl": "paths.md",
}
[

27
docs/paths.md Normal file
View File

@ -0,0 +1,27 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
Public API
<a id="#relative_file"></a>
## relative_file
<pre>
relative_file(<a href="#relative_file-to_file">to_file</a>, <a href="#relative_file-frm_file">frm_file</a>)
</pre>
Resolves a relative path between two files, "to_file" and "frm_file", they must share the same root
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="relative_file-to_file"></a>to_file | the path with file name to resolve to, from frm | none |
| <a id="relative_file-frm_file"></a>frm_file | the path with file name to resolve from | none |
**RETURNS**
The relative path from frm_file to to_file, including the file name

110
docs/utils.md Normal file
View File

@ -0,0 +1,110 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
Public API
<a id="#glob_directories"></a>
## glob_directories
<pre>
glob_directories(<a href="#glob_directories-include">include</a>, <a href="#glob_directories-kwargs">kwargs</a>)
</pre>
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="glob_directories-include"></a>include | <p align="center"> - </p> | none |
| <a id="glob_directories-kwargs"></a>kwargs | <p align="center"> - </p> | none |
<a id="#is_external_label"></a>
## is_external_label
<pre>
is_external_label(<a href="#is_external_label-param">param</a>)
</pre>
Returns True if the given Label (or stringy version of a label) represents a target outside of the workspace
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="is_external_label-param"></a>param | a string or label | none |
**RETURNS**
a bool
<a id="#path_to_workspace_root"></a>
## path_to_workspace_root
<pre>
path_to_workspace_root()
</pre>
Retuns the path to the workspace root under bazel
**RETURNS**
Path to the workspace root
<a id="#propagate_well_known_tags"></a>
## propagate_well_known_tags
<pre>
propagate_well_known_tags(<a href="#propagate_well_known_tags-tags">tags</a>)
</pre>
Returns a list of tags filtered from the input set that only contains the ones that are considered "well known"
These are listed in Bazel's documentation:
https://docs.bazel.build/versions/main/test-encyclopedia.html#tag-conventions
https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="propagate_well_known_tags-tags"></a>tags | List of tags to filter | <code>[]</code> |
**RETURNS**
List of tags that only contains the well known set
<a id="#to_label"></a>
## to_label
<pre>
to_label(<a href="#to_label-param">param</a>)
</pre>
Converts a string to a Label. If Label is supplied, the same label is returned.
**PARAMETERS**
| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="to_label-param"></a>param | a string representing a label or a Label | none |
**RETURNS**
a Label

View File

@ -1,10 +1,7 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
# For stardoc to reference the files
exports_files([
"expand_make_vars.bzl",
"params_file.bzl",
])
exports_files(glob(["*.bzl"]))
filegroup(
name = "package_content",
@ -30,3 +27,17 @@ bzl_library(
visibility = ["//visibility:public"],
deps = ["//lib/private:params_file"],
)
bzl_library(
name = "paths",
srcs = ["paths.bzl"],
visibility = ["//visibility:public"],
deps = ["//lib/private:paths"],
)
bzl_library(
name = "utils",
srcs = ["utils.bzl"],
visibility = ["//visibility:public"],
deps = ["//lib/private:utils"],
)

5
lib/paths.bzl Normal file
View File

@ -0,0 +1,5 @@
"Public API"
load("//lib/private:paths.bzl", "paths")
relative_file = paths.relative_file

View File

@ -16,8 +16,21 @@ bzl_library(
deps = [":expand_make_vars"],
)
bzl_library(
name = "paths",
srcs = ["paths.bzl"],
visibility = ["//lib:__subpackages__"],
deps = ["@bazel_skylib//lib:paths"],
)
bzl_library(
name = "expand_make_vars",
srcs = ["expand_make_vars.bzl"],
visibility = ["//lib:__subpackages__"],
)
bzl_library(
name = "utils",
srcs = ["utils.bzl"],
visibility = ["//lib:__subpackages__"],
)

47
lib/private/paths.bzl Normal file
View File

@ -0,0 +1,47 @@
"""Path utils built on top of Skylib's path utils"""
load("@bazel_skylib//lib:paths.bzl", _spaths = "paths")
def _relative_file(to_file, frm_file):
"""Resolves a relative path between two files, "to_file" and "frm_file", they must share the same root
Args:
to_file: the path with file name to resolve to, from frm
frm_file: the path with file name to resolve from
Returns:
The relative path from frm_file to to_file, including the file name
"""
to_segments = _spaths.normalize(_spaths.join("/", to_file)).split("/")[:-1]
frm_segments = _spaths.normalize(_spaths.join("/", frm_file)).split("/")[:-1]
if len(to_segments) == 0 and len(frm_segments) == 0:
return to_file
if to_segments[0] != frm_segments[0]:
fail("paths must share a common root, got '%s' and '%s'" % (to_file, frm_file))
longest_common = []
for to_seg, frm_seg in zip(to_segments, frm_segments):
if to_seg == frm_seg:
longest_common.append(to_seg)
else:
break
split_point = len(longest_common)
if split_point == 0:
fail("paths share no common ancestor, '%s' -> '%s'" % (frm_file, to_file))
return _spaths.join(
*(
[".."] * (len(frm_segments) - split_point) +
to_segments[split_point:] +
[_spaths.basename(to_file)]
)
)
paths = struct(
relative_file = _relative_file,
)

94
lib/private/utils.bzl Normal file
View File

@ -0,0 +1,94 @@
"""General utility functions"""
def _propagate_well_known_tags(tags = []):
"""Returns a list of tags filtered from the input set that only contains the ones that are considered "well known"
These are listed in Bazel's documentation:
https://docs.bazel.build/versions/main/test-encyclopedia.html#tag-conventions
https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes
Args:
tags: List of tags to filter
Returns:
List of tags that only contains the well known set
"""
WELL_KNOWN_TAGS = [
"no-sandbox",
"no-cache",
"no-remote-cache",
"no-remote-exec",
"no-remote",
"local",
"requires-network",
"block-network",
"requires-fakeroot",
"exclusive",
"manual",
"external",
]
return [tag for tag in tags if tag in WELL_KNOWN_TAGS]
def _to_label(param):
"""Converts a string to a Label. If Label is supplied, the same label is returned.
Args:
param: a string representing a label or a Label
Returns:
a Label
"""
param_type = type(param)
if param_type == "string":
if not param.startswith("@") and not param.startswith("//"):
# resolve the relative label from the current package
# if 'param' is in another workspace, then this would return the label relative to that workspace, eg:
# Label("@my//foo:bar").relative("@other//baz:bill") == Label("@other//baz:bill")
if param.startswith(":"):
param = param[1:]
if native.package_name():
return Label("//" + native.package_name()).relative(param)
else:
return Label("//:" + param)
return Label(param)
elif param_type == "Label":
return param
else:
fail("Expected 'string' or 'Label' but got '%s'" % param_type)
def _is_external_label(param):
"""Returns True if the given Label (or stringy version of a label) represents a target outside of the workspace
Args:
param: a string or label
Returns:
a bool
"""
return len(_to_label(param).workspace_root) > 0
# Path to the root of the workspace
def _path_to_workspace_root():
""" Retuns the path to the workspace root under bazel
Returns:
Path to the workspace root
"""
return "/".join([".."] * len(native.package_name().split("/")))
# Like glob() but returns directories only
def _glob_directories(include, **kwargs):
all = native.glob(include, exclude_directories = 0, **kwargs)
files = native.glob(include, **kwargs)
directories = [p for p in all if p not in files]
return directories
utils = struct(
is_external_label = _is_external_label,
glob_directories = _glob_directories,
path_to_workspace_root = _path_to_workspace_root,
propagate_well_known_tags = _propagate_well_known_tags,
to_label = _to_label,
)

View File

@ -1,3 +1,9 @@
load(":expand_make_vars_test.bzl", "expand_make_vars_test_suite")
load(":utils_test.bzl", "utils_test_suite")
load(":paths_test.bzl", "paths_test_suite")
expand_make_vars_test_suite(name = "expand_make_vars_test")
expand_make_vars_test_suite()
paths_test_suite()
utils_test_suite()

View File

@ -25,5 +25,5 @@ def _variables_test_impl(ctx):
# but their names are arbitrary and don't appear anywhere.
t0_test = unittest.make(_variables_test_impl)
def expand_make_vars_test_suite(name):
unittest.suite(name, t0_test)
def expand_make_vars_test_suite():
unittest.suite("make_vars_tests", t0_test)

122
lib/tests/paths_test.bzl Normal file
View File

@ -0,0 +1,122 @@
"""unit tests for paths"""
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load("//lib/private:paths.bzl", "paths")
def _relative_file_test_impl(ctx):
env = unittest.begin(ctx)
asserts.equals(
env,
"lib/requirements.in",
paths.relative_file(
"bazel/python/internal/pip/test/lib/requirements.in",
"bazel/python/internal/pip/test/requirements.out",
),
)
asserts.equals(
env,
"pip/test/lib/requirements.in",
paths.relative_file(
"bazel/python/internal/pip/test/lib/requirements.in",
"bazel/python/internal/requirements.out",
),
)
asserts.equals(
env,
"bazel/python/internal/pip/test/lib/requirements.in",
paths.relative_file(
"/bazel/python/internal/pip/test/lib/requirements.in",
"/requirements.out",
),
)
asserts.equals(
env,
"../requirements.in",
paths.relative_file(
"bazel/python/internal/pip/test/requirements.in",
"bazel/python/internal/pip/test/lib/requirements.in",
),
)
asserts.equals(
env,
"../requirements.out",
paths.relative_file(
"bazel/python/internal/pip/test/requirements.out",
"bazel/python/internal/pip/test/lib/requirements.in",
),
)
asserts.equals(
env,
"requirements.out",
paths.relative_file(
"bazel/python/internal/pip/example/service/requirements.out",
"bazel/python/internal/pip/example/service/requirements.in",
),
)
asserts.equals(
env,
"../service/requirements.out",
paths.relative_file(
"bazel/python/internal/pip/example/service/requirements.out",
"bazel/python/internal/pip/example/lib/requirements.in",
),
)
asserts.equals(
env,
"../lib/bar/requirements.in",
paths.relative_file(
"bazel/python/internal/pip/example/lib/bar/requirements.in",
"bazel/python/internal/pip/example/service/requirements.out",
),
)
asserts.equals(
env,
"../../../example/lib/bar/requirements.in",
paths.relative_file(
"bazel/python/internal/pip/example/lib/bar/requirements.in",
"bazel/python/internal/pip/lib/example/service/requirements.out",
),
)
asserts.equals(
env,
"requirements.in",
paths.relative_file(
"requirements.in",
"requirements.out",
),
)
asserts.equals(
env,
"requirements.in",
paths.relative_file(
"/bazel/requirements.in",
"/bazel/requirements.out",
),
)
asserts.equals(
env,
"../requirements.in",
paths.relative_file(
"/requirements.in",
"/bazel/requirements.out",
),
)
return unittest.end(env)
relative_file_test = unittest.make(_relative_file_test_impl)
def paths_test_suite():
unittest.suite("relative_file_tests", relative_file_test)

76
lib/tests/utils_test.bzl Normal file
View File

@ -0,0 +1,76 @@
"Unit tests for test.bzl"
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load("//lib/private:utils.bzl", "utils")
def _to_label_test_impl(ctx):
env = unittest.begin(ctx)
# assert label/string comparisons work as expected
asserts.true(env, "//some/label" != Label("//some/label"))
asserts.true(env, "//some/label" != Label("//some/other/label"))
asserts.true(env, Label("//some/label") == Label("//some/label"))
asserts.true(env, "//some/label" != Label("//some/label"))
asserts.true(env, "//some/label" != Label("//some/other/label"))
# assert that to_label can convert from string to label
asserts.true(env, utils.to_label("//hello/world") == Label("//hello/world:world"))
asserts.true(env, utils.to_label("//hello/world:world") == Label("//hello/world:world"))
# assert that to_label will handle a Label as an argument
asserts.true(env, utils.to_label(Label("//hello/world")) == Label("//hello/world:world"))
asserts.true(env, utils.to_label(Label("//hello/world:world")) == Label("//hello/world:world"))
# relative labels
for (actual, expected) in ctx.attr.relative_asserts.items():
asserts.true(env, actual.label == Label(expected))
return unittest.end(env)
def _is_external_label_test_impl(ctx):
env = unittest.begin(ctx)
# assert that labels and strings that are constructed within this workspace return false
asserts.false(env, utils.is_external_label("//some/label"))
asserts.false(env, utils.is_external_label(Label("//some/label")))
asserts.false(env, utils.is_external_label(Label("@aspect_bazel_lib//some/label")))
asserts.false(env, ctx.attr.internal_with_workspace_as_string)
# assert that labels and string that give a workspace return true
asserts.true(env, utils.is_external_label(Label("@foo//some/label")))
asserts.true(env, ctx.attr.external_as_string)
return unittest.end(env)
to_label_test = unittest.make(
_to_label_test_impl,
attrs = {
"relative_asserts": attr.label_keyed_string_dict(
allow_files = True,
mandatory = True,
),
},
)
is_external_label_test = unittest.make(
_is_external_label_test_impl,
attrs = {
"external_as_string": attr.bool(
mandatory = True,
),
"internal_with_workspace_as_string": attr.bool(
mandatory = True,
),
},
)
def utils_test_suite():
to_label_test(name = "to_label_tests", relative_asserts = {
utils.to_label(":utils_test.bzl"): "//lib/tests:utils_test.bzl",
})
is_external_label_test(
name = "is_external_label_tests",
external_as_string = utils.is_external_label("@foo//some/label"),
internal_with_workspace_as_string = utils.is_external_label("@aspect_bazel_lib//some/label"),
)

9
lib/utils.bzl Normal file
View File

@ -0,0 +1,9 @@
"Public API"
load("//lib/private:utils.bzl", "utils")
is_external_label = utils.is_external_label
glob_directories = utils.glob_directories
path_to_workspace_root = utils.path_to_workspace_root
propagate_well_known_tags = utils.propagate_well_known_tags
to_label = utils.to_label