Skip to content

Commit

Permalink
Add symlink_file rule
Browse files Browse the repository at this point in the history
Updates #248
  • Loading branch information
Yannic committed May 29, 2020
1 parent 560d7b2 commit ce5780a
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 5 deletions.
7 changes: 7 additions & 0 deletions docs/BUILD
Expand Up @@ -101,6 +101,13 @@ stardoc(
deps = ["//rules:copy_file"],
)

stardoc(
name = "symlink_file_docs",
out = "symlink_file_doc_gen.md",
input = "//rules:symlink_file.bzl",
deps = ["//rules:symlink_file"],
)

stardoc(
name = "write_file_docs",
out = "write_file_doc_gen.md",
Expand Down
7 changes: 7 additions & 0 deletions docs/copy_file_doc.md
@@ -1,3 +1,7 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->

<a name="#copy_file"></a>

## copy_file

<pre>
Expand All @@ -10,6 +14,9 @@ Copies a file to another location.

This rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command on Windows (no Bash is required).

NOTE: Copying a file is a potentially expensive operation. Prefer
`symlink_file` unless you really need the file to be a copy.


### Parameters

Expand Down
82 changes: 82 additions & 0 deletions docs/symlink_file_doc.md
@@ -0,0 +1,82 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->

<a name="#symlink_file"></a>

## symlink_file

<pre>
symlink_file(<a href="#symlink_file-name">name</a>, <a href="#symlink_file-src">src</a>, <a href="#symlink_file-out">out</a>, <a href="#symlink_file-is_executable">is_executable</a>, <a href="#symlink_file-kwargs">kwargs</a>)
</pre>

Creates a symlink to another file.

`native.genrule()` is sometimes used to create symlinks (often wishing to
rename files). The `symlink_file` rule does this with a simpler interface
than genrule.

WARNING: This rule may copy the file if symlinking is not supported by the
underlaying filesystem (e.g. on Windows). Do not rely on the output actually
beeing a symlink!


### Parameters

<table class="params-table">
<colgroup>
<col class="col-param" />
<col class="col-description" />
</colgroup>
<tbody>
<tr id="symlink_file-name">
<td><code>name</code></td>
<td>
required.
<p>
Name of the rule.
</p>
</td>
</tr>
<tr id="symlink_file-src">
<td><code>src</code></td>
<td>
required.
<p>
A Label. The target of the symlink (i.e. the file the symlink will
point to). Can also be the label of a rule that generates a file.
</p>
</td>
</tr>
<tr id="symlink_file-out">
<td><code>out</code></td>
<td>
required.
<p>
Path of the output file, relative to this package.
</p>
</td>
</tr>
<tr id="symlink_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.
If this is true, Bazel will verify that `src` is also executable.
</p>
</td>
</tr>
<tr id="symlink_file-kwargs">
<td><code>kwargs</code></td>
<td>
optional.
<p>
further keyword arguments, e.g. `visibility`
</p>
</td>
</tr>
</tbody>
</table>


6 changes: 6 additions & 0 deletions rules/BUILD
Expand Up @@ -16,6 +16,12 @@ bzl_library(
deps = ["//rules/private:copy_file_private"],
)

bzl_library(
name = "symlink_file",
srcs = ["symlink_file.bzl"],
deps = ["//rules/private:copy_file_private"],
)

bzl_library(
name = "write_file",
srcs = ["write_file.bzl"],
Expand Down
3 changes: 3 additions & 0 deletions rules/copy_file.bzl
Expand Up @@ -19,6 +19,9 @@ 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).
NOTE: Copying a file is a potentially expensive operation. Prefer `symlink_file`
unless you really need the file to be a copy.
"""

load(
Expand Down
70 changes: 65 additions & 5 deletions rules/private/copy_file_private.bzl
Expand Up @@ -59,10 +59,17 @@ def copy_bash(ctx, src, dst):
)

def _common_impl(ctx, is_executable):
if ctx.attr.is_windows:
copy_cmd(ctx, ctx.file.src, ctx.outputs.out)
if ctx.attr.is_symlink:
ctx.actions.symlink(
output = ctx.outputs.out,
target_file = ctx.file.src,
is_executable = 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])
Expand All @@ -78,9 +85,10 @@ def _ximpl(ctx):
return _common_impl(ctx, True)

_ATTRS = {
"src": attr.label(mandatory = True, allow_single_file = True),
"out": attr.output(mandatory = True),
"is_symlink": attr.bool(mandatory = True),
"is_windows": attr.bool(mandatory = True),
"out": attr.output(mandatory = True),
"src": attr.label(mandatory = True, allow_single_file = True),
}

_copy_file = rule(
Expand All @@ -103,6 +111,9 @@ def copy_file(name, src, out, is_executable = False, **kwargs):
This rule uses a Bash command on Linux/macOS/non-Windows, and a cmd.exe command on Windows (no Bash is required).
NOTE: Copying a file is a potentially expensive operation. Prefer
`symlink_file` unless you really need the file to be a copy.
Args:
name: Name of the rule.
src: A Label. The file to make a copy of. (Can also be the label of a rule
Expand All @@ -118,6 +129,54 @@ def copy_file(name, src, out, is_executable = False, **kwargs):
name = name,
src = src,
out = out,
is_symlink = False,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
}),
**kwargs
)
else:
_copy_file(
name = name,
src = src,
out = out,
is_symlink = False,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
}),
**kwargs
)

def symlink_file(name, src, out, is_executable = False, **kwargs):
"""Creates a symlink to another file.
`native.genrule()` is sometimes used to create symlinks (often wishing to
rename files). The `symlink_file` rule does this with a simpler interface
than genrule.
WARNING: This rule may copy the file if symlinking is not supported by the
underlaying filesystem (e.g. on Windows). Do not rely on the output actually
beeing a symlink!
Args:
name: Name of the rule.
src: A Label. The target of the symlink (i.e. the file the symlink will
point to). 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.
If this is true, Bazel will verify that `src` is also executable.
**kwargs: further keyword arguments, e.g. `visibility`
"""
if is_executable:
_copy_xfile(
name = name,
src = src,
out = out,
is_symlink = True,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
Expand All @@ -129,6 +188,7 @@ def copy_file(name, src, out, is_executable = False, **kwargs):
name = name,
src = src,
out = out,
is_symlink = True,
is_windows = select({
"@bazel_tools//src/conditions:host_windows": True,
"//conditions:default": False,
Expand Down
31 changes: 31 additions & 0 deletions rules/symlink_file.bzl
@@ -0,0 +1,31 @@
# 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.

"""A rule that creates a symlink to another file.
`native.genrule()` is sometimes used to create symlinks (often wishing to
rename files). The `symlink_file` rule does this with a simpler interface
than genrule.
WARNING: This rule may copy the file if symlinking is not supported by the
underlaying filesystem (e.g. on Windows). Do not rely on the output actually
beeing a symlink!
"""

load(
"//rules/private:copy_file_private.bzl",
_symlink_file = "symlink_file",
)

symlink_file = _symlink_file
122 changes: 122 additions & 0 deletions tests/symlink_file/BUILD
@@ -0,0 +1,122 @@
# This package aids testing the 'symlink_file' rule.
#
# The package contains 4 symlink_file rules:
# - 'symlink_src' and 'symlink_gen' copy a source file and a generated file
# respectively
# - 'symlink_xsrc' and 'symlink_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
# 'symlink_xsrc' and 'symlink_xgen' rules respectively. The sh_binary rule
# requires its source to be executable, so building these two rules successfully
# means that 'symlink_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 'symlink_file_tests' test.
#
# The 'file_deps' filegroup depends on 'symlink_src'. The filegroup rule uses
# the DefaultInfo.files field from its dependencies. When we data-depend on the
# filegroup from 'symlink_file_tests', we transitively data-depend on the
# DefaultInfo.files of the 'symlink_src' rule.
#
# The 'symlink_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 'symlink_file' rule, and thereby assert
# that that field contains the output file of the rule
# - the 'symlink_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:symlink_file.bzl", "symlink_file")

licenses(["notice"])

package(default_testonly = 1)

sh_test(
name = "symlink_file_tests",
srcs = ["symlink_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",
"//tests:unittest.bash",
],
deps = ["@bazel_tools//tools/bash/runfiles"],
)

filegroup(
name = "file_deps",
# Use DefaultInfo.files from 'copy_src'.
srcs = [
":copy_src",
],
)

# If 'run_executables' is built, then 'bin_gen' and 'bin_src' are
# executable, asserting that symlink_file makes the output executable.
genrule(
name = "run_executables",
outs = [
"xsrc-out.txt",
"xgen-out.txt",
],
cmd = ("$(location :bin_src) > $(location xsrc-out.txt) && " +
"$(location :bin_gen) > $(location xgen-out.txt)"),
output_to_bindir = 1,
tools = [
":bin_gen",
":bin_src",
],
)

# If 'bin_src' is built, then 'copy_xsrc' made its output executable.
sh_binary(
name = "bin_src",
srcs = [":copy_xsrc"],
)

# If 'bin_gen' is built, then 'copy_xgen' made its output executable.
sh_binary(
name = "bin_gen",
srcs = [":copy_xgen"],
)

symlink_file(
name = "copy_src",
src = "a.txt",
out = "out/a-out.txt",
)

symlink_file(
name = "copy_gen",
src = ":gen",
out = "out/gen-out.txt",
)

symlink_file(
name = "copy_xsrc",
src = "a.txt",
out = "xout/a-out.sh",
is_executable = True,
)

symlink_file(
name = "copy_xgen",
src = ":gen",
out = "xout/gen-out.sh",
is_executable = True,
)

genrule(
name = "gen",
outs = ["b.txt"],
cmd = "echo -e '#!/bin/bash\necho potato' > $@",
)

0 comments on commit ce5780a

Please sign in to comment.