| #!/bin/bash |
| # |
| # 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. |
| # |
| # Tests ninja_build build rule. |
| |
| # Load the test setup defined in the parent directory |
| CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| source "${CURRENT_DIR}/../integration_test_setup.sh" \ |
| || { echo "integration_test_setup.sh not found!" >&2; exit 1; } |
| |
| function setup_basic_ninja_build() { |
| cat > WORKSPACE <<'EOF' |
| toplevel_output_directories(paths = ["out"]) |
| EOF |
| |
| mkdir -p test |
| cat > test/BUILD <<'EOF' |
| |
| ninja_graph( |
| name = "ninjagraph", |
| main = "build.ninja", |
| output_root = "out", |
| ) |
| |
| ninja_build( |
| name = "ninjabuild", |
| ninja_graph = "ninjagraph", |
| output_groups = { |
| "out" : ["out/test/output.txt"], |
| } |
| ) |
| EOF |
| |
| cat > test/build.ninja <<'EOF' |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/output.txt: cattool test/cattool.sh |
| EOF |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/{one,two} > $OUTPUT |
| |
| echo "$1 : test/one test/two" > $DEPFILE |
| EOF |
| chmod +x test/cattool.sh |
| printf "a" > test/one |
| echo "b" > test/two |
| |
| bazel clean |
| } |
| |
| # Test that the depfile specified in the ninja file is used to determine |
| # action inputs (and thus ninja actions are incrementally correct with |
| # respect to discovered dependencies). |
| function test_basic_depfile_processing() { |
| setup_basic_ninja_build |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| |
| printf "HELLO" > test/one |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "HELLOb" |
| } |
| |
| function test_null_build() { |
| setup_basic_ninja_build |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 2 processes: 1 internal, 1 local" |
| |
| # Verify null build with hot server. |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 1 process: 1 internal" |
| |
| bazel shutdown |
| |
| # Verify null build even after restart. |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 1 process: 1 internal" |
| } |
| |
| # Tests that newly discovered dependencies cause a rebuild after restart. |
| function test_rebuild_discovered_deps_after_restart() { |
| setup_basic_ninja_build |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| |
| bazel shutdown |
| printf "HELLO" > test/one |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "HELLOb" |
| } |
| |
| # Tests that newly discovered dependencies cause a rebuild after restart. |
| function test_depfile_existing_dependencies() { |
| setup_basic_ninja_build |
| |
| # Override ninja file to make test/one and test/two explicit dependencies. |
| cat > test/build.ninja <<'EOF' |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/output.txt: cattool test/cattool.sh test/one test/two |
| EOF |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| |
| printf "HELLO" > test/one |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "HELLOb" |
| } |
| |
| # Tests Bazel behaves appropriately when depfile output is malformed. |
| function test_depfile_junk() { |
| setup_basic_ninja_build |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/{one,two} > $OUTPUT |
| |
| echo "Haha this depfile is a bunch of garbage" > $DEPFILE |
| EOF |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| } |
| |
| # Tests Bazel behaves appropriately when depfiles contain invalid files. |
| function test_depfile_invalid_file() { |
| setup_basic_ninja_build |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/{one,two} > $OUTPUT |
| |
| echo "$1 : test/one test/two test/doesntexist" > $DEPFILE |
| EOF |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| } |
| |
| # Tests build when depfiles contain generated (declared) inputs. |
| function test_depfile_generated_inputs() { |
| setup_basic_ninja_build |
| |
| cat > test/build.ninja <<'EOF' |
| rule filecopy |
| command = cat ${in} > ${out} |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/generated: filecopy test/two |
| build out/test/output.txt: cattool test/cattool.sh out/test/generated |
| EOF |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one out/test/generated > $OUTPUT |
| |
| echo "$1 : test/one out/test/generated" > $DEPFILE |
| EOF |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| |
| printf "z" > test/two |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "az" |
| } |
| |
| # Tests error of build when depfiles contain generated (undeclared) inputs. |
| function test_depfile_undeclared_generated_inputs() { |
| setup_basic_ninja_build |
| |
| cat > test/build.ninja <<'EOF' |
| rule filecopy |
| command = cat ${in} > ${out} |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/generated: filecopy test/two |
| build out/test/output.txt: cattool test/cattool.sh test/one |
| EOF |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one out/test/generated > $OUTPUT |
| |
| echo "$1 : test/one out/test/generated" > $DEPFILE |
| EOF |
| |
| ! bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have failed" |
| |
| expect_log "depfile-declared dependency 'out/test/generated' is invalid" |
| } |
| |
| |
| # Tests Bazel behaves appropriately when depfiles aren't generated. |
| function test_depfile_not_generated() { |
| setup_basic_ninja_build |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| |
| cat test/{one,two} > $OUTPUT |
| EOF |
| |
| ! bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have failed" |
| |
| expect_log "out/test/depfile.d (No such file or directory)" |
| } |
| |
| function test_depfile_pruned_generated_input() { |
| setup_basic_ninja_build |
| |
| cat > test/build.ninja <<'EOF' |
| rule filecopy |
| command = cat ${in} > ${out} |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/generated: filecopy test/two |
| build out/test/output.txt: cattool test/cattool.sh test/one out/test/generated |
| EOF |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one > $OUTPUT |
| |
| echo "$1 : test/cattool.sh test/one" > $DEPFILE |
| EOF |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 3 processes: 1 internal, 2 local" |
| |
| # test/two is a dependency of out/test/generated, which was an originally |
| # declared input, but not according to the depfile. |
| |
| echo "z" > test/two |
| # Verify the root action is not run, as test/two is not an input to the action. |
| # Note that ideally this build would be a null build, as the root action |
| # should no longer depend on the filecopy action. However, Skyframe currently |
| # does not support action dependency pruning. Thus, the only savings is |
| # an action cache hit on the root action. |
| bazel build -s //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 2 processes: 1 internal, 1 local" |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one out/test/generated > $OUTPUT |
| |
| echo "$1 : test/cattool.sh test/one out/test/generated" > $DEPFILE |
| EOF |
| |
| # Build should re-execute, as cattool.sh has changed. |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| expect_log "INFO: 2 processes: 1 internal, 1 local" |
| |
| # test/two should again be reflected as an input to the build via |
| # the inclusion of out/test/generated in the depfile. |
| echo "x" > test/two |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have failed" |
| expect_log "INFO: 3 processes: 1 internal, 2 local" |
| } |
| |
| function test_external_source_dependency() { |
| setup_basic_ninja_build |
| |
| cat > BUILD <<'EOF' |
| ninja_graph( |
| name = "rootgraph", |
| main = "build.ninja", |
| output_root = "out", |
| ) |
| |
| ninja_build( |
| name = "rootbuild", |
| ninja_graph = "rootgraph", |
| output_groups = { |
| "out" : ["out/test/output.txt"], |
| } |
| ) |
| EOF |
| |
| cat > build.ninja <<'EOF' |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/output.txt: cattool test/cattool.sh test/one external/two |
| EOF |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one external/two > $OUTPUT |
| |
| echo "$1 : test/one external/two" > $DEPFILE |
| EOF |
| mkdir -p external |
| echo "b" > external/two |
| |
| bazel build //:rootbuild --experimental_sibling_repository_layout --experimental_disable_external_package --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have succeeded" |
| cat bazel-workspace/out/test/output.txt > $TEST_log |
| expect_log "ab" |
| |
| echo "z" > external/two |
| |
| bazel build //:rootbuild --experimental_sibling_repository_layout --experimental_disable_external_package --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have succeeded" |
| cat bazel-workspace/out/test/output.txt > $TEST_log |
| expect_log "az" |
| } |
| |
| # Tests a dependency on external package sources in the scenario where the |
| # ninja_build target has a dependency on //external:cc_toolchain. This is a |
| # regression test for a bug in which this dependency interfered with external |
| # source resolution. |
| function test_external_dependency_cctoolchain() { |
| setup_basic_ninja_build |
| |
| cat > BUILD <<'EOF' |
| ninja_graph( |
| name = "rootgraph", |
| main = "build.ninja", |
| output_root = "out", |
| ) |
| |
| genrule( |
| name = "g", |
| cmd = "echo 'c' > $@", |
| outs = ["generated.txt"], |
| # This ensures rootbuild depends on cc_toolchain transitively. |
| srcs = ["//external:cc_toolchain"], |
| ) |
| |
| ninja_build( |
| name = "rootbuild", |
| ninja_graph = "rootgraph", |
| output_groups = { |
| "out" : ["out/test/output.txt"], |
| }, |
| deps_mapping = { |
| "out/dummy" : ":g", |
| }, |
| ) |
| EOF |
| |
| cat > build.ninja <<'EOF' |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/output.txt: cattool test/cattool.sh test/one |
| EOF |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat test/one external/foo/two > $OUTPUT |
| |
| echo "$1 : test/one external/foo/two" > $DEPFILE |
| EOF |
| mkdir -p external/foo |
| touch external/foo/BUILD |
| echo "b" > external/foo/two |
| |
| bazel build //:rootbuild --experimental_sibling_repository_layout --experimental_disable_external_package --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have succeeded" |
| cat bazel-workspace/out/test/output.txt > $TEST_log |
| expect_log "ab" |
| |
| echo "z" > external/foo/two |
| |
| bazel build //:rootbuild --experimental_sibling_repository_layout --experimental_disable_external_package --experimental_ninja_actions &> $TEST_log \ |
| || fail "build should have succeeded" |
| cat bazel-workspace/out/test/output.txt > $TEST_log |
| expect_log "az" |
| } |
| |
| function test_basic_depfile_processing() { |
| setup_basic_ninja_build |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "ab" |
| |
| printf "HELLO" > test/one |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> $TEST_log \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> $TEST_log |
| expect_log "HELLOb" |
| } |
| |
| function test_depfile_undeclared_generated_inputs() { |
| setup_basic_ninja_build |
| |
| cat > test/build.ninja <<'EOF' |
| rule filecopy_with_side_effect |
| command = test/filecopy_with_side_effect.sh ${in} ${out} |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/generated: filecopy_with_side_effect test/one |
| build out/test/output.txt: cattool test/cattool.sh out/test/generated |
| EOF |
| |
| cat > test/filecopy_with_side_effect.sh <<'EOF' |
| cat $1 > $2 |
| cat $1 > out/side_effect |
| EOF |
| chmod +x test/filecopy_with_side_effect.sh |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat out/test/generated out/side_effect > "$OUTPUT" |
| |
| echo "$1 : out/test/generated out/side_effect" > "$DEPFILE" |
| EOF |
| |
| printf "HELLO" > test/one |
| bazel build //test:ninjabuild --experimental_ninja_actions &> "$TEST_log" \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> "$TEST_log" |
| expect_log "HELLOHELLO" |
| |
| printf "GOODBYE" > test/one |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> "$TEST_log" \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> "$TEST_log" |
| expect_log "GOODBYEGOODBYE" |
| } |
| |
| function test_depfile_modify_side_effect_file() { |
| setup_basic_ninja_build |
| |
| cat > test/build.ninja <<'EOF' |
| rule filecopy_with_side_effect |
| command = test/filecopy_with_side_effect.sh ${in} ${out} |
| rule cattool |
| depfile = out/test/depfile.d |
| command = ${in} ${out} |
| build out/test/generated: filecopy_with_side_effect test/one |
| build out/test/output.txt: cattool test/cattool.sh out/test/generated |
| EOF |
| |
| cat > test/filecopy_with_side_effect.sh <<'EOF' |
| cat $1 > $2 |
| cat $1 > out/side_effect |
| EOF |
| chmod +x test/filecopy_with_side_effect.sh |
| |
| cat > test/cattool.sh <<'EOF' |
| OUTPUT=${!#} |
| DEPFILE="$(dirname $OUTPUT)/depfile.d" |
| |
| cat out/test/generated out/side_effect > "$OUTPUT" |
| |
| echo "$1 : out/test/generated out/side_effect" > "$DEPFILE" |
| EOF |
| |
| printf "HELLO" > test/one |
| bazel build //test:ninjabuild --experimental_ninja_actions &> "$TEST_log" \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> "$TEST_log" |
| expect_log "HELLOHELLO" |
| |
| printf "GOODBYE" > bazel-workspace/out/side_effect |
| |
| bazel build //test:ninjabuild --experimental_ninja_actions &> "$TEST_log" \ |
| || fail "should have generated output successfully" |
| |
| cat bazel-workspace/out/test/output.txt &> "$TEST_log" |
| # This verifies that changing out/side_effect retriggers the action. |
| # ("HELLO" is from out/test/generated, "GOODBYE" is from out/side_effect) |
| expect_log "HELLOGOODBYE" |
| } |
| |
| function test_switching_directory_from_source_to_toplevel_output_directory() { |
| |
| mkdir -p out n |
| touch WORKSPACE |
| |
| cat > BUILD <<'EOF' |
| genrule( |
| name = "top", |
| srcs = [], |
| outs = ["topo"], |
| cmd = "echo TOP > $@", |
| ) |
| |
| ninja_graph( |
| name = "g", |
| main = "n/x.ninja", |
| ninja_srcs = ["n/x.ninja"], |
| output_root = "out", |
| output_root_inputs = ["input"], |
| ) |
| |
| ninja_build( |
| name = "b", |
| ninja_graph = ":g", |
| output_groups = {"o": ["out/outfile"]}, |
| ) |
| EOF |
| |
| cat > n/x.ninja <<'EOF' |
| rule cmd |
| command = ${cmd} |
| |
| build out/outfile: cmd out/input |
| cmd = echo OUTFILE > out/outfile |
| EOF |
| |
| echo 123 > out/input |
| |
| # Clean+expunge to ensure that 'out' is created as a symlink in the execroot |
| bazel clean --expunge |
| |
| bazel build --experimental_ninja_actions //:top |
| # test that the 'out' directory has been symlinked, since SymlinkForest |
| # has a mode where it symlinks only the packages that have been loaded. |
| execroot="$(bazel info --experimental_ninja_actions execution_root)" |
| test -L "$execroot/out" || fail "$execroot/out should be a symlink" |
| |
| # switch the "out" directory from a source directory to a toplevel output directory |
| cat > WORKSPACE <<'EOF' |
| toplevel_output_directories(paths=["out"]) |
| EOF |
| |
| bazel build --experimental_ninja_actions //:b |
| # when switching 'out' to an output directory, it should be a real directory |
| # and not a symlink, with a symlink to the output_root_symlink in it as |
| # created by the SymlinkActions in NinjaGraph.createSymlinkActions(). |
| execroot="$(bazel info --experimental_ninja_actions execution_root)" |
| if [[ -L "$execroot/out" ]]; then fail "$execroot/out should not be a symlink"; fi |
| test -d "$execroot/out" || fail "$execroot/out should be a directory" |
| test -L "$execroot/out/input" || fail "$execroot/out/input should be a symlink" |
| |
| # now switch "out" back to a source directory, do a build, and $execroot/out |
| # should be back to a symlink to the directory in the source tree. |
| echo "" > WORKSPACE |
| bazel build --experimental_ninja_actions //:top |
| execroot="$(bazel info --experimental_ninja_actions execution_root)" |
| test -L "$execroot/out" || fail "$execroot/out should be a symlink" |
| } |
| |
| run_suite "ninja_build rule tests" |