diff options
author | David Hubbard <dsp@google.com> | 2022-11-26 19:17:26 -0800 |
---|---|---|
committer | David Hubbard <dsp@google.com> | 2022-11-30 09:52:40 -0800 |
commit | 378951b8ded9d3e3df52fa1eeb1209bf9ae6652d (patch) | |
tree | bdb05de4fe16e8516fa08fb19e9665df51eba824 | |
parent | 4b5a2177cb2f246e6cf5fa1316ff4323ece37408 (diff) | |
download | ninja-378951b8ded9d3e3df52fa1eeb1209bf9ae6652d.tar.gz |
Prework for --experimentalEnvvar
This CL adds the --experimentalEnvvar flag in Options and passes it to
ManifestParserOptions, which passes it to ChunkParser where subninja chdir
parsing happens. This sets the stage for a future CL to parse environment
variable assignments.
Also add to struct Include a std::unordered_map to contain those environment
variable assignments, and add support to struct Edge for commands that
have a cmdEnviron. This sets the stage for a future CL to compute an
environment that differs from the startup environment variables when
ninja was started.
This is all the practical refactoring to make the meat of the
--experimentalEnvvar code simple, while not making any change to ninja
behavior; the flag --experimentalEnvvar does nothing.
Change-Id: I7654fbcb3389fc4a6384ac395ae0f8740b9e39ec
-rw-r--r-- | src/build.cc | 9 | ||||
-rw-r--r-- | src/build_log.cc | 5 | ||||
-rw-r--r-- | src/build_test.cc | 24 | ||||
-rw-r--r-- | src/eval_env.cc | 11 | ||||
-rw-r--r-- | src/eval_env.h | 14 | ||||
-rw-r--r-- | src/graph.cc | 8 | ||||
-rw-r--r-- | src/graph.h | 23 | ||||
-rw-r--r-- | src/graph_test.cc | 10 | ||||
-rw-r--r-- | src/manifest_chunk_parser.cc | 12 | ||||
-rw-r--r-- | src/manifest_chunk_parser.h | 5 | ||||
-rw-r--r-- | src/manifest_parser.cc | 23 | ||||
-rw-r--r-- | src/manifest_parser.h | 1 | ||||
-rw-r--r-- | src/manifest_parser_perftest.cc | 10 | ||||
-rw-r--r-- | src/manifest_parser_test.cc | 62 | ||||
-rw-r--r-- | src/ninja.cc | 37 | ||||
-rw-r--r-- | src/state.cc | 1 | ||||
-rw-r--r-- | src/state_test.cc | 4 | ||||
-rw-r--r-- | src/status.cc | 10 | ||||
-rw-r--r-- | src/subprocess-posix.cc | 13 | ||||
-rw-r--r-- | src/subprocess-win32.cc | 12 | ||||
-rw-r--r-- | src/subprocess.h | 6 | ||||
-rw-r--r-- | src/subprocess_test.cc | 53 |
22 files changed, 253 insertions, 100 deletions
diff --git a/src/build.cc b/src/build.cc index 068f635..f152317 100644 --- a/src/build.cc +++ b/src/build.cc @@ -496,8 +496,9 @@ bool RealCommandRunner::CanRunMore() { } bool RealCommandRunner::StartCommand(Edge* edge) { - string command = edge->EvaluateCommand(); - Subprocess* subproc = subprocs_.Add(command, edge->use_console()); + EdgeCommand c; + edge->EvaluateCommand(&c); + Subprocess* subproc = subprocs_.Add(c); if (!subproc) return false; subproc_to_edge_.insert(make_pair(subproc, edge)); @@ -767,7 +768,9 @@ bool Builder::StartEdge(Edge* edge, string* err) { // start command computing and run it if (!command_runner_->StartCommand(edge)) { - err->assign("command '" + edge->EvaluateCommand() + "' failed."); + EdgeCommand c; + edge->EvaluateCommand(&c); + err->assign("command '" + c.command + "' failed."); return false; } diff --git a/src/build_log.cc b/src/build_log.cc index 17256ee..a5c8825 100644 --- a/src/build_log.cc +++ b/src/build_log.cc @@ -163,8 +163,9 @@ bool BuildLog::OpenForWrite(const string& path, const BuildLogUser& user, bool BuildLog::RecordCommand(Edge* edge, int start_time, int end_time, TimeStamp mtime) { - string command = edge->EvaluateCommand(true); - uint64_t command_hash = LogEntry::HashCommand(command); + EdgeCommand c; + edge->EvaluateCommand(&c, true); + uint64_t command_hash = LogEntry::HashCommand(c.command); for (Node* out : edge->outputs_) { HashedStrView path = out->globalPath().h; LogEntry* log_entry; diff --git a/src/build_test.cc b/src/build_test.cc index c403f9f..17ec834 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -624,7 +624,9 @@ bool FakeCommandRunner::StartCommand(Edge* edge) { assert(active_edges_.size() < max_active_edges_); assert(find(active_edges_.begin(), active_edges_.end(), edge) == active_edges_.end()); - commands_ran_.push_back(edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + commands_ran_.push_back(cmd.command); if (edge->rule().name() == "cat" || edge->rule().name() == "cat_rsp" || edge->rule().name() == "cat_rsp_out" || @@ -1207,7 +1209,9 @@ TEST_F(BuildTest, DepFileOK) { ASSERT_EQ(3u, edge->inputs_.size()); // Expect the command line we generate to only use the original input. - ASSERT_EQ("cc foo.c", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + ASSERT_EQ("cc foo.c", cmd.command); } TEST_F(BuildTest, DepFileOKWithPhonyOutputs) { @@ -1286,7 +1290,9 @@ TEST_F(BuildTest, OrderOnlyDeps) { EXPECT_EQ("otherfile", edge->inputs_[3]->path()); // Expect the command line we generate to only use the original input. - ASSERT_EQ("cc foo.c", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + ASSERT_EQ("cc foo.c", cmd.command); // explicit dep dirty, expect a rebuild. EXPECT_TRUE(builder_.Build(&err)); @@ -1406,7 +1412,9 @@ TEST_F(BuildTest, DepFileCanonicalize) { // Expect the command line we generate to only use the original input, and // using the slashes from the manifest. - ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + ASSERT_EQ("cc x\\y/z\\foo.c", cmd.command); } #endif @@ -2491,7 +2499,9 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) { ASSERT_EQ(3u, edge->inputs_.size()); // Expect the command line we generate to only use the original input. - ASSERT_EQ("cc foo.c", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + ASSERT_EQ("cc foo.c", cmd.command); deps_log.Close(); builder.command_runner_.release(); @@ -2556,7 +2566,9 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) { // Expect the command line we generate to only use the original input. // Note, slashes from manifest, not .d. - ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + ASSERT_EQ("cc x\\y/z\\foo.c", cmd.command); deps_log.Close(); builder.command_runner_.release(); diff --git a/src/eval_env.cc b/src/eval_env.cc index 924da07..507fb6d 100644 --- a/src/eval_env.cc +++ b/src/eval_env.cc @@ -244,3 +244,14 @@ std::string Scope::ResolveChdir(Scope* child, std::string path) { void Scope::AddAllBuiltinRules() { AddRule(&State::kPhonyRule); } + +Scope::~Scope() { + if (cmdEnviron_) { + for (char** p = cmdEnviron_; *p; p++) { + delete[] *p; + *p = NULL; + } + delete[] cmdEnviron_; + cmdEnviron_ = NULL; + } +} diff --git a/src/eval_env.h b/src/eval_env.h index db7edd6..d1f486d 100644 --- a/src/eval_env.h +++ b/src/eval_env.h @@ -151,14 +151,20 @@ struct Scope { // Simple constructor for a new Scope. Scope(ScopePosition parent) : parent_(parent) + , cmdEnviron_(NULL) , chdirParent_(nullptr) , chdir_(parent.scope ? parent.scope->chdir_ : "") {} // Constructor which creates a new Scope that: // - Does not search a parent ScopePosition when evaluating variables. // - Does know the parent Scope for traversing the directory tree. - Scope(Scope* chdirParent, std::string chdir) - : parent_(nullptr), chdirParent_(chdirParent), chdir_(chdir) {} + Scope(Scope* chdirParent, std::string chdir, char** cmdEnviron) + : parent_(nullptr) + , cmdEnviron_(cmdEnviron) + , chdirParent_(chdirParent) + , chdir_(chdir) {} + + ~Scope(); /// Preallocate space in the hash tables so adding bindings and rules is more /// efficient. @@ -238,11 +244,15 @@ struct Scope { // Used for testing. Scope* parent() const { return parent_.scope; } + char** getCmdEnviron() const { return cmdEnviron_; } + private: /// The position of this scope within its parent scope. /// (ScopePosition::parent will be nullptr for the root scope.) ScopePosition parent_; + char** cmdEnviron_ = NULL; + // A Scope with chdir_ set to non-empty must not allow variable evaluation // to continue beyond its scope, so parent_ (above) is set to nullptr. // diff --git a/src/graph.cc b/src/graph.cc index 414d170..b1ed28b 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -659,12 +659,12 @@ bool Edge::EvaluateCommand(std::string* out_append, bool incl_rsp_file, return true; } -std::string Edge::EvaluateCommand(bool incl_rsp_file) { - std::string command; +void Edge::EvaluateCommand(EdgeCommand* out, bool incl_rsp_file) { std::string err; - if (!EvaluateCommand(&command, incl_rsp_file, &err)) + if (!EvaluateCommand(&out->command, incl_rsp_file, &err)) Fatal("%s", err.c_str()); - return command; + out->use_console = use_console(); + out->env = cmdEnviron; } static const HashedStrView kRestat { "restat" }; diff --git a/src/graph.h b/src/graph.h index adf25cb..001b68d 100644 --- a/src/graph.h +++ b/src/graph.h @@ -297,6 +297,12 @@ typedef std::vector<Node*> DepPath; // Find all dependency paths between an input and output node. std::vector<DepPath> GetDependencyPaths(Node* in, Node* out); +struct EdgeCommand { + std::string command; + bool use_console = false; + char** env = NULL; +}; + struct EdgeEval { enum EvalPhase { kParseTime, kFinalScope }; enum EscapeKind { kShellEscape, kDoNotEscape }; @@ -362,7 +368,7 @@ struct Edge { /// Convenience method. This method must not be called from a worker thread, /// because it could abort with a fatal error. (For consistency with other /// Get*/Evaluate* methods, a better name might be GetCommand.) - std::string EvaluateCommand(bool incl_rsp_file = false); + void EvaluateCommand(EdgeCommand* out, bool incl_rsp_file = false); /// Attempts to evaluate info needed for scanning dependencies. bool PrecomputeDepScanInfo(std::string* err); @@ -388,11 +394,26 @@ struct Edge { EdgeEval::EscapeKind escape=EdgeEval::kShellEscape); private: + char** cmdEnviron = NULL; std::string GetBindingImpl(const HashedStrView& key, EdgeEval::EvalPhase phase, EdgeEval::EscapeKind escape); public: + /// onPosResolvedToScope updates cmdEnviron from pos_.scope(). Then + /// Edge::EvaluateCommand has the cmdEnviron as a precomputed value. + void onPosResolvedToScope(Scope* scope) { + // This is NULL if scope is the root scope. However, comparing a Scope* to + // State::root_scope_ gets hairy (since the global State object is not + // yet declared). Instead, always call getCmdEnviron() and it will return + // NULL. + // (for posix, NULL is checked in Subprocess and environ is given instead.) + // (for win32, NULL is passed straight to CreateProcessA which then passes + // the environ to the command per its defined behavior.) + + cmdEnviron = scope->getCmdEnviron(); + } + /// Convenience method for EvaluateVariable. On failure, it issues a fatal /// error. This function must not be called from a worker thread because: /// - Error reporting should be deterministic, and diff --git a/src/graph_test.cc b/src/graph_test.cc index 47f4441..361123c 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -218,12 +218,14 @@ TEST_F(GraphTest, VarInOutPathEscaping) { "build a$ b: cat no'space with$ space$$ no\"space2\n")); Edge* edge = GetNode("a b")->in_edge(); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); #if _WIN32 EXPECT_EQ("cat no'space \"with space$\" \"no\\\"space2\" > \"a b\"", - edge->EvaluateCommand()); + cmd.command); #else EXPECT_EQ("cat 'no'\\''space' 'with space$' 'no\"space2' > 'a b'", - edge->EvaluateCommand()); + cmd.command); #endif } @@ -278,7 +280,9 @@ TEST_F(GraphTest, RuleVariablesInScope) { " command = depfile is $depfile\n" "build out: r in\n")); Edge* edge = GetNode("out")->in_edge(); - EXPECT_EQ("depfile is x", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + EXPECT_EQ("depfile is x", cmd.command); } // Check that build statements can override rule builtins like depfile. diff --git a/src/manifest_chunk_parser.cc b/src/manifest_chunk_parser.cc index 2d0283e..5d0b928 100644 --- a/src/manifest_chunk_parser.cc +++ b/src/manifest_chunk_parser.cc @@ -28,6 +28,7 @@ namespace manifest_chunk { class ChunkParser { const LoadedFile& file_; + const bool experimentalEnvvar_; Lexer lexer_; const char* chunk_end_ = nullptr; std::vector<ParserItem>* out_ = nullptr; @@ -64,8 +65,10 @@ class ChunkParser { public: ChunkParser(const LoadedFile& file, StringPiece chunk_content, - std::vector<ParserItem>* out) + std::vector<ParserItem>* out, + bool experimentalEnvvar) : file_(file), + experimentalEnvvar_(experimentalEnvvar), lexer_(file.filename(), file.content(), chunk_content.data()), chunk_end_(chunk_content.data() + chunk_content.size()), out_(out) {} @@ -115,6 +118,7 @@ bool ChunkParser::ParseFileInclude(bool new_scope) { // 'ninja: build.ninja:NNN: unexpected indent'. This might be a slightly more // helpful message. if (lexer_.PeekToken(Lexer::INDENT)) { + (void)experimentalEnvvar_; // TODO: add envvar here. if (!new_scope) return LexerError("indent after 'include' line is invalid."); if (!lexer_.PeekToken(Lexer::CHDIR)) @@ -379,6 +383,8 @@ bool ChunkParser::ParseEdge() { Clump* clump = MakeClump(); edge->pos_ = clump->AllocNextPos(); + // Can't edge->onPosResolvedToScope(clump->pos_.scope.scope) right here, since + // clump->pos_.scope.scope is not set until DfsParser::HandleClump(). clump->edges_.push_back(edge); clump->edge_output_count_ += edge->explicit_outs_; return true; @@ -417,8 +423,8 @@ bool ChunkParser::ParseChunk() { } void ParseChunk(const LoadedFile& file, StringPiece chunk_content, - std::vector<ParserItem>* out) { - ChunkParser parser(file, chunk_content, out); + std::vector<ParserItem>* out, bool experimentalEnvvar) { + ChunkParser parser(file, chunk_content, out, experimentalEnvvar); if (!parser.ParseChunk()) { assert(!out->empty()); assert(out->back().kind == ParserItem::kError); diff --git a/src/manifest_chunk_parser.h b/src/manifest_chunk_parser.h index 5da677d..ebb970c 100644 --- a/src/manifest_chunk_parser.h +++ b/src/manifest_chunk_parser.h @@ -18,6 +18,7 @@ #include <stdlib.h> #include <string> +#include <unordered_map> #include <vector> #include "disk_interface.h" @@ -44,6 +45,8 @@ struct Include { std::string chdir_plus_slash_; bool new_scope_ = false; size_t diag_pos_ = 0; + + std::unordered_map<std::string, std::string> envvar; }; struct DefaultTarget { @@ -105,7 +108,7 @@ struct ParserItem { /// Parse a chunk of a manifest. If the parser encounters a syntactic error, the /// final item will be an error object. void ParseChunk(const LoadedFile& file, StringPiece chunk_content, - std::vector<ParserItem>* out); + std::vector<ParserItem>* out, bool experimentalEnvvar); /// Split the input into chunks of declarations. std::vector<StringPiece> SplitManifestIntoChunks(StringPiece input); diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index 225e0a2..1c5e7de 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -47,7 +47,8 @@ static bool DecorateError(const LoadedFile& file, size_t file_offset, /// Split a single manifest file into chunks, parse the chunks in parallel, and /// return the resulting parser output. static std::vector<ParserItem> ParseManifestChunks(const LoadedFile& file, - ThreadPool* thread_pool) { + ThreadPool* thread_pool, + bool experimentalEnvvar) { std::vector<ParserItem> result; const std::vector<StringPiece> chunk_views = manifest_chunk::SplitManifestIntoChunks(file.content_with_nul()); @@ -55,9 +56,9 @@ static std::vector<ParserItem> ParseManifestChunks(const LoadedFile& file, METRIC_RECORD(".ninja load : parse chunks"); for (std::vector<ParserItem>& chunk_items : - ParallelMap(thread_pool, chunk_views, [&file](StringPiece view) { + ParallelMap(thread_pool, chunk_views, [&file, experimentalEnvvar](StringPiece view) { std::vector<ParserItem> chunk_items; - manifest_chunk::ParseChunk(file, view, &chunk_items); + manifest_chunk::ParseChunk(file, view, &chunk_items, experimentalEnvvar); return chunk_items; })) { std::move(chunk_items.begin(), chunk_items.end(), @@ -130,8 +131,8 @@ bool ManifestFileSet::LoadFile(const std::string& filename, } struct DfsParser { - DfsParser(ManifestFileSet* file_set, State* state, ThreadPool* thread_pool) - : file_set_(file_set), state_(state), thread_pool_(thread_pool) {} + DfsParser(ManifestFileSet* file_set, State* state, ThreadPool* thread_pool, const ManifestParserOptions& options) + : options_(options), file_set_(file_set), state_(state), thread_pool_(thread_pool) {} private: void HandleRequiredVersion(const RequiredVersion& item, Scope* scope); @@ -146,6 +147,7 @@ private: std::string* err); public: + const ManifestParserOptions options_; /// Load the tree of manifest files and do initial parsing of all the chunks. /// This function always runs on the main thread. bool LoadManifestTree(const LoadedFile& file, Scope* scope, @@ -183,7 +185,7 @@ bool DfsParser::HandleInclude(Include& include, const LoadedFile& file, include.chdir_.str_ = StringPiece(include.chdir_plus_slash_); // Create scope so that subninja chdir variable lookups cannot see parent_ - *child_scope = new Scope(scope, include.chdir_plus_slash_); + *child_scope = new Scope(scope, include.chdir_plus_slash_, NULL /*cmdEnviron*/); (*child_scope)->AddAllBuiltinRules(); } else if (include.new_scope_) { *child_scope = new Scope(scope->GetCurrentEndOfScope()); @@ -277,6 +279,11 @@ bool DfsParser::HandleClump(Clump* clump, const LoadedFile& file, Scope* scope, } } } + { + for (Edge* edge : clump->edges_) { + edge->onPosResolvedToScope(clump->pos_.scope.scope); + } + } for (Pool* pool : clump->pools_) { if (!HandlePool(pool, file, err)) { return false; @@ -288,7 +295,7 @@ bool DfsParser::HandleClump(Clump* clump, const LoadedFile& file, Scope* scope, bool DfsParser::LoadManifestTree(const LoadedFile& file, Scope* scope, std::vector<Clump*>* out_clumps, std::string* err) { - std::vector<ParserItem> items = ParseManifestChunks(file, thread_pool_); + std::vector<ParserItem> items = ParseManifestChunks(file, thread_pool_, options_.experimentalEnvvar); ReserveSpaceInScopeTables(scope, items); // With the chunks parsed, do a depth-first parse of the ninja manifest using @@ -703,7 +710,7 @@ bool ManifestLoader::FinishLoading(const std::vector<Clump*>& clumps, bool ManifestLoader::Load(ManifestFileSet* file_set, const LoadedFile& root_manifest, std::string* err) { - DfsParser dfs_parser(file_set, state_, thread_pool_); + DfsParser dfs_parser(file_set, state_, thread_pool_, options_); std::vector<Clump*> clumps; if (!dfs_parser.LoadManifestTree(root_manifest, &state_->root_scope_, &clumps, err)) { diff --git a/src/manifest_parser.h b/src/manifest_parser.h index 3f1895d..d9f31ae 100644 --- a/src/manifest_parser.h +++ b/src/manifest_parser.h @@ -33,6 +33,7 @@ enum PhonyCycleAction { struct ManifestParserOptions { DupeEdgeAction dupe_edge_action_ = kDupeEdgeActionWarn; PhonyCycleAction phony_cycle_action_ = kPhonyCycleActionWarn; + bool experimentalEnvvar = false; }; struct ManifestParser { diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc index 67d11f9..40ba276 100644 --- a/src/manifest_parser_perftest.cc +++ b/src/manifest_parser_perftest.cc @@ -65,9 +65,13 @@ int LoadManifests(bool measure_command_evaluation) { // commands required for the requested targets. So include command // evaluation in the perftest by default. int optimization_guard = 0; - if (measure_command_evaluation) - for (size_t i = 0; i < state.edges_.size(); ++i) - optimization_guard += state.edges_[i]->EvaluateCommand().size(); + if (measure_command_evaluation) { + for (size_t i = 0; i < state.edges_.size(); ++i) { + EdgeCommand c; + state.edges_[i]->EvaluateCommand(&c); + optimization_guard += c.command.size(); + } + } return optimization_guard; } diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 308cc11..a82ca49 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -152,7 +152,9 @@ TEST_F(ParserTest, InNewline) { EXPECT_EQ("cat $in_newline > $out\n", *rule->GetBinding("command")); Edge* edge = state.edges_[0]; - EXPECT_EQ("cat in\nin2 > out", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + EXPECT_EQ("cat in\nin2 > out", cmd.command); } TEST_F(ParserTest, Variables) { @@ -170,14 +172,16 @@ TEST_F(ParserTest, Variables) { " extra = $nested2/3\n")); ASSERT_EQ(2u, state.edges_.size()); - Edge* edge = state.edges_[0]; + EdgeCommand cmd; + state.edges_[0]->EvaluateCommand(&cmd); EXPECT_EQ("ld one-letter-test -pthread -under -o a b c", - edge->EvaluateCommand()); + cmd.command); EXPECT_EQ("1/2", state.root_scope_.LookupVariable("nested2")); - edge = state.edges_[1]; + cmd.command.clear(); + state.edges_[1]->EvaluateCommand(&cmd); EXPECT_EQ("ld one-letter-test 1/2/3 -under -o supernested x", - edge->EvaluateCommand()); + cmd.command); } TEST_F(ParserTest, VariableScope) { @@ -193,8 +197,12 @@ TEST_F(ParserTest, VariableScope) { )); ASSERT_EQ(2u, state.edges_.size()); - EXPECT_EQ("cmd baz a inner", state.edges_[0]->EvaluateCommand()); - EXPECT_EQ("cmd bar b outer", state.edges_[1]->EvaluateCommand()); + EdgeCommand cmd; + state.edges_[0]->EvaluateCommand(&cmd); + EXPECT_EQ("cmd baz a inner", cmd.command); + cmd.command.clear(); + state.edges_[1]->EvaluateCommand(&cmd); + EXPECT_EQ("cmd bar b outer", cmd.command); } TEST_F(ParserTest, Continuation) { @@ -239,10 +247,12 @@ TEST_F(ParserTest, Dollars) { "build $x: foo y\n" )); EXPECT_EQ("$dollar", state.root_scope_.LookupVariable("x")); + EdgeCommand cmd; + state.edges_[0]->EvaluateCommand(&cmd); #ifdef _WIN32 - EXPECT_EQ("$dollarbar$baz$blah", state.edges_[0]->EvaluateCommand()); + EXPECT_EQ("$dollarbar$baz$blah", cmd.command); #else - EXPECT_EQ("'$dollar'bar$baz$blah", state.edges_[0]->EvaluateCommand()); + EXPECT_EQ("'$dollar'bar$baz$blah", cmd.command); #endif } @@ -256,7 +266,9 @@ TEST_F(ParserTest, EscapeSpaces) { EXPECT_EQ(state.edges_[0]->outputs_[0]->path(), "foo bar"); EXPECT_EQ(state.edges_[0]->inputs_[0]->path(), "$one"); EXPECT_EQ(state.edges_[0]->inputs_[1]->path(), "two$ three"); - EXPECT_EQ(state.edges_[0]->EvaluateCommand(), "something"); + EdgeCommand cmd; + state.edges_[0]->EvaluateCommand(&cmd); + EXPECT_EQ(cmd.command, "something"); } TEST_F(ParserTest, CanonicalizeFile) { @@ -951,9 +963,15 @@ TEST_F(ParserTest, SubNinja) { EXPECT_TRUE(inner); ASSERT_EQ(3u, state.edges_.size()); - EXPECT_EQ("varref outer", state.edges_[0]->EvaluateCommand()); - EXPECT_EQ("varref inner", state.edges_[1]->EvaluateCommand()); - EXPECT_EQ("varref outer", state.edges_[2]->EvaluateCommand()); + EdgeCommand cmd; + state.edges_[0]->EvaluateCommand(&cmd); + EXPECT_EQ("varref outer", cmd.command); + cmd.command.clear(); + state.edges_[1]->EvaluateCommand(&cmd); + EXPECT_EQ("varref inner", cmd.command); + cmd.command.clear(); + state.edges_[2]->EvaluateCommand(&cmd); + EXPECT_EQ("varref outer", cmd.command); // Verify inner scope links back to parent. ASSERT_TRUE(state.edges_[1]->pos_.scope()->parent()); @@ -1005,11 +1023,13 @@ TEST_F(ParserTest, SubNinjaChdir) { // Verify command includes correct chdir for subninja chdir. Edge* edge = inner2node->in_edge(); EXPECT_TRUE(edge); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); #ifdef _WIN32 EXPECT_EQ(NINJA_WIN32_CD_DELIM "b/" NINJA_WIN32_CD_DELIM - "foo \"inner\" \"def\" \"inner2\"", edge->EvaluateCommand()); + "foo \"inner\" \"def\" \"inner2\"", cmd.command); #else /* _WIN32 */ - EXPECT_EQ("cd \"b/\" && foo \"inner\" \"def\" \"inner2\"", edge->EvaluateCommand()); + EXPECT_EQ("cd \"b/\" && foo \"inner\" \"def\" \"inner2\"", cmd.command); #endif /* _WIN32 */ // Verify chdir scope does *not* allow variables from a parent scope to @@ -1085,20 +1105,24 @@ TEST_F(ParserTest, TwoChdirs) { // Verify edge command includes correct 'cd test-a' Edge* edge = GetNode("test-a/final")->in_edge(); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); #if _WIN32 EXPECT_EQ(NINJA_WIN32_CD_DELIM "test-a/" NINJA_WIN32_CD_DELIM - "cat out1 > final", edge->EvaluateCommand()); + "cat out1 > final", cmd.command); #else - EXPECT_EQ("cd \"test-a/\" && cat out1 > final", edge->EvaluateCommand()); + EXPECT_EQ("cd \"test-a/\" && cat out1 > final", cmd.command); #endif // Verify edge command includes correct 'cd test-b' + cmd.command.clear(); edge = GetNode("test-b/final")->in_edge(); + edge->EvaluateCommand(&cmd); #if _WIN32 EXPECT_EQ(NINJA_WIN32_CD_DELIM "test-b/" NINJA_WIN32_CD_DELIM - "cat out3 > final", edge->EvaluateCommand()); + "cat out3 > final", cmd.command); #else - EXPECT_EQ("cd \"test-b/\" && cat out3 > final", edge->EvaluateCommand()); + EXPECT_EQ("cd \"test-b/\" && cat out3 > final", cmd.command); #endif } diff --git a/src/ninja.cc b/src/ninja.cc index 53771f5..00372eb 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -91,6 +91,9 @@ struct Options { /// Whether to remain persistent. bool persistent; + + /// Experimental, buggy envvar option. + bool experimentalEnvvar; }; /// The Ninja main() loads up a series of data structures; various tools need @@ -848,8 +851,11 @@ void PrintCommands(Edge* edge, EdgeSet* seen, PrintCommandMode mode) { PrintCommands((*in)->in_edge(), seen, mode); } - if (!edge->is_phony()) - puts(edge->EvaluateCommand().c_str()); + if (!edge->is_phony()) { + EdgeCommand c; + edge->EvaluateCommand(&c); + puts(c.command.c_str()); + } } int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) { @@ -956,18 +962,18 @@ enum EvaluateCommandMode { ECM_NORMAL, ECM_EXPAND_RSPFILE }; -string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) { - string command = edge->EvaluateCommand(); +void EvaluateCommandWithRspfile(EdgeCommand* out, Edge* edge, EvaluateCommandMode mode) { + edge->EvaluateCommand(out); if (mode == ECM_NORMAL) - return command; + return; string rspfile = edge->GetUnescapedRspfile(); if (rspfile.empty()) - return command; + return; - size_t index = command.find(rspfile); - if (index == 0 || index == string::npos || command[index - 1] != '@') - return command; + size_t index = out->command.find(rspfile); + if (index == 0 || index == string::npos || out->command[index - 1] != '@') + return; string rspfile_content = edge->GetBinding("rspfile_content"); size_t newline_index = 0; @@ -976,8 +982,7 @@ string EvaluateCommandWithRspfile(Edge* edge, EvaluateCommandMode mode) { rspfile_content.replace(newline_index, 1, 1, ' '); ++newline_index; } - command.replace(index - 1, rspfile.length() + 1, rspfile_content); - return command; + out->command.replace(index - 1, rspfile.length() + 1, rspfile_content); } int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, @@ -1036,7 +1041,9 @@ int NinjaMain::ToolCompilationDatabase(const Options* options, int argc, printf("\n {\n \"directory\": \""); EncodeJSONString(&cwd[0]); printf("\",\n \"command\": \""); - EncodeJSONString(EvaluateCommandWithRspfile(*e, eval_mode).c_str()); + EdgeCommand cmd; + EvaluateCommandWithRspfile(&cmd, *e, eval_mode); + EncodeJSONString(cmd.command.c_str()); printf("\",\n \"file\": \""); EncodeJSONString((*e)->inputs_[0]->path().c_str()); printf("\",\n \"output\": \""); @@ -1498,6 +1505,7 @@ int ReadFlags(int* argc, char*** argv, OPT_FRONTEND = 2, OPT_FRONTEND_FILE = 3, OPT_QUIET = 4, + OPT_EXPERIMENTALENVVAR = 5, }; const option kLongOptions[] = { #ifndef _WIN32 @@ -1508,6 +1516,7 @@ int ReadFlags(int* argc, char*** argv, { "version", no_argument, NULL, OPT_VERSION }, { "verbose", no_argument, NULL, 'v' }, { "quiet", no_argument, NULL, OPT_QUIET }, + { "experimentalEnvvar", no_argument, NULL, OPT_EXPERIMENTALENVVAR }, { NULL, 0, NULL, 0 } }; @@ -1591,6 +1600,9 @@ int ReadFlags(int* argc, char*** argv, case OPT_FRONTEND_FILE: config->frontend_file = optarg; break; + case OPT_EXPERIMENTALENVVAR: + options->experimentalEnvvar = true; + break; case 'h': default: Usage(*config); @@ -1690,6 +1702,7 @@ NORETURN void real_main(int argc, char** argv) { if (options.phony_cycle_should_err) { parser_opts.phony_cycle_action_ = kPhonyCycleActionError; } + parser_opts.experimentalEnvvar = options.experimentalEnvvar; ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts); string err; if (!parser.Load(options.input_file, &err)) { diff --git a/src/state.cc b/src/state.cc index df7966f..2ae3dde 100644 --- a/src/state.cc +++ b/src/state.cc @@ -85,6 +85,7 @@ bool State::AddPool(Pool* pool) { Edge* State::AddEdge(const Rule* rule) { Edge* edge = new Edge(); edge->pos_.base = new BasePosition {{ &root_scope_, 0 }}; // leaked + edge->onPosResolvedToScope(&root_scope_); edge->rule_ = rule; edge->pool_ = &State::kDefaultPool; edge->id_ = edges_.size(); diff --git a/src/state_test.cc b/src/state_test.cc index 898fdb1..170a7be 100644 --- a/src/state_test.cc +++ b/src/state_test.cc @@ -30,7 +30,9 @@ TEST(State, Basic) { state.AddIn(edge, state.root_scope_.GlobalPath("in2"), 0); state.AddOut(edge, state.root_scope_.GlobalPath("out"), 0); - EXPECT_EQ("cat in1 in2 > out", edge->EvaluateCommand()); + EdgeCommand cmd; + edge->EvaluateCommand(&cmd); + EXPECT_EQ("cat in1 in2 > out", cmd.command); EXPECT_FALSE(state.GetNode(state.root_scope_.GlobalPath("in1"), 0)->dirty()); EXPECT_FALSE(state.GetNode(state.root_scope_.GlobalPath("in2"), 0)->dirty()); diff --git a/src/status.cc b/src/status.cc index 03e0642..4f8a5eb 100644 --- a/src/status.cc +++ b/src/status.cc @@ -75,7 +75,9 @@ void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis, outputs += (*o)->globalPath().h.str_view().AsString() + " "; printer_.PrintOnNewLine("FAILED: " + outputs + "\n"); - printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n"); + EdgeCommand c; + edge->EvaluateCommand(&c); + printer_.PrintOnNewLine(c.command + "\n"); } if (!result->output.empty()) { @@ -278,8 +280,10 @@ StatusSerializer::StatusSerializer(const BuildConfig& config) : f_ = fdopen(output_pipe[1], "wb"); // Launch the frontend as a subprocess with write-end of the pipe as fd 3 - subprocess_ = subprocess_set_.Add(config.frontend, /*use_console=*/true, - output_pipe[0]); + EdgeCommand c; + c.command = config.frontend; + c.use_console = true; + subprocess_ = subprocess_set_.Add(c, output_pipe[0]); close(output_pipe[0]); } else if (config.frontend_file != NULL) { f_ = fopen(config.frontend_file, "wb"); diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index d7c1a13..72a169c 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -41,7 +41,7 @@ Subprocess::~Subprocess() { Finish(); } -bool Subprocess::Start(SubprocessSet* set, const string& command, +bool Subprocess::Start(SubprocessSet* set, const EdgeCommand& cmd, int extra_fd) { int output_pipe[2]; if (pipe(output_pipe) < 0) @@ -116,9 +116,9 @@ bool Subprocess::Start(SubprocessSet* set, const string& command, if (err != 0) Fatal("posix_spawnattr_setflags: %s", strerror(err)); - const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL }; + const char* spawned_args[] = { "/bin/sh", "-c", cmd.command.c_str(), NULL }; err = posix_spawn(&pid_, "/bin/sh", &action, &attr, - const_cast<char**>(spawned_args), environ); + const_cast<char**>(spawned_args), cmd.env ? cmd.env : environ); if (err != 0) Fatal("posix_spawn: %s", strerror(err)); @@ -231,10 +231,9 @@ SubprocessSet::~SubprocessSet() { Fatal("sigprocmask: %s", strerror(errno)); } -Subprocess *SubprocessSet::Add(const string& command, bool use_console, - int extra_fd) { - Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command, extra_fd)) { +Subprocess *SubprocessSet::Add(const EdgeCommand& cmd, int extra_fd) { + Subprocess *subprocess = new Subprocess(cmd.use_console); + if (!subprocess->Start(this, cmd, extra_fd)) { delete subprocess; return 0; } diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc index f83643f..4673334 100644 --- a/src/subprocess-win32.cc +++ b/src/subprocess-win32.cc @@ -73,7 +73,7 @@ HANDLE Subprocess::SetupPipe(HANDLE ioport) { return output_write_child; } -bool Subprocess::Start(SubprocessSet* set, const string& command) { +bool Subprocess::Start(SubprocessSet* set, const EdgeCommand& cmd) { // chdir is passed to CreateProcessA. If it is NULL, no chdir is needed. // // BUG: CreateProcessA does the search for argv[0] before changing to chdir @@ -104,7 +104,7 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { cleanedCommand = command.substr(endCD + delimSize); } } else { - cleanedCommand = command; + cleanedCommand = cmd.command; } HANDLE child_pipe = SetupPipe(set->ioport_); @@ -143,7 +143,7 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { // lines greater than 8,191 chars. if (!CreateProcessA(NULL, (char*)cleanedCommand.c_str(), NULL, NULL, /* inherit handles */ TRUE, process_flags, - NULL, chdir, + cmd.env, chdir, &startup_info, &process_info)) { DWORD error = GetLastError(); if (chdir) { @@ -268,9 +268,9 @@ BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) { return FALSE; } -Subprocess *SubprocessSet::Add(const string& command, bool use_console) { - Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command)) { +Subprocess *SubprocessSet::Add(const EdgeCommand& cmd) { + Subprocess *subprocess = new Subprocess(cmd.use_console); + if (!subprocess->Start(this, cmd)) { delete subprocess; return 0; } diff --git a/src/subprocess.h b/src/subprocess.h index db8a030..6e13207 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -35,6 +35,7 @@ using namespace std; # endif #endif +#include "graph.h" #include "exit_status.h" /// Subprocess wraps a single async subprocess. It is entirely @@ -58,7 +59,7 @@ struct Subprocess { private: Subprocess(bool use_console); - bool Start(struct SubprocessSet* set, const string& command, int extra_fd); + bool Start(struct SubprocessSet* set, const EdgeCommand& cmd, int extra_fd); void OnPipeReady(); string buf_; @@ -90,8 +91,7 @@ struct SubprocessSet { SubprocessSet(); ~SubprocessSet(); - Subprocess* Add(const string& command, bool use_console = false, - int extra_fd = -1); + Subprocess* Add(const EdgeCommand& cmd, int extra_fd = -1); bool DoWork(); Subprocess* NextFinished(); void Clear(); diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 839ec69..475504d 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -40,7 +40,9 @@ struct SubprocessTest : public testing::Test { // Run a command that fails and emits to stderr. TEST_F(SubprocessTest, BadCommandStderr) { - Subprocess* subproc = subprocs_.Add("cmd /c ninja_no_such_command"); + EdgeCommand c; + c.command = "cmd /c ninja_no_such_command"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -54,7 +56,9 @@ TEST_F(SubprocessTest, BadCommandStderr) { // Run a command that does not exist TEST_F(SubprocessTest, NoSuchCommand) { - Subprocess* subproc = subprocs_.Add("ninja_no_such_command"); + EdgeCommand c; + c.command = "ninja_no_such_command"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -73,7 +77,9 @@ TEST_F(SubprocessTest, NoSuchCommand) { #ifndef _WIN32 TEST_F(SubprocessTest, InterruptChild) { - Subprocess* subproc = subprocs_.Add("kill -INT $$"); + EdgeCommand c; + c.command = "kill -INT $$"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -84,7 +90,9 @@ TEST_F(SubprocessTest, InterruptChild) { } TEST_F(SubprocessTest, InterruptParent) { - Subprocess* subproc = subprocs_.Add("kill -INT $PPID ; sleep 1"); + EdgeCommand c; + c.command = "kill -INT $PPID ; sleep 1"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -97,7 +105,9 @@ TEST_F(SubprocessTest, InterruptParent) { } TEST_F(SubprocessTest, InterruptChildWithSigTerm) { - Subprocess* subproc = subprocs_.Add("kill -TERM $$"); + EdgeCommand c; + c.command = "kill -TERM $$"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -108,7 +118,9 @@ TEST_F(SubprocessTest, InterruptChildWithSigTerm) { } TEST_F(SubprocessTest, InterruptParentWithSigTerm) { - Subprocess* subproc = subprocs_.Add("kill -TERM $PPID ; sleep 1"); + EdgeCommand c; + c.command = "kill -TERM $PPID ; sleep 1"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -121,7 +133,9 @@ TEST_F(SubprocessTest, InterruptParentWithSigTerm) { } TEST_F(SubprocessTest, InterruptChildWithSigHup) { - Subprocess* subproc = subprocs_.Add("kill -HUP $$"); + EdgeCommand c; + c.command = "kill -HUP $$"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -132,7 +146,9 @@ TEST_F(SubprocessTest, InterruptChildWithSigHup) { } TEST_F(SubprocessTest, InterruptParentWithSigHup) { - Subprocess* subproc = subprocs_.Add("kill -HUP $PPID ; sleep 1"); + EdgeCommand c; + c.command = "kill -HUP $PPID ; sleep 1"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -147,8 +163,11 @@ TEST_F(SubprocessTest, InterruptParentWithSigHup) { TEST_F(SubprocessTest, Console) { // Skip test if we don't have the console ourselves. if (isatty(0) && isatty(1) && isatty(2)) { + EdgeCommand c; + c.command = "test -t 0 -a -t 1 -a -t 2"; + c.use_console = true; Subprocess* subproc = - subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*use_console=*/true); + subprocs_.Add(c); ASSERT_NE((Subprocess*)0, subproc); while (!subproc->Done()) { @@ -162,7 +181,9 @@ TEST_F(SubprocessTest, Console) { #endif TEST_F(SubprocessTest, SetWithSingle) { - Subprocess* subproc = subprocs_.Add(kSimpleCommand); + EdgeCommand c; + c.command = kSimpleCommand; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); while (!subproc->Done()) { @@ -188,7 +209,9 @@ TEST_F(SubprocessTest, SetWithMulti) { }; for (int i = 0; i < 3; ++i) { - processes[i] = subprocs_.Add(kCommands[i]); + EdgeCommand c; + c.command = kCommands[i]; + processes[i] = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, processes[i]); } @@ -231,7 +254,9 @@ TEST_F(SubprocessTest, SetWithLots) { vector<Subprocess*> procs; for (size_t i = 0; i < kNumProcs; ++i) { - Subprocess* subproc = subprocs_.Add("/bin/echo"); + EdgeCommand c; + c.command = "/bin/echo"; + Subprocess* subproc = subprocs_.Add(c); ASSERT_NE((Subprocess *) 0, subproc); procs.push_back(subproc); } @@ -251,7 +276,9 @@ TEST_F(SubprocessTest, SetWithLots) { // Verify that a command that attempts to read stdin correctly thinks // that stdin is closed. TEST_F(SubprocessTest, ReadStdin) { - Subprocess* subproc = subprocs_.Add("cat -"); + EdgeCommand c; + c.command = "cat -"; + Subprocess* subproc = subprocs_.Add(c); while (!subproc->Done()) { subprocs_.DoWork(); } |