ninja启动过程

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;
}

BuildConfigOptions 主要用来保存一些配置相关的信息,常用选项中:

  • 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值