1. 程式人生 > >android編譯系統分析(一)source build/envsetup.sh與lunch

android編譯系統分析(一)source build/envsetup.sh與lunch

雖然已經有很多人分析過Android的編譯系統的程式碼了,我也看過他們的部落格,也學到了不少知識,但單純的看別人分析,終究還是理解的不深入,所以,我還是要自己再認真的分析一遍。

想想我們編譯android系統的過程:

首先:source build/envsetup.sh

其次:lunch    ---選擇一個特定的型別

最後:make

按著這個順序,追蹤這看似簡單的幾步,到底有哪些背後的祕密?   

1. source build/envsetup.sh

這個檔案雖然很大,但暫且不需要統統看一遍。它裡面定義了很多函式,這些函式在使用的時候再具體詳細學習,現在主要看看這個指令碼做的事情。即便如此,開啟這個指令碼後第一個函式還是非常吸引人的,因為它裡面介紹了這個指令碼主要要做的事情:

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. Environemnt options:  
  22. - SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that  
  23.                  ASAN_OPTIONS=detect_leaks=0 will be set by default until the  
  24.                  build is leak-check clean.  
  25. Look at the source to view more functions. The complete list is:  
  26. EOF  
  27.     T=$(gettop)  
  28.     local A  
  29.     A=""
  30.     for i in `cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function [az].*/\1/p" | sort | uniq`; do  
  31.       A="$A $i"
  32.     done  
  33.     echo $A  
  34. }  
這個函式列出來主要是它介紹了這個指令碼的一些功能,第一行cat <<EOP是一個HERE文件,意思就是把EOF後面到下一個EOF前面的內容當做一個檔案,然後cat 會接收這個檔案的內容,而cat預設的輸出是標準輸出,也就是這個檔案的內容會被列印到螢幕上來。這些內容介紹了這個指令碼的用法和功能,用法就是“. build/envsetup.sh”注意.後面有個空格,這個.命令就是source命令,也就是說你也可以執行“source build/envsetup.sh”。此外,這個函式介紹了很多函式的功能,比如lunch,m,mm,mmm,cgrep等。

函式只有在呼叫到它的時候才會執行,所以暫時統統不看,現在只看函式外面的內容:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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  
這裡呼叫了unset命令,unset是一個bash命令,它會刪除給定的變數。也就是說它會刪除LUNCH_MENU_CHOICES變數。既然這裡刻意把它刪除了,那麼它肯定要立刻重新構造這個變量了,果然: [plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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  
這幾行呼叫了add_lunch_combo函式,並且傳入了一些引數,這使得我們執行lunch函式的時候,多了這幾條可以選擇的選項。

而這個函式,就是就是重新構造LUNCH_MENU_CHOICES變數:

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  

瞭解過shell程式設計就知道,$1表示傳入函式的第一個引數,然後又定義了一個變數c。

從for迴圈中可以看出,LUNCH_MENU_CHOICES是一個數組,在shell中,可以通過 "變數名[@]"或者“變數名[*]”的方式過得陣列的所有項。然後注意比較陣列中的每一項,如果陣列中已經有傳入的引數項,就繼續返回,否則,就把新的傳入的引數加入到LUNCH_MENU_CHOIES陣列中。

這個指令碼雖然很長,但是正真執行的程式碼沒有多少,就是說當執行source build/envsetup.sh的時候執行的程式碼沒有多少,它裡面大多數內容都是函式的定義。在該檔案的最後,又執行了一點程式碼:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. # Execute the contents of any vendorsetup.sh files we can find.  
  11. for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \  
  12.          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`  
  13. do  
  14.     echo "including $f"  
  15.     . $f  
  16. done  
  17. unset f  


前面的if語句塊就是檢查支援的shell解析器。後面的for語句塊比較關鍵。

這裡使用了shell的反引號的用法,它的作用就是把反引號的內容當做shell命令來執行。然後把執行結果使用echo輸出到螢幕。反引號中,首先使用test命令測試 device目錄是否存在,存在的話就查詢vendorsetup.sh指令碼,並且設定了查詢深度為4層目錄。2> /dev/null 是shell中的重定向語法,這裡把標準錯誤重定向到/dev/null中,也就是,把錯誤統統刪掉,左後把找到的vendorsetup.sh指令碼使用sort命令進行排序。

. $f相當於source $f,也就是source /device和/vendor下找到的所有vendorsetup.sh指令碼。然後,這個指令碼就分析結束了,接下來,自然是要去分析/device和/vendor下的vendorsetup.sh指令碼了。

2./device/vendor下的vendorsetup.sh

執行一下source build/envsetup.sh,會列印一下內容:

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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  
這裡只列出了一部分,那以第一條為例,看看它的內容: [html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. add_lunch_combo mini_emulator_arm64-userdebug  
這個函式我們已經分析過了,而且這個檔案也只有這麼一行,這裡列出的這幾個vendorsetup.sh指令碼都是隻有這麼一行,他就是在lunch的選單中新增一項。當然也不都是如此,在有些廠家的vendorsetup.sh也會做一些其他的工作,但是,不管做多少其他的事情,第一件事情似乎都是一定的,就是呼叫add_lunch_combo 新增一項。

因此,總結來說,envsetup.sh指令碼做了這樣的事情:


3.lunch

編譯android的時候,執行完souce build/envsetup.sh,我們還需要執行lunch,選擇一個特定的單板。

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. function lunch()  
  2. {  
  3.     local answer  
  4.     if [ "$1" ] ; then  
  5.         answer=$1  
  6.     else  
  7.         print_lunch_menu  
  8.         echo -n "Which would you like? [aosp_arm-eng] "  
  9.         read answer  
  10.     fi  
  11.     local selection=  
  12.     if [ -z "$answer" ]  
  13.     then  
  14.         selection=aosp_arm-eng  
  15.     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")  
  16.     then  
  17.         if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]  
  18.         then  
  19.             selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}  
  20.         fi  
  21.     elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")  
  22.     then  
  23.         selection=$answer  
  24.     fi  
  25.     if [ -z "$selection" ]  
  26.     then  
  27.         echo  
  28.         echo "Invalid lunch combo: $answer"  
  29.         return 1  
  30.     fi  
  31.     export TARGET_BUILD_APPS=  
  32.     localproduct=$(echo -n $selection | sed -e "s/-.*$//")  
  33.     check_product $product  
  34.     if [ $? -ne 0 ]  
  35.     then  
  36.         echo  
  37.         echo "** Don't have a product spec for: '$product'"  
  38.         echo "** Do you have the right repo manifest?"  
  39.         product=  
  40.     fi
  41.     local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")  
  42.     check_variant $variant  
  43.     if [ $? -ne 0 ]  
  44.     then  
  45.         echo  
  46.         echo "** Invalid variant: '$variant'"  
  47.         echo "** Must be one of ${VARIANT_CHOICES[@]}"  
  48.         variant=  
  49.     fi
  50.     if [ -z "$product" -o -z "$variant" ]  
  51.     then  
  52.         echo  
  53.         return 1  
  54.     fi  
  55.     export TARGET_PRODUCT=$product  
  56.     export TARGET_BUILD_VARIANT=$variant  
  57.     export TARGET_BUILD_TYPE=release
  58.     echo  
  59.     set_stuff_for_environment  
  60.     printconfig  
  61. }  
這個函式首先判斷有沒有傳入引數,如果傳入了就把值賦給answer這個變數,如果沒有傳引數,也就是說知識執行了lunch,那麼就會呼叫fprint_lunch_menu函式顯示一份選單出來,顯示出來後,接受使用者的輸入。

print_lunch_menu函式如下:

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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.     local i=1
  9.     local choice  
  10.     for choice in ${LUNCH_MENU_CHOICES[@]}  
  11.     do  
  12.         echo "     $i. $choice"  
  13.         i=$(($i+1))  
  14.     done  
  15.     echo  
  16. }  
可以看到,這個函式輸出了一些頭資訊以後,就輸出LUNCH_MENU_CHOICES陣列,這裡通過for遍歷整個這個陣列,列印每一項。這個陣列在之前就已經分析過,它的每一項是通過呼叫add_lunch_combo函式新增進來的。

1.不管執行lunch的時候有沒有傳入引數,最終都會將選擇的結果存入到answer變數中,那麼接下來,當然是檢查輸入的引數的合法性了。這裡首先判斷answer變數是不是為0,如果為零的話,selection變數就會賦值為aosp_arm-eng,但是如果不為零的話,首先會輸出answer的值,使用echo -n $answer,-n選項是的輸出的時候不輸出換行符,answer變數的值並沒有輸出到螢幕上,而是通過管道傳給了後面一條命令:grep -q -e "^[0-9][0-9]*$",這條命令從answer變數中搜尋以兩位數字開頭的字串,如果找到,就認為是輸入的是數字。然後進一步對這個數字做有沒有越界的檢查。如果這個數字小於LUNCH_MENU_CHOICES的大小,就會把LUNCH_MENU_CHOICES的地$answer-1項複製給selection變數。

2.如果傳入的不是數字,就會嘗試匹配這樣的字串grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$",-q標示quiet模式,也就是不列印資訊,-e表示後面跟的是一個用於匹配的模式,這裡,^標示匹配開始,[]中的^則標示排除,\是轉譯字元,因為-可能是表示範圍的符號。所以,這裡匹配的是不以連續兩個--開始,後面跟任意字元,然後必須有個-,最後又不以--結束的字串。其實,這裡就是期望得到product-varient的形式。也就是我們之前使用add_lunch_combo新增的那些字串的格式。比如:aosp_arm-eng,product就是aosp_arm,varient就是eng.中間的-是必須的。如果發現符合要求的格式的話selection變數就會被$answer賦值,也就是說,selection其實就是一個product-varient模式的字串。

3.如果既不是數字,又不是合法的字串,或者是數字,這個時候,selection應該就沒有被賦值過,這個時候-z就成立了,那麼就會提示你輸入的東西不對。並且直接返回。

如果輸入合法,通過檢測後,  export TARGET_BUILD_APPS=   這行匯出了一個變數,但是它的值是空的。而之後的 local product=$(echo -n $selection | sed -e "s/-.*$//")這句則是得到了product部分,也就是把varient部分砍掉了。這裡使用了sed編輯器,-e 表示執行多條命令,但這裡只有一條,雙引號的s表示替換,這裡就是把-後面接任意字元,然後以任意字元結尾的部分替換為空,也就是砍掉-後面的了。得到produce後就開始檢查product的合法性。 check_product $produc  ,這裡使用了check_product函式:

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  

函式的一開始就呼叫了gettop函式,所以,我們得先弄明白這個函式的功能:

[java] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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 \) -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. }  

gettop函式一開始定義了一個區域性變數TOPFILE,並且給他賦了值,然後是一個測試語句:if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then,這裡-n 是判斷 $TOP是否不為空, -a 就是and的意思,和C語言中的&&相同, -f是判斷給定的變數是不是檔案,那麼,這個測試語句就是如果 $TOP不為空 切同時 $TOP/$TOPFILE檔案存在,就執行下面的程式碼:

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

也就是進入到$TOP目錄下,並且給PWD變數賦一個pwd命令的返回值,也就是當前目錄的路勁。我試著在這個指令碼中搜索TOP變數,發現它並沒有出現並且賦值,所以,這裡應該執行else部分。else中,首先判斷build/core/envsetup.mk這個檔案是否存在,當在原始碼頂層目錄下的時候,這個檔案是存在的,那麼這裡為真,然後PWD變數就是android程式碼的根目錄。所以如果souce build/envsetup.sh的時候,如果處於android原始碼的頂級目錄,那麼這個函式就返回了。關於shell函式的返回值問題,還需要留意一下,當一個函式沒有返回任何內容的時候,預設返回的是最後一條命令的執行結果,也就是這裡的/bin/pwd的結果。那當然就是android原始碼的頂級目錄了。這個時候如果不在頂級目錄,build/core/envsetup.mk應該不存在,這個時候就會while迴圈不斷的進入道上層目錄,然後判斷$TOPFILE是否存在,並且判斷是否到達根目錄了,如果這個檔案不存在且沒有到達根目錄,那麼就會一個往上一級目錄查詢。最終如果找到了這個檔案,就意味著找到了android原始碼的頂層目錄,並把這個路勁返回。前面的兩次判斷如果都成立的話也沒有返回任何東西,是因為,當前目錄肯定就是原始碼的頂級目錄了。也就是說,這個函式就是找到原始碼的頂級目錄,如果當前目錄就是頂級目錄,就什麼也不返回,如果當前目錄不是頂級目錄,就返回頂級目錄的路勁。
再回過頭來看check_product函式,可以看到在獲取到android原始碼的頂級目錄以後,就會判斷這個T是不是空值,空的的話就說明沒有獲取到頂級目錄,這個時候這個函式就直接返回了。如果一切正常,那麼就會定義幾個變數。

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

這幾個變數只有一個變數都是全域性變數,因為沒有加local關鍵字修飾,它們中,只有第一個變數賦值為$1,也就是這個函式的第一個引數。目前,這幾個變數的作用還不得而知,所以,我們繼續向下分析。

這個函式還有最後一件事情要做:

get_build_var TARGET_DEVICE > /dev/null

這裡呼叫了get_build_var這個函式,這個函式如下:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  
乍看之下,這個函式非常簡單,一開始,就是獲得android原始碼的頂層目錄,然後檢查是否獲得成功,這在之前已經分析過了。做完這些以後,迎來了一個看著很奇怪的語句。這個語句先是進入到android原始碼的頂層目錄,然後然後定義了兩個變數,之後使用command執行了一條命令。command是一個shell中的命令,它的功能是執行指定的命令,
這裡就是執行make命令,-f給它指定了makefile,同時還傳入了一個目標。然後,config.mk就會執行。

這個函式,我們不妨感性認識一下先:在命令列中執行get_build_var TARGET_DEVICE

可以看到列印了generic這個值。這個函式雖然分析起來比較複雜,但是它做的非常簡單。config.mk會include dumpvar.mk,這個檔案中會提取我們傳入的dumpvar-TARGET_DEVICE變數中的TARGET_DEVICE,然後列印$(TARGET_DEVICE)。所以它做的事情很簡單。

這個函式執行完以後,有返回到lunch函式繼續執行:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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  
這裡就是判斷這個函式的返回值,-ne是不等於的意思,如果返回值不等於0,那麼就出問題了。

假定一切正常,繼續執行程式碼:

[plain] view plain copy print?在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  
這段程式碼時提取variant並且檢查是否合法,不要忘了前面說過,add_lunch_combo新增的字串就是product-variant格式的字串,這兩部分程式碼非常相似,就不具體分析這段程式碼了。

下面的程式碼是:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. if [ -z "$product" -o -z "$variant" ]  
  2. then  
  3.     echo  
  4.     return 1  
  5. fi  
檢查得到的product和variant是不是為空,空就不可以 了。 [plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. export TARGET_PRODUCT=$product  
  2. export TARGET_BUILD_VARIANT=$variant  
  3. export TARGET_BUILD_TYPE=release  
然後把辛辛苦苦得到的product和variant變數複製給全域性變數並且匯出為環境變數。以後看到這幾個變數,知道它們的值就可以了。

然後呼叫了set_stuff_for_environment函式,這個函式內容如下:

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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. }  

這個函式會繼續補充一些變數。其中set_java_home會檢查匯出JAVA_HOME這個環境變數,這個環境變數就是JDK坐在的路勁,setpaths函式會給PATH環境變數補充編譯Android需要的一些路徑。

最後lunch呼叫了 printconfig函式,這個函式打印出了配置資訊。

至此,source build/envsetup.sh 和 lunch就分析完了。接下來將分析make 命令所做的事情。

小結:source build/envsetup.sh會呼叫add_lunch_combo函式新增很多單板資訊進來,同時還會查詢/device和/vendor下的vendorsetup.sh檔案,查詢深度為4級目錄,找到後就執行它,它裡面至少會有這麼一行:add_lunch_combo xxxx,繼續新增單板資訊。lunch函式則會打印出所有的單板資訊供你選擇,你輸入選擇後,luch命令會對你的選擇做一系列檢測,並從中提取出product和varient,並最終匯出這些資訊,供正式編譯的時候使用。