ninja启动过程
代码中的Warning基本都是我自己打的用来测试
- 源码获取
git clone https://android.googlesource.com/platform/external/ninja
- 编译指令
python3 configure.py --bootstrap
编译列表 : build.ninja生成
文件列表 : https://github.com/zhchbin/DN/wiki/ninja%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0
底层的数据结构
根据这张图,我们来看Ninja的底层的如何处理的(以下数据结构只保留到最简的部分)
底层的数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N0MDcgX7-1658137232199)(image/Ninja3.jpg)]
回到这张图,我们来看Ninja的底层的如何处理的(以下数据结构只保留到最简的部分)
State
State
保存单次运行的全局状态
struct State {
//内置pool和Rule使用这个虚拟的内置scope来初始化它们的关系位置字段。这个范围内没有任何东西。
static Scope kBuiltinScope;
static Pool kDefaultPool;
static Pool kConsolePool;
static Rule kPhonyRule;
// 内置的hashmap 保存所有的Node
typedef ConcurrentHashMap<HashedStrView, Node*> Paths;
Paths paths_;
// 保存所有的Pool
std::unordered_map<HashedStrView, Pool*> pools_;
// 保存所有的edge
vector<Edge*> edges_;
// 根作用域
Scope root_scope_ {
ScopePosition {
} };
vector<Node*> defaults_; // 默认目标
private:
/// Position 0 is used for built-in decls (e.g. pools).
DeclIndex dfs_location_ = 1;
};
Scope
Scope
作用域:变量的作用范围,有rule与build语句的块级,也有文件级别。包含Rule,同时保存了父Scope的位置
struct Scope {
Scope(ScopePosition parent) : parent_(parent) {
}
private:
ScopePosition parent_; // 父位置
DeclIndex pos_ = 0; // 自己的哈希位置
// 变量
std::unordered_map<HashedStrView, std::vector<Binding*>> bindings_;
// Rule
std::unordered_map<HashedStrView, Rule*> rules_;
};
Rule
Rule
文件的构建规则,存在局部变量
struct Rule {
Rule() {
}
struct {
// 该规则在其源文件中的位置。
size_t rule_name_diag_pos = 0;
} parse_state_;
RelativePosition pos_; // 偏移值
HashedStr name_; // 规则名
std::vector<std::pair<HashedStr, std::string>> bindings_;//保存局部变量
};
Binding & DefaultTarget
Binding
以键值对的形式存在用来变量
DefaultTarget
保存默认的输出的target
struct Binding {
RelativePosition pos_; // 偏移位置
HashedStr name_; //变量名
StringPiece parsed_value_; // 变量值
};
struct DefaultTarget {
RelativePosition pos_; // 偏移值
LexedPath parsed_path_; // StringPiece
size_t diag_pos_ = 0;
};
Node
Node
是最边界的数据结构,ninja语法中的input
,output
,target
,default
的底层保存都是Node
struct Node {
Node(const HashedStrView& path, uint64_t initial_slash_bits)
: path_(path),
first_reference_({
kLastDeclIndex, initial_slash_bits }) {
}
~Node();
private:
// 路径值
const HashedStr path_;
std::atomic<NodeFirstReference> first_reference_;
// 作为output所在的Edge位置
Edge* in_edge_ = nullptr;
// 使用此Node作为输入的所有Edge.列表顺序不确定,每次访问都是对其重新排序
struct EdgeList {
EdgeList(Edge* edge=nullptr, EdgeList* next=nullptr)
: edge(edge), next(next) {
}
Edge* edge = nullptr;
EdgeList* next = nullptr;
};
std::atomic<EdgeList*> out_edges_ {
nullptr };
std::atomic<EdgeList*> validation_out_edges_ {
nullptr };
std::vector<Edge*> dep_scan_out_edges_;
};
Edge
Edge
是最核心的数据结构,会将Node
Rule
Binding
等数据结构组合起来
struct Edge {
// 固定的属性值 在Rule下进行配置
struct DepScanInfo {
bool valid = false;
bool restat = false;
bool generator = false;
bool deps = false;
bool depfile = false;
bool phony_output = false;
uint64_t command_hash = 0;
};
public:
struct {
StringPiece rule_name; // 保存rule_name
size_t rule_name_diag_pos = 0;
size_t final_diag_pos = 0;
} parse_state_;
const Rule* rule_ = nullptr; // 使用的rule
Pool* pool_ = nullptr; // 所在的pool
// 在一个edge中的input,output
vector<Node*> inputs_;
vector<Node*> outputs_;
std::vector<std::pair<HashedStr, std::string>> unevaled_bindings_; // 存储局部变量值
int explicit_deps_ = 0; // 显式输入
int implicit_deps_ = 0; // 隐式输入
int order_only_deps_ = 0; // 隐式order-only依赖
int explicit_outs_ = 0; // 显示输出
int implicit_outs_ = 0; // 隐式输出
};
如何区分显隐式,input和output会按照按照 显式 -> 隐式 -> order-only(仅依赖) 的顺序进行push_back()
根据当前的值的位置与显隐式的数量做对比就可以知道
edge->outputs_.reserve(edge->explicit_outs_ + edge->implicit_outs_);
edge->inputs_.reserve(edge->explicit_deps_ + edge->implicit_deps_ +
edge->order_only_deps_);
入口函数 real_main()
ninja.cc::main() -> ninja.cc::real_main()
NORETURN void real_main(int argc, char** argv) {
//在这个函数中使用exit()而不是返回,以避免在破坏NinjaMain时可能发生的昂贵的清理。
BuildConfig config;//
Options options = {
}; //
options.input_file = "build.ninja";
options.dupe_edges_should_err = true;
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
const char* ninja_command = argv[0];
// 处理参数
int exit_code = ReadFlags(&argc, &argv, &options, &config); // return 1 exit
if (exit_code >= 0)
exit(exit_code);
// 在不同的行上有多个目标的删除文件是否应该警告或打印错误。
if (options.depfile_distinct_target_lines_should_err) {
config.depfile_parser_options.depfile_distinct_target_lines_action_ =
kDepfileDistinctTargetLinesActionError;
}
// NULL
if (options.working_dir) {
if (!options.tool)
Info("Entering directory `%s'", options.working_dir);
if (chdir(options.working_dir) < 0) {
Error("chdir to '%s' - %s", options.working_dir, strerror(errno));
exit(1);
}
}
//if (-d nothreads) 获取CPU数量
SetThreadPoolThreadCount(g_use_threads ? GetProcessorCount() : 1);
// 只有 urtle 使用Tool::RUN_AFTER_FLAGS)
if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) {
NinjaMain ninja(ninja_command, config);
exit((ninja.*options.tool->func)(&options, argc, argv));
}
// 跟踪构建状态
Status* status = NULL;
// 限制重建的数量,以防止无限循环。
const int kCycleLimit = 100;
for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
// 加载了一系列的数据结构,用来构建文件
NinjaMain ninja(ninja_command, config);
if (status == NULL) {
status = new StatusPrinter(config);
}
// Manifest解析器选项
ManifestParserOptions parser_opts;
if (options.dupe_edges_should_err) {
parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
}
if (options.phony_cycle_should_err) {
parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
}
// Manifest解析器
ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts);
Warning("parser : %s",ninja.ninja_command_); // parser : ./ninja
string err;
if (!parser.Load(options.input_file, &err)) {
status->Error("%s", err.c_str());
exit(1);
}
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
exit((ninja.*options.tool->func)(&options, argc, argv));
// 保证BuildDir存在
if (!ninja.EnsureBuildDirExists())
exit(1);
if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog())
exit(1);
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
exit((ninja.*options.tool->func)(&options, argc, argv));
// 尝试在构建其他内容之前重新构建Manifest
if (ninja.RebuildManifest(options.input_file, &err, status)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return immediately.
if (config.dry_run)
exit(0);
// 使用新的manifest重新开始构建。
continue;
} else if (!err.empty()) {
status->Error("rebuilding '%s': %s", options.input_file, err.c_str());
exit(1);
}
int result = 0;
do {
// 一个秒表,调用后返回时间
Stopwatch stopwatch;
if (options.persistent) {
WaitForInput(config);
stopwatch.Restart();
}
//
result = ninja.RunBuild(argc, argv, status);
if (options.persistent) {
fprintf(stderr, "build %s in %0.3f seconds\n",
result == 0 ? "succeeded" : "failed", stopwatch.Elapsed());
ninja.state_.Reset();
}
} while (options.persistent);
if (g_metrics)
ninja.DumpMetrics(status);
delete status;
exit(result);
}
status->Error("manifest '%s' still dirty after %d tries",
options.input_file, kCycleLimit);
delete status;
exit(1);
}
} // anonymous namespace
参数处理及判断
参数处理 ReadFlag()
NORETURN void real_main(int argc, char** argv) {
BuildConfig config;//
Options options = {
}; //
options.input_file = "build.ninja";
options.dupe_edges_should_err = true;
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
const char* ninja_command = argv[0];
// 处理参数
int exit_code = ReadFlags(&argc, &argv, &options, &config); // return 1 exit
if (exit_code >= 0)
exit(exit_code);
...
}
int ReadFlags(int* argc, char*** argv,
Options* options, BuildConfig* config) {
config->parallelism = GuessParallelism(); // 18
enum {
OPT_VERSION = 1,
OPT_FRONTEND = 2,
OPT_FRONTEND_FILE = 3,
};
const option kLongOptions[] = {
#ifndef _WIN32
{
"frontend", required_argument, NULL, OPT_FRONTEND },
{
"frontend_file", required_argument, NULL, OPT_FRONTEND_FILE },
#endif
{
"help", no_argument, NULL, 'h' },
{
"version", no_argument, NULL, OPT_VERSION },
{
"verbose", no_argument, NULL, 'v' },
{
NULL, 0, NULL, 0 }
};
int opt;
while (!options->tool &&
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:mnt:vw:o:C:ph", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
if (!DebugEnable(optarg)) // list false 直接退出
return 1;
break;
case 'f':
options->input_file = optarg;
break;
// 线程数
case 'j': {
char* end;
int value = strtol(optarg, &end, 10);
if (*end != 0 || value < 0)
Fatal("invalid -j parameter");
// We want to run N jobs in parallel. For N = 0, INT_MAX
// is close enough to infinite for most sane builds.
// 线程数
config->parallelism = value > 0 ? value : INT_MAX;
break;
}
// 允许失败的个数
case 'k': {
char* end;
//strtol : 参数nptr字符串根据参数base来转换成长整型数
int value = strtol(optarg, &end, 10);
if (*end != 0)
Fatal("-k parameter not numeric; did you mean -k 0?");
// We want to go until N jobs fail, which means we should allow
// N failures and then stop. For N <= 0, INT_MAX is close enough
// to infinite for most sane builds.
// 我们想去直到N个工作失败,这意味着我们应该允许N失败,然后停止。对于N<=0,对于最理智的构建,INT_MAX足够接近到无限。
config->failures_allowed = value > 0 ? value : INT_MAX;
break;
}
case 'l': {
char* end;
// strtod : 字符串转换为浮点数;
double value = strtod(optarg, &end);
if (end == optarg)
Fatal("-l parameter not numeric: did you mean -l 0.0?");
config->max_load_average = value;
break;
}
case 'n':
config->dry_run = true;
break;
case 't':
options->tool = ChooseTool(optarg);
if (!options->tool)
return 0;
break;
case 'v':
config->verbosity = BuildConfig::VERBOSE;
break;
case 'w':
if (!WarningEnable(optarg, options, config))
return 1;
break;
case 'o':
if (!OptionEnable(optarg, options, config))
return 1;
break;
// 切换工作路径
case 'C':
options->working_dir = optarg;
break;
case 'p':
options->persistent = true;
break;
case OPT_VERSION:
printf("%s\n", kNinjaVersion);
return 0;
case OPT_FRONTEND:
config->frontend = optarg;
break;
case OPT_FRONTEND_FILE:
config->frontend_file = optarg;
break;
case 'h':
default:
Usage(*config);
return 1;
}
}
*argv += optind;
*argc -= optind;
if (config->frontend != NULL && config->frontend_file != NULL) {
Fatal("only one of --frontend or --frontend_file may be specified.");
}
if (config->pre_remove_output_files && !config->uses_phony_outputs) {
Fatal("preremoveoutputs=yes requires usesphonyoutputs=yes.");
}
return -1;
}
BuildConfig
和 Options
主要用来保存一些配置相关的信息,常用选项中:
Options
主要保存输入文件[input_file(-f 默认 build.ninja)]
,工作路径[working_dir(-C 默认 当期路径,打印为NULL)]
,以及一个工具类[Tool*(-t 默认为NULL)]
以及一些打印相关的标识BuildConfig
主要是一些构建选项等,一般在使用时都是使用的默认值
参数判断
接下来就是一系列的参数判断
NORETURN void real_main(int argc, char** argv) {
...
if (options.depfile_distinct_target_lines_should_err) {
config.depfile_parser_options.depfile_distinct_target_lines_act