Blade构建工具
1.簡(jiǎn)介
Blade是騰訊為了解決GNU Make使用繁瑣的問(wèn)題而開(kāi)發(fā)的一個(gè)開(kāi)源構(gòu)建工具,旨在簡(jiǎn)化大型項(xiàng)目的構(gòu)建,能夠自動(dòng)分析依賴,集成了編譯、鏈接、測(cè)試、靜態(tài)代碼檢查等功能,支持C/C++, Java, Python, Scala, protobuf等多種語(yǔ)言(主要面向C/C++)(借鑒自Bazel)。
注意:構(gòu)建(build)和編譯(compile)不同——編譯器負(fù)責(zé)將源代碼轉(zhuǎn)換為庫(kù)文件或可執(zhí)行文件;構(gòu)建工具負(fù)責(zé)分析構(gòu)建目標(biāo)之間的依賴關(guān)系,并調(diào)用編譯器來(lái)生成構(gòu)建目標(biāo)。
例如,自己的代碼依賴A庫(kù),A庫(kù)又依賴B庫(kù),如果手動(dòng)編譯則需要寫(xiě)復(fù)雜的編譯和鏈接命令,當(dāng)依賴庫(kù)代碼發(fā)生變化時(shí)還需要重新編譯,構(gòu)建工具旨在自動(dòng)化這一過(guò)程。
- 項(xiàng)目主頁(yè):https://github.com/chen3feng/blade-build
- 官方文檔:https://github.com/chen3feng/blade-build/blob/master/doc/en/README.md
- 用戶手冊(cè):https://github.com/chen3feng/blade-build/blob/master/doc/blade_user_manual.pdf
- 介紹ppt:https://github.com/chen3feng/blade-build/blob/master/doc/blade.pdf
特性:
- 自動(dòng)分析庫(kù)之間的依賴關(guān)系
- 遞歸構(gòu)建:當(dāng)依賴庫(kù)的源文件發(fā)生變化時(shí)會(huì)自動(dòng)重新構(gòu)建依賴庫(kù),而GNU Make無(wú)法實(shí)現(xiàn)遞歸構(gòu)建
- 增量構(gòu)建:未發(fā)生變化的依賴庫(kù)不會(huì)重新構(gòu)建,加快構(gòu)建速度
- 提供了對(duì)protobuf和測(cè)試(使用gtest)的內(nèi)置支持
2.依賴軟件
Blade需要以下依賴:
- Linux或Mac操作系統(tǒng)
- Python 2.7
- Ninja 1.8+
構(gòu)建特定語(yǔ)言所需的編譯器:
- C/C++: GCC 4.0+
- Java: JDK 1.6+
- Scala: 2.10+
3.安裝
3.1 安裝Python
Linux或Mac系統(tǒng)默認(rèn)已經(jīng)安裝了Python 2.7。
3.2 安裝Ninja
下載地址:https://github.com/ninja-build/ninja/releases
解壓后只有一個(gè)可執(zhí)行文件ninja,將其放到PATH環(huán)境變量包含的某個(gè)目錄下(例如/usr/local/bin),從而能夠直接在命令行中直接執(zhí)行ninja命令:
$ ninja --version 1.10.23.3 安裝Blade
安裝方式:下載源代碼,執(zhí)行install腳本
$ git clone https://github.com/chen3feng/blade-build.git $ cd blade-build/ $ git checkout v2.0 $ ./install執(zhí)行完成后Blade將被安裝在~/bin目錄下,該目錄也被添加到PATH環(huán)境變量,執(zhí)行source ~/.profile命令或重啟終端使其生效,此時(shí)應(yīng)該能夠在命令行中直接執(zhí)行blade命令:
$ blade -h usage: blade [-h] [--version] {build,run,test,clean,query,dump} ...blade <subcommand> [options...] [targets...] ...注意:不能刪除blade-build目錄,因?yàn)閎lade命令會(huì)用到其中的源代碼。
4.簡(jiǎn)單示例
下面使用Blade創(chuàng)建一個(gè)Hello World項(xiàng)目。
官方文檔:https://github.com/chen3feng/blade-build/blob/master/doc/en/quick_start.md
4.1 創(chuàng)建工作目錄
首先創(chuàng)建項(xiàng)目根目錄blade-demo和一個(gè)子目錄quick-start:
mkdir blade-demo && cd blade-demo touch BLADE_ROOT mkdir quick-start其中項(xiàng)目根目錄blade-demo可以在任意位置,BLADE_ROOT文件用于標(biāo)識(shí)項(xiàng)目根目錄。
4.2 實(shí)現(xiàn)say庫(kù)
在quick-start目錄下創(chuàng)建say.h和say.cc兩個(gè)文件,內(nèi)容如下:
say.h
#pragma once #include <string>// Say a message void Say(const std::string& msg);say.cc
#include "quick-start/say.h"#include <iostream>void Say(const std::string& msg) {std::cout << msg << "!\n"; }這兩個(gè)文件組成了一個(gè)庫(kù)(library),可以將其編譯為庫(kù)文件供其他代碼使用。創(chuàng)建一個(gè)BUILD文件來(lái)描述say庫(kù):
cc_library(name = 'say',srcs = 'say.cc',hdrs = 'say.h', )其中cc_library表示該構(gòu)建目標(biāo)是一個(gè)C++庫(kù),srcs為源文件,hdrs為公共接口頭文件。
4.3 實(shí)現(xiàn)hello庫(kù)
下面創(chuàng)建另一個(gè)庫(kù)hello,并調(diào)用say庫(kù)提供的函數(shù)。
在quick-start目錄下創(chuàng)建hello.h和hello.cc兩個(gè)文件,內(nèi)容如下:
hello.h
#pragma once #include <string>// Say hello to `to` void Hello(const std::string& to);hello.cc
#include "quick-start/hello.h"#include "quick-start/say.h"void Hello(const std::string& to) {Say("Hello, " + to); }其中函數(shù)Hello()調(diào)用了say庫(kù)提供的函數(shù)Say(),因此hello庫(kù)依賴say庫(kù)。
在BUILD文件中添加hello庫(kù)的定義:
cc_library(name = 'hello',srcs = 'hello.cc',hdrs = 'hello.h',deps = ':say', )其中deps為該構(gòu)建目標(biāo)的依賴,:say表示當(dāng)前BUILD文件中名為say的構(gòu)建目標(biāo),即前面的say庫(kù)。
4.4 實(shí)現(xiàn)hello_world程序
下面創(chuàng)建一個(gè)hello_world程序,在main()函數(shù)中調(diào)用hello庫(kù)提供的函數(shù)來(lái)打印信息。
創(chuàng)建源文件hello_world.cc,內(nèi)容如下:
#include "quick-start/hello.h"int main() {Hello("World");return 0; }在BUILD文件中添加hello_world的定義:
cc_binary(name = 'hello_world',srcs = 'hello_world.cc',deps = ':hello', )cc_binary表示該構(gòu)建目標(biāo)是一個(gè)可執(zhí)行程序。
注意:依賴只需要添加:hello,而不需要添加:say,因?yàn)檫@是hello庫(kù)的實(shí)現(xiàn)細(xì)節(jié),在編譯和鏈接過(guò)程中Blade會(huì)自動(dòng)處理這樣的傳遞依賴。但是,如果hello_world.cc中顯式包含了say.h,則依賴中需要添加:say。
下面構(gòu)建并運(yùn)行hello_world程序:
$ cd quick-start $ blade build :hello_world $ blade run :hello_world Hello, World!blade build命令底層了調(diào)用g++編譯器和ld鏈接器,可使用--verbose參數(shù)查看具體執(zhí)行的命令,生成的庫(kù)文件和可執(zhí)行文件在build64_release目錄下。
注:對(duì)于這個(gè)簡(jiǎn)單的示例,直接執(zhí)行
g++ -o hello_world hello_world.cc hello.cc say.cc即可完成編譯,但是對(duì)于具有成百上千個(gè)源文件、包含很多模塊的大型項(xiàng)目,使用構(gòu)建工具就很有必要了。
完整的項(xiàng)目目錄結(jié)構(gòu)如下:
blade-demo/BLADE_ROOTquick-start/BUILDsay.hsay.cchello.hhello.cchello_world.ccbuild64_release/ # Blade自動(dòng)創(chuàng)建quick-start/libsay.a # say庫(kù)libhello.a # hello庫(kù)hello_world # 可執(zhí)行程序...注:該示例只有quick-start一個(gè)子目錄和一個(gè)BUILD文件。實(shí)際的項(xiàng)目會(huì)按模塊將文件分為多個(gè)不同的子目錄,每個(gè)子目錄下都包含一個(gè)BUILD文件。
完整代碼:https://github.com/chen3feng/blade-build/blob/master/example/quick-start
5.代碼組織結(jié)構(gòu)
Blade要求項(xiàng)目有一個(gè)顯式的根目錄,即BLADE_ROOT文件所在目錄。根目錄下是自己的模塊子目錄和第三方庫(kù)目錄,每個(gè)子目錄下都有一個(gè)BUILD文件來(lái)聲明該模塊所包含的構(gòu)建目標(biāo)。
以下是一個(gè)示例目錄結(jié)構(gòu):
my-project/BLADE_ROOTcommon/string/BUILDalgorithm.halgorithm.ccfoo/BUILDfoo.hfoo.ccthirdparty/gtest/BUILDgtest.hgtest.cc...注:這是Google推薦的源代碼管理方式——將所有代碼放在同一個(gè)倉(cāng)庫(kù)中,包括第三方庫(kù)源代碼。
源文件中包含頭文件的相對(duì)路徑是基于項(xiàng)目根目錄的,因此#include "quick-start/say.h"包含的是blade-demo/quick-start/say.h。雖然對(duì)于相同目錄下的頭文件來(lái)說(shuō)這樣有些繁瑣(寫(xiě)成#include "say.h"即可),但對(duì)于跨目錄包含的頭文件來(lái)說(shuō)這樣可以清楚地知道頭文件所在位置。例如,在上面的目錄結(jié)構(gòu)中,要在foo.cc中包含algorithm.h,直接寫(xiě)#include "common/string/algorithm.h"即可,而不必寫(xiě)成#include "../common/string/algorithm.h。
6.BUILD文件
Blade通過(guò)名為BUILD(全部大寫(xiě))的文件聲明構(gòu)建目標(biāo),構(gòu)建目標(biāo)可以是庫(kù)、可執(zhí)行文件、測(cè)試等,由若干源文件(和頭文件)組成。
構(gòu)建目標(biāo)可以相互依賴。在BUILD文件中只需要目標(biāo)的直接依賴,Blade將自動(dòng)分析傳遞依賴關(guān)系,并調(diào)用編譯器和鏈接器來(lái)生成構(gòu)建目標(biāo),構(gòu)建一個(gè)目標(biāo)時(shí)會(huì)先構(gòu)建其依賴的目標(biāo)。
6.1 示例
假設(shè)common/string目錄下定義了一些字符串輔助函數(shù),并且依賴common/int目錄下的int庫(kù),則common/string/BUILD文件如下:
cc_library(name = 'string',srcs = ['algorithm.cc','concat.cc','format.cc',],hdrs = ['algorithm.h','concat.h','format.h',],deps = ['//common/int:int',], )其他構(gòu)建目標(biāo)通過(guò)'//common/string:string'引用該目標(biāo)。
注:當(dāng)srcs、hdrs或deps有多個(gè)時(shí),可以使用列表[](BUILD文件實(shí)際上就是Python函數(shù)調(diào)用)
6.2 風(fēng)格建議
- 縮進(jìn)4個(gè)空格
- 使用單引號(hào)而不是雙引號(hào)
- 目標(biāo)名稱使用小寫(xiě)
- srcs中的文件按字母順序排列
- deps先寫(xiě)當(dāng)前目錄下的依賴(:name),再寫(xiě)其他目錄下的依賴(//path/to/dir:name),按字母順序排列
- 當(dāng)每行一個(gè)參數(shù)時(shí),最后一個(gè)參數(shù)也以逗號(hào)結(jié)尾,從而減少當(dāng)增加或刪除參數(shù)時(shí)影響的行數(shù)
- 不同目標(biāo)之間空一行,每個(gè)目標(biāo)前添加注釋,注釋以#開(kāi)頭
6.3 構(gòu)建目標(biāo)
Blade支持多種語(yǔ)言,每種語(yǔ)言支持多種構(gòu)建目標(biāo)。以下是幾種常用的構(gòu)建目標(biāo)的語(yǔ)法。
完整列表參考:https://github.com/chen3feng/blade-build/blob/master/doc/en/build_file.md#build-rules
公共屬性:
- name:字符串,指定構(gòu)建目標(biāo)的名稱,和路徑一起構(gòu)成目標(biāo)的唯一標(biāo)識(shí)
- srcs:字符串列表,指定源文件,位于當(dāng)前目錄或當(dāng)前目錄的子目錄下,可使用glob函數(shù)
- hdrs:字符串列表,指定公共接口頭文件
- deps:字符串列表,指定依賴目標(biāo),支持以下格式:
- //path/to/dir:name:項(xiàng)目根目錄下path/to/dir/BUILD文件中聲明的名為name的目標(biāo)
- :name:當(dāng)前BUILD文件中名為name的目標(biāo)
- #name:系統(tǒng)庫(kù),例如#pthread將添加鏈接選項(xiàng)-lpthread
- visibility:字符串列表,僅對(duì)列出的目標(biāo)可見(jiàn)(格式見(jiàn)7.2節(jié)),可使用特殊值'PUBLIC'指定對(duì)所有目標(biāo)可見(jiàn)
- 在Blade 2中,目標(biāo)默認(rèn)是私有的,即只對(duì)當(dāng)前目錄下的目標(biāo)可見(jiàn)
注:類型為字符串列表的屬性如果只有一項(xiàng)則可省略中括號(hào),例如srcs = ['foo.cc']等價(jià)于srcs = 'foo.cc'。
6.3.1 C++
6.3.1.1 cc_library
構(gòu)建C++庫(kù)文件。語(yǔ)法(僅包含了常用屬性):
cc_library(name = 'foo',srcs = ['foo.cc', 'bar.cc', ...],hdrs = ['foo.h', 'bar.h', ...],deps = [':name', '//path/to/dir:name', ...], )注:
- 公共接口頭文件應(yīng)聲明在hdrs中,私有頭文件應(yīng)聲明在srcs中。如果通過(guò)#include包含了一個(gè)頭文件,則應(yīng)該將該頭文件所屬的庫(kù)添加到deps中。
- 默認(rèn)只生成靜態(tài)鏈接庫(kù)文件(.a),如果在blade build時(shí)指定--generate-dynamic選項(xiàng),或依賴該目標(biāo)的cc_binary指定了dynamic_link = True,則生成動(dòng)態(tài)鏈接庫(kù)文件(.so)。
6.3.1.2 cc_binary
構(gòu)建C++可執(zhí)行文件。語(yǔ)法:
cc_binary(name = 'foo',srcs = ['foo.cc', 'bar.cc', ...],deps = [':name', '//path/to/dir:name', ...],dynamic_link = False, )- dynamic_link:布爾值(默認(rèn)為False),如果為T(mén)rue則使用動(dòng)態(tài)鏈接,生成的可執(zhí)行文件較小,但啟動(dòng)較慢
- 注:如果使用動(dòng)態(tài)鏈接,則生成的可執(zhí)行文件必須使用blade run執(zhí)行,或者在項(xiàng)目根目錄下執(zhí)行,否則會(huì)找不到庫(kù)文件
6.3.1.3 cc_test
構(gòu)建C++單元測(cè)試,使用GoogleTest測(cè)試框架,本質(zhì)上就是自動(dòng)鏈接了gtest和gtest_main的cc_binary。語(yǔ)法:
cc_test(name = 'foo_test',srcs = 'foo_test.cc',deps = [':foo', ...],testdata = testdata = ['data1.txt', '//path/to/data2.txt'], )- testdata:字符串列表,測(cè)試代碼只能訪問(wèn)列表指定的文件(例如測(cè)試數(shù)據(jù))
具體用法見(jiàn)8.1節(jié)。
6.3.2 Protobuf
6.3.2.1 proto_library
構(gòu)建protobuf庫(kù),使用protoc編譯器。語(yǔ)法:
proto_library(name = 'foo_proto',srcs = 'foo.proto',deps = [':bar_proto', ...],target_languages = ['cpp', 'java', 'python'], )- target_languages:字符串列表,生成指定語(yǔ)言的源代碼。默認(rèn)只生成C++代碼,當(dāng)protobuf庫(kù)被其他構(gòu)建目標(biāo)依賴,或者blade build指定了--generate-*選項(xiàng)時(shí)也會(huì)生成對(duì)應(yīng)語(yǔ)言的代碼。例如,如果被java_library目標(biāo)依賴,或指定了--generate-java選項(xiàng),則會(huì)生成Java代碼,對(duì)應(yīng)protoc編譯器的--java_out選項(xiàng)。
具體示例見(jiàn)Protocol Buffers入門(mén)教程 3.1.7.1 (3)和4.1.2節(jié)。
6.3.3 Java
(官方文檔很不全,很多細(xì)節(jié)根本沒(méi)有說(shuō)明)
6.3.3.1 maven_jar
表示Maven倉(cāng)庫(kù)中的一個(gè)jar文件。語(yǔ)法:
maven_jar(name = 'commons-lang3',id = 'org.apache.commons:commons-lang3:3.12.0',transitive = True )- id:字符串,指定Maven id,格式為groupId:artifactId:version,見(jiàn)Maven Naming Conventions
- transitive:布爾值(默認(rèn)為T(mén)rue),指定該目標(biāo)被打包僅fat jar時(shí)是否包含傳遞依賴,不影響編譯和測(cè)試分析傳遞依賴
直接構(gòu)建該目標(biāo)將什么都不做,只有其他目標(biāo)依賴該目標(biāo)時(shí)才會(huì)下載jar文件。
6.3.3.2 java_library
從Java源代碼構(gòu)建jar文件。語(yǔ)法:
java_library(name = 'Foo',srcs = ['Foo.java', 'Bar.java', ...],resources = ['resources/foo.conf', ...],deps = [':name', '//path/to/dir:name', ...],exported_deps = [':name', '//path/to/dir:name', ...],provided_deps = [':name', '//path/to/dir:name', ...], )- resources:字符串列表,指定要打包進(jìn)jar的資源文件
- deps:字符串列表,指定依賴目標(biāo),無(wú)傳遞性
- exported_deps:字符串列表,指定導(dǎo)出依賴目標(biāo),有傳遞性
- provided_deps:字符串列表,指定由運(yùn)行環(huán)境提供的依賴(例如Hadoop、Spark等)
- 如果當(dāng)前目標(biāo)被java_binary、java_test、java_fat_library或scala_fat_library目標(biāo)依賴或傳遞依賴,則provided_deps及其上游依賴不會(huì)被打包進(jìn)上述目標(biāo)中。
生成的jar文件名為name.jar,僅包含類文件,不包含依賴。srcs和resources支持glob()函數(shù)。三種依賴的區(qū)別詳見(jiàn)6.3.3.6節(jié)。
6.3.3.3 java_binary
從Java源代碼構(gòu)建可執(zhí)行jar文件,包含依賴。語(yǔ)法:
java_binary(name = 'Foo',srcs = ['Foo.java', 'Bar.java', ...],resources = ['resources/foo.conf', ...],deps = [':name', '//path/to/dir:name', ...],main_class = 'foo.Foo',exclusions = ['org.slf4j:*:*', 'org.apache.hadoop:*:*', ...], )- main_class:字符串,指定程序入口類(全名)
- exclusions:字符串列表,指定要排除的依賴庫(kù),格式為Maven id,支持通配符*
生成的jar文件名為name.one.jar,包含類文件、依賴、傳遞依賴以及資源文件各自生成的jar。
One-JAR是一個(gè)用于將Java應(yīng)用及其依賴打包為單個(gè)可執(zhí)行jar文件的開(kāi)源項(xiàng)目。構(gòu)建java_binary目標(biāo)需要提前下載one-jar-boot-0.97.jar,并將其所在路徑添加到BLADE_ROOT文件的one_jar_boot_jar配置中:
java_binary_config(one_jar_boot_jar = '/path/to/one-jar-boot-0.97.jar' )否則會(huì)報(bào)錯(cuò) “Blade(error): Blade build tool java_onejar error: [Errno 2] No such file or directory: ‘’”。
6.3.3.4 java_fat_library
從Java源代碼構(gòu)建fat jar文件。語(yǔ)法:
java_fat_library(name = 'Foo',srcs = ['Foo.java', 'Bar.java', ...],resources = ['resources/foo.conf', ...],deps = [':name', '//path/to/dir:name', ...],exclusions = ['org.slf4j:*:*', 'org.apache.hadoop:*:*', ...], )生成的jar文件名為name.fat.jar。
Fat jar(也叫uber jar)即包含依賴的jar,類似于Maven的jar-with-dependencies。Fat jar與one-jar的區(qū)別是:fat jar將所有子jar的內(nèi)容提取出來(lái)聚合成一個(gè)超級(jí)jar,而one-jar中每個(gè)子jar單獨(dú)存在。
通過(guò)exclusions和上游java_library依賴的provided_deps可以排除最終打包到fat jar中的依賴庫(kù),可以減小fat jar文件的大小,避免與運(yùn)行環(huán)境的依賴沖突。
6.3.3.5 java_test
從Java源代碼構(gòu)建JUnit測(cè)試jar文件。語(yǔ)法:
java_test(name = 'FooTest',srcs = ['FooTest.java', ...],resources = ['resources/foo.conf', ...],deps = [':Foo', ...],exclusions = ['org.slf4j:*:*', 'org.apache.hadoop:*:*', ...],testdata = testdata = ['data1.txt', '//path/to/data2.txt'], )具體用法見(jiàn)8.2節(jié)。
6.3.3.6 Java的依賴處理
- 編譯依賴 = 直接依賴 + 直接依賴的導(dǎo)出依賴 + 導(dǎo)出依賴的遞歸導(dǎo)出依賴 + Maven依賴的傳遞依賴
- 測(cè)試依賴 = 直接依賴 + 直接依賴的所有傳遞依賴 + Maven依賴的傳遞依賴
- 打包依賴 = 直接依賴 - provided依賴 + (直接依賴 - provided依賴)的打包依賴 - exclusions
6.3.3.7 示例
下面用Java實(shí)現(xiàn)第4節(jié)中的Hello World示例。
Say.java
package hello;public class Say {public static void say(String msg) {System.out.println(msg);} }Hello.java
package hello;public class Hello {public static void hello(String to) {Say.say("Hello, " + to);} }HelloWorld.java
package hello;public class HelloWorld {public static void main(String[] args) {Hello.hello("world");} }BUILD
java_library(name = 'Say',srcs = 'Say.java', )java_library(name = 'Hello',srcs = 'Hello.java',deps = ':Say', )java_binary(name = 'HelloWorld',srcs = 'HelloWorld.java',deps = ':Hello',main_class = 'hello.HelloWorld', )java_fat_library(name = 'HelloWorldFat',srcs = 'HelloWorld.java',deps = ':Hello', )BLADE_ROOT中需要添加one_jar_boot_jar配置,如6.3.3.3節(jié)所述。
目錄結(jié)構(gòu):
blade-demo/BLADE_ROOTjava/hello/BUILDSay.javaHello.javaHelloWorld.java在blade-demo/java/hello目錄下執(zhí)行
blade build :HelloWorld :HelloWorldFat則在blade-demo/build64_release/java/hello目錄下會(huì)生成HelloWorld.one.jar和HelloWorldFat.fat.jar兩個(gè)文件:
$ jar tf HelloWorld.one.jar META-INF/ META-INF/MANIFEST.MF OneJar.class com/simontuffs/onejar/JarClassLoader.class ... main/HelloWorld.jar lib/Hello.jar lib/Say.jar$ jar tf HelloWorldFat.fat.jar META-INF/ hello/ hello/HelloWorld.class hello/Hello.class hello/Say.class META-INF/blade/JAR.LIST META-INF/blade/MERGE-INFO META-INF/MANIFEST.MF要運(yùn)行程序,可以使用blade run命令,直接執(zhí)行one-jar,或者使用fat jar手動(dòng)指定類路徑。可以在blade-demo/java/hello目錄下執(zhí)行
$ blade run :HelloWorld ... Blade(info): Run '['.../blade-demo/build64_release/java/hello/HelloWorld']' Hello, world或者在blade-demo/build64_release/java/hello目錄下執(zhí)行
$ java -jar HelloWorld.one.jar Hello, world$ java -cp Say.jar:Hello.jar:HelloWorld.jar hello.HelloWorld Hello, world$ java -cp HelloWorldFat.fat.jar hello.HelloWorld Hello, world注:
- deps不具有傳遞性:如果HelloWorld類直接使用了Say類,則必須將:Say也添加到目標(biāo)HelloWorld的依賴中,或者將:Say添加到目標(biāo)Hello的exported_deps中。
- 如果目標(biāo)Hello的依賴:Say聲明為provided_deps,則Say.class不會(huì)被打包到HelloWorldFat.fat.jar中,因此最后一種運(yùn)行方式需要將Say.jar也添加到類路徑。
- 該示例并沒(méi)有采用Maven標(biāo)準(zhǔn)目錄結(jié)構(gòu)。盡管習(xí)慣上按照包名組織Java源代碼的目錄結(jié)構(gòu),但Java編譯器并沒(méi)有強(qiáng)制要求(但輸出的.class文件的目錄結(jié)構(gòu)必須與包名一致)。
6.3.4 Scala
Scala構(gòu)建目標(biāo)和Java構(gòu)建目標(biāo)基本一致,但沒(méi)有scala_binary。
6.3.4.1 scala_library
從Scala源代碼構(gòu)建jar文件。語(yǔ)法:
scala_library(name = 'Foo',srcs = ['Foo.scala', 'Bar.scala', ...],resources = ['resources/foo.conf', ...],deps = [':name', '//path/to/dir:name', ...],exported_deps = [':name', '//path/to/dir:name', ...],provided_deps = [':name', '//path/to/dir:name', ...], )6.3.4.2 scala_fat_library
從Scala源代碼構(gòu)建fat jar文件。語(yǔ)法:
scala_fat_library(name = 'Foo',srcs = ['Foo.scala', 'Bar.scala', ...],resources = ['resources/foo.conf', ...],deps = [':name', '//path/to/dir:name', ...],exclusions = ['org.slf4j:*:*', 'org.apache.hadoop:*:*', ...], )6.3.4.3 scala_test
從Scala源代碼構(gòu)建scala-test測(cè)試jar文件。語(yǔ)法:
scala_test(name = 'FooTest',srcs = ['FooTest.scala', ...],resources = ['resources/foo.conf', ...],deps = [':Foo', ...],exclusions = ['org.slf4j:*:*', 'org.apache.hadoop:*:*', ...],testdata = testdata = ['data1.txt', '//path/to/data2.txt'], )具體用法見(jiàn)8.3節(jié)。
6.3.4.4 示例
下面用Scala實(shí)現(xiàn)第4節(jié)中的Hello World示例。
Say.scala
package helloobject Say {def say(msg: String): Unit = println(msg) }Hello.scala
package helloobject Hello {def hello(to: String): Unit = Say.say("Hello, " + to) }HelloWorld.scala
package helloobject HelloWorld {def main(args: Array[String]): Unit = Hello.hello("world") }BUILD
scala_library(name = 'Say',srcs = 'Say.scala', )scala_library(name = 'Hello',srcs = 'Hello.scala',deps = ':Say', )scala_fat_library(name = 'HelloWorldFat',srcs = 'HelloWorld.scala',deps = ':Hello', )目錄結(jié)構(gòu):
blade-demo/BLADE_ROOTscala/hello/BUILDSay.scalaHello.scalaHelloWorld.scala在blade-demo/scala/hello目錄下執(zhí)行
blade build :HelloWorldFat則在blade-demo/build64_release/scala/hello目錄下會(huì)生成HelloWorldFat.fat.jar文件:
$ jar tf HelloWorldFat.fat.jar hello/HelloWorld.class hello/HelloWorld$.class hello/Hello.class hello/Hello$.class hello/Say.class hello/Say$.class META-INF/blade/JAR.LIST META-INF/blade/MERGE-INFO META-INF/MANIFEST.MF要運(yùn)行程序,在blade-demo/build64_release/scala/hello目錄下執(zhí)行
$ scala -cp HelloWorldFat.fat.jar hello.HelloWorld Hello, world注:如果用java命令運(yùn)行會(huì)報(bào)錯(cuò) “Exception in thread “main” java.lang.NoClassDefFoundError: scala/collection/mutable/StringBuilder”,因?yàn)槿鄙賁cala標(biāo)準(zhǔn)庫(kù)。
6.3.5 Python
Python是解釋型語(yǔ)言,因此Python的構(gòu)建規(guī)則不需要執(zhí)行任何編譯操作,只生成一個(gè)包含源代碼位置的文件。
6.3.5.1 py_library
構(gòu)建Python庫(kù)。語(yǔ)法:
py_library(name = 'foo',srcs = ['foo.py', 'bar.py', ...]deps = [':name', '//path/to/dir:name', ...],base = '//path/to/base', )- base:字符串,指定導(dǎo)入模塊起始路徑,默認(rèn)為項(xiàng)目根目錄
6.3.5.2 py_binary
構(gòu)建Python可執(zhí)行程序。語(yǔ)法:
py_binary(name = 'foo',srcs = ['foo.py', 'bar.py', ...]deps = [':name', '//path/to/dir:name', ...],base = '//path/to/base',main = 'foo.py', )- main:字符串,當(dāng)srcs包含多個(gè)文件時(shí)指定程序入口文件
6.3.5.3 py_test
構(gòu)建Python測(cè)試程序。語(yǔ)法:
py_test(name = 'foo_test',srcs = ['foo_test.py', ...],deps = [':name', '//path/to/dir:name', ...],base = '//path/to/base',main = 'foo_test.py',testdata = testdata = ['data1.txt', '//path/to/data2.txt'], )具體用法見(jiàn)8.4節(jié)。
6.3.5.4 示例
下面用Python實(shí)現(xiàn)第4節(jié)中的Hello World示例。
say.py
def say(msg):print(msg)hello.py
from python.hello import saydef hello(to):say.say('Hello, ' + to)hello_world.py
from python.hello import hellodef main():hello.hello('world')if __name__ == '__main__':main()BUILD
py_library(name = 'say',srcs = 'say.py', )py_library(name = 'hello',srcs = 'hello.py',deps = ':say', )py_binary(name = 'hello_world',srcs = 'hello_world.py',deps = ':hello', )目錄結(jié)構(gòu):
blade-demo/BLADE_ROOTpython/hello/BUILDsay.pyhello.pyhello_world.py其中import路徑相對(duì)于根目錄blade-demo,因此python.hello對(duì)應(yīng)python/hello目錄。
在blade-demo/python/hello目錄下執(zhí)行
blade build :hello_world則在blade-demo/build64_release/python/hello目錄下會(huì)生成三個(gè).pylib文件和一個(gè)可執(zhí)行文件hello_world:
$ ls hello.build.ninja hello.pylib hello_world hello_world.build.ninja hello_world.pylib say.build.ninja say.pylib$ cat hello_world.pylib {'srcs': [('python/hello/hello_world.py', 'df892f737d6f06060254cb903c597286')], 'base_dir': ''}$ head -3 hello_world #!/bin/shPYTHONPATH="$0:$PYTHONPATH" exec python -m "python.hello.hello_world" "$@"可以看出,.pylib文件只是記錄了源代碼位置和文件md5,可執(zhí)行文件hello_world就是一個(gè)Shell腳本(后面還有一些二進(jìn)制數(shù)據(jù))。
要運(yùn)行程序,可以在blade-demo/python/hello目錄下執(zhí)行
$ blade run :hello_world ... Blade(info): Run '['.../blade-demo/build64_release/python/hello/hello_world']' Hello, world或者在blade-demo/build64_release/python/hello目錄下執(zhí)行
$ ./hello_world Hello, world或者直接在blade-demo目錄下執(zhí)行(不需要Blade)
$ python3 -m python.hello.hello_world Hello, world6.3.6 文件打包
從源代碼目錄和構(gòu)建目錄打包文件。語(yǔ)法:
package(name = 'foo_package',type = 'tgz',shell = True,srcs = [('$(location //path/to/src:name)', 'path/to/dst/name'),('//path/to/src/file', 'path/to/dst/file'),] )- type:字符串,指定壓縮文件后綴名,可以是zip、tar、tar.gz、tgz、tar.bz2、tbz
- shell:布爾值,如果為T(mén)rue則使用Shell創(chuàng)建壓縮文件
- srcs:二元組(src, dst)的列表,指定要打包的內(nèi)容,其中src可以是源代碼目錄中的文件(例如配置文件)或構(gòu)建目標(biāo)的產(chǎn)物(例如可執(zhí)行程序),dst是壓縮文件中的相對(duì)路徑,src支持的格式如下:
- $(location //path/to/src:name):目標(biāo)//path/to/src:name的構(gòu)建產(chǎn)物
- //path/to/src/file:源代碼目錄中的文件,如果以//開(kāi)頭則表示相對(duì)于項(xiàng)目根目錄,否則相對(duì)于當(dāng)前目錄;file可以是單個(gè)文件,也可以是整個(gè)目錄
package規(guī)則在構(gòu)建時(shí)默認(rèn)不執(zhí)行,除非blade build顯式指定該目標(biāo),或指定了--generate-package選項(xiàng)。
例如:在第4節(jié)Hello World項(xiàng)目的基礎(chǔ)上增加一個(gè)配置文件conf/hello_world.conf和一個(gè)數(shù)據(jù)文件data.txt:
blade-demo/BLADE_ROOTquick-start/BUILDsay.hsay.cchello.hhello.cchello_world.ccdata.txtconf/hello_world.conf在BUILD文件中增加一個(gè)package目標(biāo):
package(name = 'hello_world_package',type = 'tgz',shell = True,srcs = [('$(location //quick-start:hello_world)', 'bin/hello_world'),('$(location //quick-start:hello)', 'lib/libhello.a'),('//quick-start/conf', 'conf'),('data.txt', 'data/foo.txt'),] )在quick-start目錄下執(zhí)行
$ blade build :hello_world_package $ alt # 等價(jià)于cd ../build64_release/quick-start $ pwd .../blade-demo/build64_release/quick-start $ tar -tf hello_world_package.tgz conf/hello_world.conf data/foo.txt bin/hello_world lib/libhello.a6.3.7 自定義構(gòu)建規(guī)則
語(yǔ)法:
gen_rule(name = 'foo',srcs = ['bar', 'baz', ...],deps = ['//path/to/dir:name', ...],outs = ['foo'],cmd = 'shell command to generate foo',cmd_name = 'FOO', )- srcs:字符串列表(可選),指定構(gòu)建目標(biāo)的輸入文件
- outs:字符串列表(必需),指定構(gòu)建目標(biāo)的輸出文件
- cmd:字符串(必需),生成輸出文件的Shell命令,可使用以下變量:
- $SRCS:空格分隔的輸入文件列表,相對(duì)于項(xiàng)目根目錄
- $OUTS :空格分隔的輸出文件列表,相對(duì)于項(xiàng)目根目錄
- $FIRST_SRC:第一個(gè)輸入文件
- $FIRST_OUT:第一個(gè)輸出文件
- $SRC_DIR:輸入文件所在目錄
- $OUT_DIR:輸出文件所在目錄
- $BUILD_DIR:根輸出目錄
- cmd_name:字符串(可選),命令名稱的簡(jiǎn)寫(xiě),默認(rèn)為COMMAND
執(zhí)行命令的工作目錄是項(xiàng)目根目錄。如果最后沒(méi)有生成outs指定的文件則報(bào)錯(cuò)。
例如,有在子目錄foo中a.txt和b.txt兩個(gè)文件:
blade-demo/BLADE_ROOTfoo/BUILDa.txtb.txta.txt和b.txt的內(nèi)容分別為“123”和“abc”,BUILD文件包含生成c.txt的自定義規(guī)則,通過(guò)拼接a.txt和b.txt生成:
gen_rule(name = 'c',srcs = ['a.txt', 'b.txt'],outs = ['c.txt'],cmd = 'cat $SRCS > $OUTS',cmd_name = 'CAT', )在foo目錄下執(zhí)行
$ blade build :c Blade(info): Building... [1/1] CAT //foo:c Blade(info): Build success. Blade(info): Cost time 0.199s$ alt $ pwd .../blade-demo/build64_release/foo $ cat c.txt 123 abc在這個(gè)示例中,各變量的值如下:
$SRCS = "foo/a.txt foo/b.txt" $OUTS = "build64_release/foo/c.txt" $FIRST_SRC = "foo/a.txt" $FIRST_OUT = "build64_release/foo/c.txt" $SRC_DIR = "foo" $OUT_DIR = "build64_release/foo" $BUILD_DIR = "build64_release"7.命令行參考
官方文檔:https://github.com/chen3feng/blade-build/blob/master/doc/en/command_line.md
Blade命令行語(yǔ)法:
blade <subcommand> [options...] [targets...]7.1 子命令
- build:構(gòu)建指定的目標(biāo)
- run:構(gòu)建并運(yùn)行指定的目標(biāo)
- test:構(gòu)建指定的目標(biāo)并運(yùn)行測(cè)試
- clean:刪除指定目標(biāo)的構(gòu)建產(chǎn)物
- query:分析指定的目標(biāo)依賴或被依賴的目標(biāo)
- dump:打印指定目標(biāo)的內(nèi)部信息
7.2 構(gòu)建目標(biāo)模式
子命令需要指定一個(gè)或多個(gè)構(gòu)建目標(biāo)參數(shù),稱為目標(biāo)模式(target pattern),支持以下語(yǔ)法:
- path:name:path目錄下名為name的目標(biāo)
- :name:當(dāng)前目錄下名為name的目標(biāo)
- path:*或path:path目錄下的所有目標(biāo),不包括子目錄
- path/...:path目錄及其子目錄下的所有目標(biāo)
如果path以//開(kāi)頭則表示從項(xiàng)目根目錄開(kāi)始的路徑,否則表示基于當(dāng)前目錄的相對(duì)路徑。
如果沒(méi)有指定目標(biāo)則表示當(dāng)前目錄下的所有目標(biāo),不包括子目錄。
7.3 示例
# 構(gòu)建當(dāng)前目錄下的所有目標(biāo),不包括子目錄 blade build# 構(gòu)建當(dāng)前目錄及其子目錄下的所有目標(biāo) blade build ...# 構(gòu)建當(dāng)前目錄下名為hello的目標(biāo) blade build :hello# 構(gòu)建項(xiàng)目根目錄/common/string目錄下名為string的目標(biāo) blade build //common/string:string# 構(gòu)建當(dāng)前目錄/string目錄下名為string的目標(biāo) blade build string:string# 構(gòu)建項(xiàng)目根目錄/common及其子目錄下的所有目標(biāo) blade build //common/...8.測(cè)試
官方文檔:https://github.com/chen3feng/blade-build/blob/master/doc/en/test.md
8.1 C++ - GoogleTest
cc_test_config
TODO 鏈接GoogleTest博客
8.2 Java - JUnit
java_test_config
8.3 Scala - scala-test
scala_test_config
8.4 Python - unittest
總結(jié)
- 上一篇: AI中怎么给文字加粗
- 下一篇: 华为认证HCNA-IoT物联网工程师培训