Previously, globs worked by having soong_build rewrite a ninja file that ran the globs, and then dependended on the results of that ninja file. soong_build also pre-filled their outputs so that it wouldn't be immediately rerun on the 2nd build. However, the pre-filling of outputs worked for ninja, because although it updated their timestamps, the soong ninja file was then touched by soong_build after that, so the soong_build ninja file was newer and ninja wouldn't rerun soong. But N2 reruns actions if their inputs' mtimes change in any way, not just if they're newer. Similarly, hashed-based ninja implementations could not enforce an order on file contents, so they would have the same problem. To fix this, lift the glob checking out of ninja and into soong_ui. Soong_build will output a globs report file every time it's run, and every time soong_ui is run it will check the globs file, and if any globs change, update an input to soong_build. soong_ui is essentially doing what was done in ninja with bpglob actions before. Bug: 364749114 Test: m nothing, m nothing again doesn't reanalyze, create a new file under a glob directory, m nothing again reanalyzes Change-Id: I0dbc5ec58c89b869b59cd0602b82215c4972d799
628 lines
16 KiB
Bash
Executable File
628 lines
16 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"
|
|
|
|
readonly target_product="${TARGET_PRODUCT:-aosp_arm}"
|
|
|
|
function test_smoke {
|
|
setup
|
|
run_soong
|
|
}
|
|
|
|
function test_null_build() {
|
|
setup
|
|
run_soong
|
|
local -r bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
|
|
local -r output_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
run_soong
|
|
local -r bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
|
|
local -r output_mtime2=$(stat -c "%y" out/soong/build."${target_product}".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 -r mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
|
|
|
|
sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
|
|
|
|
run_soong
|
|
local -r mtime2=$(stat -c "%y" out/soong/bootstrap.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."${target_product}".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."${target_product}".ninja && fail "old module found"
|
|
grep -q "^# Module:.*my_great_binary_host" out/soong/build."${target_product}".ninja || fail "new module not found"
|
|
}
|
|
|
|
function test_add_android_bp() {
|
|
setup
|
|
run_soong
|
|
local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".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 -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" == "$mtime2" ]]; then
|
|
fail "Output Ninja file did not change"
|
|
fi
|
|
|
|
grep -q "^# Module:.*my_little_binary_host$" out/soong/build."${target_product}".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."${target_product}".ninja || fail "Module not in output"
|
|
|
|
rm a/Android.bp
|
|
run_soong
|
|
|
|
if grep -q "^# Module:.*my_little_binary_host$" out/soong/build."${target_product}".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 -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
run_soong
|
|
local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
|
|
fail "Ninja file rewritten on null incremental build"
|
|
fi
|
|
|
|
run_soong
|
|
local -r ninja_mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then
|
|
fail "Ninja file rewritten on 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 -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
touch a/my_little_library.py
|
|
run_soong
|
|
|
|
local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" == "$mtime2" ]]; then
|
|
fail "Output Ninja file did not change"
|
|
fi
|
|
|
|
grep -q my_little_library.py out/soong/build."${target_product}".ninja || fail "new file is not in output"
|
|
}
|
|
|
|
function test_soong_build_rerun_iff_environment_changes() {
|
|
setup
|
|
|
|
mkdir -p build/soong/cherry
|
|
cat > build/soong/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 > build/soong/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."${target_product}".ninja \
|
|
|| fail "first value of environment variable is not used"
|
|
|
|
export CHERRY=RED
|
|
run_soong
|
|
grep -q "CHERRY IS RED" out/soong/build."${target_product}".ninja \
|
|
|| fail "second value of environment variable not used"
|
|
local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
run_soong
|
|
local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" != "$mtime2" ]]; then
|
|
fail "Output Ninja file changed when environment variable did not"
|
|
fi
|
|
|
|
}
|
|
|
|
function test_create_global_include_directory() {
|
|
setup
|
|
run_soong
|
|
local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
# Soong needs to know if top level directories like hardware/ exist for use
|
|
# as global include directories. Make sure that doesn't cause regens for
|
|
# unrelated changes to the top level directory.
|
|
mkdir -p system/core
|
|
|
|
run_soong
|
|
local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" != "$mtime2" ]]; then
|
|
fail "Output Ninja file changed when top level directory changed"
|
|
fi
|
|
|
|
# Make sure it does regen if a missing directory in the path of a global
|
|
# include directory is added.
|
|
mkdir -p system/core/include
|
|
|
|
run_soong
|
|
local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime2" = "$mtime3" ]]; then
|
|
fail "Output Ninja file did not change when global include directory created"
|
|
fi
|
|
|
|
}
|
|
|
|
function test_add_file_to_soong_build() {
|
|
setup
|
|
run_soong
|
|
local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
mkdir -p vendor/foo/picard
|
|
cat > vendor/foo/picard/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 > vendor/foo/picard/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 -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" == "$mtime2" ]]; then
|
|
fail "Output Ninja file did not change"
|
|
fi
|
|
|
|
grep -q "Make it so" out/soong/build."${target_product}".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 build/soong/picard
|
|
cat > build/soong/picard/Android.bp <<'EOF'
|
|
build=["foo*.bp"]
|
|
EOF
|
|
cat > build/soong/picard/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 > build/soong/picard/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 -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
grep -q "Make it so" out/soong/build."${target_product}".ninja || fail "Original action not present"
|
|
|
|
cat > build/soong/picard/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 > build/soong/picard/worf.go <<'EOF'
|
|
package worf
|
|
|
|
import "android/soong/picard"
|
|
|
|
func init() {
|
|
picard.Message = "Engage."
|
|
}
|
|
EOF
|
|
|
|
run_soong
|
|
local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$mtime1" == "$mtime2" ]]; then
|
|
fail "Output Ninja file did not change"
|
|
fi
|
|
|
|
grep -q "Engage" out/soong/build."${target_product}".ninja || fail "New action not present"
|
|
|
|
if grep -q "Make it so" out/soong/build."${target_product}".ninja; then
|
|
fail "Original action still present"
|
|
fi
|
|
}
|
|
|
|
function test_soong_docs_smoke() {
|
|
setup
|
|
|
|
run_soong soong_docs
|
|
|
|
[[ -e "out/soong/docs/soong_build.html" ]] || fail "Documentation for main page not created"
|
|
[[ -e "out/soong/docs/cc.html" ]] || fail "Documentation for C++ modules not created"
|
|
}
|
|
|
|
function test_null_build_after_soong_docs() {
|
|
setup
|
|
|
|
run_soong
|
|
local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
run_soong soong_docs
|
|
local -r docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html)
|
|
|
|
run_soong soong_docs
|
|
local -r docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html)
|
|
|
|
if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then
|
|
fail "Output Ninja file changed on null build"
|
|
fi
|
|
|
|
run_soong
|
|
local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
|
|
fail "Output Ninja file changed on null build"
|
|
fi
|
|
}
|
|
|
|
function test_write_to_source_tree {
|
|
setup
|
|
mkdir -p a
|
|
cat > a/Android.bp <<EOF
|
|
genrule {
|
|
name: "write_to_source_tree",
|
|
out: ["write_to_source_tree"],
|
|
cmd: "touch file_in_source_tree && touch \$(out)",
|
|
}
|
|
EOF
|
|
readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree
|
|
readonly ERROR_LOG=${MOCK_TOP}/out/error.log
|
|
readonly ERROR_MSG="Read-only file system"
|
|
readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR"
|
|
# Test in ReadOnly source tree
|
|
run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \
|
|
fail "Write to source tree should not work in a ReadOnly source tree"
|
|
|
|
if grep -q "${ERROR_MSG}" "${ERROR_LOG}" && grep -q "${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then
|
|
echo Error message and error hint found in logs >/dev/null
|
|
else
|
|
fail "Did not find Read-only error AND error hint in error.log"
|
|
fi
|
|
|
|
# Test in ReadWrite source tree
|
|
run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \
|
|
fail "Write to source tree did not succeed in a ReadWrite source tree"
|
|
|
|
if grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then
|
|
fail "Found read-only error OR error hint in error.log"
|
|
fi
|
|
}
|
|
|
|
function test_dump_json_module_graph() {
|
|
setup
|
|
run_soong json-module-graph
|
|
if [[ ! -r "out/soong/module-graph.json" ]]; then
|
|
fail "JSON file was not created"
|
|
fi
|
|
}
|
|
|
|
function test_json_module_graph_back_and_forth_null_build() {
|
|
setup
|
|
|
|
run_soong
|
|
local -r ninja_mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
run_soong json-module-graph
|
|
local -r json_mtime1=$(stat -c "%y" out/soong/module-graph.json)
|
|
|
|
run_soong
|
|
local -r ninja_mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
|
|
fail "Output Ninja file changed after writing JSON module graph"
|
|
fi
|
|
|
|
run_soong json-module-graph
|
|
local -r json_mtime2=$(stat -c "%y" out/soong/module-graph.json)
|
|
if [[ "$json_mtime1" != "$json_mtime2" ]]; then
|
|
fail "JSON module graph file changed after writing Ninja file"
|
|
fi
|
|
|
|
}
|
|
|
|
function test_queryview_null_build() {
|
|
setup
|
|
|
|
run_soong queryview
|
|
local -r output_mtime1=$(stat -c "%y" out/soong/queryview.marker)
|
|
|
|
run_soong queryview
|
|
local -r output_mtime2=$(stat -c "%y" out/soong/queryview.marker)
|
|
|
|
if [[ "$output_mtime1" != "$output_mtime2" ]]; then
|
|
fail "Queryview marker file changed on null build"
|
|
fi
|
|
}
|
|
|
|
# This test verifies that adding a new glob to a blueprint file only
|
|
# causes build."${target_product}".ninja to be regenerated on the *next* build, and *not*
|
|
# the build after. (This is a regression test for a bug where globs
|
|
# resulted in two successive regenerations.)
|
|
function test_new_glob_incrementality {
|
|
setup
|
|
|
|
run_soong nothing
|
|
local -r mtime1=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
mkdir -p globdefpkg/
|
|
cat > globdefpkg/Android.bp <<'EOF'
|
|
filegroup {
|
|
name: "fg_with_glob",
|
|
srcs: ["*.txt"],
|
|
}
|
|
EOF
|
|
|
|
run_soong nothing
|
|
local -r mtime2=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
if [[ "$mtime1" == "$mtime2" ]]; then
|
|
fail "Ninja file was not regenerated, despite a new bp file"
|
|
fi
|
|
|
|
run_soong nothing
|
|
local -r mtime3=$(stat -c "%y" out/soong/build."${target_product}".ninja)
|
|
|
|
if [[ "$mtime2" != "$mtime3" ]]; then
|
|
fail "Ninja file was regenerated despite no previous bp changes"
|
|
fi
|
|
}
|
|
|
|
scan_and_run_tests
|