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