From 8f3151fb4a91d5f2ae4cad5901ea72fe30a2aba0 Mon Sep 17 00:00:00 2001 From: Yannic Date: Fri, 10 Jul 2020 20:08:02 +0200 Subject: [PATCH] copy_file: Add parameter to allow symlinks (#252) * copy_file: Add parameter to allow symlinks This change adds a new parameter `allow_symlinks` to `copy_file` that allows the action to create a symlink instead of doing an expensive copy if the execution platform (host) allows it. Updates #248 * Update docs * Refactor `is_executable` into attribute * Fix typo * s/_impl/_copy_file_impl/ --- docs/copy_file_doc.md | 21 +++++++- rules/private/copy_file_private.bzl | 75 +++++++++++++++-------------- tests/copy_file/BUILD | 56 ++++++++++++++++++++- tests/copy_file/a_with_exec_bit.txt | 2 + tests/copy_file/copy_file_tests.sh | 22 +++++++++ 5 files changed, 138 insertions(+), 38 deletions(-) create mode 100755 tests/copy_file/a_with_exec_bit.txt diff --git a/docs/copy_file_doc.md b/docs/copy_file_doc.md index e2b6025..67148bc 100755 --- a/docs/copy_file_doc.md +++ b/docs/copy_file_doc.md @@ -1,7 +1,11 @@ + + + + ## copy_file
-copy_file(name, src, out, is_executable, kwargs)
+copy_file(name, src, out, is_executable, allow_symlink, kwargs)
 
Copies a file to another location. @@ -55,6 +59,21 @@ This rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command 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 + + optional. default is False +

+ 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).

diff --git a/rules/private/copy_file_private.bzl b/rules/private/copy_file_private.bzl index 7549585..75cb027 100644 --- a/rules/private/copy_file_private.bzl +++ b/rules/private/copy_file_private.bzl @@ -58,45 +58,48 @@ def copy_bash(ctx, src, dst): use_default_shell_env = True, ) -def _common_impl(ctx, is_executable): - if ctx.attr.is_windows: - copy_cmd(ctx, ctx.file.src, ctx.outputs.out) +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: - copy_bash(ctx, ctx.file.src, ctx.outputs.out) + 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 is_executable: + if ctx.attr.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, False) - -def _ximpl(ctx): - return _common_impl(ctx, True) - _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 = _impl, + implementation = _copy_file_impl, provides = [DefaultInfo], attrs = _ATTRS, ) _copy_xfile = rule( - implementation = _ximpl, + implementation = _copy_file_impl, executable = True, provides = [DefaultInfo], attrs = _ATTRS, ) -def copy_file(name, src, out, is_executable = False, **kwargs): +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. @@ -111,27 +114,29 @@ def copy_file(name, src, out, is_executable = False, **kwargs): 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_xfile( - name = name, - src = src, - out = out, - is_windows = select({ - "@bazel_tools//src/conditions:host_windows": True, - "//conditions:default": False, - }), - **kwargs - ) - else: - _copy_file( - name = name, - src = src, - out = out, - is_windows = select({ - "@bazel_tools//src/conditions:host_windows": True, - "//conditions:default": False, - }), - **kwargs - ) + 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/tests/copy_file/BUILD b/tests/copy_file/BUILD index 7c8a4c3..79ab7ba 100644 --- a/tests/copy_file/BUILD +++ b/tests/copy_file/BUILD @@ -46,6 +46,7 @@ sh_test( ":file_deps", # Use DefaultInfo.runfiles from 'copy_gen'. ":copy_gen", + ":copy_gen_symlink", "//tests:unittest.bash", ], deps = ["@bazel_tools//tools/bash/runfiles"], @@ -56,6 +57,7 @@ filegroup( # Use DefaultInfo.files from 'copy_src'. srcs = [ ":copy_src", + ":copy_src_symlink", ], ) @@ -64,15 +66,23 @@ filegroup( genrule( name = "run_executables", outs = [ + "xsrc-out-symlink.txt", + "xgen-out-symlink.txt", "xsrc-out.txt", "xgen-out.txt", ], - cmd = ("$(location :bin_src) > $(location xsrc-out.txt) && " + - "$(location :bin_gen) > $(location 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", ], ) @@ -82,22 +92,48 @@ sh_binary( 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( @@ -107,6 +143,14 @@ copy_file( 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", @@ -114,6 +158,14 @@ copy_file( 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"], 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 index 23f45d5..dfee635 100755 --- a/tests/copy_file/copy_file_tests.sh +++ b/tests/copy_file/copy_file_tests.sh @@ -44,20 +44,42 @@ function test_copy_src() { 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"