aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Hubbard <dsp@google.com>2022-11-26 19:17:26 -0800
committerDavid Hubbard <dsp@google.com>2022-11-30 09:52:40 -0800
commit378951b8ded9d3e3df52fa1eeb1209bf9ae6652d (patch)
treebdb05de4fe16e8516fa08fb19e9665df51eba824
parent4b5a2177cb2f246e6cf5fa1316ff4323ece37408 (diff)
downloadninja-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.cc9
-rw-r--r--src/build_log.cc5
-rw-r--r--src/build_test.cc24
-rw-r--r--src/eval_env.cc11
-rw-r--r--src/eval_env.h14
-rw-r--r--src/graph.cc8
-rw-r--r--src/graph.h23
-rw-r--r--src/graph_test.cc10
-rw-r--r--src/manifest_chunk_parser.cc12
-rw-r--r--src/manifest_chunk_parser.h5
-rw-r--r--src/manifest_parser.cc23
-rw-r--r--src/manifest_parser.h1
-rw-r--r--src/manifest_parser_perftest.cc10
-rw-r--r--src/manifest_parser_test.cc62
-rw-r--r--src/ninja.cc37
-rw-r--r--src/state.cc1
-rw-r--r--src/state_test.cc4
-rw-r--r--src/status.cc10
-rw-r--r--src/subprocess-posix.cc13
-rw-r--r--src/subprocess-win32.cc12
-rw-r--r--src/subprocess.h6
-rw-r--r--src/subprocess_test.cc53
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();
}