免费高清特黄a大片,九一h片在线免费看,a免费国产一级特黄aa大,国产精品国产主播在线观看,成人精品一区久久久久,一级特黄aa大片,俄罗斯无遮挡一级毛片

分享

跨平臺:GN實踐詳解(ninja, 編譯, windows/mac/android實戰(zhàn))

 oskycar 2021-05-21

目錄

一、概覽
二、跨平臺代碼編輯器
三、GN入門
四、示范工程
五、關鍵細節(jié)
六、結語 [編譯器選項]
 
其中前兩部分是前綴部分,原本沒有跨平臺構建經驗和知識的同學可以借助來幫助理解,后四部分則是講述GN工程的基本結構、如何搭建一個GN構建的工程、以及關鍵的一些GN知識

一、概覽

如何開始這個話題是我比較在意的,因為對于部分人而言,真正從思維和理解上切入這篇文章真正要闡述的點是有困難的。這在于跨平臺編譯和開發(fā)這塊,如果沒有一定的經歷,可能會很難理解這些跨平臺工具出現(xiàn)的真正意義,它們所要解決的問題是什么,所以這里我需要做一點前綴工作,如果對GN/GYP/CMake這類話題已經有上下文的同學可以直接跳過這部分贅述內容,同時也希望借助對這篇文章的理解,大家往后對于跨平臺工具的理解不局限于GN,而是有自己的認知。在不斷發(fā)展和變化的大潮中,工具始終是在演變,能夠始終抓住緊要的精髓,見到陌生的工具時對它從根上有淡定從容的認知,以最小的精力消耗掌握它,最快發(fā)揮它的價值服務于我們,才是我們的取勝之道。

集成編譯開發(fā)環(huán)境

以實際工作為例,我們在開發(fā)過程中需要接觸到的有源代碼工程管理、代碼編輯、代碼編譯,而統(tǒng)一囊括了這幾部分的開發(fā)環(huán)境就是集成開發(fā)環(huán)境,由于集成開發(fā)環(huán)境的存在,大部分人其實只需要重點關注代碼編輯這塊,工程管理和編譯集成開發(fā)環(huán)境已經內置做了絕大部分工作,所以理所當然也就容易忽略它們,我們也有必要重新認識下這幾部分工作。

工程管理:源代碼包含、模塊輸出設置(動態(tài)庫/靜態(tài)庫/可執(zhí)行文件、模塊依賴關系配置、編譯選項設置、鏈接選項設置、生成后執(zhí)行事件……等非常多的設置。
 
代碼編輯:自動補齊、格式對齊、定義跳轉、關鍵字高亮、代碼提示……等我們編寫代碼時用著很人性化的功能。
 
代碼編譯:語法語音分析、代碼優(yōu)化、錯誤提示、警告提示、最終生成二進制代碼和符號表。

各操作系統(tǒng)應用開發(fā)的集成開發(fā)環(huán)境

Windows應用開發(fā):Visual Studio,簡稱VS,是微軟提供和支持的集成開發(fā)編譯環(huán)境,用于開發(fā)Windows上應用。
Mac/iOS應用開發(fā): Xcode,是蘋果提供和支持的集成開發(fā)編譯環(huán)境,用于開發(fā)Mac和iOS應用。
Android應用開發(fā):Android Studio,簡稱AS,是谷歌提供和支持的集成開發(fā)環(huán)境,用于開發(fā)Android應用。
Ubuntu等Linux應用開發(fā):沒有集成開發(fā)環(huán)境,工程管理使用CMake/GN/GYP…各類工具(也可以使用人腦)、代碼編輯使用vim等工具,編譯器使用GCC。

但實際上,這些集成開發(fā)環(huán)境都不約而同的將編譯相關的細節(jié)很好的隱藏起來了,因為這塊實在是太晦澀和繁瑣,這里我們需要有一個很清晰的認知,那就是這些集成開發(fā)環(huán)境之下,隱藏的編譯器基本上只有:微軟的msvc,GNU的gcc,蘋果的clang+llvm這三大類,讓我們再次展開這幾個集成開發(fā)環(huán)境,一窺的它的全貌
在這里插入圖片描述其中藍色部分就是跨平臺工具的主戰(zhàn)場,旨在脫離各平臺開發(fā)環(huán)境的限制,統(tǒng)一進行工程管理和工程設置,最終由工具自動生成統(tǒng)一的各平臺makefile文件,調用各平臺編譯器完成編譯。

沿途的風景

在通往終點的路上,總有一些風景值得欣賞,如果有感興趣的同學,可以品味下編譯器的技術歷史,以及gcc+llvm這種畸形產物的閑聞逸事:
 
1.msvc,對于微軟而言,閉源是它一直以來的做派,如今有所改善,而編譯器則是那時候的產物,所以理所當然,宇宙最強IDE中的編譯器,只限于windows這個平臺上
 
2.gcc,是GNU開發(fā)的編程語言編譯器,以GPL及LGPL許可證所發(fā)行的自由軟件,開放給所有人,是最通用和廣泛的編譯器
 
3.gcc+llvm,蘋果早期使用的gcc,gcc非常通用,支持多種語言,但對蘋果官方提出的Objective-C編譯器優(yōu)化的訴求愛答不理,導致蘋果只能想辦法自己來做,一般而言,按照保守的重構做法一部分一部分漸進式替換原有模塊是比較理性的思路,蘋果也是采用這種策略,將編譯器分為前后兩端,前端做語義語法分析,后端做指令生成編譯,而后端則是蘋果實現(xiàn)自己愿望的第一步,由此llvm橫空出世。
后端使用llvm,在編譯期間針對性優(yōu)化,例如以顯卡特性為例,將指令轉換為更為高效的GPU指令,提升運行性能。
前端使用gcc,在編譯前期做語義語法分析等行為
 
4.clang+llvm,由于gcc語義語法分析上,對于Objective-C做得不夠細致,gcc+llvm這種組合無法提供更為高級的語法提示、錯誤糾正等能力,所以蘋果開始更近一步,開發(fā)實現(xiàn)了編譯器前端clang,所以到目前為止xcode中缺省設置是clang+llvm,clang在語義和語法上更為先進和人性化,但缺點顯而易見,Clang只支持C,C++和Objective-C,Swift這幾種蘋果體系內會用到的語言。

跨平臺開發(fā)遇到的問題

一般而言,如果一個應用需要在各個操作系統(tǒng)上都有實現(xiàn),基本的方式都是將大部分能復用的代碼和邏輯封裝到底層,減少冗余的開發(fā)和維護工作,像微信、QQ、支付寶等知名軟件,都是這種方式。由于各個操作系統(tǒng)平臺的集成開發(fā)環(huán)境不同,這部分底層工程需要在各個集成開發(fā)環(huán)境中搭建和維護工程、編譯應用,這樣就會產生大量重復工作,任何一個源碼文件的增加/刪除、模塊調整都會涉及到多個集成開發(fā)環(huán)境的工程調整。這個時候,大多數人都會想,如果有這么一個工具,我們用它管理工程和編譯,一份工程能夠自動編譯出各個平臺的應用或底層庫,那將是多么美好的一個事情。

幸運的是,已經有前人替我們感受過這個痛苦的過程,也有前人為我們種好了樹,我們只需要找好一個合適的樹,背靠著它,調整好坐姿,懷著無比感恩的心情在它的樹蔭下乘涼。樹有好多課,該選哪一顆,大家興趣至上自行選擇,這里我們主要介紹我個人傾向的GN這一棵。

原始跨平臺
編寫makefile文件,使用各平臺上的make(微軟的MS nmake、GNU的make)來編譯makefile文件,這種做法的缺點是各平臺的make實現(xiàn)不同,導致這種原始的做法其實復用度并不高,需要針對各平臺單獨編寫差異巨大的makefile文件,那為什么要介紹它呢,因為這是跨平臺的根,所有跨平臺工具,最終都是要依賴各平臺應用的集成開發(fā)環(huán)境的編譯器來執(zhí)行編譯,這是固定不變的,也就是說各平臺的編譯,最終還是需要各平臺的makefile,這一點是無法逃避的,而怎么由人工轉為自動化,才是跨平臺編譯的進階之路。
 
進階跨平臺
使用cmake,編寫統(tǒng)一的makefile文件,最后由cmake自動生成各平臺相關的makefile文件執(zhí)行編譯,這一點上,cmake已經是比較好的跨平臺工具了,一般的跨平臺工程基本已經滿足需求了。
 
現(xiàn)代跨平臺
當工程規(guī)模增大到難以想象的量級時,編譯速度和工程模塊的劃分變得尤為重要,其中chromium工程就遇到這兩個問題,于是最初誕生了gyp,最后演化升級為gn,其旨在追求工程更加清晰的模塊和結構呈現(xiàn),以及更快的編譯速度。前者通過語法層面實現(xiàn),后者則依靠ninja來提升編譯速度,因為大型工程的編譯,很大一部分時間都花在了源文件編譯依賴樹的分析這塊,而ninja更像是一個編譯器的預處理,其主要目的是舍棄gcc、msvc、clang等編譯器在編譯過程中遞歸查找依賴的方式,因為這里存在很多重復的依賴查找,而ninja改進了這一過程,提前生成編譯依賴樹,編譯期間按照編譯依賴樹的順序依次編譯,這樣就大大減少了編譯期間雜亂的編譯順序造成的重復依賴關系查找。
 
關于一點說明:
本文主要針對底層庫領域的跨平臺構建,這也是比較常見和通用的跨平臺應用方式,但不排除有例外,像Qt就是一套從底層開發(fā)到UI框架,再到代碼編輯、工程管理的C++整體跨平臺解決方案,它的涵蓋面更大,只有應用層使用Qt的UI框架時才能發(fā)揮它的真正威力(有興趣的同學可以研究它,目前我所在團隊研發(fā)的CCtalk客戶端Windows和Mac,以及即將外發(fā)的Chromebook版都是使用它,我也打算在一個合適的時機從我最擅長的UI框架領域來介紹Qt的實戰(zhàn)應用),而這篇文章尋求的是目前為止更通用化的跨平臺方式,也就是C++跨平臺底層+原生UI應用層的組合方式,所以本文跨平臺的切入重點也是針對底層庫這一塊,做得好的應用,一般會把網絡、數據、業(yè)務上下文狀態(tài)管理歸屬到底層由跨平臺實現(xiàn),這部分可以達到整個應用程序代碼量的60~70%,完成這塊的復用是非常值得的。

 

二、跨平臺代碼編輯器

gn解決了跨平臺編譯問題,但是各平臺的代碼編輯,并不屬于底層C++跨平臺構建工具的范疇(全框架C++流程的Qt例外)。一種做法是,通過gn生成各個平臺的工程(xcode工程、vs工程)然后再進行代碼編寫,源文件和工程模塊的修改需要另外同步到gn工程文件中;另一種做法是使用vscode。顯而易見,使用vscode+gn是最佳選擇,這樣就相當于一個各平臺統(tǒng)一的集成開發(fā)環(huán)境,這里我給大家預覽下vscode和gn配合之下的預覽圖:

  • 代碼編寫使用vscode,有各種插件提供了語法提示和檢測、自動補齊、高亮等編輯器功能
  • 代碼編譯使用gn,在vscode中直接有內置的命令行,直接運行編譯腳本執(zhí)行編譯,編譯腳本中是gn的編譯命令的調用
  • 在vscode+gn的使用過程中,我們會慢慢體會到代碼編輯、編譯的白盒流程,而不是以前的黑盒,對編輯和編譯流程的理解也將越來越深刻,這是軟件開發(fā)越來越上層的當下,最難能可貴的地方,是抱有一顆求知之心的人最珍視的東西。*
    在這里插入圖片描述

 

三、GN入門

官方文檔

https://gn./gn/+/master/docs
 
GN雖然有非常完善和詳細的官方文檔,但如果要真正搭建一個跨平臺的工程,則往往寸步難行,這在于GN終究只是chromium的附產物,在于更好的服務chromium工程,所以缺少一些必要詳細的入門模版,尤其是在各個編譯器下的編譯、鏈接選項設置這塊,需要完全理解各個編譯器的編譯參數和設置,這對于我們的精力來說,幾乎是不可能一下子就能了解清楚的,而這些編譯選項往往是一次性工作,一旦配置好了,以后都不需要修改,或者僅需要修改個別選項。好在有大量成熟的跨平臺開源項目,以chromium為例,我們從中扒出GN的最小集,再應用到我們一個自定義的工程中,搞清楚各個環(huán)節(jié)的含義,那么基本就算已經掌握了GN了。

示例工程

為了能夠對GN有一個全貌的了解,我準備了一個簡單的demo工程,請大家務必下載下來,后續(xù)的介紹都是基于這個示例工程來做
騰訊微云:https://share./5ymLr5u
 
如果大家遇到下載不了或者最終編譯錯誤的異常情況可以評論中聯(lián)系我,目前已在windows、mac、android編譯環(huán)境驗證

鳥瞰一個GN工程

我們先從工程的全貌來看,這是我們demo工程的全局視圖,GN組織一個跨平臺工程分為3大塊:
第1部分:整體工程入口,這部分常年都不用做修改
第2部分:GN通用文件,這部分常年都不用做修改
第3部分:GN源代碼工程文件,這部分與平常我們在集成開發(fā)環(huán)境中類似,源文件的組織和管理就在這個部分

在這里插入圖片描述

 

詳解各GN文件職責

在這里插入圖片描述

四、示范工程

一般而言,這一部分仔細閱讀后,基本就可以依葫蘆畫瓢使用起GN了,更加細節(jié)的部分則需要靠大家閱讀官方文檔以及后續(xù)的第五部分內容

準備環(huán)境

1.安裝vscode(主要是方便大家閱讀工程結構,也可以跳過這一步,只是會增加工程的理解難度而已)
vscode的使用非常簡單,指定打開某個文件夾,然后整個文件夾的目錄結構就自動呈現(xiàn)在vscode的左側了,同時插件安裝異常方便,直接在vscode左側最后一個tab中搜索關鍵字就會有插件的在線安裝提示,根據提示安裝好安裝C++插件、gn插件等插件后,就可以作為代碼編輯器編寫代碼了。
 
2.安裝python3
關于python的語法,簡單了解就好,遇到看不懂的稍微查下語法,腳本語言總是易于理解和使用的,大家要有信心。

工程目錄結構

在這里插入圖片描述

各平臺編譯腳本執(zhí)行

windows:在gn_project目錄下,命令行中運行(vscode內置的更方便)命令build_for_win.bat debug
mac:gn_project目錄下,命令行中運行(vscode內置的更方便)命令./build_for_mac.sh debug
android: gn_project目錄下,命令行中運行(vscode內置的更方便)命令./build_for_android.sh debug arm

 

mac平臺編譯入口腳本

./build_for_mac.sh,關鍵腳本如下(只是截取部分關鍵代碼,旨在讓大家看到各平臺下gn編譯命令的調用):

# ninja files
$gn gen $build_cache_path --args="is_debug=$debug_mode target_cpu=“x64"”
if [ $? != 0 ]; then
    echo “generate ninja failed”
    exit 1
fi
 
echo -
echo -
echo ---------------------------------------------------------------
echo 第4步:開始ninja編譯…
echo ---------------------------------------------------------------
 
# build
$ninja -C $build_cache_path > $log_path
if [ $? != 0 ]; then
    echo “build failed. log: $log_path”
    exit 1
fi

 

windows平臺編譯入口腳本

./build_for_win.sh 和 ./build_for_win.py,關鍵腳本如下(只是截取部分關鍵代碼,旨在讓大家看到各平臺下gn編譯命令的調用):

# --------------------------------------------------------------------------------
# 3. build kernel
# --------------------------------------------------------------------------------
# 1) generate ninja file for build
# 2) build
# 3) print end log
# --------------------------------------------------------------------------------
print(“build kernel …”)
gn_cmd = '"{}" gen “{}” --args=“is_debug={} target_cpu=\“x86\” win_vc=\”{}\" win_vc_ver=\"{}\" win_xp={}"’.format(gn, build_cache_path, debug_mode, win_vc, vs_ver, support_xp)
 
proc = subprocess.run(gn_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False)
stdout = buildtools.console_to_str(proc.stdout)
stderr = buildtools.console_to_str(proc.stderr)
if 'Done.’ not in stdout:
    print(stdout)
    print(stderr)
    with open(os.path.join(build_cache_path, 'build.log’), 'w’) as log:
        log.write(stdout)
    sys.exit(1)
 
ninja_cmd = '"{}" -C “{}”’.format(ninja, build_cache_path)
proc = subprocess.run(ninja_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False)
stdout = buildtools.console_to_str(proc.stdout)
stderr = buildtools.console_to_str(proc.stderr)
if proc.returncode != 0:
    print(stdout)
    print(stderr)
    with open(os.path.join(build_cache_path, 'build.log’), 'w’) as log:
        log.write(stdout)
    sys.exit(1)
 
with open(os.path.join(build_cache_path, 'build.log’), 'w’) as log:
    log.write(stdout)

 

android平臺編譯入口腳本

./build_for_android.sh,關鍵腳本如下(只是截取部分關鍵代碼,旨在讓大家看到各平臺下gn編譯命令的調用):

echo -
echo -
echo ---------------------------------------------------------------
echo 第5步:使用gn生成ninja文件
echo ---------------------------------------------------------------
 
# ninja file
$gn gen $build_cache_path --args="
        target_os = “android”
        target_cpu = “$target_config”
        use_qt_for_android = false
        ndk = “$ndk”
        qt_sdk = “$qt_sdk”"
if [ $? != 0 ]; then
    echo “generate ninja failed”
    exit 1
fi
 
 
echo -
echo -
echo ---------------------------------------------------------------
echo 第6步:使用ninja開始編譯…
echo ---------------------------------------------------------------
 
# build
$ninja -C $build_cache_path > $log_path
if [ $? != 0 ]; then
    exit 1
fi

 

整個工程的各個代碼模塊

工程總共有多少模塊,需要在./BUILD.gn文件中配置,而各個模塊的工程則在各個模塊中,關鍵代碼如下(只是截取部分關鍵代碼,旨在呈現(xiàn)整體工程各個模塊):
在這里插入圖片描述

具體代碼模塊

以整體工程文件./BUILD.gn中各個模塊的配置為例,"http://system_wrappers:system_wrappers"表示在system_wrappers目錄下,尋找BUILD.gn文件,在這個BUILD.gn文件中定義了system_wrappers模塊,以及該模塊的源文件、編譯選項設置等,具體代碼如下:
在這里插入圖片描述

library_template.gni文件

這是一個GN模版文件,是我們自定義的一個GN文件,文件名可以根據大家的喜好自行修改,確保在要使用的地方import進來即可,每個代碼模塊的編譯選項設置其實是有蠻多編譯選項和工程定義需要設置,而這些編譯選項和定義的設置其實是重復的,如果每個模塊都寫一遍,會比較冗余,所以我們抽取了公共的模版,使得各個代碼模塊的BUILD.gn文件可以只關注源代碼包含、附件包含目錄、附加包含庫等工程相關的事項。
在這里插入圖片描述
 

五、關鍵細節(jié)

這是GN比較進階的一部分,需要參考官方文檔中語法部分進行解讀

https://gn./gn/+/master/docs/reference.md
官方語法文檔的查閱方法是,“關鍵字 + :”,例如需要看template的定義時,文檔中搜索“template:”即可出現(xiàn)詳細的定義和解釋

1.template

template(模版)的作用是抽取公共gn代碼,節(jié)省整體的gn代碼量,使得工程文件更容易閱讀,以library_template.gni為例,有幾個關鍵語法我們需要熟知:

  • 模版中的變量和模版實例之間的數據傳遞,是通過invoker來承載,例如以sources的值為例,模版中可以通過sources = invoker.sources來傳遞值
  • 模版實例的名稱,則通過target_name來承載,例如,我們定義了模版libaray,那么library(“aysnevent”)的target_name值就是”aysnevent"

template(“l(fā)ibrary”) {
    assert(defined(invoker.sources), “Need sources in $target_name listing the source files.”)
    type = “static_library”
    if (is_win) {
        type = “shared_library”
    }
    if (is_mac || is_android) {
        type = “source_set”
    }
    target(type, target_name) {
        configs += [
            “//:qt_config”,
        ]
        sources = invoker.sources
        # output_prefix_override = true
        output_dir = “$root_out_dir”
        lib_dirs = [
            rebase_path("$output_dir/libs"),
        ]
        if (type == “static_library”) {
            output_dir += “/libs”
        }
        if (defined(invoker.deps)) {
            if (defined(deps)) {
                deps += invoker.deps
            } else {
                deps = invoker.deps
            }
        }
        if (defined(invoker.data_deps)) {
            if (defined(data_deps)) {
                data_deps += invoker.data_deps
            } else {
                data_deps = invoker.data_deps
            }
        }
        if (defined(invoker.include_dirs)) {
            if (defined(include_dirs)) {
                include_dirs += invoker.include_dirs
            } else {
                include_dirs = invoker.include_dirs
            }
        }
        if (defined(invoker.configs)) {
            if (defined(configs)) {
                configs += invoker.configs
            } else {
                configs = invoker.configs
            }
        }
        if (defined(invoker.public_configs)) {
            if (defined(public_configs)) {
                public_configs += invoker.public_configs
            } else {
                public_configs = invoker.public_configs
            }
        }
        if (defined(invoker.cflags)) {
            if (defined(cflags)) {
                cflags += invoker.cflags
            } else {
                cflags = invoker.cflags
            }
        }
        if (defined(invoker.cflags_cc)) {
            if (defined(cflags_cc)) {
                cflags_cc += invoker.cflags_cc
            } else {
                cflags_cc = invoker.cflags_cc
            }
        }
        if (defined(invoker.del_configs) && defined(configs)) {
            configs -= invoker.del_configs
        }
        if (defined(invoker.del_cflags_cc) && defined(cflags_cc)) {
            cflags_cc -= invoker.del_cflags_cc
        }
        if (defined(invoker.libs)) {
            if (defined(libs)) {
                libs += invoker.libs
            } else {
                libs = invoker.libs
            }
        }
        if (defined(invoker.defines)) {
            if (defined(defines)) {
                defines += invoker.defines
            } else {
                defines = invoker.defines
            }
        }
}

2.BUILD.gn和xxx.gni

BUILD.gn一般作為模塊的工程入口文件,可以配合.gni文件來劃分源碼或模塊。

  • 當模塊比較大時,可以用.gni來劃分內部子模塊,減輕工程文件的負擔;
  • 當多個模塊之間差異很小時,可以利用BUILD.gn來統(tǒng)一管理這些模塊的設置,利用.gni來專屬負責各個模塊源文件管理。

以./modules/BUILD.gn為例,它包含了多個代碼模塊,各個模塊的源文件和工程配置則在各個.gni中管理
在這里插入圖片描述

3.source_set/static_library/shared_library

source_set:編譯后的代碼集合,它與靜態(tài)庫的唯一區(qū)別是沒有鏈接的符號文件,就是一般編譯后生成的.o文件,目前我們demo工程里頭,非windows系統(tǒng)上使用的是source_set(具體參見 library_template.gni),編譯完成后,使用ar和ranlib命令重新打包成帶符號表的.a靜態(tài)庫
 
特別說明:最終打包出來的是.a庫,那么為什么demo中不直接用static_library呢,這是因為,整個工程的各個模塊,由于沒有一個統(tǒng)一的地方調用這些模塊,這就會導致編譯出來的結果是各個分散的.a庫,使用source_set,然后再手動打包生成符號表最終可以生成一個統(tǒng)一的.a庫
 
static_library:靜態(tài)庫,windows上是.lib庫,其他平臺是.a庫
 
shared_library: 動態(tài)庫,windows上是.dll庫,mac和iOS上是.dylib庫,andorid是.so庫

4.”.gn“根文件

這是一個入口設置的文件,是GN中的固定規(guī)則文件,會自動被GN讀取,但由于它沒有文件名,只有擴展名,各個系統(tǒng)上如果沒有打開文件夾選項中查看擴展名的設置,會看不到,這里需要注意。如果使用的vscode,則不需要擔心這個問題,在vscode中可以默認看到該文件

5.windows上對XP支持

如果windows上安裝的是VS2010以上版本,默認編譯出來的結果是不支持XP系統(tǒng)的,如果需要支持,則需要手動增加支持選項,在./gn/toolchain/BUILD.gn中對應修改即可
具體參見關鍵代碼(只截取了部分關鍵代碼)

修改1:./gn/BUILDCONFIG.gn中declare_args中增加XP參數聲明“win_xp = false”,這樣windows上調用ninja編譯命令時,傳入是否支持XP的選項

# --------------------------------------------------------------------------------
# 3. build kernel
# --------------------------------------------------------------------------------
# 1) generate ninja file for build
# 2) build
# 3) print end log
# --------------------------------------------------------------------------------
print(“build kernel …”)
gn_cmd = '"{}" gen “{}” --args=“is_debug={} target_cpu=\“x86\” win_vc=\”{}\" win_vc_ver=\"{}\" win_xp={}"’.format(gn, build_cache_path, debug_mode, win_vc, vs_ver, support_xp)

修改2:./gn/toolchain/BUILD.gn中toolchain("msvc”) 下tool(“cc”)中增加”/D_USING_V110_SDK71_”編譯選項

# Label names may have spaces so pdbname must be quoted.
command = “cmd /c “$env_setup $cc_wrapper $cl /FS /nologo /showIncludes /FC @$rspfile /c {{source}} /Fo{{output}} /Fd”$pdbname”""
if (win_xp) {
    command += " /D_USING_V110_SDK71_"
}

修改3:./gn/toolchain/BUILD.gn中toolchain("msvc”) 下tool(“cxx”)中增加”/D_USING_V110_SDK71_”編譯選項

# Label names may have spaces so pdbname must be quoted.
command = “cmd /c “$env_setup $cc_wrapper $cl /FS /nologo /showIncludes /FC @$rspfile /c {{source}} /Fo{{output}} /Fd”$pdbname”""
if (win_xp) {
    command += " /D_USING_V110_SDK71_"
}

修改4:./gn/toolchain/BUILD.gn中toolchain(“msvc”) 下tool(“alink”)中增加”/SUBSYSTEM:WINDOWS,5.01”編譯選項

command = “cmd /c “$env_setup l i b / n o l o g o / i g n o r e : 4221 a r f l a g s / O U T : o u t p u t @ lib /nologo /ignore:4221 {{arflags}} /OUT:{{output}} @ lib/nologo/ignore:4221arflags/OUT:output@rspfile””
if (win_xp) {
    command += " /SUBSYSTEM:WINDOWS,5.01"
}

修改5:./gn/toolchain/BUILD.gn中toolchain(“msvc”) 下tool(“solink”)中增加”/SUBSYSTEM:WINDOWS,5.01”編譯選項

command = “cmd /c “$env_setup $lnk /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:$pdbname @$rspfile””
if (win_xp) {
    command += " /SUBSYSTEM:WINDOWS,5.01"
}

修改6:./gn/toolchain/BUILD.gn中toolchain(“msvc”) 下tool(“l(fā)ink”)中增加”/SUBSYSTEM:WINDOWS,5.01”編譯選項

command = “cmd /c “$env_setup $lnk /nologo /OUT:$exename /PDB:$pdbname @$rspfile””
if (win_xp) {
command += " /SUBSYSTEM:WINDOWS,5.01"
}

6.是否生成調試信息

release編譯選項下,生成調試信息的情況下,才會對應生成符號文件,用于crash時分析崩潰,例如windows上的pdb、mac上的dSYM

為了調用方便,我們在./gn/BUILD.gn中自定義了配置選項,具體代碼如下

# -------------------------------------------------------------------------
# debug_symbols
# 調試符號文件
# -------------------------------------------------------------------------
config(“debug_symbols”) {
    # It’s annoying to wait for full debug symbols to push over
    # to Android devices. -gline-tables-only is a lot slimmer.
    if (is_android) {
        cflags = [
            “-gline-tables-only”,
            “-funwind-tables”, # Helps make in-process backtraces fuller.
        ]
        } else if (is_win) {
            cflags = [ “/Zi” ]
            ldflags = [ “/DEBUG” ]
        } else {
            cflags = [ “-g” ]
        }
}

然后在./gn/BUILDCONFIG.gn中,將該自定義配置增加到gn的缺省配置中,具體代碼如下

# Default configs
default_configs = [
    “//gn:default”,
    # “//gn:no_exceptions”,
    # “//gn:no_rtti”,
    # “//gn:warnings”,
    “//gn:warnings_except_public_headers”,
]
if (!is_debug) {
    default_configs += [ “//gn:release” ]
}
default_configs += [ “//gn:debug_symbols” ]
default_configs += [ “//gn:extra_flags” ]

7.dispatch_for_ide.py

這是我們自定義的編譯完成后執(zhí)行動作,通過python實現(xiàn),主要實現(xiàn)非windows下.a庫打包和生成文件同步到指定輸出目錄,這里有幾個基礎的概念需要理解,才能明白這個編譯完成后事件調用的機制

action:gn中的執(zhí)行動作,它用于定義一個執(zhí)行的腳本,一般配合python來使用,在./BUILD.gn中,我們定義了action("dispatch_for_ide”),從而指定了需要執(zhí)行的腳本是dispatch_for_ide.py。
 
action中的deps:指定腳本的執(zhí)行需要在這些依賴項完成之后,而我們定義的action中依賴項是project,也就是整個工程編譯完成后才能開始執(zhí)行腳本。
 
我們可以從過官方文檔中看到具體的定義描述(頁面中查找關鍵字“action:”即可看到)
the “deps” and “public_deps” for an action will always be completed before any part of the action is run so it can depend on the output of previous steps.

為了便于理解,我們先看下action("dispatch_for_ide”)的調用代碼,代碼位于./BUILG.gn

group(“ccore”){
    deps = projects
    deps += [ “//:dispatch_for_ide” ]
}

然后,我們再看action("dispatch_for_ide”)具體定義代碼,代碼位于./BUILG.gn中

# 編譯完成后執(zhí)行動作
action(“dispatch_for_ide”) {
    arg = [
        “–kernel”, rebase_path("http://"),
        “–outpath”, rebase_path("http://out"),
        “–cachepath”, rebase_path("$root_out_dir"),
    ]
    if (is_debug) {
        arg += [ “–buildmode”, “Debug” ]
    } else {
        arg += [ “–buildmode”, “Release” ]
    }
    if (is_mac) {
        arg += ["–platform", “mac”]
    } else if (is_ios) {
        arg += ["–platform", “ios”]
    } else if (is_android) {
        arg += ["–platform", “android”]
    } else if (is_win) {
        arg += ["–platform", “win”]
    }
    arg += ["–targetcpu", “${target_cpu}”]
 
    script = “dispatch_for_ide.py”
    outputs = [ “$root_out_dir/libccore.a” ]
    args = arg
    deps = projects
}

8.data_deps和deps

data_deps:
非鏈接依賴關系,如果依賴的模塊是一個靜態(tài)庫,則需要代碼中手動通過命令引入對應lib庫,鏈接時才能成功,
例如,代碼中#pragma comment(lib, “xxx.lib”),一般用于特指windows上隱式鏈接(靜態(tài)鏈接)的dll時,需要代碼中如是操作
Specifies dependencies of a target that are not actually linked into the current target. Such dependencies will be built and will be available at runtime.
This is normally used for things like plugins or helper programs that a target needs at runtime.
Note: On iOS and macOS, create_bundle targets will not be recursed into when gathering data_deps. See “gn help create_bundle” for details.
 
deps:
私有鏈接依賴關系,如果依賴的模塊是一個靜態(tài)庫,則不需要代碼中再人工鏈入指定的lib庫文件,一般特指依賴的靜態(tài)庫
Specifies private dependencies of a target. Private dependencies are propagated up the dependency tree and linked to dependent targets, but do not grant the ability to include headers from the dependency. Public configs are not forwarded.

9.Android下編譯環(huán)境

Android下編譯,需要額外的環(huán)境依賴設置,具體在./build_for_android.sh中設置,然后在./gn/BUILDCONFIG.gn中應用

./build_for_android.sh中對應代碼如下

ndk="/Library/android-ndk-r16b"
PATH=$PATH:$ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin
echo ndk路徑:$ndk
 
#qt_sdk="/Users/taoshu/Qt5.9.6/5.9.6"
#echo qt路徑:$qt_sdk
 

 
# ninja file
$gn gen $build_cache_path --args="
        target_os = “android”
        target_cpu = “$target_config”
        use_qt_for_android = false
        ndk = “$ndk”
        qt_sdk = “$qt_sdk”"
 
if [ $? != 0 ]; then
    echo “generate ninja failed”
    exit 1
fi

NDK路徑設置

編譯針對Android環(huán)境的目標文件時,NDK是必不可少的設置,它提供了各種針對Android標準庫以及交叉編譯的工具集

Qt路徑設置

應用層是Qt框架時:
需要將編譯參數use_qt_for_android設置為true,并設置好Qt路徑,這是由于Qt的UI線程和java主線程不在一個線程,是自定義實現(xiàn),所以多線程轉異步處理依賴Qt的消息循壞實現(xiàn)
 
應用層是Java原聲框架時:
需要將編譯參數use_qt_for_android設置為false即可,Qt路徑可以忽略

10.Windows下Visual Stuido的版本

由于GN中需要指定windows下編譯時使用的VS版本,這是GN之外我們需要做的事情,目前是通過python腳本去檢查指定安裝目錄,從而自動獲取本機的VS版本,這塊理論上不會有問題,但電腦環(huán)境千差萬別,難免會有問題,所以demo工程中,直接強制指定使用的是VS2015,如果大家需要修改VS版本,需要在如下腳本文件中做稍許調整

./build_for_win.py:其中有參數指定強制使用VS2015

# --------------------------------------------------------------------------------
# 3. build kernel
# --------------------------------------------------------------------------------
# 1) generate ninja file for build
# 2) build
# 3) print end log
# --------------------------------------------------------------------------------
print(“build kernel …”)
gn_cmd = '"{}" gen “{}” --args=“is_debug={} target_cpu=\“x86\” win_vc=\”{}\" win_vc_ver=\"{}\" win_xp={}"’.format(gn, build_cache_path, debug_mode, win_vc, vs_ver, support_xp)
proc = subprocess.run(gn_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=False)
stdout = buildtools.console_to_str(proc.stdout)
stderr = buildtools.console_to_str(proc.stderr)

./gn/BUILDCONFIG.gn:其中有根據用戶安裝目錄去搜尋VS版本和VS環(huán)境的代碼

# *********************************************************************************
# Config msvc builder
# 配置MSVC編譯,首先會嘗試從系統(tǒng)中自動尋找vc編譯器,
# 沒有匹配到結果時,再從缺省硬編碼配置路徑中讀取
# *********************************************************************************
msvc = win_vc_ver
if (target_os == “win”) {
    # By default we look for 2017 (Pro & Community), then 2015. If MSVC is installed in a
    # non-default location, you can set win_vc to inform us where it is.
    vc_2017_pro_default = “C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC”
    vc_2017_com_default = “C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC”
    vc_2017_bt_default = “C:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/VC”
    vc_2015_default = “C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC”
    vc_2013_default = “C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC”
 
    if (win_vc == “”) {
        if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py",
                                          [ “$vc_2017_pro_default” ],
                                          “trim string”)) {
            win_vc = vc_2017_pro_default
            msvc = 2017
        } else if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py",
                                                   [ “$vc_2017_com_default” ],
                                                   “trim string”)) {
           win_vc = vc_2017_com_default
           msvc = 2017
        } else if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py",
                                                   [ “$vc_2017_bt_default” ],
                                                   “trim string”)) {
            win_vc = vc_2017_bt_default
            msvc = 2017
        } else if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py",
                                                   [ “$vc_2015_default” ],
                                                   “trim string”)) {
            win_vc = vc_2015_default
            msvc = 2015
        } else if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py",
                                                   [ “$vc_2013_default” ],
                                                   “trim string”)) {
            win_vc = vc_2013_default
            msvc = 2013
        }
    }
    assert(win_vc != “”) # Could not find VC installation. Set win_vc to your VC directory.
 
    if (msvc == “”) {
        if (“True” == exec_script("http://gn/BUIDCONFIG_win_check_vcdir.py", [ “$win_vc/Tools” ], “trim string”)) {
            msvc = 2017
        } else {
            msvc = 2015
        }
    }
}
 
if (target_os == “win”) {
    if (msvc == 2017 && win_toolchain_version == “”) {
        win_toolchain_version = exec_script("http://gn/BUIDCONFIG_win_highest_version_vcdir.py",
                                                               [
                                                                  “$win_vc/Tools/MSVC”,
                                                                  “[0-9]{2}.[0-9]{2}.[0-9]{5}”,
                                                               ],
                                                               “trim string”)
    }
    if (win_sdk_version == “”) {
        win_sdk_version = exec_script("http://gn/BUIDCONFIG_win_highest_version_vcdir.py",
                                                       [
                                                       “$win_sdk/Include”,
                                                       “[0-9]{2}.[0-9].[0-9]{5}.[0-9]”,
                                                       ],
                                                       “trim string”)
    }
}

 

六、結語 [編譯器選項]

這篇文章講到這里,使用GN已經不在話下,但是要真正做到無惑,則和GN無關,因為編譯器選項不是跨平臺工具要解決問題的范疇,文章開頭也有做說明。在./gn/BUILD.gn和./gn/BUILDCONFIG.gn這兩個文件中,有大量編譯器相關的選項設置,這些選項設置由于各平臺編譯器的差異,導致千差萬別,非?;逎?,包括我自己也只了解其中一小部分。幸運的是,這部分內容我們有大量的時間去慢慢熟悉它,我們也無需記住每一項,其中99%以上設置項是我們也許永遠不會關注到的,我相信經過實際工作開發(fā)后,有那么一小部分選項我們會接觸到,并去調整它,屆時我們只需要掌握那一部分就可以了。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多