android Compiler System Analysis I: source build/envsetup.sh and lunch

Keywords: Attribute Android shell xml

Although many people have analyzed the code of android compiler system, I have read their blog and learned a lot of knowledge, but simply looking at other people's analysis, ultimately not in-depth understanding, so I still have to carefully analyze it again.

Think about how we compiled the android system:

First: source build/envsetup.sh

Secondly: lunch - Choose a particular type

Finally:make

Follow these seemingly simple steps in this order. What are the secrets behind them?   

1. source build/envsetup.sh

Although this document is very large, it does not need to be looked at all for the time being. It defines a lot of functions, these functions in the use of specific and detailed study, now mainly look at the script to do. Even so, the first function after opening the script is very attractive because it describes the main things the script does:

  1. function hmm() {  
  2. cat <<EOF  
  3. Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:  
  4. - lunch:   lunch <product_name>-<build_variant>  
  5. - tapas:   tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]  
  6. - croot:   Changes directory to the top of the tree.  
  7. - m:       Makes from the top of the tree.  
  8. - mm:      Builds all of the modules in the current directory, but not their dependencies.  
  9. - mmm:     Builds all of the modules in the supplied directories, but not their dependencies.  
  10.            To limit the modules being built use the syntax: mmm dir/:target1,target2.  
  11. - mma:     Builds all of the modules in the current directory, and their dependencies.  
  12. - mmma:    Builds all of the modules in the supplied directories, and their dependencies.  
  13. - cgrep:   Greps on all local C/C++ files.  
  14. - ggrep:   Greps on all local Gradle files.  
  15. - jgrep:   Greps on all local Java files.  
  16. - resgrep: Greps on all local res/*.xml files.  
  17. - mangrep: Greps on all local AndroidManifest.xml files.  
  18. - sepgrep: Greps on all local sepolicy files.  
  19. - sgrep:   Greps on all local source files.  
  20. - godir:   Go to the directory containing a file.  
  21.   
  22. Environemnt options:  
  23. - SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that  
  24.                  ASAN_OPTIONS=detect_leaks=0 will be set by default until the  
  25.                  build is leak-check clean.  
  26.   
  27. Look at the source to view more functions. The complete list is:  
  28. EOF  
  29.     T=$(gettop)  
  30.     local A  
  31.     A=""  
  32.     for i in `cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function [az][a−z]∗.*/\1/p" | sort | uniq`; do  
  33.       A="$A $i"  
  34.     done  
  35.     echo $A  
  36. }  
This function lists some of the functions of the script. The first line cat is a HERE document, which means that the content from EOF back to the next EOF front is treated as a file, and then cat receives the content of the file. The default output of cat is standard output, that is, the content of the file will be printed on the screen. These contents introduce the usage and function of the script, the usage is ". build/envsetup.sh" Note. There is a space after this. The command is the source command, that is to say, you can also execute "source build/envsetup.sh". In addition, this function introduces many functions, such as lunch, m, m m, m m m, cgrep and so on.

Functions will only be executed when they are called, so for the time being, they will only look outside the function.

  1. # Clear this variable.  It will be built up again when the vendorsetup.sh  
  2. # files are included at the end of this file.  
  3. unset LUNCH_MENU_CHOICES  
The unset command is called here, and unset is a bash command that deletes a given variable. That is, it deletes the LUNCH_MENU_CHOICES variable. Now that it has been deliberately deleted here, it must immediately reconstruct the variable. Sure enough:

  1. # add the default one here  
  2. add_lunch_combo aosp_arm-eng  
  3. add_lunch_combo aosp_arm64-eng  
  4. add_lunch_combo aosp_mips-eng  
  5. add_lunch_combo aosp_mips64-eng  
  6. add_lunch_combo aosp_x86-eng  
  7. add_lunch_combo aosp_x86_64-eng  
These lines call the add_lunch_combo function and pass in some parameters, which makes us have more options to choose when we execute the lunch function.

And this function is to reconstruct the LUNCH_MENU_CHOICES variable:

  1. function add_lunch_combo()  
  2. {  
  3.     local new_combo=$1  
  4.     local c  
  5.     for c in ${LUNCH_MENU_CHOICES[@]} ; do  
  6.         if [ "$new_combo" = "$c" ] ; then  
  7.             return  
  8.         fi  
  9.     done  
  10.     LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)  
  11. }  

Knowing shell programming, you know that $1 represents the first parameter of the incoming function and then defines a variable c.

As you can see from the for loop, LUNCH_MENU_CHOICES is an array, in the shell, you can get all the items of the array by "variable name [@]" or "variable name [*]. Then notice to compare each item in the array. If there is already an incoming parameter item in the array, it will continue to return. Otherwise, the new incoming parameter will be added to the LUNCH_MENU_CHOIES array.

Although this script is very long, there is not much code actually executed, that is to say, there is not much code executed when source build/envsetup.sh is executed, and most of its contents are function definitions. At the end of the file, a little more code is executed:

  1. if [ "x$SHELL" != "x/bin/bash" ]; then  
  2.     case `ps -o command -p $$` in  
  3.         *bash*)  
  4.             ;;  
  5.         *)  
  6.             echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"  
  7.             ;;  
  8.     esac  
  9. fi  
  10.   
  11. # Execute the contents of any vendorsetup.sh files we can find.  
  12. for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \  
  13.          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`  
  14. do  
  15.     echo "including $f"  
  16.     . $f  
  17. done  
  18. unset f  


The previous if statement block checks the supported shell parser. The later for statement block is more critical.

The use of shell inverted quotes is used here, and its function is to execute the contents of inverted quotes as shell commands. Then the execution results are output to the screen using echo. In inverted quotation marks, the test command is used to test whether the device directory exists. If it exists, the vendorsetup. sh script is searched, and the depth of searching is set to four levels. 2>/dev/null is the redirection grammar in the shell, where the standard error is redirected to/dev/null, that is, delete all the errors, and then sort the found vendorsetup.sh script using the sort command.

$f equals source $f, which is all the vendorsetup.sh scripts found under source /device and / vendor. Then the script is finished, and naturally the vendorsetup.sh script under / device and / vendor is to be analyzed.


2./device/vendor vendorsetup.sh

Execute source build/envsetup.sh and print the contents:

  1. including device/generic/mini-emulator-arm64/vendorsetup.sh  
  2. including device/generic/mini-emulator-armv7-a-neon/vendorsetup.sh  
  3. including device/generic/mini-emulator-mips/vendorsetup.sh  
  4. including device/generic/mini-emulator-x86_64/vendorsetup.sh  
  5. including device/generic/mini-emulator-x86/vendorsetup.sh  
Here is only a part. Take the first article as an example to see its content.

  1. add_lunch_combo mini_emulator_arm64-userdebug  
This function has been analyzed, and this file has only one line. The vendorsetup.sh scripts listed here are all in one line. He just adds an item to the menu of lunch. Of course, it's not always the case. vendorsetup.sh of some manufacturers will do other work, but no matter how many other things it does, the first thing seems certain is to call add_lunch_combo to add one.

So, in summary, the envsetup.sh script does something like this:


3.lunch

When compiling android, after executing souce build/envsetup.sh, we also need to execute lunch and select a specific veneer.

  1. function lunch()  
  2. {  
  3.     local answer  
  4.   
  5.     if [ "$1" ] ; then  
  6.         answer=$1  
  7.     else  
  8.         print_lunch_menu  
  9.         echo -n "Which would you like? [aosp_arm-eng] "  
  10.         read answer  
  11.     fi  
  12.   
  13.     local selection=  
  14.   
  15.     if [ -z "$answer" ]  
  16.     then  
  17.         selection=aosp_arm-eng  
  18.     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")  
  19.     then  
  20.         if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]  
  21.         then  
  22.             selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}  
  23.         fi  
  24.     elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")  
  25.     then  
  26.         selection=$answer  
  27.     fi  
  28.   
  29.     if [ -z "$selection" ]  
  30.     then  
  31.         echo  
  32.         echo "Invalid lunch combo: $answer"  
  33.         return 1  
  34.     fi  
  35.   
  36.     export TARGET_BUILD_APPS=  
  37.   
  38.     local product=$(echo -n $selection | sed -e "s/-.*$//")  
  39.     check_product $product  
  40.     if [ $? -ne 0 ]  
  41.     then  
  42.         echo  
  43.         echo "** Don't have a product spec for: '$product'"  
  44.         echo "** Do you have the right repo manifest?"  
  45.         product=  
  46.     fi  
  47.   
  48.     local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")  
  49.     check_variant $variant  
  50.     if [ $? -ne 0 ]  
  51.     then  
  52.         echo  
  53.         echo "** Invalid variant: '$variant'"  
  54.         echo "** Must be one of ${VARIANT_CHOICES[@]}"  
  55.         variant=  
  56.     fi  
  57.   
  58.     if [ -z "$product" -o -z "$variant" ]  
  59.     then  
  60.         echo  
  61.         return 1  
  62.     fi  
  63.   
  64.     export TARGET_PRODUCT=$product  
  65.     export TARGET_BUILD_VARIANT=$variant  
  66.     export TARGET_BUILD_TYPE=release  
  67.   
  68.     echo  
  69.   
  70.     set_stuff_for_environment  
  71.     printconfig  
  72. }  
This function first determines whether there are incoming parameters. If there are incoming parameters, it assigns the value to the answer variable. If there are no parameters, that is to say, the knowledge implements lunch, then it will call fprint_lunch_menu function to display a menu and accept the user's input.

The print_lunch_menu function is as follows:

  1. function print_lunch_menu()  
  2. {  
  3.     local uname=$(uname)  
  4.     echo  
  5.     echo "You're building on" $uname  
  6.     echo  
  7.     echo "Lunch menu... pick a combo:"  
  8.   
  9.     local i=1  
  10.     local choice  
  11.     for choice in ${LUNCH_MENU_CHOICES[@]}  
  12.     do  
  13.         echo "     $i. $choice"  
  14.         i=$(($i+1))  
  15.     done  
  16.   
  17.     echo  
  18. }  
As you can see, after this function outputs some header information, it outputs an array of LUNCH_MENU_CHOICES, where each item is printed by traversing the entire array for. This array has been analyzed before, and each item is added by calling the add_lunch_combo function.

1. Whether or not the input parameters are passed in when lunch is executed, the result of the selection will eventually be stored in the answer variable. Then, of course, the validity of the input parameters will be checked. Here we first determine whether the answer variable is zero. If it is zero, the selection variable will be assigned to aosp_arm-eng, but if it is not zero, the answer value will be output first. When the echo-n $answer, -n option is output, no newline character will be output. The value of the answer variable is not output to the screen, but is transmitted to the following command through the pipeline: grep-q-eng. E "^[0-9] [0-9]*$", this command searches the answer variable for a string that begins with two digits, and if found, it is assumed that the input is a number. Then further check whether the number crosses the boundary. If this number is smaller than the size of LUNCH_MENU_CHOICES, the $answer-1 entry of LUNCH_MENU_CHOICES is copied to the selection variable.

2. If the incoming number is not a number, it will try to match the string grep-q-e "^[^ u -][^ u -]* - [^ u -][^][^][^]*$", -q indicates quiet mode, i.e. no printing information, -e. followed by a pattern for matching. Here, ^ indicates the beginning of matching, ^ in [] indicates exclusion, u is a translated character, because - may be a symbol indicating scope. So, what matches here is a string that doesn't start in a row, followed by any character, and then has to have a string that doesn't end in a row. In fact, here is the form of product-variable. That's the format of the strings we added earlier using add_lunch_combo. For example: aosp_arm-eng, product is aosp_arm, variable is the middle of Eng. - is necessary. If the required format is found, the selection variable is assigned by $answer, which means that the selection is actually a string in product-variable mode.

3. If it's neither a number, nor a legal string, nor a number, then selection s should not have been assigned. At this time - z is established, then you will be prompted to enter something wrong. And return directly.

If the input is valid, after checking, the export TARGET_BUILD_APPS= line exports a variable, but its value is empty. Then the sentence "local product=$" (echo-n $selection | sed-e "s/-. *$/") gets the product part, which is to cut off the variable part. Sed editor is used here, - e means to execute multiple commands, but there is only one, double quotation mark s means to replace, here is - followed by any character, and then replaced with empty at the end of any character, that is to say, chop - after. Check the validity of product as soon as you get production. check_product $produc, where the check_product function is used:

  1. # check to see if the supplied product is one we can build  
  2. function check_product()  
  3. {  
  4.     T=$(gettop)  
  5.     if [ ! "$T" ]; then  
  6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
  7.         return  
  8.     fi  
  9.         TARGET_PRODUCT=$1 \  
  10.         TARGET_BUILD_VARIANT= \  
  11.         TARGET_BUILD_TYPE= \  
  12.         TARGET_BUILD_APPS= \  
  13.         get_build_var TARGET_DEVICE > /dev/null  
  14.     # hide successful answers, but allow the errors to show  
  15. }  

The gettop function is called at the beginning of the function, so we need to understand the function of the function first.

  1. function gettop  
  2. {  
  3.     local TOPFILE=build/core/envsetup.mk  
  4.     if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then  
  5.         # The following circumlocution ensures we remove symlinks from TOP.  
  6.         (cd $TOP; PWD= /bin/pwd)  
  7.     else  
  8.         if [ -f $TOPFILE ] ; then  
  9.             # The following circumlocution (repeated below as well) ensures  
  10.             # that we record the true directory name and not one that is  
  11.             # faked up with symlink names.  
  12.             PWD= /bin/pwd  
  13.         else  
  14.             local HERE=$PWD  
  15.             T=  
  16.             while [ !\(f$TOPFILE!\(−f$TOPFILE \) -a \( $PWD != "/" \) ]; do  
  17.                 \cd ..  
  18.                 T=`PWD= /bin/pwd -P`  
  19.             done  
  20.             \cd $HERE  
  21.             if [ -f "$T/$TOPFILE" ]; then  
  22.                 echo $T  
  23.             fi  
  24.         fi  
  25.     fi  
  26. }  

The gettop function first defines a local variable TOPFILE and assigns a value to it, then it is a test statement: if [-n"$TOP"-a-f"$TOP/$TOPFILE"]; then, here - n is to determine whether $TOP is not empty, - a means and, as in C, - F is to determine whether a given variable is a file, then the test statement is if $TOP is not empty. Just as the $TOP/$TOPFILE file exists, execute the following code:

(cd $TOP; PWD= /bin/pwd)

That is to enter the $TOP directory and assign the PWD variable a return value of the PWD command, that is, the strength of the current directory. I tried to search the TOP variable in this script and found that it did not appear and assign values, so the else part should be executed here. In else, we first determine whether the file build/core/envsetup.mk exists. When it is in the top directory of the source code, the file exists, then it is true here. Then the PWD variable is the root directory of the android code. So if souce build/envsetup.sh is in the top-level directory of the android source code, then this function returns. As for the return value of shell functions, it should also be noted that when a function does not return anything, the default return is the result of the last command execution, which is the result of / bin/pwd here. That's the top-level directory of android source code, of course. If the build/core/envsetup.mk does not exist in the top directory, then the while loop will continue to enter the upper directory, and then determine whether $TOPFILE exists and whether it reaches the root directory. If the file does not exist and does not reach the root directory, then a search will be made to the upper directory. Finding this file eventually means finding the top-level directory of the android source code and returning to it. The previous two judgments, if both are true, do not return anything because the current directory must be the top-level directory of the source code. That is to say, this function is to find the top-level directory of the source code. If the current directory is the top-level directory, it will return nothing. If the current directory is not the top-level directory, it will return the strength of the top-level directory.
Looking back at the check_product function, you can see that after you get the top-level directory of the android source code, you can judge whether the T is null or not. If it is null, it means that the top-level directory has not been obtained. At this time, the function returns directly. If everything works, then several variables are defined.

        TARGET_PRODUCT=$1 \
        TARGET_BUILD_VARIANT= \
        TARGET_BUILD_TYPE= \
        TARGET_BUILD_APPS= \

These variables have only one variable which is global because they are not modified by local keywords. Among them, only the first variable is assigned to $1, which is the first parameter of the function. At present, the role of these variables is unknown, so we continue to downward analysis.

This function has one last thing to do:

get_build_var TARGET_DEVICE > /dev/null

Here we call the get_build_var function, which is as follows:

  1. # Get the exact value of a build variable.  
  2. function get_build_var()  
  3. {  
  4.     T=$(gettop)  
  5.     if [ ! "$T" ]; then  
  6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
  7.         return  
  8.     fi  
  9.     (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
  10.       command make --no-print-directory -f build/core/config.mk dumpvar-$1)  
  11. }  
At first glance, this function is very simple. First, get the top-level directory of the android source code, and then check for success, which has been analyzed before. After doing all this, a strange sentence came to us. This statement first goes into the top-level directory of the android source code, then defines two variables, and then executes a command using command. A command is a command in a shell that performs the specified command.
Here is to execute the make command, - f specifies the makefile for it, and at the same time passes in a target. Then config.mk executes.

For this function, let's get a sense of it first: execute get_build_var TARGET_DEVICE from the command line

You can see that generic is printed. Although the function is complex to analyze, it is very simple to do. Cong. MK will include dumpvar.mk, which will extract TARGET_DEVICE from the dumpvar-TARGET_DEVICE variable we passed in, and print $(TARGET_DEVICE). So what it does is simple.

After this function is executed, some return to the lunch function and continue to execute:

  1. if [ $? -ne 0 ]  
  2. then  
  3.     echo  
  4.     echo "** Don't have a product spec for: '$product'"  
  5.     echo "** Do you have the right repo manifest?"  
  6.     product=  
  7. fi  
This is to determine the return value of this function, - ne is not equal to the meaning, if the return value is not equal to 0, then there is a problem.

Assuming everything is OK, continue to execute the code:

  1. local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")  
  2. check_variant $variant  
  3. if [ $? -ne 0 ]  
  4. then  
  5.     echo  
  6.     echo "** Invalid variant: '$variant'"  
  7.     echo "** Must be one of ${VARIANT_CHOICES[@]}"  
  8.     variant=  
  9. fi  
This code extracts variant and checks for legitimacy. Don't forget that the string added by add_lunch_combo is a string in product-variant format. These two parts of the code are very similar, so we won't analyze this code specifically.

The following code is:

  1. if [ -z "$product" -o -z "$variant" ]  
  2. then  
  3.     echo  
  4.     return 1  
  5. fi  
Check if the product and variant are empty, empty is not possible.

  1. export TARGET_PRODUCT=$product  
  2. export TARGET_BUILD_VARIANT=$variant  
  3. export TARGET_BUILD_TYPE=release  
Then the hard-earned product and variant variables are copied to the global variables and exported to the environment variables. When you see these variables later, you know their values.

Then the set_stuff_for_environment function is called, which reads as follows:

  1. function set_stuff_for_environment()  
  2. {  
  3.     settitle  
  4.     set_java_home  
  5.     setpaths  
  6.     set_sequence_number  
  7.     export ANDROID_BUILD_TOP=$(gettop)  
  8.     # With this environment variable new GCC can apply colors to warnings/errors  
  9.     export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'  
  10.     export ASAN_OPTIONS=detect_leaks=0  
  11. }  

This function will continue to add some variables. set_java_home checks the exported JAVA_HOME environment variable, which is the strength of JDK sitting. setpaths function will supplement some paths needed to compile Android for PATH environment variable.

Finally, lunch calls the printconfig function, which prints out the configuration information.

So far, source build/envsetup.sh and lunch are analyzed. Next, I will analyze what the make command does.

Summary: source build/envsetup.sh calls the add_lunch_combo function to add a lot of vendorsetup.sh files under / device and / vendor. Find a directory with a depth of 4 and execute it after you find it. There will be at least one line in it: add_lunch_combo xxxx x, continue adding vendorsetup.sh files under / device and / vendor. The lunch function prints out all the veneer information for your choice. After you enter the selection, the luch command will do a series of checks on your choice, extract product s and variables from them, and eventually export the information for official compilation.

Posted by swamp on Fri, 04 Jan 2019 21:48:09 -0800