Skip to content

Commit

Permalink
Allow GN configs to have sub-configs.
Browse files Browse the repository at this point in the history
Some of the configs are getting unwieldy (the "compiler" one) and some are used to just forward to other variants like default_symbols and default_optimization.

This adds the ability for a config to have configs to add flags by reference. This allows large configs to be split apart into more logical units, and allows forwarding configs like default_optimization to be written in the obvious way.

An example of what this enables is https://codereview.chromium.org/1341373002/

Review URL: https://codereview.chromium.org/1342183003

Cr-Commit-Position: refs/heads/master@{#349258}
  • Loading branch information
brettw authored and Commit bot committed Sep 16, 2015
1 parent 05f7ef8 commit bd14442
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 61 deletions.
1 change: 1 addition & 0 deletions tools/gn/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ test("gn_unittests") {
"builder_unittest.cc",
"c_include_iterator_unittest.cc",
"command_format_unittest.cc",
"config_unittest.cc",
"config_values_extractors_unittest.cc",
"escape_unittest.cc",
"exec_process_unittest.cc",
Expand Down
14 changes: 14 additions & 0 deletions tools/gn/builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ void Builder::ItemDefined(scoped_ptr<Item> item) {
case BuilderRecord::ITEM_TARGET:
TargetDefined(record, &err);
break;
case BuilderRecord::ITEM_CONFIG:
ConfigDefined(record, &err);
break;
case BuilderRecord::ITEM_TOOLCHAIN:
ToolchainDefined(record, &err);
break;
Expand Down Expand Up @@ -233,6 +236,13 @@ bool Builder::TargetDefined(BuilderRecord* record, Err* err) {
return true;
}

bool Builder::ConfigDefined(BuilderRecord* record, Err* err) {
Config* config = record->item()->AsConfig();
if (!AddDeps(record, config->configs(), err))
return false;
return true;
}

bool Builder::ToolchainDefined(BuilderRecord* record, Err* err) {
Toolchain* toolchain = record->item()->AsToolchain();

Expand Down Expand Up @@ -398,6 +408,10 @@ bool Builder::ResolveItem(BuilderRecord* record, Err* err) {
!ResolveForwardDependentConfigs(target, err) ||
!ResolveToolchain(target, err))
return false;
} else if (record->type() == BuilderRecord::ITEM_CONFIG) {
Config* config = record->item()->AsConfig();
if (!ResolveConfigs(&config->configs(), err))
return false;
} else if (record->type() == BuilderRecord::ITEM_TOOLCHAIN) {
Toolchain* toolchain = record->item()->AsToolchain();
if (!ResolveDeps(&toolchain->deps(), err))
Expand Down
1 change: 1 addition & 0 deletions tools/gn/builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Builder : public base::RefCountedThreadSafe<Builder> {
virtual ~Builder();

bool TargetDefined(BuilderRecord* record, Err* err);
bool ConfigDefined(BuilderRecord* record, Err* err);
bool ToolchainDefined(BuilderRecord* record, Err* err);

// Returns the record associated with the given label. This function checks
Expand Down
44 changes: 26 additions & 18 deletions tools/gn/command_desc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ namespace commands {

namespace {

// The switch for displaying blame.
// Desc-specific command line switches.
const char kBlame[] = "blame";
const char kTree[] = "tree";

// Prints the given directory in a nice way for the user to view.
std::string FormatSourceDir(const SourceDir& dir) {
Expand Down Expand Up @@ -113,7 +114,7 @@ void PrintDeps(const Target* target, bool display_header) {
Label toolchain_label = target->label().GetToolchainLabel();

// Tree mode is separate.
if (cmdline->HasSwitch("tree")) {
if (cmdline->HasSwitch(kTree)) {
if (display_header)
OutputString("\nDependency tree:\n");

Expand Down Expand Up @@ -249,39 +250,46 @@ void PrintTestonly(const Target* target, bool display_header) {
OutputString(" false\n");
}

void PrintConfigsVector(const Target* target,
const LabelConfigVector& configs,
const std::string& heading,
bool display_header) {
if (configs.empty())
// Recursively prints subconfigs of a config.
void PrintSubConfigs(const Config* config, int indent_level) {
if (config->configs().empty())
return;

// Don't sort since the order determines how things are processed.
if (display_header)
OutputString("\n" + heading + " (in order applying):\n");

Label toolchain_label = target->label().GetToolchainLabel();
for (const auto& config : configs) {
OutputString(" " + config.label.GetUserVisibleName(toolchain_label) +
"\n");
std::string indent(indent_level * 2, ' ');
Label toolchain_label = config->label().GetToolchainLabel();
for (const auto& pair : config->configs()) {
OutputString(
indent + pair.label.GetUserVisibleName(toolchain_label) + "\n");
PrintSubConfigs(pair.ptr, indent_level + 1);
}
}

// This allows configs stored as either std::vector<LabelConfigPair> or
// UniqueVector<LabelConfigPair> to be printed.
template <class VectorType>
void PrintConfigsVector(const Target* target,
const UniqueVector<LabelConfigPair>& configs,
const VectorType& configs,
const std::string& heading,
bool display_header) {
if (configs.empty())
return;

bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree);

// Don't sort since the order determines how things are processed.
if (display_header)
OutputString("\n" + heading + " (in order applying):\n");
if (display_header) {
if (tree)
OutputString("\n" + heading + " tree (in order applying):\n");
else
OutputString("\n" + heading + " (in order applying, try also --tree):\n");
}

Label toolchain_label = target->label().GetToolchainLabel();
for (const auto& config : configs) {
OutputString(" " + config.label.GetUserVisibleName(toolchain_label) +
"\n");
if (tree)
PrintSubConfigs(config.ptr, 2); // 2 = start with double-indent.
}
}

Expand Down
29 changes: 28 additions & 1 deletion tools/gn/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#include "tools/gn/scheduler.h"

Config::Config(const Settings* settings, const Label& label)
: Item(settings, label) {
: Item(settings, label),
resolved_(false) {
}

Config::~Config() {
Expand All @@ -22,3 +23,29 @@ Config* Config::AsConfig() {
const Config* Config::AsConfig() const {
return this;
}

bool Config::OnResolved(Err* err) {
DCHECK(!resolved_);
resolved_ = true;

if (!configs_.empty()) {
// Subconfigs, flatten.
//
// Implementation note for the future: Flattening these here means we
// lose the ability to de-dupe subconfigs. If a subconfig is listed as
// a separate config or a subconfig that also applies to the target, the
// subconfig's flags will be duplicated.
//
// If we want to be able to de-dupe these, here's one idea. As a config is
// resolved, inline any sub-sub configs so the configs_ vector is a flat
// list, much the same way that libs and lib_dirs are pushed through
// targets. Do the same for Target.configs_ when a target is resolved. This
// will naturally de-dupe and also prevents recursive config walking to
// compute every possible flag, although it will expand the configs list on
// a target nontrivially (depending on build configuration).
composite_values_ = own_values_;
for (const auto& pair : configs_)
composite_values_.AppendValues(pair.ptr->resolved_values());
}
return true;
}
41 changes: 38 additions & 3 deletions tools/gn/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,59 @@
#ifndef TOOLS_GN_CONFIG_H_
#define TOOLS_GN_CONFIG_H_

#include "base/logging.h"
#include "base/macros.h"
#include "tools/gn/config_values.h"
#include "tools/gn/item.h"
#include "tools/gn/label_ptr.h"
#include "tools/gn/unique_vector.h"

// Represents a named config in the dependency graph.
//
// A config can list other configs. We track both the data assigned directly
// on the config, this list of sub-configs, and (when the config is resolved)
// the resulting values of everything merged together. The flatten step
// means we can avoid doing a recursive config walk for every target to compute
// flags.
class Config : public Item {
public:
Config(const Settings* settings, const Label& label);
~Config() override;

// Item implementation.
Config* AsConfig() override;
const Config* AsConfig() const override;
bool OnResolved(Err* err) override;

ConfigValues& config_values() { return config_values_; }
const ConfigValues& config_values() const { return config_values_; }
// The values set directly on this config. This will not contain data from
// sub-configs.
ConfigValues& own_values() { return own_values_; }
const ConfigValues& own_values() const { return own_values_; }

// The values that represent this config and all sub-configs combined into
// one. This is only valid after the config is resolved (when we know the
// contents of the sub-configs).
const ConfigValues& resolved_values() const {
DCHECK(resolved_);
if (configs_.empty()) // No sub configs, just use the regular values.
return own_values_;
return composite_values_;
}

// List of sub-configs.
const UniqueVector<LabelConfigPair>& configs() const { return configs_; }
UniqueVector<LabelConfigPair>& configs() { return configs_; }

private:
ConfigValues config_values_;
ConfigValues own_values_;

// Contains the own_values combined with sub-configs. Most configs don't have
// sub-configs. So as an optimization, this is not populated if there are no
// items in configs_. The resolved_values() getter handles this.
bool resolved_;
ConfigValues composite_values_;

UniqueVector<LabelConfigPair> configs_;

DISALLOW_COPY_AND_ASSIGN(Config);
};
Expand Down
85 changes: 85 additions & 0 deletions tools/gn/config_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/config.h"
#include "tools/gn/test_with_scope.h"

// Tests that the "resolved" values are the same as "own" values when there
// are no subconfigs.
TEST(Config, ResolvedNoSub) {
TestWithScope setup;
Err err;

Config config(setup.settings(), Label(SourceDir("//foo/"), "bar"));
config.own_values().defines().push_back("FOO");
ASSERT_TRUE(config.OnResolved(&err));

// The resolved values should be the same as the value we put in to
// own_values().
ASSERT_EQ(1u, config.resolved_values().defines().size());
EXPECT_EQ("FOO", config.resolved_values().defines()[0]);

// As an optimization, the string should actually refer to the original. This
// isn't required to pass for semantic correctness, though.
EXPECT_TRUE(&config.own_values() == &config.resolved_values());
}

// Tests that subconfigs are resolved in the correct order.
TEST(Config, ResolvedSub) {
TestWithScope setup;
Err err;

Config sub1(setup.settings(), Label(SourceDir("//foo/"), "1"));
sub1.own_values().defines().push_back("ONE");
ASSERT_TRUE(sub1.OnResolved(&err));

Config sub2(setup.settings(), Label(SourceDir("//foo/"), "2"));
sub2.own_values().defines().push_back("TWO");
ASSERT_TRUE(sub2.OnResolved(&err));

Config config(setup.settings(), Label(SourceDir("//foo/"), "bar"));
config.own_values().defines().push_back("FOO");
config.configs().push_back(LabelConfigPair(&sub1));
config.configs().push_back(LabelConfigPair(&sub2));
ASSERT_TRUE(config.OnResolved(&err));

// The resolved values should be the same as the value we put in to
// own_values().
ASSERT_EQ(3u, config.resolved_values().defines().size());
EXPECT_EQ("FOO", config.resolved_values().defines()[0]);
EXPECT_EQ("ONE", config.resolved_values().defines()[1]);
EXPECT_EQ("TWO", config.resolved_values().defines()[2]);

// The "own" values should be unchanged.
ASSERT_EQ(1u, config.own_values().defines().size());
EXPECT_EQ("FOO", config.own_values().defines()[0]);
}

// Tests that subconfigs of subconfigs are resolved properly.
TEST(Config, SubSub) {
TestWithScope setup;
Err err;

// Set up first -> middle -> last configs.
Config last(setup.settings(), Label(SourceDir("//foo/"), "last"));
last.own_values().defines().push_back("LAST");
ASSERT_TRUE(last.OnResolved(&err));

Config middle(setup.settings(), Label(SourceDir("//foo/"), "middle"));
middle.own_values().defines().push_back("MIDDLE");
middle.configs().push_back(LabelConfigPair(&last));
ASSERT_TRUE(middle.OnResolved(&err));

Config first(setup.settings(), Label(SourceDir("//foo/"), "first"));
first.own_values().defines().push_back("FIRST");
first.configs().push_back(LabelConfigPair(&middle));
ASSERT_TRUE(first.OnResolved(&err));

// Check final resolved defines on "first".
ASSERT_EQ(3u, first.resolved_values().defines().size());
EXPECT_EQ("FIRST", first.resolved_values().defines()[0]);
EXPECT_EQ("MIDDLE", first.resolved_values().defines()[1]);
EXPECT_EQ("LAST", first.resolved_values().defines()[2]);
}
34 changes: 34 additions & 0 deletions tools/gn/config_values.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,42 @@

#include "tools/gn/config_values.h"

namespace {

template<typename T>
void VectorAppend(std::vector<T>* append_to,
const std::vector<T>& append_this) {
if (append_this.empty())
return;
append_to->insert(append_to->end(),append_this.begin(), append_this.end());
}

} // namespace

ConfigValues::ConfigValues() {
}

ConfigValues::~ConfigValues() {
}

void ConfigValues::AppendValues(const ConfigValues& append) {
VectorAppend(&cflags_, append.cflags_);
VectorAppend(&cflags_c_, append.cflags_c_);
VectorAppend(&cflags_cc_, append.cflags_cc_);
VectorAppend(&cflags_objc_, append.cflags_objc_);
VectorAppend(&cflags_objcc_, append.cflags_objcc_);
VectorAppend(&defines_, append.defines_);
VectorAppend(&include_dirs_, append.include_dirs_);
VectorAppend(&ldflags_, append.ldflags_);
VectorAppend(&lib_dirs_, append.lib_dirs_);
VectorAppend(&libs_, append.libs_);

// Only append precompiled header if there isn't one. It might be nice to
// throw an error if there are conflicting precompiled headers, but that
// requires piping through some context of the actual configs involved, and
// conflicts here should be very unusual. Instead, use the first value.
if (!append.precompiled_header_.empty() && !precompiled_header_.empty())
precompiled_header_ = append.precompiled_header_;
if (!append.precompiled_source_.is_null() && !precompiled_source_.is_null())
precompiled_source_ = append.precompiled_source_;
}
7 changes: 5 additions & 2 deletions tools/gn/config_values.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class ConfigValues {
ConfigValues();
~ConfigValues();

// Appends the values from the given config to this one.
void AppendValues(const ConfigValues& append);

#define STRING_VALUES_ACCESSOR(name) \
const std::vector<std::string>& name() const { return name##_; } \
std::vector<std::string>& name() { return name##_; }
Expand All @@ -36,6 +39,7 @@ class ConfigValues {
STRING_VALUES_ACCESSOR(ldflags)
DIR_VALUES_ACCESSOR (lib_dirs)
STRING_VALUES_ACCESSOR(libs)
// If you add a new one, be sure to update AppendValues().

#undef STRING_VALUES_ACCESSOR
#undef DIR_VALUES_ACCESSOR
Expand Down Expand Up @@ -67,11 +71,10 @@ class ConfigValues {
std::vector<std::string> ldflags_;
std::vector<SourceDir> lib_dirs_;
std::vector<std::string> libs_;
// If you add a new one, be sure to update AppendValues().

std::string precompiled_header_;
SourceFile precompiled_source_;

DISALLOW_COPY_AND_ASSIGN(ConfigValues);
};

#endif // TOOLS_GN_CONFIG_VALUES_H_
Loading

0 comments on commit bd14442

Please sign in to comment.