platform_build_soong/tests/bootstrap_test.sh
Jingwen Chen 4fabaf52b0 bp2build/b: exit early in GENERATE_BAZEL_FILES=1.
This CL fixes a typo in writeFakeNinjaFile to correctly write
a fake out/soong/build.ninja and its depfile. It also modifies bootstrap
phase to *not* run the main soong build phase (which takes more than a
minute) if GENERATE_BAZEL_FILES=1.

This change has the side effect that `GENERATE_BAZEL_FILES=1 m nothing`
no longer generates the real build.ninja, which is fine because one
shouldn't be using GENERATE_BAZEL_FILES=1 for that anyway (or, use
USE_BAZEL_ANALYSIS=1). This change has no effect on mixed builds.

Time on a change to Soong or any Android.bp files:

Before: bp2build_workspace_marker (~20 seconds) + build.ninja (1 min)
After: bp2build_workspace_marker (~20 seconds)

Time on the second of two consecutive `b build //bionic/...`: 2.070s

Test: TH
Test: Soong integration tests
Change-Id: I43720641815994caba97b8d165d7c3fc254cbd06
2021-05-25 03:42:38 +00:00

703 lines
17 KiB
Bash
Executable file

#!/bin/bash -eu
set -o pipefail
# This test exercises the bootstrapping process of the build system
# in a source tree that only contains enough files for Bazel and Soong to work.
source "$(dirname "$0")/lib.sh"
readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
function test_smoke {
setup
run_soong
}
function test_null_build() {
setup
run_soong
local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
run_soong
local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
# Bootstrapping is always done. It doesn't take a measurable amount of time.
fail "Bootstrap Ninja file did not change on null build"
fi
if [[ "$output_mtime1" != "$output_mtime2" ]]; then
fail "Output Ninja file changed on null build"
fi
}
function test_soong_build_rebuilt_if_blueprint_changes() {
setup
run_soong
local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
run_soong
local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
if [[ "$mtime1" == "$mtime2" ]]; then
fail "Bootstrap Ninja file did not change"
fi
}
function test_change_android_bp() {
setup
mkdir -p a
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_little_binary_host",
srcs: ["my_little_binary_host.py"]
}
EOF
touch a/my_little_binary_host.py
run_soong
grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja || fail "module not found"
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_great_binary_host",
srcs: ["my_great_binary_host.py"]
}
EOF
touch a/my_great_binary_host.py
run_soong
grep -q "^# Module:.*my_little_binary_host" out/soong/build.ninja && fail "old module found"
grep -q "^# Module:.*my_great_binary_host" out/soong/build.ninja || fail "new module not found"
}
function test_add_android_bp() {
setup
run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
mkdir -p a
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_little_binary_host",
srcs: ["my_little_binary_host.py"]
}
EOF
touch a/my_little_binary_host.py
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" == "$mtime2" ]]; then
fail "Output Ninja file did not change"
fi
grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "New module not in output"
run_soong
}
function test_delete_android_bp() {
setup
mkdir -p a
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_little_binary_host",
srcs: ["my_little_binary_host.py"]
}
EOF
touch a/my_little_binary_host.py
run_soong
grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja || fail "Module not in output"
rm a/Android.bp
run_soong
if grep -q "^# Module:.*my_little_binary_host$" out/soong/build.ninja; then
fail "Old module in output"
fi
}
# Test that an incremental build with a glob doesn't rerun soong_build, and
# only regenerates the globs on the first but not the second incremental build.
function test_glob_noop_incremental() {
setup
# This test needs to start from a clean build, but setup creates an
# initialized tree that has already been built once. Clear the out
# directory to start from scratch (see b/185591972)
rm -rf out
mkdir -p a
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_little_binary_host",
srcs: ["*.py"],
}
EOF
touch a/my_little_binary_host.py
run_soong
local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
local glob_deps_file=out/soong/.primary/globs/0.d
if [ -e "$glob_deps_file" ]; then
fail "Glob deps file unexpectedly written on first build"
fi
run_soong
local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
# There is an ineffiencency in glob that requires bpglob to rerun once for each glob to update
# the entry in the .ninja_log. It doesn't update the output file, but we can detect the rerun
# by checking if the deps file was created.
if [ ! -e "$glob_deps_file" ]; then
fail "Glob deps file missing after second build"
fi
local glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file")
if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
fail "Ninja file rewritten on null incremental build"
fi
run_soong
local ninja_mtime3=$(stat -c "%y" out/soong/build.ninja)
local glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file")
if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then
fail "Ninja file rewritten on null incremental build"
fi
# The bpglob commands should not rerun after the first incremental build.
if [[ "$glob_deps_mtime2" != "$glob_deps_mtime3" ]]; then
fail "Glob deps file rewritten on second null incremental build"
fi
}
function test_add_file_to_glob() {
setup
mkdir -p a
cat > a/Android.bp <<'EOF'
python_binary_host {
name: "my_little_binary_host",
srcs: ["*.py"],
}
EOF
touch a/my_little_binary_host.py
run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
touch a/my_little_library.py
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" == "$mtime2" ]]; then
fail "Output Ninja file did not change"
fi
grep -q my_little_library.py out/soong/build.ninja || fail "new file is not in output"
}
function test_soong_build_rerun_iff_environment_changes() {
setup
mkdir -p cherry
cat > cherry/Android.bp <<'EOF'
bootstrap_go_package {
name: "cherry",
pkgPath: "android/soong/cherry",
deps: [
"blueprint",
"soong",
"soong-android",
],
srcs: [
"cherry.go",
],
pluginFor: ["soong_build"],
}
EOF
cat > cherry/cherry.go <<'EOF'
package cherry
import (
"android/soong/android"
"github.com/google/blueprint"
)
var (
pctx = android.NewPackageContext("cherry")
)
func init() {
android.RegisterSingletonType("cherry", CherrySingleton)
}
func CherrySingleton() android.Singleton {
return &cherrySingleton{}
}
type cherrySingleton struct{}
func (p *cherrySingleton) GenerateBuildActions(ctx android.SingletonContext) {
cherryRule := ctx.Rule(pctx, "cherry",
blueprint.RuleParams{
Command: "echo CHERRY IS " + ctx.Config().Getenv("CHERRY") + " > ${out}",
CommandDeps: []string{},
Description: "Cherry",
})
outputFile := android.PathForOutput(ctx, "cherry", "cherry.txt")
var deps android.Paths
ctx.Build(pctx, android.BuildParams{
Rule: cherryRule,
Output: outputFile,
Inputs: deps,
})
}
EOF
export CHERRY=TASTY
run_soong
grep -q "CHERRY IS TASTY" out/soong/build.ninja \
|| fail "first value of environment variable is not used"
export CHERRY=RED
run_soong
grep -q "CHERRY IS RED" out/soong/build.ninja \
|| fail "second value of environment variable not used"
local mtime1=$(stat -c "%y" out/soong/build.ninja)
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" != "$mtime2" ]]; then
fail "Output Ninja file changed when environment variable did not"
fi
}
function test_add_file_to_soong_build() {
setup
run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
mkdir -p a
cat > a/Android.bp <<'EOF'
bootstrap_go_package {
name: "picard-soong-rules",
pkgPath: "android/soong/picard",
deps: [
"blueprint",
"soong",
"soong-android",
],
srcs: [
"picard.go",
],
pluginFor: ["soong_build"],
}
EOF
cat > a/picard.go <<'EOF'
package picard
import (
"android/soong/android"
"github.com/google/blueprint"
)
var (
pctx = android.NewPackageContext("picard")
)
func init() {
android.RegisterSingletonType("picard", PicardSingleton)
}
func PicardSingleton() android.Singleton {
return &picardSingleton{}
}
type picardSingleton struct{}
func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) {
picardRule := ctx.Rule(pctx, "picard",
blueprint.RuleParams{
Command: "echo Make it so. > ${out}",
CommandDeps: []string{},
Description: "Something quotable",
})
outputFile := android.PathForOutput(ctx, "picard", "picard.txt")
var deps android.Paths
ctx.Build(pctx, android.BuildParams{
Rule: picardRule,
Output: outputFile,
Inputs: deps,
})
}
EOF
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" == "$mtime2" ]]; then
fail "Output Ninja file did not change"
fi
grep -q "Make it so" out/soong/build.ninja || fail "New action not present"
}
# Tests a glob in a build= statement in an Android.bp file, which is interpreted
# during bootstrapping.
function test_glob_during_bootstrapping() {
setup
mkdir -p a
cat > a/Android.bp <<'EOF'
build=["foo*.bp"]
EOF
cat > a/fooa.bp <<'EOF'
bootstrap_go_package {
name: "picard-soong-rules",
pkgPath: "android/soong/picard",
deps: [
"blueprint",
"soong",
"soong-android",
],
srcs: [
"picard.go",
],
pluginFor: ["soong_build"],
}
EOF
cat > a/picard.go <<'EOF'
package picard
import (
"android/soong/android"
"github.com/google/blueprint"
)
var (
pctx = android.NewPackageContext("picard")
)
func init() {
android.RegisterSingletonType("picard", PicardSingleton)
}
func PicardSingleton() android.Singleton {
return &picardSingleton{}
}
type picardSingleton struct{}
var Message = "Make it so."
func (p *picardSingleton) GenerateBuildActions(ctx android.SingletonContext) {
picardRule := ctx.Rule(pctx, "picard",
blueprint.RuleParams{
Command: "echo " + Message + " > ${out}",
CommandDeps: []string{},
Description: "Something quotable",
})
outputFile := android.PathForOutput(ctx, "picard", "picard.txt")
var deps android.Paths
ctx.Build(pctx, android.BuildParams{
Rule: picardRule,
Output: outputFile,
Inputs: deps,
})
}
EOF
run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
grep -q "Make it so" out/soong/build.ninja || fail "Original action not present"
cat > a/foob.bp <<'EOF'
bootstrap_go_package {
name: "worf-soong-rules",
pkgPath: "android/soong/worf",
deps: [
"blueprint",
"soong",
"soong-android",
"picard-soong-rules",
],
srcs: [
"worf.go",
],
pluginFor: ["soong_build"],
}
EOF
cat > a/worf.go <<'EOF'
package worf
import "android/soong/picard"
func init() {
picard.Message = "Engage."
}
EOF
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" == "$mtime2" ]]; then
fail "Output Ninja file did not change"
fi
grep -q "Engage" out/soong/build.ninja || fail "New action not present"
if grep -q "Make it so" out/soong/build.ninja; then
fail "Original action still present"
fi
}
function test_null_build_after_docs {
setup
run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
prebuilts/build-tools/linux-x86/bin/ninja -f out/soong/build.ninja soong_docs
run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" != "$mtime2" ]]; then
fail "Output Ninja file changed on null build"
fi
}
function test_bp2build_smoke {
setup
GENERATE_BAZEL_FILES=1 run_soong
[[ -e out/soong/.bootstrap/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
[[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
}
function test_bp2build_generates_fake_ninja_file {
setup
create_mock_bazel
run_bp2build
if [[ ! -f "./out/soong/build.ninja" ]]; then
fail "./out/soong/build.ninja was not generated"
fi
if ! grep "build nothing: phony" "./out/soong/build.ninja"; then
fail "missing phony nothing target in out/soong/build.ninja"
fi
}
function test_bp2build_add_android_bp {
setup
mkdir -p a
touch a/a.txt
cat > a/Android.bp <<'EOF'
filegroup {
name: "a",
srcs: ["a.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
[[ -e out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
[[ -L out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
mkdir -p b
touch b/b.txt
cat > b/Android.bp <<'EOF'
filegroup {
name: "b",
srcs: ["b.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
[[ -e out/soong/bp2build/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
[[ -L out/soong/workspace/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
}
function test_bp2build_null_build {
setup
GENERATE_BAZEL_FILES=1 run_soong
local mtime1=$(stat -c "%y" out/soong/build.ninja)
GENERATE_BAZEL_FILES=1 run_soong
local mtime2=$(stat -c "%y" out/soong/build.ninja)
if [[ "$mtime1" != "$mtime2" ]]; then
fail "Output Ninja file changed on null build"
fi
}
function test_bp2build_add_to_glob {
setup
mkdir -p a
touch a/a1.txt
cat > a/Android.bp <<'EOF'
filegroup {
name: "a",
srcs: ["*.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
grep -q a1.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a1.txt not in ${GENERATED_BUILD_FILE_NAME} file"
touch a/a2.txt
GENERATE_BAZEL_FILES=1 run_soong
grep -q a2.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a2.txt not in ${GENERATED_BUILD_FILE_NAME} file"
}
function test_dump_json_module_graph() {
setup
SOONG_DUMP_JSON_MODULE_GRAPH="$MOCK_TOP/modules.json" run_soong
if [[ ! -r "$MOCK_TOP/modules.json" ]]; then
fail "JSON file was not created"
fi
}
function test_bp2build_bazel_workspace_structure {
setup
mkdir -p a/b
touch a/a.txt
touch a/b/b.txt
cat > a/b/Android.bp <<'EOF'
filegroup {
name: "b",
srcs: ["b.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
[[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
[[ -d out/soong/workspace/a/b ]] || fail "module directory not a directory"
[[ -L "out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
[[ "$(readlink -f out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/b/${GENERATED_BUILD_FILE_NAME}"$ ]] \
|| fail "BUILD files symlinked at the wrong place"
[[ -L out/soong/workspace/a/b/b.txt ]] || fail "a/b/b.txt not symlinked"
[[ -L out/soong/workspace/a/a.txt ]] || fail "a/b/a.txt not symlinked"
[[ ! -e out/soong/workspace/out ]] || fail "out directory symlinked"
}
function test_bp2build_bazel_workspace_add_file {
setup
mkdir -p a
touch a/a.txt
cat > a/Android.bp <<EOF
filegroup {
name: "a",
srcs: ["a.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
touch a/a2.txt # No reference in the .bp file needed
GENERATE_BAZEL_FILES=1 run_soong
[[ -L out/soong/workspace/a/a2.txt ]] || fail "a/a2.txt not symlinked"
}
function test_bp2build_build_file_precedence {
setup
mkdir -p a
touch a/a.txt
touch a/${GENERATED_BUILD_FILE_NAME}
cat > a/Android.bp <<EOF
filegroup {
name: "a",
srcs: ["a.txt"],
bazel_module: { bp2build_available: true },
}
EOF
GENERATE_BAZEL_FILES=1 run_soong
[[ -L "out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
[[ "$(readlink -f out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/${GENERATED_BUILD_FILE_NAME}"$ ]] \
|| fail "${GENERATED_BUILD_FILE_NAME} files symlinked to the wrong place"
}
function test_bp2build_reports_multiple_errors {
setup
mkdir -p "a/${GENERATED_BUILD_FILE_NAME}"
touch a/a.txt
cat > a/Android.bp <<EOF
filegroup {
name: "a",
srcs: ["a.txt"],
bazel_module: { bp2build_available: true },
}
EOF
mkdir -p "b/${GENERATED_BUILD_FILE_NAME}"
touch b/b.txt
cat > b/Android.bp <<EOF
filegroup {
name: "b",
srcs: ["b.txt"],
bazel_module: { bp2build_available: true },
}
EOF
if GENERATE_BAZEL_FILES=1 run_soong >& "$MOCK_TOP/errors"; then
fail "Build should have failed"
fi
grep -q "a/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for a/${GENERATED_BUILD_FILE_NAME} not found"
grep -q "b/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for b/${GENERATED_BUILD_FILE_NAME} not found"
}
test_smoke
test_null_build
test_null_build_after_docs
test_soong_build_rebuilt_if_blueprint_changes
test_glob_noop_incremental
test_add_file_to_glob
test_add_android_bp
test_change_android_bp
test_delete_android_bp
test_add_file_to_soong_build
test_glob_during_bootstrapping
test_soong_build_rerun_iff_environment_changes
test_dump_json_module_graph
test_bp2build_smoke
test_bp2build_generates_fake_ninja_file
test_bp2build_null_build
test_bp2build_add_android_bp
test_bp2build_add_to_glob
test_bp2build_bazel_workspace_structure
test_bp2build_bazel_workspace_add_file
test_bp2build_build_file_precedence
test_bp2build_reports_multiple_errors