1. 程式人生 > >Android編譯命令

Android編譯命令

生成 sha mds 賦值 config pty 格式 n) rgs

目錄

  • 說在前面
  • 編譯流程
  • 編譯指令
    • 代碼編譯
    • 代碼檢索
    • 其他指令

說在前面

從最開始接觸Android系統開始,每次進行代碼編譯都需要網上搜索編譯指令。後來大致熟悉了Android的編譯體系,加深了對Android編譯的理解。

編譯流程

編譯 android 系統的流程,首先執行 source build/envsetup.sh,然後執行 lunch 選擇板級配置,最後執行 make 編譯

  • source build/envsetup.sh
    流程

腳本中的第一個函數是hmm,介紹了腳本的一些功能,cat <<EOF 表示把 EOF 中的內容當做文件打印到標準輸出,包含了腳本的使用說明和功能介紹。

function hmm() {
cat <<EOF
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:     lunch <product_name>-<build_variant>
- tapas:     tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
- croot:     Changes directory to the top of the tree.
- m:         Makes from the top of the tree.
- mm:        Builds all of the modules in the current directory, but not their dependencies.
- mmm:       Builds all of the modules in the supplied directories, but not their dependencies.
             To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:       Builds all of the modules in the current directory, and their dependencies.
- mmma:      Builds all of the modules in the supplied directories, and their dependencies.
- provision: Flash device with all required partitions. Options will be passed on to fastboot.
- cgrep:     Greps on all local C/C++ files.
- ggrep:     Greps on all local Gradle files.
- jgrep:     Greps on all local Java files.
- resgrep:   Greps on all local res/*.xml files.
- mangrep:   Greps on all local AndroidManifest.xml files.
- mgrep:     Greps on all local Makefiles files.
- sepgrep:   Greps on all local sepolicy files.
- sgrep:     Greps on all local source files.
- godir:     Go to the directory containing a file.

Environment options:
- SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that
                 ASAN_OPTIONS=detect_leaks=0 will be set by default until the
                 build is leak-check clean.

Look at the source to view more functions. The complete list is:
EOF
    local T=$(gettop)
    local A=""
    local i
    for i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
      A="$A $i"
    done
    echo $A
}

函數只有在調用時才會執行,在執行 source build/envsetup.sh 時該函數並不會被調用,而只有在終端運行 hmm 時才會輸出,輸出使用說明和腳本中所有函數名稱,但顯然不夠準確。

[android_8.1]$ source build/envsetup.sh
including device/rockchip/rk3399/vendorsetup.sh
including sdk/bash_completion/adb.bash
[android_8.1]$ hmm
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:     lunch <product_name>-<build_variant>
- tapas:     tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
- croot:     Changes directory to the top of the tree.
- m:         Makes from the top of the tree.
- mm:        Builds all of the modules in the current directory, but not their dependencies.
- mmm:       Builds all of the modules in the supplied directories, but not their dependencies.
             To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:       Builds all of the modules in the current directory, and their dependencies.
- mmma:      Builds all of the modules in the supplied directories, and their dependencies.
- provision: Flash device with all required partitions. Options will be passed on to fastboot.
- cgrep:     Greps on all local C/C++ files.
- ggrep:     Greps on all local Gradle files.
- jgrep:     Greps on all local Java files.
- resgrep:   Greps on all local res/*.xml files.
- mangrep:   Greps on all local AndroidManifest.xml files.
- mgrep:     Greps on all local Makefiles files.
- sepgrep:   Greps on all local sepolicy files.
- sgrep:     Greps on all local source files.
- godir:     Go to the directory containing a file.

Environment options:
- SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that
                 ASAN_OPTIONS=detect_leaks=0 will be set by default until the
                 build is leak-check clean.

Look at the source to view more functions. The complete list is:
addcompletions add_lunch_combo build_build_var_cache cgrep check_product check_variant choosecombo chooseproduct choosetype choosevariant core coredump_enable coredump_setup cproj croot destroy_build_var_cache findmakefile get_abs_build_var getbugreports get_build_var getdriver getlastscreenshot get_make_command getprebuilt getscreenshotpath getsdcardpath gettargetarch gettop ggrep godir hmm is isviewserverstarted jgrep key_back key_home key_menu lunch _lunch m make mangrep mgrep mm mma mmm mmma pez pid printconfig print_lunch_menu provision qpid rcgrep resgrep runhat runtest sepgrep set_java_home setpaths set_sequence_number set_stuff_for_environment settitle sgrep smoketest stacks startviewserver stopviewserver systemstack tapas tracedmdump treegrep _wrap_build

下面先挑出只在函數外的內容進行分析

(1)
VARIANT_CHOICES=(user userdebug eng)

(2)
unset LUNCH_MENU_CHOICES

(3)
# add the default one here
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng

(4)
# Execute the contents of any vendorsetup.sh files we can find.
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`          `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
    echo "including $f"
    . $f
done
unset f

(5) addcompletions

(1) VARIANT_CHOICES 定義為一個數組,包含3種模式可選,在某些腳本函數中會使用到。

(2) unset LUNCH_MENU_CHOICES 刪除給定的環境變量內容,重新進行設置

(3) 調用 add_lunch_combo 函數, 並傳入參數,看下函數的具體實現

function add_lunch_combo()
{
    local new_combo=$1
    local c
    for c in ${LUNCH_MENU_CHOICES[@]} ; do
        if [ "$new_combo" = "$c" ] ; then
            return
        fi
    done
    LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}

這裏對 LUNCH_MENU_CHOICES 數組的內容進行循環,與函數後傳入的參數進行對比,如果數組中已經包含傳入的參數項,直接返回;否則就把傳入的參數加入到數組項中。

(4)檢查是否存在 devicevendorproduct 目錄,如果存在的話,就在目錄下查找腳本 vendorsetup.sh,並設定了查找深度為4層目錄,2> /dev/null 這裏將標準錯誤重定向到/dev/null,也就是不輸出,然後把找到的腳本文件進行排序,最後調用找到的腳本文件。當前的腳本文件內容如下:

including device/rockchip/rk3399/vendorsetup.sh

add_lunch_combo rk3399-userdebug
add_lunch_combo rk3399-user
add_lunch_combo rk3399_mid-userdebug
add_lunch_combo rk3399_mid-user
add_lunch_combo rk3399pro-userdebug
add_lunch_combo rk3399pro-user
add_lunch_combo rk3399_box-userdebug
add_lunch_combo rk3399_box-user

(5) 查找 sdk/bash_completion 目錄下所有以 .bash 結尾的文件,當前僅有一個

including sdk/bash_completion/adb.bash

前面已經分析過這個函數了,實際上還是把這些板級配置添加到數組中,而通過運行 lunch 命令時進行選擇。

  • lunch 流程

通過執行 lunch 選擇合適的板級配置

function lunch()
{
    local answer

    if [ "$1" ] ; then
        answer=$1
    else
        print_lunch_menu
        echo -n "Which would you like? [aosp_arm-eng] "
        read answer
    fi

    local selection=

    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    else
        selection=$answer
    fi

    export TARGET_BUILD_APPS=

    local product variant_and_version variant version

    product=${selection%%-*} # Trim everything after first dash
    variant_and_version=${selection#*-} # Trim everything up to first dash
    if [ "$variant_and_version" != "$selection" ]; then
        variant=${variant_and_version%%-*}
        if [ "$variant" != "$variant_and_version" ]; then
            version=${variant_and_version#*-}
        fi
    fi
    
    if [ -z "$product" ]
    then
        echo
        echo "Invalid lunch combo: $selection"
        return 1
    fi

    TARGET_PRODUCT=$product     TARGET_BUILD_VARIANT=$variant     TARGET_PLATFORM_VERSION=$version     build_build_var_cache
    if [ $? -ne 0 ]
    then
        return 1
    fi

    export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
    if [ -n "$version" ]; then
      export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
    else
      unset TARGET_PLATFORM_VERSION
    fi
    export TARGET_BUILD_TYPE=release

    echo

    set_stuff_for_environment
    printconfig
    destroy_build_var_cache
}

首先判斷有沒有傳入參數,如果有參數就把值賦給answer變量,否則調用 print_lunch_menu 打印板級配置菜單項,並接受用戶終端輸入。

function print_lunch_menu()
{
    local uname=$(uname)
    echo
    echo "You're building on" $uname
    echo
    echo "Lunch menu... pick a combo:"

    local i=1
    local choice
    for choice in ${LUNCH_MENU_CHOICES[@]}
    do
        echo "     $i. $choice"
        i=$(($i+1))
    done

    echo
}

該函數遍歷 LUNCH_MENU_CHOICES 數組,輸出每一項內容,而這些數組項則是通過調用 add_lunch_combo 函數添加進來的。

如果傳入的參數為零,selection 變量就會賦值為 aosp_arm-eng,否則會輸出answer的值,並從該值中搜索以2位數開始的字符串,如果找到的話,再進一步對這個數字進行數組越界的檢查。如果這個數字小於 LUNCH_MENU_CHOICES 的數組項,就會把 LUNCH_MENU_CHOICES 的這一項賦給 selection 變量。

如果 selection 為空,則直接報錯退出。

然後根據獲取的數組項,進行字符串截取,從 selection 中取出 - 前面的字符串保存到 product,後面的字符串保存到 variant_and_version,然後再對 variant_and_version 進行字符串截取,保存到 variant

然後根據以上的數據賦值給 TARGET_PRODUCTTARGET_BUILD_VARIANTTARGET_PLATFORM_VERSION等,然後調用 build_build_var_cache 對編譯時必需的環境變量進行賦值和處理。

最後
調用 set_stuff_for_environment 函數設置編譯環境的變量,包括 ANDROID_BUILD_PATHSJAVA_HOME等。
調用 printconfig 打印最終準備好的環境變量。參考如下:

============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=8.1.0
TARGET_PRODUCT=rk3399
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_PLATFORM_VERSION=OPM1
TARGET_BUILD_APPS=
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=cortex-a53
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a15
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.10.0-862.el7.x86_64-x86_64-with-centos-7.5.1804-Core
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=OPM8.181205.001
OUT_DIR=out
AUX_OS_VARIANT_LIST=
============================================

調用 destroy_build_var_cache 清除不需要的中間環節產生的變量值。

  • make 流程

谷歌在 Android 7.0 開始引入了 ninja 進行系統編譯,相對於 Makefile 組織編譯系統來說,ninja 在大的項目管理的速度和並行方面有突出的優勢。但是現有的 Android 還是由 Makefile 組織,所以谷歌引入了 kati soong,將 Android.mk 文件轉化成 ninja 文件,使用 ninja 文件對編譯系統進行管理。

但從 Android 8.0 開始,引入了 Android.bp 文件來替代之前的 Android.mk 文件。與 Android.mk 文件不同,Android.bp 只是純粹的配置文件,不包括分支、循環等流程控制,它使用 blueprint 框架來解析,最終轉換成 ninja 文件。

Android 9.0 則強制使用 Android.bp 來構建編譯。

blueprint 是生成、解析 Android.bp 的工具,是 soong 的一部分。 soong 則是專為 Android 編譯而設計的工具,blueprint 只是解析文件的形式,而 soong 則解釋內容的含義。

Android.mk 可以通過 soong 提供的 androidmk 轉換成 Android.bp,但僅限簡單配置。目前 Android 8.0 的編譯流程中,仍然是使用 kati 來做的轉換。

Android.bp --> blueprint --> soong --> ninja
Makefile && Android.mk --> kati --> ninja
Android.mk --> soong --> blueprint --> Android.bp

現存的 Android.mkAndroid.bp 都會被轉換為 ninjaAndroid.mk 和其他的 Makefile,會生成out/build-<product_name>.ninja文件,而Android.bp則會生成out/soong/build.ninja。此外,還會生成一個較小的out/combined-<product_name>.ninja文件,負責把二者結合起來,作為執行入口。

當執行 make 的時候,會查找當前目錄價的 Makefile 文件並執行,內容如下:

### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###

所以,真正的 Makefile 入口是 build/core/main.mk

而我們在執行編譯的時候並沒有傳入目標,那麽就一定會有一個默認的目標,在 build/core/main.mk 能夠找到如下內容:

ifndef KATI

host_prebuilts := linux-x86
ifeq ($(shell uname),Darwin)
host_prebuilts := darwin-x86
endif

.PHONY: run_soong_ui
run_soong_ui:
    +@prebuilts/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(MAKECMDGOALS)

.PHONY: $(MAKECMDGOALS)
$(sort $(MAKECMDGOALS)) : run_soong_ui
    @#empty

else # KATI

其中 MAKECMDGOALS 這個命名是 make 執行時後面的參數賦值,也就是我們執行任何 make 的時候都是執行 run_soong_ui 這個目標,這樣 androidMakefile 切換為 soong 進行了編譯流程,後面跟 make 就沒有關系了。

  • mm 流程

當我們單獨編譯某個模塊時,可以在這個模塊目錄下輸入 mm 命令進行編譯,流程和make差不多,只不過目標是單獨模塊組成,會生成獨立的ninja文件。

function mm()
{
    local T=$(gettop)
    local DRV=$(getdriver $T)
    # If we're sitting in the root of the build tree, just do a
    # normal build.
    if [ -f build/soong/soong_ui.bash ]; then
        _wrap_build $DRV $T/build/soong/soong_ui.bash --make-mode $@
    else
        # Find the closest Android.mk file.
        local M=$(findmakefile)
        local MODULES=
        local GET_INSTALL_PATH=
        local ARGS=
        # Remove the path to top as the makefilepath needs to be relative
        local M=`echo $M|sed 's:'$T'/::'`
        if [ ! "$T" ]; then
            echo "Couldn't locate the top of the tree.  Try setting TOP."
            return 1
        elif [ ! "$M" ]; then
            echo "Couldn't locate a makefile from the current directory."
            return 1
        else
            local ARG
            for ARG in $@; do
                case $ARG in
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;
                esac
            done
            if [ -n "$GET_INSTALL_PATH" ]; then
              MODULES=
              ARGS=GET-INSTALL-PATH-IN-$(dirname ${M})
              ARGS=${ARGS//\//-}
            else
              MODULES=MODULES-IN-$(dirname ${M})
              # Convert "/" to "-".
              MODULES=${MODULES//\//-}
              ARGS=$@
            fi
            if [ "1" = "${WITH_TIDY_ONLY}" -o "true" = "${WITH_TIDY_ONLY}" ]; then
              MODULES=tidy_only
            fi
            ONE_SHOT_MAKEFILE=$M _wrap_build $DRV $T/build/soong/soong_ui.bash --make-mode $MODULES $ARGS
        fi
    fi
}

整個編譯體系的Makefile大致可區分為:系統核心的Makefile,產品級的Makefile,具體模塊的Makefile.

  • Android.mk語法

在源碼樹中每一個模塊的根目錄下都有一個Android.mk文件,編譯系統正是以模塊為單位進行編譯的,每個模塊有唯一的模塊名,一個模塊可以依賴多個其他模塊,模塊之間的依賴關系是通過模塊名來引用的(熟悉OpenWRT或者Buildroot編譯體系的同學對此應該不會陌生)。

frameworks/native/cmds/servicemanager/Android.mk 為例,解析下具體的語法格式:

LOCAL_PATH:= $(call my-dir) //設置為當前文件夾所在路徑

svc_c_flags =       -Wall -Wextra -Werror \ //設置svc_c_flags編譯選項

ifneq ($(TARGET_USES_64_BIT_BINDER),true)
ifneq ($(TARGET_IS_64_BIT),true)
svc_c_flags += -DBINDER_IPC_32BIT=1
endif
endif

include $(CLEAR_VARS)   //清空編譯環境的變量,可能由其他模塊設置過的變量
LOCAL_SHARED_LIBRARIES := liblog    //當前模塊在運行時依賴的動態庫
LOCAL_SRC_FILES := bctest.c binder.c        //當前模塊包含的所有源碼
LOCAL_CFLAGS += $(svc_c_flags)  //設置CFLAGS編譯選項
LOCAL_MODULE := bctest  //當前模塊的名稱,具有唯一性
LOCAL_MODULE_TAGS := optional   //當前模塊的標簽,設置為默認值(可能為debug,eng,user等)
include $(BUILD_EXECUTABLE) //編譯成可執行程序

include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog libcutils libselinux
LOCAL_SRC_FILES := service_manager.c binder.c
LOCAL_CFLAGS += $(svc_c_flags)
LOCAL_MODULE := servicemanager
LOCAL_INIT_RC := servicemanager.rc
include $(BUILD_EXECUTABLE)

除了上面的環境變量,編譯系統還設置了很多其他的環境變量,如下:

環境變量 說明
LOCAL_PACKAGE_NAME 當前APK應用的名稱(具有唯一性)
LOCAL_C_INCLUDES C/C++所需的頭文件路徑
LOCAL_STATIC_LIBRARIES 當前模塊靜態連接需要的庫
LOCAL_STATIC_JAVA_LIBRARIES 當前模塊依賴的Java靜態庫
LOCAL_JAVA_LIBRARIES 當前模塊依賴的Java動態庫
LOCAL_CERTIFICATE 簽署當前應用的證書名稱
BUILD_EXECUTABLE 編譯成可執行程序
BUILD_STATIC_JAVA_LIBRARY 編譯成Java靜態庫
BUILD_SHARED_LIBRARY 編譯成C/C++動態庫

此外,編譯系統還定義了一些函數,如下:

    $(call all-java-files-under, ):獲取指定目錄下的所有Java文件;
    $(call all-c-files-under, ):獲取指定目錄下的所有C文件;
    $(call all-Iaidl-files-under, ) :獲取指定目錄下的所有AIDL文件;
    $(call all-makefiles-under, ):獲取指定目錄下的所有Make文件;
  • Android.bp語法

Android.bp 是一種配置文件,語法類似於JSON,文件中僅記錄當前模塊的信息,沒有條件判斷或者控制流語句,控制邏輯需要Go來處理。bp文件的語法類似於Bazel,由Go語言進行解析,轉換為Ninja文件。

Bazel的參考文檔

frameworks/base/Android.bp 文件為例,具體如下:

// ====  c++ proto device library  ==============================
cc_library {
    name: "libplatformprotos",
    host_supported: true,
    proto: {
        export_proto_headers: true,
        include_dirs: ["external/protobuf/src"],
    },

    target: {
        host: {
            proto: {
                type: "full",
            },
            srcs: [
                "core/proto/**/*.proto",
                "libs/incident/**/*.proto",
            ],
        },
        android: {
            proto: {
                type: "lite",
            },
            // We only build the protos that are optimized for the lite
            // runtime, as well as the only protos that are actually
            // needed by the device.
            srcs: [
                "core/proto/android/service/graphicsstats.proto",
            ],
            shared: {
                enabled: false,
            },
        },
    },
}

subdirs = [
    "core/jni",
    "libs/*",
    "media/*",
    "tools/*",
    "native/android",
    "native/graphics/jni",
]

optional_subdirs = [
    "core/tests/utiltests/jni",
]

定義一個模塊從模塊的類型開始,如例子中的"cc_library",模塊會包含一些屬性,格式為"{name: value}".

編譯指令

代碼編譯

編譯指令 說明
m 在源碼樹的根目錄執行編譯
mm 編譯當前路徑下所有模塊,不包含依賴
mmm [module_path] 編譯指定路徑下所有模塊,不包含依賴
mma 編譯當前路徑下所有模塊,包含依賴
mmma [module_path] 編譯指定路徑下所有模塊,包含依賴
make [module_name] 無參數,則表示編譯整個Android代碼

部分模塊的編譯指令:

# make help

Common make targets:
--------------------------------------------------------------------------------
droid                   Default target
clean                   (aka clobber) equivalent to rm -rf out/
snod                    Quickly rebuild the system image from built packages
vnod                    Quickly rebuild the vendor image from built packages
offline-sdk-docs        Generate the HTML for the developer SDK docs
doc-comment-check-docs  Check HTML doc links & validity, without generating HTML
libandroid_runtime      All the JNI framework stuff
framework               All the java framework stuff
services                The system server (Java) and friends
help                    You're reading it right now

#### build completed successfully  ####

模塊 make mmm
init make init mmm system/core/init
zygote make app_process mmm frameworks/base/cmds/app_process
system_services make services mmm frameworks/base/services
java framework make framework mmm framworks/base
framework-res make framework-res mmm frameworks/base/core/res
jni framework make libandroid_runtime mmm frameworks/base/core/jni
binder make libbinder mmm frameworks/native/libs/binder

上述mmm命令同樣適用於mm/mma/mmma,編譯系統采用的是增量編譯,只會編譯發生變化的目標文件。

代碼檢索

Android源碼非常龐大,直接使用grep/ag來搜索代碼,可能會耗費大量時間。根據具體需求,可以選擇合適的檢索指令,有效節省代碼搜索時間,提高準確度。

指令 說明
cgrep Greps on all local C/C++ files.
ggrep Greps on all local Gradle files.
jgrep Greps on all local Java files.
resgrep Greps on all local res/*.xml files.
mangrep Greps on all local AndroidManifest.xml files.
mgrep Greps on all local Makefiles files.
sepgrep Greps on all local sepolicy files.
sgrep Greps on all local source files.

其他指令

上面介紹了常用的編譯和檢索指令,還有一些其他指令,可以執行 hmm 查詢指令的幫助信息。

指令 說明
hmm 查詢所有指令的幫助信息
gettop 查詢Android源碼的根目錄
printconfig 打印設置的編譯參數配置
print_lunch_menu 打印lunch可選的板級配置
godir 跳轉到包含某個文件的目錄
findmakefile 查詢當前目錄所在工程的Android.mk文件路徑

Android編譯命令