blob: bab9b46c56057154f3ab5ae50843133da5a47c94 [file] [log] [blame]
#!/bin/bash
#
# 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.
#
# Tests Starlark API pertaining to action inspection via aspect.
# --- begin runfiles.bash initialization ---
# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
set -euo pipefail
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
"$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
else
echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
exit 1
fi
# --- end runfiles.bash initialization ---
source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
|| { echo "integration_test_setup.sh not found!" >&2; exit 1; }
case "$(uname -s | tr [:upper:] [:lower:])" in
msys*|mingw*|cygwin*)
declare -r is_windows=true
;;
*)
declare -r is_windows=false
;;
esac
if "$is_windows"; then
export MSYS_NO_PATHCONV=1
export MSYS2_ARG_CONV_EXCL="*"
fi
add_to_bazelrc "build --package_path=%workspace%"
function test_directory_args_inspection() {
mkdir -p package
cat > package/makes_tree_artifacts.sh <<EOF
#!/bin/bash
my_dir=\$1
touch \$my_dir/a.txt
touch \$my_dir/b.txt
touch \$my_dir/c.txt
EOF
chmod 755 package/makes_tree_artifacts.sh
cat > package/write.sh <<EOF
#!/bin/bash
output_file=\$1
shift;
echo "\$@" > \$output_file
EOF
chmod 755 package/write.sh
cat > package/lib.bzl <<EOF
def _tree_art_impl(ctx):
my_dir = ctx.actions.declare_directory('dir')
ctx.actions.run(
executable = ctx.executable._makes_tree,
outputs = [my_dir],
arguments = [my_dir.path])
digest = ctx.actions.declare_file('digest')
digest_args = ctx.actions.args()
digest_args.add(digest.path)
digest_args.add_all([my_dir])
ctx.actions.run(executable = ctx.executable._write,
inputs = [my_dir],
outputs = [digest],
arguments = [digest_args])
return [DefaultInfo(files=depset([digest]))]
def _actions_test_impl(target, ctx):
action = target.actions[1] # digest action
aspect_out = ctx.actions.declare_file('aspect_out')
ctx.actions.run_shell(inputs = action.inputs,
outputs = [aspect_out],
command = "echo \$@ > " + aspect_out.path,
arguments = action.args)
return [OutputGroupInfo(out=[aspect_out])]
tree_art_rule = rule(implementation = _tree_art_impl,
attrs = {
"_makes_tree" : attr.label(allow_single_file = True,
cfg = "exec",
executable = True,
default = "//package:makes_tree_artifacts.sh"),
"_write" : attr.label(allow_single_file = True,
cfg = "exec",
executable = True,
default = "//package:write.sh")})
actions_test_aspect = aspect(implementation = _actions_test_impl)
EOF
cat > package/BUILD <<EOF
load(":lib.bzl", "tree_art_rule")
tree_art_rule(name = "x")
EOF
bazel build package:x || fail "Unexpected build failure"
cat "${PRODUCT_NAME}-bin/package/digest" | grep "a.txt.*b.txt.*c.txt" \
|| fail "rule digest does not contain tree artifact args"
bazel build package:x --aspects=//package:lib.bzl%actions_test_aspect \
--output_groups=out
cat "${PRODUCT_NAME}-bin/package/aspect_out" | grep "a.txt.*b.txt.*c.txt" \
|| fail "aspect Args do not contain tree artifact args"
}
function test_directory_args_inspection_param_file() {
mkdir -p package
cat > package/makes_tree_artifacts.sh <<EOF
#!/bin/bash
my_dir=\$1
touch \$my_dir/a.txt
touch \$my_dir/b.txt
touch \$my_dir/c.txt
EOF
chmod 755 package/makes_tree_artifacts.sh
cat > package/write.sh <<EOF
#!/bin/bash
param_file=\$1
shift;
output_file="\$(head -n 1 \$param_file)"
tail \$param_file -n +2 | tr '\n' ' ' > "\$output_file"
EOF
chmod 755 package/write.sh
cat > package/lib.bzl <<EOF
def _tree_art_impl(ctx):
my_dir = ctx.actions.declare_directory('dir')
ctx.actions.run(
executable = ctx.executable._makes_tree,
outputs = [my_dir],
arguments = [my_dir.path])
digest = ctx.actions.declare_file('digest')
digest_args = ctx.actions.args()
digest_args.add(digest.path)
digest_args.add_all([my_dir])
digest_args.use_param_file("%s", use_always=True)
ctx.actions.run(executable = ctx.executable._write,
inputs = [my_dir],
outputs = [digest],
arguments = [digest_args])
return [DefaultInfo(files=depset([digest]))]
def _actions_test_impl(target, ctx):
action = target.actions[1] # digest action
raw_args_out = ctx.actions.declare_file('raw_args_out')
param_file_out = ctx.actions.declare_file('param_file_out')
ctx.actions.run_shell(inputs = action.inputs,
outputs = [raw_args_out],
command = "echo \$@ > " + raw_args_out.path,
arguments = action.args)
ctx.actions.run_shell(inputs = action.inputs,
outputs = [param_file_out],
command = "cat \$2 > " + param_file_out.path,
arguments = action.args)
return [OutputGroupInfo(out=[raw_args_out, param_file_out])]
tree_art_rule = rule(implementation = _tree_art_impl,
attrs = {
"_makes_tree" : attr.label(allow_single_file = True,
cfg = "exec",
executable = True,
default = "//package:makes_tree_artifacts.sh"),
"_write" : attr.label(allow_single_file = True,
cfg = "exec",
executable = True,
default = "//package:write.sh")})
actions_test_aspect = aspect(implementation = _actions_test_impl)
EOF
cat > package/BUILD <<EOF
load(":lib.bzl", "tree_art_rule")
tree_art_rule(name = "x")
EOF
bazel build package:x || fail "Unexpected build failure"
cat "${PRODUCT_NAME}-bin/package/digest" | grep "a.txt.*b.txt.*c.txt" \
|| fail "rule digest does not contain tree artifact args"
bazel build package:x --aspects=//package:lib.bzl%actions_test_aspect \
--output_groups=out
cat "${PRODUCT_NAME}-bin/package/raw_args_out" | grep ".params" \
|| fail "aspect Args does not contain a params file"
cat "${PRODUCT_NAME}-bin/package/param_file_out" | tr '\n' ' ' \
| grep "a.txt.*b.txt.*c.txt" \
|| fail "aspect params file does not contain tree artifact args"
}
# Test that a starlark action shadowing another action is not rerun after blaze
# shutdown.
function test_starlark_action_with_shadowed_action_not_rerun_after_shutdown() {
local package="a1"
create_starlark_action_with_shadowed_action_cache_test_files "${package}"
bazel build "${package}:a" \
--aspects="//${package}:lib.bzl%actions_test_aspect" \
--output_groups=out || \
fail "bazel build should've succeeded"
cp "bazel-bin/${package}/run_timestamp" "${package}/run_1_timestamp"
# Test that the Starlark action is not rerun after bazel shutdown if
# the inputs did not change
bazel shutdown
bazel build "${package}:a" \
--aspects="//${package}:lib.bzl%actions_test_aspect" \
--output_groups=out || \
fail "bazel build should've succeeded"
cp "bazel-bin/${package}/run_timestamp" "${package}/run_2_timestamp"
diff "${package}/run_1_timestamp" "${package}/run_2_timestamp" \
|| fail "Starlark action should not rerun after bazel shutdown"
}
# Test that a starlark action shadowing another action is rerun if the inputs
# of the shadowed_action change.
function test_starlark_action_rerun_after_shadowed_action_inputs_change() {
local package="a2"
create_starlark_action_with_shadowed_action_cache_test_files "${package}"
bazel build "${package}:a" \
--aspects="//${package}:lib.bzl%actions_test_aspect" \
--output_groups=out || \
fail "bazel build should've succeeded"
cp "bazel-bin/${package}/run_timestamp" "${package}/run_1_timestamp"
# Test that the Starlark action would rerun if the inputs of the
# shadowed action changed
rm -f "${package}/x.h"
echo "inline int x() { return 0; }" > "${package}/x.h"
bazel build "${package}:a" \
--aspects="//${package}:lib.bzl%actions_test_aspect" \
--output_groups=out || \
fail "bazel build should've succeeded"
cp "bazel-bin/${package}/run_timestamp" "${package}/run_2_timestamp"
diff "${package}/run_1_timestamp" "${package}/run_2_timestamp" \
&& fail "Starlark action should rerun after shadowed action inputs change" \
|| :
}
function create_starlark_action_with_shadowed_action_cache_test_files() {
local package="$1"
mkdir -p "${package}"
cat > "${package}/lib.bzl" <<EOF
def _actions_test_impl(target, ctx):
compile_action = None
for action in target.actions:
if action.mnemonic == "CppCompile":
compile_action = action
if not compile_action:
fail("Couldn't find compile action")
aspect_out = ctx.actions.declare_file("run_timestamp")
ctx.actions.run_shell(
shadowed_action = compile_action,
mnemonic = "AspectAction",
outputs = [aspect_out],
# record the timestamp in output file to validate action rerun
command = "cat ${package}/x.h > %s && date '+%%s' >> %s" % (
aspect_out.path,
aspect_out.path,
),
)
return [OutputGroupInfo(out = [aspect_out])]
actions_test_aspect = aspect(implementation = _actions_test_impl)
EOF
echo "inline int x() { return 42; }" > "${package}/x.h"
cat > "${package}/a.cc" <<EOF
#include "${package}/x.h"
int a() { return x(); }
EOF
cat > "${package}/BUILD" <<EOF
cc_library(
name = "x",
hdrs = ["x.h"],
)
cc_library(
name = "a",
srcs = ["a.cc"],
deps = [":x"],
)
EOF
}
function test_aspect_requires_aspect_no_action_conflict() {
local package="test"
mkdir -p "${package}"
cat > "${package}/write.sh" <<EOF
#!/bin/bash
output_file=\$1
unused_file=\$2
echo 'output' > \$output_file
echo 'unused' > \$unused_file
EOF
chmod 755 "${package}/write.sh"
cat > "${package}/defs.bzl" <<EOF
def _aspect_b_impl(target, ctx):
file = ctx.actions.declare_file('{}_aspect_b_file.txt'.format(target.label.name))
unused_file = ctx.actions.declare_file('{}.unused'.format(target.label.name))
args = ctx.actions.args()
args.add(file.path)
args.add(unused_file.path)
ctx.actions.run(
executable = ctx.executable._write,
outputs = [file, unused_file],
arguments = [args],
# Adding unused_inputs_list just to make this action not shareable
unused_inputs_list = unused_file,
)
return [OutputGroupInfo(aspect_b_out = [file])]
aspect_b = aspect(
implementation = _aspect_b_impl,
attrs = {
"_write": attr.label(
allow_single_file = True,
cfg = "exec",
executable = True,
default = "//${package}:write.sh")
}
)
def _aspect_a_impl(target, ctx):
print(['aspect_a can see file ' +
f.path.split('/')[-1] for f in target[OutputGroupInfo].aspect_b_out.to_list()])
files = target[OutputGroupInfo].aspect_b_out.to_list()
return [OutputGroupInfo(aspect_a_out = files)]
aspect_a = aspect(
implementation = _aspect_a_impl,
requires = [aspect_b],
attr_aspects = ['dep_2'],
)
def _rule_1_impl(ctx):
files = []
if ctx.attr.dep_1:
files += ctx.attr.dep_1[OutputGroupInfo].rule_2_out.to_list()
if ctx.attr.dep_2:
files += ctx.attr.dep_2[OutputGroupInfo].aspect_a_out.to_list()
return [DefaultInfo(files=depset(files))]
rule_1 = rule(
implementation = _rule_1_impl,
attrs = {
'dep_1': attr.label(),
'dep_2': attr.label(aspects = [aspect_a]),
}
)
def _rule_2_impl(ctx):
files = []
if ctx.attr.dep:
print([ctx.label.name +
' can see file ' +
f.path.split('/')[-1] for f in ctx.attr.dep[OutputGroupInfo].aspect_b_out.to_list()])
files += ctx.attr.dep[OutputGroupInfo].aspect_b_out.to_list()
return [OutputGroupInfo(rule_2_out = files)]
rule_2 = rule(
implementation = _rule_2_impl,
attrs = {
'dep': attr.label(aspects = [aspect_b]),
}
)
EOF
cat > "${package}/BUILD" <<EOF
load('//${package}:defs.bzl', 'rule_1', 'rule_2')
exports_files(["write.sh"])
rule_1(
name = 'main_target',
dep_1 = ':dep_target_1',
dep_2 = ':dep_target_2',
)
rule_2(
name = 'dep_target_1',
dep = ':dep_target_2',
)
rule_2(name = 'dep_target_2')
EOF
bazel build "//${package}:main_target" &> $TEST_log || fail "Build failed"
expect_log "aspect_a can see file dep_target_2_aspect_b_file.txt"
expect_log "dep_target_1 can see file dep_target_2_aspect_b_file.txt"
}
run_suite "Tests Starlark API pertaining to action inspection via aspect"