feat: copy_to_directory copies files and directories to an output directory
This commit is contained in:
parent
905cbb582f
commit
0d2981f288
4
.bazelrc
4
.bazelrc
|
@ -2,6 +2,10 @@
|
|||
# Take care to document any settings that you expect users to apply.
|
||||
# Settings that apply only to CI are in .github/workflows/ci.bazelrc
|
||||
|
||||
# Allow the Bazel server to check directory sources for changes
|
||||
# Avoids warning spam with rules_nodejs feature that models node_modules as directories
|
||||
# See https://github.com/bazelbuild/rules_nodejs/releases/tag/3.6.0
|
||||
startup --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1
|
||||
|
||||
# Load any settings specific to the current user.
|
||||
# .bazelrc.user should appear in .gitignore so that settings are not shared with team members
|
||||
|
|
|
@ -15,6 +15,12 @@ load("@bazel_skylib//lib:unittest.bzl", "register_unittest_toolchains")
|
|||
|
||||
register_unittest_toolchains()
|
||||
|
||||
# An external repository for test to use
|
||||
local_repository(
|
||||
name = "external_test_repo",
|
||||
path = "./lib/tests/external_test_repo",
|
||||
)
|
||||
|
||||
############################################
|
||||
# Gazelle, for generating bzl_library targets
|
||||
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
# so that the dependency on stardoc doesn't leak to them.
|
||||
load("//lib:docs.bzl", "stardoc_with_diff_test", "update_docs")
|
||||
|
||||
stardoc_with_diff_test(
|
||||
bzl_library_target = "//lib/private:copy_to_directory",
|
||||
out_label = "//docs:copy_to_directory.md",
|
||||
)
|
||||
|
||||
stardoc_with_diff_test(
|
||||
bzl_library_target = "//lib:docs",
|
||||
out_label = "//docs:docs.md",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
|
||||
|
||||
Copy files and directories to an output directory
|
||||
|
||||
<a id="#copy_to_directory"></a>
|
||||
|
||||
## copy_to_directory
|
||||
|
||||
<pre>
|
||||
copy_to_directory(<a href="#copy_to_directory-name">name</a>, <a href="#copy_to_directory-is_windows">is_windows</a>, <a href="#copy_to_directory-replace_prefixes">replace_prefixes</a>, <a href="#copy_to_directory-root_paths">root_paths</a>, <a href="#copy_to_directory-srcs">srcs</a>)
|
||||
</pre>
|
||||
|
||||
Copies files and directories to an output directory.
|
||||
|
||||
Files and directories can be arranged as needed in the output directory using
|
||||
the `root_paths` and `replace_prefixes` attributes.
|
||||
|
||||
NB: This rule is not yet implemented for Windows
|
||||
|
||||
|
||||
**ATTRIBUTES**
|
||||
|
||||
|
||||
| Name | Description | Type | Mandatory | Default |
|
||||
| :------------- | :------------- | :------------- | :------------- | :------------- |
|
||||
| <a id="copy_to_directory-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
|
||||
| <a id="copy_to_directory-is_windows"></a>is_windows | - | Boolean | required | |
|
||||
| <a id="copy_to_directory-replace_prefixes"></a>replace_prefixes | Map of paths prefixes to replace in the output directory path when copying files.<br><br>If the output directory path for a file or directory starts with or is equal to a key in the dict then the matching portion of the output directory path is replaced with the dict value for that key.<br><br>Forward slashes (<code>/</code>) should be used as path separators. The final path segment of the key can be a partial match in the corresponding segment of the output directory path.<br><br>If there are multiple keys that match, the longest match wins. | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
|
||||
| <a id="copy_to_directory-root_paths"></a>root_paths | List of paths that are roots in the output directory. If a file or directory being copied is in one of the listed paths or one of its subpaths, the output directory path is the path relative to the root path instead of the path relative to the file's workspace.<br><br>Forward slashes (<code>/</code>) should be used as path separators. Partial matches on the final path segment of a root path against the corresponding segment in the full workspace relative path of a file are not matched.<br><br>If there are multiple root paths that match, the longest match wins.<br><br>Defaults to [package_name()] so that the output directory path of files in the target's package and and sub-packages are relative to the target's package and files outside of that retain their full workspace relative paths. | List of strings | optional | [] |
|
||||
| <a id="copy_to_directory-srcs"></a>srcs | Files and/or directories to copy into the output directory | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
|
||||
|
||||
|
||||
<a id="#copy_to_directory_lib.impl"></a>
|
||||
|
||||
## copy_to_directory_lib.impl
|
||||
|
||||
<pre>
|
||||
copy_to_directory_lib.impl(<a href="#copy_to_directory_lib.impl-ctx">ctx</a>)
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
**PARAMETERS**
|
||||
|
||||
|
||||
| Name | Description | Default Value |
|
||||
| :------------- | :------------- | :------------- |
|
||||
| <a id="copy_to_directory_lib.impl-ctx"></a>ctx | <p align="center"> - </p> | none |
|
||||
|
||||
|
|
@ -33,7 +33,11 @@ The relative path from frm_file to to_file, including the file name
|
|||
to_manifest_path(<a href="#to_manifest_path-ctx">ctx</a>, <a href="#to_manifest_path-file">file</a>)
|
||||
</pre>
|
||||
|
||||
The runfiles manifest entry for a file
|
||||
The runfiles manifest entry path for a file
|
||||
|
||||
This is the full runfiles path of a file including its workspace name as
|
||||
the first segment. We refert to it as the manifest path as it is the path
|
||||
flavor that is used for in the runfiles MANIFEST file.
|
||||
|
||||
We must avoid using non-normalized paths (workspace/../other_workspace/path)
|
||||
in order to locate entries by their key.
|
||||
|
@ -49,6 +53,34 @@ in order to locate entries by their key.
|
|||
|
||||
**RETURNS**
|
||||
|
||||
a key that can lookup the path from the runfiles manifest
|
||||
The runfiles manifest entry path for a file
|
||||
|
||||
|
||||
<a id="#to_workspace_path"></a>
|
||||
|
||||
## to_workspace_path
|
||||
|
||||
<pre>
|
||||
to_workspace_path(<a href="#to_workspace_path-ctx">ctx</a>, <a href="#to_workspace_path-file">file</a>)
|
||||
</pre>
|
||||
|
||||
The workspace relative path for a file
|
||||
|
||||
This is the full runfiles path of a file excluding its workspace name.
|
||||
This differs from root path and manifest path as it does not include the
|
||||
repository name if the file is from an external repository.
|
||||
|
||||
|
||||
**PARAMETERS**
|
||||
|
||||
|
||||
| Name | Description | Default Value |
|
||||
| :------------- | :------------- | :------------- |
|
||||
| <a id="to_workspace_path-ctx"></a>ctx | starlark rule execution context | none |
|
||||
| <a id="to_workspace_path-file"></a>file | a File object | none |
|
||||
|
||||
**RETURNS**
|
||||
|
||||
The workspace relative path for a file
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||
|
||||
# For stardoc to reference the files
|
||||
exports_files(glob(["*.bzl"]))
|
||||
exports_files(
|
||||
glob(["*.bzl"]),
|
||||
visibility = [
|
||||
"//docs:__pkg__",
|
||||
"//lib:__subpackages__",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package_content",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# 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.
|
||||
|
||||
# LOCAL MODIFICATIONS
|
||||
# this has two PRs patched in on top of the original
|
||||
# https://github.com/bazelbuild/bazel-skylib/blob/7b859037a673db6f606661323e74c5d4751595e6/rules/private/copy_file_private.bzl
|
||||
# 1) https://github.com/bazelbuild/bazel-skylib/pull/323
|
||||
# 2) https://github.com/bazelbuild/bazel-skylib/pull/324
|
||||
|
||||
"""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(
|
||||
"//lib/private:copy_file.bzl",
|
||||
_copy_file = "copy_file",
|
||||
)
|
||||
|
||||
copy_file = _copy_file
|
|
@ -0,0 +1,25 @@
|
|||
"Public API for copy_to_directory"
|
||||
|
||||
load(
|
||||
"//lib/private:copy_to_directory.bzl",
|
||||
lib = "copy_to_directory_lib",
|
||||
)
|
||||
|
||||
_copy_to_directory = rule(
|
||||
implementation = lib.impl,
|
||||
provides = lib.provides,
|
||||
attrs = lib.attrs,
|
||||
)
|
||||
|
||||
def copy_to_directory(name, root_paths = None, **kwargs):
|
||||
if root_paths == None:
|
||||
root_paths = [native.package_name()]
|
||||
_copy_to_directory(
|
||||
name = name,
|
||||
root_paths = root_paths,
|
||||
is_windows = select({
|
||||
"@bazel_tools//src/conditions:host_windows": True,
|
||||
"//conditions:default": False,
|
||||
}),
|
||||
**kwargs
|
||||
)
|
|
@ -4,10 +4,11 @@ load("//lib/private:paths.bzl", "paths")
|
|||
|
||||
relative_file = paths.relative_file
|
||||
to_manifest_path = paths.to_manifest_path
|
||||
to_workspace_path = paths.to_workspace_path
|
||||
|
||||
# Bash helper function for looking up runfiles.
|
||||
# See windows_utils.bzl for the cmd.exe equivalent.
|
||||
# Vendored from
|
||||
# Vendored from
|
||||
# https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash
|
||||
BASH_RLOCATION_FUNCTION = r"""
|
||||
# --- begin runfiles.bash initialization v2 ---
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||
|
||||
exports_files(
|
||||
glob(["*.bzl"]),
|
||||
visibility = ["//docs:__pkg__"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package_content",
|
||||
srcs = glob([
|
||||
|
@ -9,6 +14,16 @@ filegroup(
|
|||
visibility = ["//lib:__pkg__"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "copy_to_directory",
|
||||
srcs = ["copy_to_directory.bzl"],
|
||||
visibility = ["//docs:__pkg__"],
|
||||
deps = [
|
||||
":paths",
|
||||
"@bazel_skylib//lib:paths",
|
||||
],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "params_file",
|
||||
srcs = ["params_file.bzl"],
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
# 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.
|
||||
|
||||
# LOCAL MODIFICATIONS
|
||||
# this has two PRs patched in on top of the original
|
||||
# https://github.com/bazelbuild/bazel-skylib/blob/7b859037a673db6f606661323e74c5d4751595e6/rules/private/copy_file_private.bzl
|
||||
# 1) https://github.com/bazelbuild/bazel-skylib/pull/323
|
||||
# 2) https://github.com/bazelbuild/bazel-skylib/pull/324
|
||||
|
||||
"""Implementation of copy_file macro and underlying rules.
|
||||
|
||||
These rules copy a file or directory to another location using Bash (on Linux/macOS) or
|
||||
cmd.exe (on Windows). `_copy_xfile` marks the resulting file executable,
|
||||
`_copy_file` does not.
|
||||
"""
|
||||
|
||||
# Hints for Bazel spawn strategy
|
||||
_execution_requirements = {
|
||||
# Copying files is entirely IO-bound and there is no point doing this work remotely.
|
||||
# Also, remote-execution does not allow source directory inputs, see
|
||||
# https://github.com/bazelbuild/bazel/commit/c64421bc35214f0414e4f4226cc953e8c55fa0d2
|
||||
# So we must not attempt to execute remotely in that case.
|
||||
"no-remote-exec": "1",
|
||||
}
|
||||
|
||||
def _hash_file(file):
|
||||
return str(hash(file.path))
|
||||
|
||||
# buildifier: disable=function-docstring
|
||||
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.
|
||||
# Put a hash of the file name into the name of the generated batch file to
|
||||
# make it unique within the package, so that users can define multiple copy_file's.
|
||||
bat = ctx.actions.declare_file("%s-%s-cmd.bat" % (ctx.label.name, _hash_file(src)))
|
||||
|
||||
# Flags are documented at
|
||||
# https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/copy
|
||||
# https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/xcopy
|
||||
if dst.is_directory:
|
||||
cmd_tmpl = "@xcopy \"%s\" \"%s\\\" /V /E /H /Y /Q >NUL"
|
||||
mnemonic = "CopyDirectory"
|
||||
progress_message = "Copying directory"
|
||||
else:
|
||||
cmd_tmpl = "@copy /Y \"%s\" \"%s\" >NUL"
|
||||
mnemonic = "CopyFile"
|
||||
progress_message = "Copying file"
|
||||
|
||||
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 = cmd_tmpl % (
|
||||
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 = mnemonic,
|
||||
progress_message = progress_message,
|
||||
use_default_shell_env = True,
|
||||
execution_requirements = _execution_requirements,
|
||||
)
|
||||
|
||||
# buildifier: disable=function-docstring
|
||||
def copy_bash(ctx, src, dst):
|
||||
if dst.is_directory:
|
||||
cmd_tmpl = "rm -rf \"$2\" && cp -rf \"$1/\" \"$2\""
|
||||
mnemonic = "CopyDirectory"
|
||||
progress_message = "Copying directory"
|
||||
else:
|
||||
cmd_tmpl = "cp -f \"$1\" \"$2\""
|
||||
mnemonic = "CopyFile"
|
||||
progress_message = "Copying file"
|
||||
|
||||
ctx.actions.run_shell(
|
||||
tools = [src],
|
||||
outputs = [dst],
|
||||
command = cmd_tmpl,
|
||||
arguments = [src.path, dst.path],
|
||||
mnemonic = mnemonic,
|
||||
progress_message = progress_message,
|
||||
use_default_shell_env = True,
|
||||
execution_requirements = _execution_requirements,
|
||||
)
|
||||
|
||||
def _copy_file_impl(ctx):
|
||||
# When creating a directory, declare that to Bazel so downstream rules
|
||||
# see it as a TreeArtifact and handle correctly, e.g. for remote execution
|
||||
if getattr(ctx.attr, "is_directory", False):
|
||||
output = ctx.actions.declare_directory(ctx.attr.out)
|
||||
else:
|
||||
output = ctx.outputs.out
|
||||
if ctx.attr.allow_symlink:
|
||||
if output.is_directory:
|
||||
fail("Cannot use both is_directory and allow_symlink")
|
||||
ctx.actions.symlink(
|
||||
output = output,
|
||||
target_file = ctx.file.src,
|
||||
is_executable = ctx.attr.is_executable,
|
||||
)
|
||||
elif ctx.attr.is_windows:
|
||||
copy_cmd(ctx, ctx.file.src, output)
|
||||
else:
|
||||
copy_bash(ctx, ctx.file.src, output)
|
||||
|
||||
files = depset(direct = [output])
|
||||
runfiles = ctx.runfiles(files = [output])
|
||||
if ctx.attr.is_executable:
|
||||
return [DefaultInfo(files = files, runfiles = runfiles, executable = output)]
|
||||
else:
|
||||
return [DefaultInfo(files = files, runfiles = runfiles)]
|
||||
|
||||
_ATTRS = {
|
||||
"src": attr.label(mandatory = True, allow_single_file = True),
|
||||
"is_windows": attr.bool(mandatory = True),
|
||||
"is_executable": attr.bool(mandatory = True),
|
||||
"allow_symlink": attr.bool(mandatory = True),
|
||||
}
|
||||
|
||||
_copy_directory = rule(
|
||||
implementation = _copy_file_impl,
|
||||
provides = [DefaultInfo],
|
||||
attrs = dict(_ATTRS, **{
|
||||
"is_directory": attr.bool(default = True),
|
||||
# Cannot declare out as an output here, because there's no API for declaring
|
||||
# TreeArtifact outputs.
|
||||
"out": attr.string(mandatory = True),
|
||||
}),
|
||||
)
|
||||
|
||||
_copy_file = rule(
|
||||
implementation = _copy_file_impl,
|
||||
provides = [DefaultInfo],
|
||||
attrs = dict(_ATTRS, **{
|
||||
"out": attr.output(mandatory = True),
|
||||
}),
|
||||
)
|
||||
|
||||
_copy_xfile = rule(
|
||||
implementation = _copy_file_impl,
|
||||
executable = True,
|
||||
provides = [DefaultInfo],
|
||||
attrs = dict(_ATTRS, **{
|
||||
"out": attr.output(mandatory = True),
|
||||
}),
|
||||
)
|
||||
|
||||
def copy_file(name, src, out, is_directory = False, is_executable = False, allow_symlink = False, **kwargs):
|
||||
"""Copies a file or directory 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).
|
||||
|
||||
If using this rule with source directories, it is recommended that you use the
|
||||
`--host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1` startup option so that changes
|
||||
to files within source directories are detected. See
|
||||
https://github.com/bazelbuild/bazel/commit/c64421bc35214f0414e4f4226cc953e8c55fa0d2
|
||||
for more context.
|
||||
|
||||
Args:
|
||||
name: Name of the rule.
|
||||
src: A Label. The file or directory to make a copy of.
|
||||
(Can also be the label of a rule that generates a file or directory.)
|
||||
out: Path of the output file, relative to this package.
|
||||
is_directory: treat the source file as a directory
|
||||
Workaround for https://github.com/bazelbuild/bazel/issues/12954
|
||||
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
|
||||
elif is_directory:
|
||||
copy_file_impl = _copy_directory
|
||||
|
||||
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
|
||||
)
|
|
@ -0,0 +1,149 @@
|
|||
"Copy files and directories to an output directory"
|
||||
|
||||
load("@bazel_skylib//lib:paths.bzl", skylib_paths = "paths")
|
||||
load(":paths.bzl", "paths")
|
||||
|
||||
_DOC = """Copies files and directories to an output directory.
|
||||
|
||||
Files and directories can be arranged as needed in the output directory using
|
||||
the `root_paths` and `replace_prefixes` attributes.
|
||||
|
||||
NB: This rule is not yet implemented for Windows
|
||||
"""
|
||||
|
||||
_copy_to_directory_attr = {
|
||||
"srcs": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = """Files and/or directories to copy into the output directory""",
|
||||
),
|
||||
"root_paths": attr.string_list(
|
||||
default = [],
|
||||
doc = """
|
||||
List of paths that are roots in the output directory. If a file or directory
|
||||
being copied is in one of the listed paths or one of its subpaths, the output
|
||||
directory path is the path relative to the root path instead of the path
|
||||
relative to the file's workspace.
|
||||
|
||||
Forward slashes (`/`) should be used as path separators. Partial matches
|
||||
on the final path segment of a root path against the corresponding segment
|
||||
in the full workspace relative path of a file are not matched.
|
||||
|
||||
If there are multiple root paths that match, the longest match wins.
|
||||
|
||||
Defaults to [package_name()] so that the output directory path of files in the
|
||||
target's package and and sub-packages are relative to the target's package and
|
||||
files outside of that retain their full workspace relative paths.
|
||||
""",
|
||||
),
|
||||
"replace_prefixes": attr.string_dict(
|
||||
default = {},
|
||||
doc = """
|
||||
Map of paths prefixes to replace in the output directory path when copying files.
|
||||
|
||||
If the output directory path for a file or directory starts with or is equal to
|
||||
a key in the dict then the matching portion of the output directory path is
|
||||
replaced with the dict value for that key.
|
||||
|
||||
Forward slashes (`/`) should be used as path separators. The final path segment
|
||||
of the key can be a partial match in the corresponding segment of the output
|
||||
directory path.
|
||||
|
||||
If there are multiple keys that match, the longest match wins.
|
||||
""",
|
||||
),
|
||||
"is_windows": attr.bool(mandatory = True),
|
||||
}
|
||||
|
||||
# Hints for Bazel spawn strategy
|
||||
_execution_requirements = {
|
||||
# Copying files is entirely IO-bound and there is no point doing this work
|
||||
# remotely. Also, remote-execution does not allow source directory inputs,
|
||||
# see
|
||||
# https://github.com/bazelbuild/bazel/commit/c64421bc35214f0414e4f4226cc953e8c55fa0d2
|
||||
# So we must not attempt to execute remotely in that case.
|
||||
"no-remote-exec": "1",
|
||||
}
|
||||
|
||||
def _longest_match(subject, tests, allow_partial = False):
|
||||
match = None
|
||||
high_score = 0
|
||||
for test in tests:
|
||||
starts_with_test = test if allow_partial else test + "/"
|
||||
if subject == test or subject.startswith(starts_with_test):
|
||||
score = len(test)
|
||||
if score > high_score:
|
||||
match = test
|
||||
high_score = score
|
||||
return match
|
||||
|
||||
def _output_path(ctx, src):
|
||||
result = paths.to_workspace_path(ctx, src)
|
||||
|
||||
# strip root paths
|
||||
root_path = _longest_match(result, ctx.attr.root_paths)
|
||||
if root_path:
|
||||
strip_depth = len(root_path.split("/"))
|
||||
result = "/".join(result.split("/")[strip_depth:])
|
||||
|
||||
# apply a replacement if one is found
|
||||
match = _longest_match(result, ctx.attr.replace_prefixes.keys(), True)
|
||||
if match:
|
||||
result = ctx.attr.replace_prefixes[match] + result[len(match):]
|
||||
return result
|
||||
|
||||
def _copy_to_dir_bash(ctx, srcs, dst_dir):
|
||||
cmds = [
|
||||
"set -o errexit -o nounset -o pipefail",
|
||||
"mkdir -p \"%s\"" % dst_dir.path,
|
||||
]
|
||||
for src in srcs:
|
||||
output_path = _output_path(ctx, src)
|
||||
dst_path = skylib_paths.normalize("/".join([dst_dir.path, output_path]))
|
||||
cmds.append("""
|
||||
if [[ ! -e "{src}" ]]; then echo "file '{src}' does not exist"; exit 1; fi
|
||||
if [[ -f "{src}" ]]; then
|
||||
mkdir -p "{dst_dir}"
|
||||
cp -f "{src}" "{dst}"
|
||||
else
|
||||
mkdir -p "{dst}"
|
||||
cp -rf "{src}"/* "{dst}"
|
||||
fi
|
||||
""".format(src = src.path, dst_dir = skylib_paths.dirname(dst_path), dst = dst_path))
|
||||
|
||||
ctx.actions.run_shell(
|
||||
inputs = srcs,
|
||||
outputs = [dst_dir],
|
||||
command = "\n".join(cmds),
|
||||
mnemonic = "CopyToDirectory",
|
||||
progress_message = "Copying files to directory",
|
||||
use_default_shell_env = True,
|
||||
execution_requirements = _execution_requirements,
|
||||
)
|
||||
|
||||
def _copy_to_directory_impl(ctx):
|
||||
if not ctx.files.srcs:
|
||||
msg = "srcs must not be empty in copy_to_directory %s" % ctx.label
|
||||
fail(msg)
|
||||
output = ctx.actions.declare_directory(ctx.attr.name)
|
||||
if ctx.attr.is_windows:
|
||||
# TODO: Windows implementation
|
||||
fail("not yet implemented")
|
||||
else:
|
||||
_copy_to_dir_bash(ctx, ctx.files.srcs, output)
|
||||
return [
|
||||
DefaultInfo(files = depset([output])),
|
||||
]
|
||||
|
||||
copy_to_directory_lib = struct(
|
||||
attrs = _copy_to_directory_attr,
|
||||
impl = _copy_to_directory_impl,
|
||||
provides = [DefaultInfo],
|
||||
)
|
||||
|
||||
# For stardoc to generate documentation for the rule rather than a wrapper macro
|
||||
copy_to_directory = rule(
|
||||
doc = _DOC,
|
||||
implementation = copy_to_directory_lib.impl,
|
||||
attrs = copy_to_directory_lib.attrs,
|
||||
provides = copy_to_directory_lib.provides,
|
||||
)
|
|
@ -43,17 +43,21 @@ def _relative_file(to_file, frm_file):
|
|||
)
|
||||
|
||||
def _to_manifest_path(ctx, file):
|
||||
"""The runfiles manifest entry for a file
|
||||
"""The runfiles manifest entry path for a file
|
||||
|
||||
This is the full runfiles path of a file including its workspace name as
|
||||
the first segment. We refert to it as the manifest path as it is the path
|
||||
flavor that is used for in the runfiles MANIFEST file.
|
||||
|
||||
We must avoid using non-normalized paths (workspace/../other_workspace/path)
|
||||
in order to locate entries by their key.
|
||||
|
||||
|
||||
Args:
|
||||
ctx: starlark rule execution context
|
||||
file: a File object
|
||||
|
||||
|
||||
Returns:
|
||||
a key that can lookup the path from the runfiles manifest
|
||||
The runfiles manifest entry path for a file
|
||||
"""
|
||||
|
||||
if file.short_path.startswith("../"):
|
||||
|
@ -61,7 +65,28 @@ def _to_manifest_path(ctx, file):
|
|||
else:
|
||||
return ctx.workspace_name + "/" + file.short_path
|
||||
|
||||
def _to_workspace_path(ctx, file):
|
||||
"""The workspace relative path for a file
|
||||
|
||||
This is the full runfiles path of a file excluding its workspace name.
|
||||
This differs from root path and manifest path as it does not include the
|
||||
repository name if the file is from an external repository.
|
||||
|
||||
Args:
|
||||
ctx: starlark rule execution context
|
||||
file: a File object
|
||||
|
||||
Returns:
|
||||
The workspace relative path for a file
|
||||
"""
|
||||
|
||||
if file.short_path.startswith("../"):
|
||||
return "/".join(file.short_path.split("/")[2:])
|
||||
else:
|
||||
return file.short_path
|
||||
|
||||
paths = struct(
|
||||
relative_file = _relative_file,
|
||||
to_manifest_path = _to_manifest_path,
|
||||
to_workspace_path = _to_workspace_path,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
"tests for copy_to_directory"
|
||||
|
||||
load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
|
||||
load("//lib:copy_file.bzl", "copy_file")
|
||||
load("//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
|
||||
[
|
||||
copy_file(
|
||||
name = "%s" % d,
|
||||
src = "dir_%s" % d,
|
||||
out = "%s" % d,
|
||||
is_directory = True,
|
||||
)
|
||||
for d in [
|
||||
"a",
|
||||
"b",
|
||||
"expected_1",
|
||||
"expected_2",
|
||||
"expected_3",
|
||||
"expected_4",
|
||||
"expected_5",
|
||||
]
|
||||
]
|
||||
|
||||
case_srcs = [
|
||||
":a",
|
||||
":b",
|
||||
":c",
|
||||
":d",
|
||||
":e/e",
|
||||
":e/e2",
|
||||
"//lib/tests/copy_to_directory/f/f2:f",
|
||||
"//lib/tests/copy_to_directory/f/f2:f2",
|
||||
"@external_test_repo//:test_a",
|
||||
"@external_test_repo//:test_b",
|
||||
"@external_test_repo//:test_c",
|
||||
"@external_test_repo//:test_d",
|
||||
]
|
||||
|
||||
# Case 1: default settings
|
||||
copy_to_directory(
|
||||
name = "case_1",
|
||||
srcs = case_srcs,
|
||||
)
|
||||
|
||||
diff_test(
|
||||
name = "case_1_test",
|
||||
file1 = "case_1",
|
||||
file2 = ":expected_1",
|
||||
)
|
||||
|
||||
# Case 2: replace_prefixes
|
||||
copy_to_directory(
|
||||
name = "case_2",
|
||||
srcs = case_srcs,
|
||||
replace_prefixes = {
|
||||
# merge a, b, c, d into a new/abcd dest folder
|
||||
"a": "new/abcd",
|
||||
"b": "//new///abcd////",
|
||||
"c": "new/abcd/c",
|
||||
"d": "////new/////abcd////d",
|
||||
# put e into new/e except for e/e2 which goes into new/e2
|
||||
"e": "new/e",
|
||||
"e/e2": "new/e2/e2",
|
||||
# f/f2/f => new/ff and f/f2/f2 => new/f2/f2
|
||||
"f/f2/": "////new/////f",
|
||||
"f/f2/f2": "////new/////f2///f2",
|
||||
# flatten test_a & test_b to the root
|
||||
"test_a": "",
|
||||
"test_b": "",
|
||||
# some paths that won't match
|
||||
"a/": "wont_match_a_is_terminal_path",
|
||||
"a/a2": "wont_match_since_a2_is_in_a_tree_artifact",
|
||||
},
|
||||
)
|
||||
|
||||
diff_test(
|
||||
name = "case_2_test",
|
||||
file1 = "case_2",
|
||||
file2 = ":expected_2",
|
||||
)
|
||||
|
||||
# Case 3: no root_paths
|
||||
copy_to_directory(
|
||||
name = "case_3",
|
||||
srcs = case_srcs,
|
||||
root_paths = [],
|
||||
)
|
||||
|
||||
diff_test(
|
||||
name = "case_3_test",
|
||||
file1 = "case_3",
|
||||
file2 = ":expected_3",
|
||||
)
|
||||
|
||||
# Case 4: no root_paths + replace_prefixes
|
||||
copy_to_directory(
|
||||
name = "case_4",
|
||||
srcs = case_srcs,
|
||||
replace_prefixes = {
|
||||
# strip lib/tests from paths
|
||||
"lib/tests/": "",
|
||||
# except for a few which should match due to longest match wins
|
||||
"lib/tests/copy_to_directory/a": "lib/other/copy_to_directory",
|
||||
"lib/tests/copy_to_directory/c": "lib/other/copy_to_directory/c",
|
||||
},
|
||||
root_paths = [],
|
||||
)
|
||||
|
||||
diff_test(
|
||||
name = "case_4_test",
|
||||
file1 = "case_4",
|
||||
file2 = ":expected_4",
|
||||
)
|
||||
|
||||
# Case 5: custom root packages
|
||||
copy_to_directory(
|
||||
name = "case_5",
|
||||
srcs = case_srcs,
|
||||
root_paths = [
|
||||
package_name(),
|
||||
"%s/e" % package_name(),
|
||||
"%s/f" % package_name(),
|
||||
],
|
||||
)
|
||||
|
||||
diff_test(
|
||||
name = "case_5_test",
|
||||
file1 = "case_5",
|
||||
file2 = ":expected_5",
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
|
@ -0,0 +1 @@
|
|||
foobar
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue