Skip to content

Commit

Permalink
Add a rule to create providers for subdirectories
Browse files Browse the repository at this point in the history
  • Loading branch information
matts1 committed Apr 23, 2024
1 parent 14efc8c commit f68bc00
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
36 changes: 36 additions & 0 deletions rules/directories/subdirectory.bzl
@@ -0,0 +1,36 @@
# Copyright 2024 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 rules to create metadata about subdirectories."""

load(":providers.bzl", "DirectoryInfo")
load(":utils.bzl", "get_subdirectory")

visibility("public")

def _subdirectory_impl(ctx):
dir = get_subdirectory(ctx.attr.parent[DirectoryInfo], ctx.attr.path)
return [
dir,
DefaultInfo(files = dir.transitive_files),
]

subdirectory = rule(
implementation = _subdirectory_impl,
attrs = {
"parent": attr.label(providers = [DirectoryInfo], mandatory = True),
"path": attr.string(mandatory = True),
},
provides = [DirectoryInfo],
)
55 changes: 55 additions & 0 deletions rules/directories/utils.bzl
@@ -0,0 +1,55 @@
# Copyright 2024 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 utility functions related to directories."""

visibility("public")

_DIR_NOT_FOUND = """{directory} does not contain a directory named {dirname}.
Instead, it contains the directories {children}."""

def _check_path_relative(path):
if path.startswith("/"):
fail("Path must be relative. Got {path}".format(path = path))

def get_direct_subdirectory(directory, dirname):
"""Gets the direct subdirectory of a directory.
Args:
directory: (DirectoryInfo) The directory to look within.
dirname: (string) The name of the directory to look for.
Returns:
(DirectoryInfo) The directory contained within."""
if dirname not in directory.directories:
fail(_DIR_NOT_FOUND.format(
directory = directory.human_readable,
dirname = repr(dirname),
children = repr(sorted(directory.directories)),
))
return directory.directories[dirname]

def get_subdirectory(directory, path):
"""Gets a subdirectory contained within a tree of another directory.
Args:
directory: (DirectoryInfo) The directory to look within.
path: (string) The path of the directory to look for within it.
Returns:
(DirectoryInfo) The directory contained within.
"""
_check_path_relative(path)

for dirname in path.split("/"):
directory = get_direct_subdirectory(directory, dirname)
return directory
20 changes: 20 additions & 0 deletions tests/directories/BUILD
Expand Up @@ -2,7 +2,9 @@ load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load(
":directory_test.bzl",
"directory_test",
"nonexistent_subdirectory_test",
"outside_testdata_test",
"subdirectory_test",
)

analysis_test(
Expand All @@ -28,3 +30,21 @@ analysis_test(
impl = outside_testdata_test,
target = "//tests/directories/testdata:outside_testdata",
)

analysis_test(
name = "subdirectory_test",
impl = subdirectory_test,
targets = {
"root": "//tests/directories/testdata:root",
"dir": "//tests/directories/testdata:dir",
"subdir": "//tests/directories/testdata:subdir",
"f2": "//tests/directories/testdata:f2_filegroup",
},
)

analysis_test(
name = "nonexistent_subdirectory_test",
expect_failure = True,
impl = nonexistent_subdirectory_test,
target = "//tests/directories/testdata:nonexistent_subdirectory",
)
28 changes: 28 additions & 0 deletions tests/directories/directory_test.bzl
Expand Up @@ -86,3 +86,31 @@ def directory_test(env, targets):
newdir.direct_files().contains_exactly([f3])
newdir.transitive_files().contains_exactly([f3]).in_order()
env.expect.that_str(newdir.actual.generated_path + "/f3").equals(f3.path)

_NONEXISTENT_DIR_ERR = """@@//tests/directories/testdata/dir does not contain a directory named "nonexistent".
Instead, it contains the directories ["subdir"]."""

# buildifier: disable=function-docstring
def nonexistent_subdirectory_test(env, target):
env.expect.that_target(target).failures().contains_exactly_predicates([
matching.contains(_NONEXISTENT_DIR_ERR),
])

# buildifier: disable=function-docstring
def subdirectory_test(env, targets):
f2 = targets.f2.files.to_list()[0]

root = targets.root[DirectoryInfo]
want_dir = root.directories["dir"]
want_subdir = want_dir.directories["subdir"]

# Use that_str because it supports equality checks. They're not strings.
env.expect.that_str(targets.dir[DirectoryInfo]).equals(want_dir)
env.expect.that_str(targets.subdir[DirectoryInfo]).equals(want_subdir)

env.expect.that_collection(
targets.dir.files.to_list(),
).contains_exactly([f2])
env.expect.that_collection(
targets.subdir.files.to_list(),
).contains_exactly([f2])
20 changes: 20 additions & 0 deletions tests/directories/testdata/BUILD
@@ -1,6 +1,7 @@
load("@rules_testing//lib:util.bzl", "util")
load("//rules:copy_file.bzl", "copy_file")
load("//rules/directories:directory.bzl", "directory")
load("//rules/directories:subdirectory.bzl", "subdirectory")

package(default_visibility = ["//tests/directories:__pkg__"])

Expand Down Expand Up @@ -36,3 +37,22 @@ filegroup(
name = "f2_filegroup",
srcs = ["dir/subdir/f2"],
)

subdirectory(
name = "dir",
parent = ":root",
path = "dir",
)

subdirectory(
name = "subdir",
parent = ":root",
path = "dir/subdir",
)

util.helper_target(
subdirectory,
name = "nonexistent_subdirectory",
parent = ":root",
path = "dir/nonexistent",
)

0 comments on commit f68bc00

Please sign in to comment.