diff --git a/envsetup.sh b/envsetup.sh index 647c106380..352d66442f 100644 --- a/envsetup.sh +++ b/envsetup.sh @@ -385,6 +385,7 @@ function addcompletions() complete -F _bazel__complete -o nospace b fi complete -F _lunch lunch + complete -F _lunch_completion lunch2 complete -F _complete_android_module_names pathmod complete -F _complete_android_module_names gomod @@ -496,9 +497,18 @@ function lunch() return 1 fi + _lunch_meat $product $release $variant +} + +function _lunch_meat() +{ + local product=$1 + local release=$2 + local variant=$3 + TARGET_PRODUCT=$product \ - TARGET_BUILD_VARIANT=$variant \ TARGET_RELEASE=$release \ + TARGET_BUILD_VARIANT=$variant \ build_build_var_cache if [ $? -ne 0 ] then @@ -519,14 +529,11 @@ function lunch() set_stuff_for_environment [[ -n "${ANDROID_QUIET_BUILD:-}" ]] || printconfig - if [ "${TARGET_BUILD_VARIANT}" = "userdebug" ] && [[ -z "${ANDROID_QUIET_BUILD}" ]]; then - echo - echo "Want FASTER LOCAL BUILDS? Use -eng instead of -userdebug (however for" \ - "performance benchmarking continue to use userdebug)" - fi - if [ $used_lunch_menu -eq 1 ]; then - echo - echo "Hint: next time you can simply run 'lunch $selection'" + if [[ -z "${ANDROID_QUIET_BUILD}" ]]; then + local spam_for_lunch=$(gettop)/build/make/tools/envsetup/spam_for_lunch + if [[ -x $spam_for_lunch ]]; then + $spam_for_lunch + fi fi destroy_build_var_cache @@ -553,6 +560,112 @@ function _lunch() return 0 } +function _lunch_usage() +{ + ( + echo "The lunch command selects the configuration to use for subsequent" + echo "Android builds." + echo + echo "Usage: lunch TARGET_PRODUCT [TARGET_RELEASE [TARGET_BUILD_VARIANT]]" + echo + echo " Choose the product, release and variant to use. If not" + echo " supplied, TARGET_RELEASE will be 'trunk_staging' and" + echo " TARGET_BUILD_VARIANT will be 'eng'" + echo + echo + echo "Usage: lunch TARGET_PRODUCT-TARGET_RELEASE-TARGET_BUILD_VARIANT" + echo + echo " Chose the product, release and variant to use. This" + echo " legacy format is maintained for compatibility." + echo + echo + echo "Note that the previous interactive menu and list of hard-coded" + echo "list of curated targets has been removed. If you would like the" + echo "list of products, release configs for a particular product, or" + echo "variants, run list_products, list_release_configs, list_variants" + echo "respectively." + echo + ) 1>&2 +} + +function lunch2() +{ + if [[ $# -eq 1 && $1 = "--help" ]]; then + _lunch_usage + return 0 + fi + if [[ $# -eq 0 ]]; then + echo "No target specified. See lunch --help" 1>&2 + return 1 + fi + if [[ $# -gt 3 ]]; then + echo "Too many parameters given. See lunch --help" 1>&2 + return 1 + fi + + local product release variant + + # Handle the legacy format + local legacy=$(echo $1 | grep "-") + if [[ $# -eq 1 && -n $legacy ]]; then + IFS="-" read -r product release variant <<< "$1" + if [[ -z "$product" ]] || [[ -z "$release" ]] || [[ -z "$variant" ]]; then + echo "Invalid lunch combo: $1" 1>&2 + echo "Valid combos must be of the form -- when using" 1>&2 + echo "the legacy format. Run 'lunch --help' for usage." 1>&2 + return 1 + fi + fi + + # Handle the new format. + if [[ -z $legacy ]]; then + product=$1 + release=$2 + if [[ -z $release ]]; then + release=trunk_staging + fi + variant=$3 + if [[ -z $variant ]]; then + variant=eng + fi + fi + + # Validate the selection and set all the environment stuff + _lunch_meat $product $release $variant +} + +unset ANDROID_LUNCH_COMPLETION_PRODUCT_CACHE +unset ANDROID_LUNCH_COMPLETION_CHOSEN_PRODUCT +unset ANDROID_LUNCH_COMPLETION_RELEASE_CACHE +# Tab completion for lunch. +function _lunch_completion() +{ + # Available products + if [[ $COMP_CWORD -eq 1 ]] ; then + if [[ -z $ANDROID_LUNCH_COMPLETION_PRODUCT_CACHE ]]; then + ANDROID_LUNCH_COMPLETION_PRODUCT_CACHE=$(list_products) + fi + COMPREPLY=( $(compgen -W "${ANDROID_LUNCH_COMPLETION_PRODUCT_CACHE}" -- "${COMP_WORDS[COMP_CWORD]}") ) + fi + + # Available release configs + if [[ $COMP_CWORD -eq 2 ]] ; then + if [[ -z $ANDROID_LUNCH_COMPLETION_RELEASE_CACHE || $ANDROID_LUNCH_COMPLETION_CHOSEN_PRODUCT != ${COMP_WORDS[1]} ]] ; then + ANDROID_LUNCH_COMPLETION_RELEASE_CACHE=$(list_releases ${COMP_WORDS[1]}) + ANDROID_LUNCH_COMPLETION_CHOSEN_PRODUCT=${COMP_WORDS[1]} + fi + COMPREPLY=( $(compgen -W "${ANDROID_LUNCH_COMPLETION_RELEASE_CACHE}" -- "${COMP_WORDS[COMP_CWORD]}") ) + fi + + # Available variants + if [[ $COMP_CWORD -eq 3 ]] ; then + COMPREPLY=(user userdebug eng) + fi + + return 0 +} + + # Configures the build to build unbundled apps. # Run tapas with one or more app names (from LOCAL_PACKAGE_NAME) function tapas() diff --git a/tools/envsetup/run_envsetup_tests b/tools/envsetup/run_envsetup_tests new file mode 100755 index 0000000000..5977448d07 --- /dev/null +++ b/tools/envsetup/run_envsetup_tests @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 The Android Open Source Project +# +# 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. + +import os +import pathlib +import subprocess +import sys + +SOURCE_ENVSETUP="source build/make/envsetup.sh && " + +def update_display(): + sys.stderr.write("passed\n") + +def go_to_root(): + while True: + if os.path.exists("build/make/envsetup.sh"): + return + if os.getcwd() == "/": + sys.stderr.write("Can't find root of the source tree\n"); + print("\nFAILED") + sys.exit(1) + os.chdir("..") + +def is_test(name, thing): + if not callable(thing): + return False + if name == "test": + return False + return name.startswith("test") + + +def test(shell, command, expected_return, expected_stdout, expected_stderr, expected_env): + command += "; _rc=$?" + for env in expected_env.keys(): + command += f"; echo ENV: {env}=\\\"${env}\\\"" + command += "; exit $_rc" + + cmd = [shell, "-c", command] + result = subprocess.run(cmd, capture_output=True, text=True) + + status = True + + if result.returncode != expected_return: + print() + print(f"Expected return code: {expected_return}") + print(f"Actual return code: {result.returncode}") + status = False + + printed_stdout = False + if expected_stdout and expected_stdout not in result.stdout: + print() + print(f"Expected stdout to contain:\n{expected_stdout}") + print(f"\nActual stdout:\n{result.stdout}") + printed_stdout = True + status = False + + if expected_stderr and expected_stderr not in result.stderr: + print() + print(f"Expected stderr to contain:\n{expected_stderr}") + print(f"\nActual stderr:\n{result.stderr}") + status = False + + env_failure = False + for k, v in expected_env.items(): + if f"{k}=\"{v}\"" not in result.stdout: + print() + print(f"Expected environment variable {k} to be: {v} --- {k}=\"{v}\"") + env_failure = True + status = False + + if env_failure and not printed_stdout: + print() + print("See stdout:") + print(result.stdout) + + if not status: + print() + print("Command to reproduce:") + print(command) + print() + + return status + +NO_LUNCH = { + "TARGET_PRODUCT": "", + "TARGET_RELEASE": "", + "TARGET_BUILD_VARIANT": "", +} + +def test_invalid_lunch_target(shell): + return test(shell, SOURCE_ENVSETUP + "lunch invalid-trunk_staging-eng", + expected_return=1, expected_stdout=None, + expected_stderr="Cannot locate config makefile for product", + expected_env=NO_LUNCH) + + +def test_aosp_arm(shell): + return test(shell, SOURCE_ENVSETUP + "lunch aosp_arm-trunk_staging-eng", + expected_return=0, expected_stdout=None, expected_stderr=None, + expected_env={ + "TARGET_PRODUCT": "aosp_arm", + "TARGET_RELEASE": "trunk_staging", + "TARGET_BUILD_VARIANT": "eng", + }) + + +def test_lunch2_empty(shell): + return test(shell, SOURCE_ENVSETUP + "lunch2", + expected_return=1, expected_stdout=None, + expected_stderr="No target specified. See lunch --help", + expected_env=NO_LUNCH) + +def test_lunch2_four_params(shell): + return test(shell, SOURCE_ENVSETUP + "lunch2 a b c d", + expected_return=1, expected_stdout=None, + expected_stderr="Too many parameters given. See lunch --help", + expected_env=NO_LUNCH) + +def test_lunch2_aosp_arm(shell): + return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm", + expected_return=0, expected_stdout="=========", expected_stderr=None, + expected_env={ + "TARGET_PRODUCT": "aosp_arm", + "TARGET_RELEASE": "trunk_staging", + "TARGET_BUILD_VARIANT": "eng", + }) + +def test_lunch2_aosp_arm_trunk_staging(shell): + # Somewhat unfortunate because trunk_staging is the only config in + # aosp so we can't really test that this isn't just getting the default + return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm trunk_staging", + expected_return=0, expected_stdout="=========", expected_stderr=None, + expected_env={ + "TARGET_PRODUCT": "aosp_arm", + "TARGET_RELEASE": "trunk_staging", + "TARGET_BUILD_VARIANT": "eng", + }) + +def test_lunch2_aosp_arm_trunk_staging_userdebug(shell): + return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm trunk_staging userdebug", + expected_return=0, expected_stdout="=========", expected_stderr=None, + expected_env={ + "TARGET_PRODUCT": "aosp_arm", + "TARGET_RELEASE": "trunk_staging", + "TARGET_BUILD_VARIANT": "userdebug", + }) + +def test_list_products(shell): + return test(shell, "build/soong/bin/list_products", + expected_return=0, expected_stdout="aosp_arm", expected_stderr=None, + expected_env=NO_LUNCH) + +def test_list_releases_param(shell): + return test(shell, "build/soong/bin/list_releases aosp_arm", + expected_return=0, expected_stdout="trunk_staging", expected_stderr=None, + expected_env=NO_LUNCH) + +def test_list_releases_env(shell): + return test(shell, "TARGET_PRODUCT=aosp_arm build/soong/bin/list_releases", + expected_return=0, expected_stdout="trunk_staging", expected_stderr=None, + expected_env=NO_LUNCH) + +def test_list_releases_no_product(shell): + return test(shell, "build/soong/bin/list_releases", + expected_return=1, expected_stdout=None, expected_stderr=None, + expected_env=NO_LUNCH) + +def test_list_variants(shell): + return test(shell, "build/soong/bin/list_variants", + expected_return=0, expected_stdout="userdebug", expected_stderr=None, + expected_env=NO_LUNCH) + + +def test_get_build_var_in_path(shell): + return test(shell, SOURCE_ENVSETUP + "which get_build_var ", + expected_return=0, expected_stdout="soong/bin", expected_stderr=None, + expected_env=NO_LUNCH) + + + +TESTS=sorted([(name, thing) for name, thing in locals().items() if is_test(name, thing)]) + +def main(): + if any([x.endswith("/soong/bin") for x in os.getenv("PATH").split(":")]): + sys.stderr.write("run_envsetup_tests must be run in a shell that has not sourced" + + " envsetup.sh\n\nFAILED\n") + return 1 + + go_to_root() + + tests = TESTS + if len(sys.argv) > 1: + tests = [(name, func) for name, func in tests if name in sys.argv] + + shells = ["/usr/bin/bash", "/usr/bin/zsh"] + total_count = len(tests) * len(shells) + index = 1 + failed_tests = 0 + + for name, func in tests: + for shell in shells: + sys.stdout.write(f"\33[2K\r{index} of {total_count}: {name} in {shell}") + passed = func(shell) + if not passed: + failed_tests += 1 + index += 1 + + if failed_tests > 0: + print(f"\n\nFAILED: {failed_tests} of {total_count}") + return 1 + else: + print("\n\nSUCCESS") + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/envsetup/spam_for_lunch b/tools/envsetup/spam_for_lunch new file mode 100755 index 0000000000..2e150a692c --- /dev/null +++ b/tools/envsetup/spam_for_lunch @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright 2024, The Android Open Source Project +# +# 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. + +# This ad is kind of big, so only show it if this appears to be a clean build. +source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../shell_utils.sh +if [[ ! -e $(getoutdir)/soong/build.${TARGET_PRODUCT}.ninja ]]; then + echo + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo " Wondering whether to use user, userdebug or eng?" + echo + echo " user The builds that ship to users. Reduced debugability." + echo " userdebug High fidelity to user builds but with some debugging options" + echo " enabled. Best suited for performance testing or day-to-day use" + echo " with debugging enabled." + echo " eng More debugging options enabled and faster build times, but" + echo " runtime performance tradeoffs. Best suited for day-to-day" + echo " local development when not doing performance testing." + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo +fi +