diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-29 23:30:07 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-29 23:30:07 +0000 |
commit | c88bd8f2c08838c6730b946dc4a50d4386ef43f9 (patch) | |
tree | e9264e0a99f419ab9d52e864acd38bb06a579c3a /tools/gn | |
parent | 6e778e0ad540ff7abf5252c8af25346aec6b3371 (diff) | |
download | chromium_src-c88bd8f2c08838c6730b946dc4a50d4386ef43f9.zip chromium_src-c88bd8f2c08838c6730b946dc4a50d4386ef43f9.tar.gz chromium_src-c88bd8f2c08838c6730b946dc4a50d4386ef43f9.tar.bz2 |
Add initial prototype for the GN meta-buildsystem.
This is currently not hooked into the build. To build, add a reference to the
gn.gyp file to build/all.gyp
R=darin@chromium.org, scottmg@chromium.org
Review URL: https://codereview.chromium.org/21114002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214254 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/gn')
139 files changed, 18189 insertions, 0 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn new file mode 100644 index 0000000..ccb5411 --- /dev/null +++ b/tools/gn/BUILD.gn @@ -0,0 +1,153 @@ +static_library("gn_lib") {
+ sources = [
+ "build_settings.cc",
+ "build_settings.h",
+ "command_desc.cc",
+ "command_desc.h",
+ "command_gen.cc",
+ "command_gen.h",
+ "command.cc",
+ "config.cc",
+ "config.h",
+ "config_values.h",
+ "config_values_extractors.cc",
+ "config_values_extractors.h",
+ "config_values_generator.cc",
+ "config_values_generator.h",
+ "err.cc",
+ "err.h",
+ "escape.cc",
+ "escape.h",
+ "file_template.cc",
+ "file_template.h",
+ "filesystem_utils.cc",
+ "filesystem_utils.h",
+ "functions.cc",
+ "functions.h",
+ "functions_target.cc",
+ "function_exec_script.cc",
+ "function_process_file_template.cc",
+ "function_read_file.cc",
+ "function_set_default_toolchain.cc",
+ "function_template.cc",
+ "function_toolchain.cc",
+ "function_write_file.cc",
+ "import_manager.cc",
+ "import_manager.h",
+ "input_conversion.cc",
+ "input_conversion.h",
+ "input_file.cc",
+ "input_file.h",
+ "input_file_manager.cc",
+ "input_file_manager.h",
+ "item.cc",
+ "item.h",
+ "item_node.cc",
+ "item_node.h",
+ "item_tree.cc",
+ "item_tree.h",
+ "label.cc",
+ "label.h",
+ "location.h",
+ "ninja_build_writer.cc",
+ "ninja_build_writer.h",
+ "ninja_helper.cc",
+ "ninja_helper.h",
+ "ninja_target_writer.cc",
+ "ninja_target_writer.h",
+ "ninja_toolchain_writer.cc",
+ "ninja_toolchain_writer.h",
+ "ninja_writer.cc",
+ "ninja_writer.h",
+ "operators.cc",
+ "operators.h",
+ "output_file.h",
+ "parse_tree.cc",
+ "parse_tree.h",
+ "parser.cc",
+ "parser.h",
+ "path_output.cc",
+ "path_output.h",
+ "pattern.cc",
+ "pattern.h",
+ "scheduler.cc",
+ "scheduler.h",
+ "scope.cc",
+ "scope.h",
+ "scope_per_file_provider.cc",
+ "scope_per_file_provider.h",
+ "settings.cc",
+ "settings.h",
+ "setup.cc",
+ "setup.h",
+ "source_dir.cc",
+ "source_dir.h",
+ "source_file.cc",
+ "source_file.h",
+ "standard_out.cc",
+ "standard_out.h",
+ "string_utils.cc",
+ "string_utils.h",
+ "target.cc",
+ "target.h",
+ "target_generator.cc",
+ "target_generator.h",
+ "target_manager.cc",
+ "target_manager.h",
+ "token.cc",
+ "token.h",
+ "tokenizer.cc",
+ "tokenizer.h",
+ "toolchain.cc",
+ "toolchain.h",
+ "toolchain_manager.cc",
+ "toolchain_manager.h",
+ "value.cc",
+ "value.h",
+ "value_extractors.cc",
+ "value_extractors.h",
+ ]
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ ]
+}
+
+executable("gn") {
+ sources = [
+ "gn_main.cc",
+ ]
+ deps = [
+ ":gn_lib",
+ ]
+}
+
+test("gn_unittests") {
+ sources = [
+ "escape_unittest.cc",
+ "file_template_unittest.cc",
+ "filesystem_utils_unittest.cc",
+ "input_conversion_unittest.cc",
+ "label_unittest.cc",
+ "ninja_helper_unittest.cc",
+ "parser_unittest.cc",
+ "path_output_unittest.cc",
+ "pattern_unittest.cc",
+ "source_dir_unittest.cc",
+ "string_utils_unittest.cc",
+ "target_generator_unittest.cc",
+ "target_manager_unittest.cc",
+ "tokenizer_unittest.cc",
+ ]
+ deps = [
+ ":gn_lib",
+ "//base:run_all_unittests",
+ "//base:test_support_base",
+ "//testing:gtest",
+ ]
+}
+
+executable("generate_test_gn_data") {
+ sources = [ "generate_test_gn_data.cc" ]
+ deps = [ "//base" ]
+}
diff --git a/tools/gn/OWNERS b/tools/gn/OWNERS new file mode 100644 index 0000000..06fefbf --- /dev/null +++ b/tools/gn/OWNERS @@ -0,0 +1 @@ +brettw@chromium.org diff --git a/tools/gn/README.txt b/tools/gn/README.txt new file mode 100644 index 0000000..0a637bf --- /dev/null +++ b/tools/gn/README.txt @@ -0,0 +1,7 @@ +GN "Generate Ninja" + +This tool is an experimental metabuildsystem. It is not currently in a state +where it is ready for public consumption. + +It is not currently used in the build and there are currently no plans to +replace GYP. diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc new file mode 100644 index 0000000..09b4c99 --- /dev/null +++ b/tools/gn/build_settings.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2013 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 "tools/gn/build_settings.h" + +#include "tools/gn/filesystem_utils.h" + +BuildSettings::BuildSettings() + : item_tree_(), + target_manager_(this), + toolchain_manager_(this) { +} + +BuildSettings::~BuildSettings() { +} + +void BuildSettings::SetSecondarySourcePath(const SourceDir& d) { + secondary_source_path_ = GetFullPath(d); +} + +void BuildSettings::SetBuildDir(const SourceDir& d) { + build_dir_ = d; + build_to_source_dir_string_ = InvertDir(d); +} + +base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const { + return file.Resolve(root_path_); +} + +base::FilePath BuildSettings::GetFullPath(const SourceDir& dir) const { + return dir.Resolve(root_path_); +} + +base::FilePath BuildSettings::GetFullPathSecondary( + const SourceFile& file) const { + return file.Resolve(secondary_source_path_); +} + +base::FilePath BuildSettings::GetFullPathSecondary( + const SourceDir& dir) const { + return dir.Resolve(secondary_source_path_); +} + diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h new file mode 100644 index 0000000..fe04266 --- /dev/null +++ b/tools/gn/build_settings.h @@ -0,0 +1,110 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_BUILD_SETTINGS_H_ +#define TOOLS_GN_BUILD_SETTINGS_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "tools/gn/item_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" +#include "tools/gn/target_manager.h" +#include "tools/gn/toolchain_manager.h" + +// Settings for one build, which is one toplevel output directory. There +// may be multiple Settings objects that refer to this, one for each toolchain. +class BuildSettings { + public: + typedef base::Callback<void(const Target*)> TargetResolvedCallback; + + BuildSettings(); + ~BuildSettings(); + + // Absolute path of the source root on the local system. Everything is + // relative to this. + const base::FilePath& root_path() const { return root_path_; } + void set_root_path(const base::FilePath& r) { root_path_ = r; } + + // When nonempty, specifies a parallel directory higherarchy in which to + // search for buildfiles if they're not found in the root higherarchy. This + // allows us to keep buildfiles in a separate tree during development. + const base::FilePath& secondary_source_path() const { + return secondary_source_path_; + } + void SetSecondarySourcePath(const SourceDir& d); + + // Path of the python executable to run scripts with. + base::FilePath python_path() const { return python_path_; } + void set_python_path(const base::FilePath& p) { python_path_ = p; } + + const SourceFile& build_config_file() const { return build_config_file_; } + void set_build_config_file(const SourceFile& f) { build_config_file_ = f; } + + // The build directory is the root of all output files. The default toolchain + // files go into here, and non-default toolchains will have separate + // toolchain-specific root directories inside this. + const SourceDir& build_dir() const { return build_dir_; } + void SetBuildDir(const SourceDir& dir); + + // The inverse of relative_build_dir, ending with a separator. + // Example: relative_build_dir_ = "out/Debug/" this will be "../../" + const std::string& build_to_source_dir_string() const { + return build_to_source_dir_string_; + } + + // These accessors do not return const objects since the resulting objects + // are threadsafe. In this setting, we use constness primarily to ensure + // that the Settings object is used in a threadsafe manner. Although this + // violates the concept of logical constness, that's less important in our + // application, and actually implementing this in a way that preserves + // logical constness is cumbersome. + ItemTree& item_tree() const { return item_tree_; } + TargetManager& target_manager() const { return target_manager_; } + ToolchainManager& toolchain_manager() const { return toolchain_manager_; } + + // Returns the full absolute OS path cooresponding to the given file in the + // root source tree. + base::FilePath GetFullPath(const SourceFile& file) const; + base::FilePath GetFullPath(const SourceDir& dir) const; + + // Returns the absolute OS path inside the secondary source path. Will return + // an empty FilePath if the secondary source path is empty. When loading a + // buildfile, the GetFullPath should always be consulted first. + base::FilePath GetFullPathSecondary(const SourceFile& file) const; + base::FilePath GetFullPathSecondary(const SourceDir& dir) const; + + // This is the callback to execute when a target is marked resolved. If we + // don't need to do anything, this will be null. When a target is resolved, + // this callback should be posted to the scheduler pool so the work is + // distributed properly. + const TargetResolvedCallback& target_resolved_callback() const { + return target_resolved_callback_; + } + void set_target_resolved_callback(const TargetResolvedCallback& cb) { + target_resolved_callback_ = cb; + } + + private: + base::FilePath root_path_; + base::FilePath secondary_source_path_; + base::FilePath python_path_; + + SourceFile build_config_file_; + SourceDir build_dir_; + std::string build_to_source_dir_string_; + + TargetResolvedCallback target_resolved_callback_; + + // See getters above. + mutable ItemTree item_tree_; + mutable TargetManager target_manager_; + mutable ToolchainManager toolchain_manager_; + + DISALLOW_COPY_AND_ASSIGN(BuildSettings); +}; + +#endif // TOOLS_GN_BUILD_SETTINGS_H_ diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc new file mode 100644 index 0000000..bf91776 --- /dev/null +++ b/tools/gn/command_desc.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2013 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 <algorithm> +#include <set> + +#include "tools/gn/commands.h" +#include "tools/gn/config.h" +#include "tools/gn/item.h" +#include "tools/gn/item_node.h" +#include "tools/gn/label.h" +#include "tools/gn/setup.h" +#include "tools/gn/standard_out.h" +#include "tools/gn/target.h" + +namespace { + +struct CompareTargetLabel { + bool operator()(const Target* a, const Target* b) const { + return a->label() < b->label(); + } +}; + +const Target* GetTargetForDesc(const std::vector<std::string>& args) { + // Deliberately leaked to avoid expensive process teardown. + Setup* setup = new Setup; + if (!setup->DoSetup()) + return NULL; + + // FIXME(brettw): set the output dir to be a sandbox one to avoid polluting + // the real output dir with files written by the build scripts. + + // Do the actual load. This will also write out the target ninja files. + if (!setup->Run()) + return NULL; + + // Need to resolve the label after we know the default toolchain. + // TODO(brettw) find the current directory and resolve the input label + // relative to that. + Label default_toolchain = setup->build_settings().toolchain_manager() + .GetDefaultToolchainUnlocked(); + Value arg_value(NULL, args[0]); + Err err; + Label label = Label::Resolve(SourceDir(), default_toolchain, arg_value, &err); + if (err.has_error()) { + err.PrintToStdout(); + return NULL; + } + + ItemNode* node; + { + base::AutoLock lock(setup->build_settings().item_tree().lock()); + node = setup->build_settings().item_tree().GetExistingNodeLocked(label); + } + if (!node) { + Err(Location(), "", + "I don't know about this \"" + label.GetUserVisibleName(false) + + "\"").PrintToStdout(); + return NULL; + } + + const Target* target = node->item()->AsTarget(); + if (!target) { + Err(Location(), "Not a target.", + "The \"" + label.GetUserVisibleName(false) + "\" thing\n" + "is not a target. Somebody should probably implement this command for " + "other\nitem types."); + return NULL; + } + + return target; +} + +void RecursiveCollectDeps(const Target* target, std::set<Label>* result) { + if (result->find(target->label()) != result->end()) + return; // Already did this target. + result->insert(target->label()); + + const std::vector<const Target*>& deps = target->deps(); + for (size_t i = 0; i < deps.size(); i++) + RecursiveCollectDeps(deps[i], result); +} + +// Prints dependencies of the given target (not the target itself). +void RecursivePrintDeps(const Target* target, + const Label& default_toolchain, + int indent_level) { + std::vector<const Target*> sorted_deps = target->deps(); + std::sort(sorted_deps.begin(), sorted_deps.end(), CompareTargetLabel()); + + std::string indent(indent_level * 2, ' '); + for (size_t i = 0; i < sorted_deps.size(); i++) { + OutputString(indent + + sorted_deps[i]->label().GetUserVisibleName(default_toolchain) + "\n"); + RecursivePrintDeps(sorted_deps[i], default_toolchain, indent_level + 1); + } +} + +} // namespace + +int RunDescCommand(const std::vector<std::string>& args) { + if (args.size() != 1) { + Err(Location(), "You're holding it wrong.", + "Usage: \"gn desc <target_name>\"").PrintToStdout(); + return NULL; + } + + const Target* target = GetTargetForDesc(args); + if (!target) + return 1; + + // Generally we only want to display toolchains on labels when the toolchain + // is different than the default one for this target (which we always print + // in the header). + Label target_toolchain = target->label().GetToolchainLabel(); + + // Header. + std::string title_target = + "Target: " + target->label().GetUserVisibleName(false); + std::string title_toolchain = + "Toolchain: " + target_toolchain.GetUserVisibleName(false); + OutputString(title_target + "\n", DECORATION_YELLOW); + OutputString(title_toolchain + "\n", DECORATION_YELLOW); + OutputString(std::string( + std::max(title_target.size(), title_toolchain.size()), '=') + "\n"); + + OutputString("Sources:\n"); + const Target::FileList& sources = target->sources(); + for (size_t i = 0; i < sources.size(); i++) + OutputString(" " + sources[i].value() + "\n"); + + // Configs (don't sort since the order determines how things are processed). + OutputString("\nConfigs:\n"); + const std::vector<const Config*>& configs = target->configs(); + for (size_t i = 0; i < configs.size(); i++) { + OutputString(" " + + configs[i]->label().GetUserVisibleName(target_toolchain) + "\n"); + } + + // Deps. Sorted for convenience. Sort the labels rather than the strings so + // that "//foo:bar" comes before "//foo/third_party:bar". + OutputString("\nDirect dependencies:\n" + "(Use \"gn deps\" or \"gn tree\" to display recursive deps.)\n"); + const std::vector<const Target*>& deps = target->deps(); + std::vector<Label> sorted_deps; + for (size_t i = 0; i < deps.size(); i++) + sorted_deps.push_back(deps[i]->label()); + std::sort(sorted_deps.begin(), sorted_deps.end()); + for (size_t i = 0; i < sorted_deps.size(); i++) { + OutputString(" " + sorted_deps[i].GetUserVisibleName(target_toolchain) + + "\n"); + } + return 0; +} + +int RunDepsCommand(const std::vector<std::string>& args) { + if (args.size() != 1) { + Err(Location(), "You're holding it wrong.", + "Usage: \"gn deps <target_name>\"").PrintToStdout(); + return NULL; + } + + const Target* target = GetTargetForDesc(args); + if (!target) + return 1; + + // Generally we only want to display toolchains on labels when the toolchain + // is different than the default one for this target (which we always print + // in the header). + Label target_toolchain = target->label().GetToolchainLabel(); + + std::set<Label> all_deps; + RecursiveCollectDeps(target, &all_deps); + + OutputString("Recursive dependencies of " + + target->label().GetUserVisibleName(true) + "\n", + DECORATION_YELLOW); + + for (std::set<Label>::iterator i = all_deps.begin(); + i != all_deps.end(); ++i) + OutputString(" " + i->GetUserVisibleName(target_toolchain) + "\n"); + return 0; +} + +int RunTreeCommand(const std::vector<std::string>& args) { + if (args.size() != 1) { + Err(Location(), "You're holding it wrong.", + "Usage: \"gn tree <target_name>\"").PrintToStdout(); + return NULL; + } + + const Target* target = GetTargetForDesc(args); + if (!target) + return 1; + + OutputString(target->label().GetUserVisibleName(false) + "\n"); + RecursivePrintDeps(target, target->label().GetToolchainLabel(), 1); + + return 0; +} diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc new file mode 100644 index 0000000..ab64a8d --- /dev/null +++ b/tools/gn/command_gen.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2013 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 "base/bind.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/commands.h" +#include "tools/gn/ninja_target_writer.h" +#include "tools/gn/ninja_writer.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/setup.h" +#include "tools/gn/standard_out.h" + +namespace { + +// Suppress output on success. +const char kSwitchQuiet[] = "q"; + +} // namespace + +int RunGenCommand(const std::vector<std::string>& args) { + base::TimeTicks begin_time = base::TimeTicks::Now(); + + // Deliberately leaked to avoid expensive process teardown. + Setup* setup = new Setup; + if (!setup->DoSetup()) + return 1; + + // Cause the load to also generate the ninja files for each target. + setup->build_settings().set_target_resolved_callback( + base::Bind(&NinjaTargetWriter::RunAndWriteFile)); + + // Do the actual load. This will also write out the target ninja files. + if (!setup->Run()) + return 1; + + // Write the root ninja files. + if (!NinjaWriter::RunAndWriteFiles(&setup->build_settings())) { + Err(Location(), + "Couldn't open root buildfile(s) for writing").PrintToStdout(); + return 1; + } + + base::TimeTicks end_time = base::TimeTicks::Now(); + + if (!CommandLine::ForCurrentProcess()->HasSwitch(kSwitchQuiet)) { + OutputString("Done. ", DECORATION_GREEN); + + // TODO(brettw) get the number of targets without getting the entire list. + std::vector<const Target*> all_targets; + setup->build_settings().target_manager().GetAllTargets(&all_targets); + std::string stats = "Generated " + + base::IntToString(static_cast<int>(all_targets.size())) + + " targets from " + + base::IntToString( + setup->scheduler().input_file_manager()->GetInputFileCount()) + + " files in " + + base::IntToString((end_time - begin_time).InMilliseconds()) + "ms\n"; + OutputString(stats); + } + + return 0; +} diff --git a/tools/gn/commands.h b/tools/gn/commands.h new file mode 100644 index 0000000..1d4449c --- /dev/null +++ b/tools/gn/commands.h @@ -0,0 +1,18 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_COMMANDS_H_ +#define TOOLS_GN_COMMANDS_H_ + +#include <string> +#include <vector> + +// The different commands we have, returns the value we should return from +// main(). +int RunDepsCommand(const std::vector<std::string>& args); +int RunDescCommand(const std::vector<std::string>& args); +int RunGenCommand(const std::vector<std::string>& args); +int RunTreeCommand(const std::vector<std::string>& args); + +#endif // TOOLS_GN_COMMANDS_H_ diff --git a/tools/gn/config.cc b/tools/gn/config.cc new file mode 100644 index 0000000..1175d7f --- /dev/null +++ b/tools/gn/config.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2013 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 "tools/gn/config.h" + +#include "tools/gn/err.h" +#include "tools/gn/input_file_manager.h" +#include "tools/gn/item_node.h" +#include "tools/gn/item_tree.h" +#include "tools/gn/scheduler.h" + +Config::Config(const Label& label) : Item(label) { +} + +Config::~Config() { +} + +Config* Config::AsConfig() { + return this; +} + +const Config* Config::AsConfig() const { + return this; +} + +// static +Config* Config::GetConfig(const Settings* settings, + const LocationRange& specified_from_here, + const Label& label, + Item* dep_from, + Err* err) { + DCHECK(!label.is_null()); + + ItemTree* tree = &settings->build_settings()->item_tree(); + base::AutoLock lock(tree->lock()); + + ItemNode* node = tree->GetExistingNodeLocked(label); + Config* config = NULL; + if (!node) { + config = new Config(label); + node = new ItemNode(config); + tree->AddNodeLocked(node); + + // Only schedule loading the given target if somebody is depending on it + // (and we optimize by not re-asking it to run the current file). + // Otherwise, we're probably generating it right now. + if (dep_from && dep_from->label().dir() != label.dir()) { + settings->build_settings()->toolchain_manager().ScheduleInvocationLocked( + specified_from_here, label.GetToolchainLabel(), label.dir(), + err); + } + } else if ((config = node->item()->AsConfig())) { + // Previously saw this item as a config. + + // If we have no dep_from, we're generating it. In this case, it had better + // not already be generated. + if (!dep_from && node->state() != ItemNode::REFERENCED) { + *err = Err(specified_from_here, "Duplicate config definition.", + "You already told me about a config with this name."); + return NULL; + } + } else { + // Previously saw this thing as a non-config. + *err = Err(specified_from_here, + "Config name already used.", + "Previously you specified a " + + node->item()->GetItemTypeName() + " with this name instead."); + return NULL; + } + + // Keep a record of the guy asking us for this dependency. We know if + // somebody is adding a dependency, that guy it himself not resolved. + if (dep_from && node->state() != ItemNode::RESOLVED) + tree->GetExistingNodeLocked(dep_from->label())->AddDependency(node); + + return config; +} diff --git a/tools/gn/config.h b/tools/gn/config.h new file mode 100644 index 0000000..0ee9b7b --- /dev/null +++ b/tools/gn/config.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_CONFIG_H_ +#define TOOLS_GN_CONFIG_H_ + +#include "base/compiler_specific.h" +#include "tools/gn/config_values.h" +#include "tools/gn/item.h" + +class Err; +class ItemTree; +class LocationRange; +class Settings; + +// Represents a named config in the dependency graph. +class Config : public Item { + public: + Config(const Label& label); + virtual ~Config(); + + virtual Config* AsConfig() OVERRIDE; + virtual const Config* AsConfig() const OVERRIDE; + + ConfigValues& config_values() { return config_values_; } + const ConfigValues& config_values() const { return config_values_; } + + // Gets or creates a config. + // + // This is like the TargetManager is for Targets, but Configs are so much + // simpler that this one function is all we need. + static Config* GetConfig(const Settings* settings, + const LocationRange& specified_from_here, + const Label& label, + Item* dep_from, + Err* err); + + private: + ConfigValues config_values_; + + DISALLOW_COPY_AND_ASSIGN(Config); +}; + +#endif // TOOLS_GN_CONFIG_H_ diff --git a/tools/gn/config_values.cc b/tools/gn/config_values.cc new file mode 100644 index 0000000..53466df --- /dev/null +++ b/tools/gn/config_values.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2013 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 "tools/gn/config_values.h" + +ConfigValues::ConfigValues() { +} + +ConfigValues::~ConfigValues() { +} diff --git a/tools/gn/config_values.h b/tools/gn/config_values.h new file mode 100644 index 0000000..e7c1712 --- /dev/null +++ b/tools/gn/config_values.h @@ -0,0 +1,50 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_CONFIG_VALUES_H_ +#define TOOLS_GN_CONFIG_VALUES_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "tools/gn/source_dir.h" + +// Holds the values (includes, defines, compiler flags, etc.) for a given +// config or target. +class ConfigValues { + public: + ConfigValues(); + ~ConfigValues(); + + const std::vector<SourceDir>& includes() const { return includes_; } + void swap_in_includes(std::vector<SourceDir>* lo) { includes_.swap(*lo); } + + const std::vector<std::string>& defines() const { return defines_; } + void swap_in_defines(std::vector<std::string>* d) { defines_.swap(*d); } + + const std::vector<std::string>& cflags() const { return cflags_; } + void swap_in_cflags(std::vector<std::string>* lo) { cflags_.swap(*lo); } + + const std::vector<std::string>& cflags_c() const { return cflags_c_; } + void swap_in_cflags_c(std::vector<std::string>* lo) { cflags_c_.swap(*lo); } + + const std::vector<std::string>& cflags_cc() const { return cflags_cc_; } + void swap_in_cflags_cc(std::vector<std::string>* lo) { cflags_cc_.swap(*lo); } + + const std::vector<std::string>& ldflags() const { return ldflags_; } + void swap_in_ldflags(std::vector<std::string>* lo) { ldflags_.swap(*lo); } + + private: + std::vector<SourceDir> includes_; + std::vector<std::string> defines_; + std::vector<std::string> cflags_; + std::vector<std::string> cflags_c_; + std::vector<std::string> cflags_cc_; + std::vector<std::string> ldflags_; + + DISALLOW_COPY_AND_ASSIGN(ConfigValues); +}; + +#endif // TOOLS_GN_CONFIG_VALUES_H_ diff --git a/tools/gn/config_values_extractors.cc b/tools/gn/config_values_extractors.cc new file mode 100644 index 0000000..96b514a --- /dev/null +++ b/tools/gn/config_values_extractors.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2013 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 "tools/gn/config_values_extractors.h" + +namespace { + +struct StringWriter { + void operator()(const std::string& s, std::ostream& out) const { + out << " " << s; + } +}; + +} // namespace + +void RecursiveTargetConfigStringsToStream( + const Target* target, + const std::vector<std::string>& (ConfigValues::* getter)() const, + std::ostream& out) { + RecursiveTargetConfigToStream(target, getter, StringWriter(), out); +} diff --git a/tools/gn/config_values_extractors.h b/tools/gn/config_values_extractors.h new file mode 100644 index 0000000..0eaa2c9 --- /dev/null +++ b/tools/gn/config_values_extractors.h @@ -0,0 +1,50 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_ +#define TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_ + +#include <ostream> +#include <string> +#include <vector> + +#include "tools/gn/config.h" +#include "tools/gn/config_values.h" +#include "tools/gn/target.h" + +template<typename T, class Writer> +inline void ConfigValuesToStream( + const ConfigValues& values, + const std::vector<T>& (ConfigValues::* getter)() const, + const Writer& writer, + std::ostream& out) { + const std::vector<T>& v = (values.*getter)(); + for (size_t i = 0; i < v.size(); i++) + writer(v[i], out); +}; + +template<typename T, class Writer> +inline void RecursiveTargetConfigToStream( + const Target* target, + const std::vector<T>& (ConfigValues::* getter)() const, + const Writer& writer, + std::ostream& out) { + // Write all configs in reverse order (to get oldest first, which will look + // more normal in the output). + for (int i = static_cast<int>(target->configs().size() - 1); i >= 0; i--) { + ConfigValuesToStream(target->configs()[i]->config_values(), getter, + writer, out); + } + + // Last write from the config from the Target itself, if any. + ConfigValuesToStream(target->config_values(), getter, writer, out); +} + +// Writes the values out as strings with no transformation. +void RecursiveTargetConfigStringsToStream( + const Target* target, + const std::vector<std::string>& (ConfigValues::* getter)() const, + std::ostream& out); + +#endif // TOOLS_GN_CONFIG_VALUES_EXTRACTORS_H_ diff --git a/tools/gn/config_values_generator.cc b/tools/gn/config_values_generator.cc new file mode 100644 index 0000000..34c3f67 --- /dev/null +++ b/tools/gn/config_values_generator.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2013 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 "tools/gn/config_values_generator.h" + +#include "tools/gn/config_values.h" +#include "tools/gn/scope.h" +#include "tools/gn/value.h" +#include "tools/gn/value_extractors.h" + +namespace { + +void GetStringList( + const Scope* scope, + const char* var_name, + ConfigValues* config_values, + void (ConfigValues::* swapper_inner)(std::vector<std::string>*), + Err* err) { + const Value* value = scope->GetValue(var_name); + if (!value) + return; // No value, empty input and succeed. + + std::vector<std::string> result; + ExtractListOfStringValues(*value, &result, err); + (config_values->*swapper_inner)(&result); +} + +} // namespace + +ConfigValuesGenerator::ConfigValuesGenerator(ConfigValues* dest_values, + const Scope* scope, + const Token& function_token, + const SourceDir& input_dir, + Err* err) + : config_values_(dest_values), + scope_(scope), + function_token_(function_token), + input_dir_(input_dir), + err_(err) { +} + +ConfigValuesGenerator::~ConfigValuesGenerator() { +} + +void ConfigValuesGenerator::Run() { + FillDefines(); + FillIncludes(); + FillCflags(); + FillCflags_C(); + FillCflags_CC(); + FillLdflags(); +} + +void ConfigValuesGenerator::FillDefines() { + GetStringList(scope_, "defines", config_values_, + &ConfigValues::swap_in_defines, err_); +} + +void ConfigValuesGenerator::FillIncludes() { + const Value* value = scope_->GetValue("includes"); + if (!value) + return; // No value, empty input and succeed. + + std::vector<SourceDir> includes; + if (!ExtractListOfRelativeDirs(*value, input_dir_, &includes, err_)) + return; + config_values_->swap_in_includes(&includes); +} + +void ConfigValuesGenerator::FillCflags() { + GetStringList(scope_, "cflags", config_values_, + &ConfigValues::swap_in_cflags, err_); +} + +void ConfigValuesGenerator::FillCflags_C() { + GetStringList(scope_, "cflags_c", config_values_, + &ConfigValues::swap_in_cflags_c, err_); +} + +void ConfigValuesGenerator::FillCflags_CC() { + GetStringList(scope_, "cflags_cc", config_values_, + &ConfigValues::swap_in_cflags_cc, err_); +} + +void ConfigValuesGenerator::FillLdflags() { + GetStringList(scope_, "ldflags", config_values_, + &ConfigValues::swap_in_ldflags, err_); +} diff --git a/tools/gn/config_values_generator.h b/tools/gn/config_values_generator.h new file mode 100644 index 0000000..79d9067 --- /dev/null +++ b/tools/gn/config_values_generator.h @@ -0,0 +1,48 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_CONFIG_VALUES_GENERATOR_H_ +#define TOOLS_GN_CONFIG_VALUES_GENERATOR_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "tools/gn/source_dir.h" + +class ConfigValues; +class Err; +class Scope; +class Token; + +class ConfigValuesGenerator { + public: + ConfigValuesGenerator(ConfigValues* dest_values, + const Scope* scope, + const Token& function_token, + const SourceDir& input_dir, + Err* err); + ~ConfigValuesGenerator(); + + // Sets the error passed to the constructor on failure. + void Run(); + + private: + void FillDefines(); + void FillIncludes(); + void FillCflags(); + void FillCflags_C(); + void FillCflags_CC(); + void FillLdflags(); + + ConfigValues* config_values_; + const Scope* scope_; + const Token& function_token_; + const SourceDir input_dir_; + Err* err_; + + DISALLOW_COPY_AND_ASSIGN(ConfigValuesGenerator); +}; + +#endif // TOOLS_GN_CONFIG_VALUES_GENERATOR_H_ diff --git a/tools/gn/err.cc b/tools/gn/err.cc new file mode 100644 index 0000000..068ea6d --- /dev/null +++ b/tools/gn/err.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2013 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 "tools/gn/err.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/standard_out.h" +#include "tools/gn/tokenizer.h" +#include "tools/gn/value.h" + +namespace { + +std::string GetNthLine(const base::StringPiece& data, int n) { + size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n); + size_t end = line_off + 1; + while (end < data.size() && !Tokenizer::IsNewline(data, end)) + end++; + return data.substr(line_off, end - line_off).as_string(); +} + +void FillRangeOnLine(const LocationRange& range, int line_number, + std::string* line) { + // Only bother if the range's begin or end overlaps the line. If the entire + // line is highlighted as a result of this range, it's not very helpful. + if (range.begin().line_number() != line_number && + range.end().line_number() != line_number) + return; + + // Watch out, the char offsets in the location are 1-based, so we have to + // subtract 1. + int begin_char; + if (range.begin().line_number() < line_number) + begin_char = 0; + else + begin_char = range.begin().char_offset() - 1; + + int end_char; + if (range.end().line_number() > line_number) + end_char = line->size(); // Ending is non-inclusive. + else + end_char = range.end().char_offset() - 1; + + CHECK(end_char >= begin_char); + CHECK(begin_char >= 0 && begin_char <= static_cast<int>(line->size())); + CHECK(end_char >= 0 && end_char <= static_cast<int>(line->size())); + for (int i = begin_char; i < end_char; i++) + line->at(i) = '-'; +} + +// The line length is used to clip the maximum length of the markers we'll +// make if the error spans more than one line (like unterminated literals). +void OutputHighlighedPosition(const Location& location, + const Err::RangeList& ranges, + size_t line_length) { + // Make a buffer of the line in spaces. + std::string highlight; + highlight.resize(line_length); + for (size_t i = 0; i < line_length; i++) + highlight[i] = ' '; + + // Highlight all the ranges on the line. + for (size_t i = 0; i < ranges.size(); i++) + FillRangeOnLine(ranges[i], location.line_number(), &highlight); + + // Allow the marker to be one past the end of the line for marking the end. + highlight.push_back(' '); + CHECK(location.char_offset() - 1 >= 0 && + location.char_offset() - 1 < static_cast<int>(highlight.size())); + highlight[location.char_offset() - 1] = '^'; + + // Trim unused spaces from end of line. + while (!highlight.empty() && highlight[highlight.size() - 1] == ' ') + highlight.resize(highlight.size() - 1); + + highlight += "\n"; + OutputString(highlight, DECORATION_BLUE); +} + +} // namespace + +Err::Err() : has_error_(false) { +} + +Err::Err(const Location& location, + const std::string& msg, + const std::string& help) + : has_error_(true), + location_(location), + message_(msg), + help_text_(help) { +} + +Err::Err(const LocationRange& range, + const std::string& msg, + const std::string& help) + : has_error_(true), + location_(range.begin()), + message_(msg), + help_text_(help) { + ranges_.push_back(range); +} + +Err::Err(const Token& token, + const std::string& msg, + const std::string& help) + : has_error_(true), + location_(token.location()), + message_(msg), + help_text_(help) { + ranges_.push_back(token.range()); +} + +Err::Err(const ParseNode* node, + const std::string& msg, + const std::string& help_text) + : has_error_(true), + message_(msg), + help_text_(help_text) { + // Node will be null in certain tests. + if (node) { + LocationRange range = node->GetRange(); + location_ = range.begin(); + ranges_.push_back(range); + } +} + +Err::Err(const Value& value, + const std::string msg, + const std::string& help_text) + : has_error_(true), + message_(msg), + help_text_(help_text) { + if (value.origin()) { + LocationRange range = value.origin()->GetRange(); + location_ = range.begin(); + ranges_.push_back(range); + } +} + +Err::~Err() { +} + +void Err::PrintToStdout() const { + InternalPrintToStdout(false); +} + +void Err::AppendSubErr(const Err& err) { + sub_errs_.push_back(err); +} + +void Err::InternalPrintToStdout(bool is_sub_err) const { + DCHECK(has_error_); + + if (!is_sub_err) + OutputString("ERROR ", DECORATION_RED); + + // File name and location. + const InputFile* input_file = location_.file(); + std::string loc_str; + if (input_file) { + std::string path8; + path8.assign(input_file->name().value()); + + if (is_sub_err) + loc_str = "See "; + else + loc_str = "at "; + loc_str += path8 + ": " + + base::IntToString(location_.line_number()) + ":" + + base::IntToString(location_.char_offset()) + ": "; + } + OutputString(loc_str + message_ + "\n"); + + // Quoted line. + if (input_file) { + std::string line = GetNthLine(input_file->contents(), + location_.line_number()); + if (!ContainsOnlyWhitespaceASCII(line)) { + OutputString(line + "\n", DECORATION_BOLD); + OutputHighlighedPosition(location_, ranges_, line.size()); + } + } + + // Optional help text. + if (!help_text_.empty()) + OutputString(help_text_ + "\n"); + + // Sub errors. + for (size_t i = 0; i < sub_errs_.size(); i++) + sub_errs_[i].InternalPrintToStdout(true); +} diff --git a/tools/gn/err.h b/tools/gn/err.h new file mode 100644 index 0000000..3e077e9 --- /dev/null +++ b/tools/gn/err.h @@ -0,0 +1,85 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_ERR_H_ +#define TOOLS_GN_ERR_H_ + +#include <string> +#include <vector> + +#include "tools/gn/location.h" +#include "tools/gn/token.h" + +class ParseNode; +class Value; + +// Result of doing some operation. Check has_error() to see if an error +// occurred. +// +// An error has a location and a message. Below that, is some optional help +// text to go with the annotation of the location. +// +// An error can also have sub-errors which are additionally printed out +// below. They can provide additional context. +class Err { + public: + typedef std::vector<LocationRange> RangeList; + + // Indicates no error. + Err(); + + // Error at a single point. + Err(const Location& location, + const std::string& msg, + const std::string& help = std::string()); + + // Error at a given range. + Err(const LocationRange& range, + const std::string& msg, + const std::string& help = std::string()); + + // Error at a given token. + Err(const Token& token, + const std::string& msg, + const std::string& help_text = std::string()); + + // Error at a given node. + Err(const ParseNode* node, + const std::string& msg, + const std::string& help_text = std::string()); + + // Error at a given value. + Err(const Value& value, + const std::string msg, + const std::string& help_text = std::string()); + + ~Err(); + + bool has_error() const { return has_error_; } + const Location& location() const { return location_; } + const std::string& message() const { return message_; } + const std::string& help_text() const { return help_text_; } + + void AppendRange(const LocationRange& range) { ranges_.push_back(range); } + const RangeList& ranges() const { return ranges_; } + + void AppendSubErr(const Err& err); + + void PrintToStdout() const; + + private: + void InternalPrintToStdout(bool is_sub_err) const; + + bool has_error_; + Location location_; + + std::vector<LocationRange> ranges_; + + std::string message_; + std::string help_text_; + + std::vector<Err> sub_errs_; +}; + +#endif // TOOLS_GN_ERR_H_ diff --git a/tools/gn/escape.cc b/tools/gn/escape.cc new file mode 100644 index 0000000..5fc7b6f --- /dev/null +++ b/tools/gn/escape.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2013 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 "tools/gn/escape.h" + +#include "base/containers/stack_container.h" + +namespace { + +template<typename DestString> +void EscapeStringToString(const base::StringPiece& str, + const EscapeOptions& options, + DestString* dest) { + bool used_quotes = false; + + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '$' && options.mode == ESCAPE_NINJA) { + // Escape dollars signs since ninja treats these specially. + dest->push_back('$'); + dest->push_back('$'); + } else if (str[i] == '"' && options.mode == ESCAPE_SHELL) { + // Escape quotes with backslashes for the command-line (Ninja doesn't + // care). + dest->push_back('\\'); + dest->push_back('"'); + } else if (str[i] == ' ') { + if (options.mode == ESCAPE_NINJA) { + // For ninja just escape spaces with $. + dest->push_back('$'); + } else if (options.mode == ESCAPE_SHELL && !options.inhibit_quoting) { + // For the shell, quote the whole string. + if (!used_quotes) { + used_quotes = true; + dest->insert(dest->begin(), '"'); + } + } + dest->push_back(' '); +#if defined(OS_WIN) + } else if (str[i] == '/' && options.convert_slashes) { + // Convert slashes on Windows if requested. + dest->push_back('\\'); +#else + } else if (str[i] == '\\' && options.mode == ESCAPE_SHELL) { + // For non-Windows shell, escape backslashes. + dest->push_back('\\'); + dest->push_back('\\'); +#endif + } else { + dest->push_back(str[i]); + } + } + + if (used_quotes) + dest->push_back('"'); +} + +} // namespace + +std::string EscapeString(const base::StringPiece& str, + const EscapeOptions& options) { + std::string result; + result.reserve(str.size() + 4); // Guess we'll add a couple of extra chars. + EscapeStringToString(str, options, &result); + return result; +} + +void EscapeStringToStream(std::ostream& out, + const base::StringPiece& str, + const EscapeOptions& options) { + // Escape to a stack buffer and then write out to the stream. + base::StackVector<char, 256> result; + result->reserve(str.size() + 4); // Guess we'll add a couple of extra chars. + EscapeStringToString(str, options, &result.container()); + if (!result->empty()) + out.write(result->data(), result->size()); +} diff --git a/tools/gn/escape.h b/tools/gn/escape.h new file mode 100644 index 0000000..1791161 --- /dev/null +++ b/tools/gn/escape.h @@ -0,0 +1,53 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_ESCAPE_H_ +#define TOOLS_GN_ESCAPE_H_ + +#include <iosfwd> + +#include "base/strings/string_piece.h" + +// TODO(brettw) we may need to make this a bitfield. If we want to write a +// shell command in a ninja file, we need the shell characters to be escaped, +// and THEN the ninja characters. Or maybe we require the caller to do two +// passes. +enum EscapingMode { + ESCAPE_NONE, // No escaping. + ESCAPE_NINJA, // Ninja string escaping. + ESCAPE_SHELL, // Shell string escaping. +}; + +struct EscapeOptions { + EscapeOptions() + : mode(ESCAPE_NONE), + convert_slashes(false), + inhibit_quoting(false) { + } + + EscapingMode mode; + + // When set, converts forward-slashes to system-specific path separators. + bool convert_slashes; + + // When the escaping mode is ESCAPE_SHELL, the escaper will normally put + // quotes around things with spaces. If this value is set to true, we'll + // disable the quoting feature and just add the spaces. + // + // This mode is for when quoting is done at some higher-level. Defaults to + // false. + bool inhibit_quoting; +}; + +// Escapes the given input, returnining the result. +std::string EscapeString(const base::StringPiece& str, + const EscapeOptions& options); + +// Same as EscapeString but writes the results to the given stream, saving a +// copy. +void EscapeStringToStream(std::ostream& out, + const base::StringPiece& str, + const EscapeOptions& options); + +#endif // TOOLS_GN_ESCAPE_H_ diff --git a/tools/gn/escape_unittest.cc b/tools/gn/escape_unittest.cc new file mode 100644 index 0000000..a7c19b3 --- /dev/null +++ b/tools/gn/escape_unittest.cc @@ -0,0 +1,4 @@ +// Copyright (c) 2013 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. + diff --git a/tools/gn/file_template.cc b/tools/gn/file_template.cc new file mode 100644 index 0000000..8b5d09f --- /dev/null +++ b/tools/gn/file_template.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2013 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 "tools/gn/file_template.h" + +#include "tools/gn/filesystem_utils.h" + +const char FileTemplate::kSource[] = "{{source}}"; +const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}"; + +FileTemplate::FileTemplate(const Value& t, Err* err) { + ParseInput(t, err); +} + +FileTemplate::FileTemplate(const std::vector<std::string>& t) { + for (size_t i = 0; i < t.size(); i++) + ParseOneTemplateString(t[i]); +} + +FileTemplate::~FileTemplate() { +} + +void FileTemplate::Apply(const Value& sources, + const ParseNode* origin, + std::vector<Value>* dest, + Err* err) const { + if (!sources.VerifyTypeIs(Value::LIST, err)) + return; + dest->reserve(sources.list_value().size() * templates_.container().size()); + + // Temporary holding place, allocate outside to re-use- buffer. + std::vector<std::string> string_output; + + const std::vector<Value>& sources_list = sources.list_value(); + for (size_t i = 0; i < sources_list.size(); i++) { + if (!sources_list[i].VerifyTypeIs(Value::STRING, err)) + return; + + ApplyString(sources_list[i].string_value(), &string_output); + for (size_t out_i = 0; out_i < string_output.size(); out_i++) + dest->push_back(Value(origin, string_output[i])); + } +} + +void FileTemplate::ApplyString(const std::string& str, + std::vector<std::string>* output) const { + // Compute all substitutions needed so we can just do substitutions below. + // We skip the LITERAL one since that varies each time. + std::string subst[Subrange::NUM_TYPES]; + if (types_required_[Subrange::SOURCE]) + subst[Subrange::SOURCE] = str; + if (types_required_[Subrange::NAME_PART]) + subst[Subrange::NAME_PART] = FindFilenameNoExtension(&str).as_string(); + + output->resize(templates_.container().size()); + for (size_t template_i = 0; + template_i < templates_.container().size(); template_i++) { + const Template& t = templates_[template_i]; + (*output)[template_i].clear(); + for (size_t subrange_i = 0; subrange_i < t.container().size(); + subrange_i++) { + if (t[subrange_i].type == Subrange::LITERAL) + (*output)[template_i].append(t[subrange_i].literal); + else + (*output)[template_i].append(subst[t[subrange_i].type]); + } + } +} + +void FileTemplate::ParseInput(const Value& value, Err* err) { + switch (value.type()) { + case Value::STRING: + ParseOneTemplateString(value.string_value()); + break; + case Value::LIST: + for (size_t i = 0; i < value.list_value().size(); i++) { + if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err)) + return; + ParseOneTemplateString(value.list_value()[i].string_value()); + } + break; + default: + *err = Err(value, "File template must be a string or list.", + "A sarcastic comment about your skills goes here."); + } +} + +void FileTemplate::ParseOneTemplateString(const std::string& str) { + templates_.container().resize(templates_.container().size() + 1); + Template& t = templates_[templates_.container().size() - 1]; + + size_t cur = 0; + while (true) { + size_t next = str.find("{{", cur); + + // Pick up everything from the previous spot to here as a literal. + if (next == std::string::npos) { + if (cur != str.size()) + t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur))); + break; + } else if (next > cur) { + t.container().push_back( + Subrange(Subrange::LITERAL, str.substr(cur, next - cur))); + } + + // Decode the template param. + if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) { + t.container().push_back(Subrange(Subrange::SOURCE)); + types_required_[Subrange::SOURCE] = true; + cur = next + arraysize(kSource) - 1; + } else if (str.compare(next, arraysize(kSourceNamePart) - 1, + kSourceNamePart) == 0) { + t.container().push_back(Subrange(Subrange::NAME_PART)); + types_required_[Subrange::NAME_PART] = true; + cur = next + arraysize(kSourceNamePart) - 1; + } else { + // If it's not a match, treat it like a one-char literal (this will be + // rare, so it's not worth the bother to add to the previous literal) so + // we can keep going. + t.container().push_back(Subrange(Subrange::LITERAL, "{")); + cur = next + 1; + } + } +} diff --git a/tools/gn/file_template.h b/tools/gn/file_template.h new file mode 100644 index 0000000..25d2251 --- /dev/null +++ b/tools/gn/file_template.h @@ -0,0 +1,74 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_FILE_TEMPLATE_H_ +#define TOOLS_GN_FILE_TEMPLATE_H_ + +#include "base/basictypes.h" +#include "base/containers/stack_container.h" +#include "tools/gn/err.h" +#include "tools/gn/value.h" + +class ParseNode; + +class FileTemplate { + public: + struct Subrange { + enum Type { + LITERAL = 0, + SOURCE, + NAME_PART, + NUM_TYPES // Must be last + }; + Subrange(Type t, const std::string& l = std::string()) + : type(t), + literal(l) { + } + + Type type; + + // When type_ == LITERAL, this specifies the literal. + std::string literal; + }; + + // Constructs a template from the given value. On error, the err will be + // set. In this case you should not use this object. + FileTemplate(const Value& t, Err* err); + FileTemplate(const std::vector<std::string>& t); + ~FileTemplate(); + + // Applies this template to the given list of sources, appending all + // results to the given dest list. The sources must be a list for the + // one that takes a value as an input, otherwise the given error will be set. + void Apply(const Value& sources, + const ParseNode* origin, + std::vector<Value>* dest, + Err* err) const; + void ApplyString(const std::string& input, + std::vector<std::string>* output) const; + + // Known template types. + static const char kSource[]; + static const char kSourceNamePart[]; + + private: + typedef base::StackVector<Subrange, 8> Template; + typedef base::StackVector<Template, 8> TemplateVector; + + void ParseInput(const Value& value, Err* err); + + // Parses a template string and adds it to the templates_ list. + void ParseOneTemplateString(const std::string& str); + + TemplateVector templates_; + + // The corresponding value is set to true if the given subrange type is + // required. This allows us to precompute these types whem applying them + // to a given source file. + bool types_required_[Subrange::NUM_TYPES]; + + DISALLOW_COPY_AND_ASSIGN(FileTemplate); +}; + +#endif // TOOLS_GN_FILE_TEMPLATE_H_ diff --git a/tools/gn/file_template_unittest.cc b/tools/gn/file_template_unittest.cc new file mode 100644 index 0000000..47c84f0 --- /dev/null +++ b/tools/gn/file_template_unittest.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2013 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/file_template.h" + +TEST(FileTemplate, Static) { + std::vector<std::string> templates; + templates.push_back("something_static"); + FileTemplate t(templates); + + std::vector<std::string> result; + t.ApplyString("", &result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("something_static", result[0]); + + t.ApplyString("lalala", &result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("something_static", result[0]); +} + +TEST(FileTemplate, Typical) { + std::vector<std::string> templates; + templates.push_back("foo/{{source_name_part}}.cc"); + templates.push_back("foo/{{source_name_part}}.h"); + FileTemplate t(templates); + + std::vector<std::string> result; + t.ApplyString("sources/ha.idl", &result); + ASSERT_EQ(2u, result.size()); + EXPECT_EQ("foo/ha.cc", result[0]); + EXPECT_EQ("foo/ha.h", result[1]); +} + +TEST(FileTemplate, Weird) { + std::vector<std::string> templates; + templates.push_back("{{{source}}{{source}}{{"); + FileTemplate t(templates); + + std::vector<std::string> result; + t.ApplyString("foo/lalala.c", &result); + ASSERT_EQ(1u, result.size()); + EXPECT_EQ("{foo/lalala.cfoo/lalala.c{{", result[0]); +} diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc new file mode 100644 index 0000000..625a358 --- /dev/null +++ b/tools/gn/filesystem_utils.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2013 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 "tools/gn/filesystem_utils.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "tools/gn/location.h" +#include "tools/gn/source_dir.h" + +namespace { + +enum DotDisposition { + // The given dot is just part of a filename and is not special. + NOT_A_DIRECTORY, + + // The given dot is the current directory. + DIRECTORY_CUR, + + // The given dot is the first of a double dot that should take us up one. + DIRECTORY_UP +}; + +// When we find a dot, this function is called with the character following +// that dot to see what it is. The return value indicates what type this dot is +// (see above). This code handles the case where the dot is at the end of the +// input. +// +// |*consumed_len| will contain the number of characters in the input that +// express what we found. +DotDisposition ClassifyAfterDot(const std::string& path, + size_t after_dot, + size_t* consumed_len) { + if (after_dot == path.size()) { + // Single dot at the end. + *consumed_len = 1; + return DIRECTORY_CUR; + } + if (path[after_dot] == '/') { + // Single dot followed by a slash. + *consumed_len = 2; // Consume the slash + return DIRECTORY_CUR; + } + + if (path[after_dot] == '.') { + // Two dots. + if (after_dot + 1 == path.size()) { + // Double dot at the end. + *consumed_len = 2; + return DIRECTORY_UP; + } + if (path[after_dot + 1] == '/') { + // Double dot folowed by a slash. + *consumed_len = 3; + return DIRECTORY_UP; + } + } + + // The dots are followed by something else, not a directory. + *consumed_len = 1; + return NOT_A_DIRECTORY; +} + +} // namesapce + +SourceFileType GetSourceFileType(const SourceFile& file, + Settings::TargetOS os) { + base::StringPiece extension = FindExtension(&file.value()); + if (extension == "cc" || extension == "cpp" || extension == "cxx") + return SOURCE_CC; + if (extension == "h") + return SOURCE_H; + if (extension == "c") + return SOURCE_C; + + switch (os) { + case Settings::MAC: + if (extension == "m") + return SOURCE_M; + if (extension == "mm") + return SOURCE_MM; + break; + + case Settings::WIN: + if (extension == "rc") + return SOURCE_RC; + break; + + default: + break; + } + + // TODO(brettw) asm files. + // TODO(brettw) weird thing with .S on non-Windows platforms. + return SOURCE_UNKNOWN; +} + +const char* GetExtensionForOutputType(Target::OutputType type, + Settings::TargetOS os) { + switch (os) { + case Settings::WIN: + switch (type) { + case Target::NONE: + NOTREACHED(); + return ""; + case Target::EXECUTABLE: + return "exe"; + case Target::SHARED_LIBRARY: + return "dll.lib"; // Extension of import library. + case Target::STATIC_LIBRARY: + return "lib"; + case Target::LOADABLE_MODULE: + return "dll"; // TODO(brettw) what's this? + default: + NOTREACHED(); + } + break; + + default: + NOTREACHED(); + } + return ""; +} + +std::string FilePathToUTF8(const base::FilePath& path) { +#if defined(OS_WIN) + return WideToUTF8(path.value()); +#else + return path.value(); +#endif +} + +base::FilePath UTF8ToFilePath(const base::StringPiece& sp) { +#if defined(OS_WIN) + return base::FilePath(UTF8ToWide(sp)); +#else + return base::FilePath(sp.as_string()); +#endif +} + +size_t FindExtensionOffset(const std::string& path) { + for (int i = static_cast<int>(path.size()); i >= 0; i--) { + if (path[i] == '/') + break; + if (path[i] == '.') + return i + 1; + } + return std::string::npos; +} + +base::StringPiece FindExtension(const std::string* path) { + size_t extension_offset = FindExtensionOffset(*path); + if (extension_offset == std::string::npos) + return base::StringPiece(); + return base::StringPiece(&path->data()[extension_offset], + path->size() - extension_offset); +} + +size_t FindFilenameOffset(const std::string& path) { + for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) { + if (path[i] == '/') + return i + 1; + } + return 0; // No filename found means everything was the filename. +} + +base::StringPiece FindFilename(const std::string* path) { + size_t filename_offset = FindFilenameOffset(*path); + if (filename_offset == 0) + return base::StringPiece(*path); // Everything is the file name. + return base::StringPiece(&(*path).data()[filename_offset], + path->size() - filename_offset); +} + +base::StringPiece FindFilenameNoExtension(const std::string* path) { + if (path->empty()) + return base::StringPiece(); + size_t filename_offset = FindFilenameOffset(*path); + size_t extension_offset = FindExtensionOffset(*path); + + size_t name_len; + if (extension_offset == std::string::npos) + name_len = path->size() - filename_offset; + else + name_len = extension_offset - filename_offset - 1; + + return base::StringPiece(&(*path).data()[filename_offset], name_len); +} + +void RemoveFilename(std::string* path) { + path->resize(FindFilenameOffset(*path)); +} + +bool EndsWithSlash(const std::string& s) { + return !s.empty() && s[s.size() - 1] == '/'; +} + +base::StringPiece FindDir(const std::string* path) { + size_t filename_offset = FindFilenameOffset(*path); + if (filename_offset == 0u) + return base::StringPiece(); + return base::StringPiece(path->data(), filename_offset); +} + +bool EnsureStringIsInOutputDir(const SourceDir& dir, + const std::string& str, + const Value& originating, + Err* err) { + // The last char of the dir will be a slash. We don't care if the input ends + // in a slash or not, so just compare up until there. + // + // This check will be wrong for all proper prefixes "e.g. "/output" will + // match "/out" but we don't really care since this is just a sanity check. + const std::string& dir_str = dir.value(); + if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1) + != 0) { + *err = Err(originating, "File not inside output directory.", + "The given file should be in the output directory. Normally you would " + "specify\n\"$target_output_dir/foo\" or " + "\"$target_gen_dir/foo\". I interpreted this as\n\"" + + str + "\"."); + return false; + } + return true; +} + +std::string InvertDir(const SourceDir& path) { + const std::string value = path.value(); + if (value.empty()) + return std::string(); + + DCHECK(value[0] == '/'); + size_t begin_index = 1; + + // If the input begins with two slashes, skip over both (this is a + // source-relative dir). + if (value.size() > 1 && value[1] == '/') + begin_index = 2; + + std::string ret; + for (size_t i = begin_index; i < value.size(); i++) { + if (value[i] == '/') + ret.append("../"); + } + return ret; +} + +void NormalizePath(std::string* path) { + char* pathbuf = path->empty() ? NULL : &(*path)[0]; + + // top_index is the first character we can modify in the path. Anything + // before this indicates where the path is relative to. + size_t top_index = 0; + bool is_relative = true; + if (!path->empty() && pathbuf[0] == '/') { + is_relative = false; + + if (path->size() > 1 && pathbuf[1] == '/') { + // Two leading slashes, this is a path into the source dir. + top_index = 2; + } else { + // One leading slash, this is a system-absolute path. + top_index = 1; + } + } + + size_t dest_i = top_index; + for (size_t src_i = top_index; src_i < path->size(); /* nothing */) { + if (pathbuf[src_i] == '.') { + if (src_i == 0 || pathbuf[src_i - 1] == '/') { + // Slash followed by a dot, see if it's something special. + size_t consumed_len; + switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) { + case NOT_A_DIRECTORY: + // Copy the dot to the output, it means nothing special. + pathbuf[dest_i++] = pathbuf[src_i++]; + break; + case DIRECTORY_CUR: + // Current directory, just skip the input. + src_i += consumed_len; + break; + case DIRECTORY_UP: + // Back up over previous directory component. If we're already + // at the top, preserve the "..". + if (dest_i > top_index) { + // The previous char was a slash, remove it. + dest_i--; + } + + if (dest_i == top_index) { + if (is_relative) { + // We're already at the beginning of a relative input, copy the + // ".." and continue. We need the trailing slash if there was + // one before (otherwise we're at the end of the input). + pathbuf[dest_i++] = '.'; + pathbuf[dest_i++] = '.'; + if (consumed_len == 3) + pathbuf[dest_i++] = '/'; + + // This also makes a new "root" that we can't delete by going + // up more levels. Otherwise "../.." would collapse to + // nothing. + top_index = dest_i; + } + // Otherwise we're at the beginning of an absolute path. Don't + // allow ".." to go up another level and just eat it. + } else { + // Just find the previous slash or the beginning of input. + while (dest_i > 0 && pathbuf[dest_i - 1] != '/') + dest_i--; + } + src_i += consumed_len; + } + } else { + // Dot not preceeded by a slash, copy it literally. + pathbuf[dest_i++] = pathbuf[src_i++]; + } + } else if (pathbuf[src_i] == '/') { + if (src_i > 0 && pathbuf[src_i - 1] == '/') { + // Two slashes in a row, skip over it. + src_i++; + } else { + // Just one slash, copy it. + pathbuf[dest_i++] = pathbuf[src_i++]; + } + } else { + // Input nothing special, just copy it. + pathbuf[dest_i++] = pathbuf[src_i++]; + } + } + path->resize(dest_i); +} + +void ConvertPathToSystem(std::string* path) { +#if defined(OS_WIN) + for (size_t i = 0; i < path->size(); i++) { + if ((*path)[i] == '/') + (*path)[i] = '\\'; + } +#endif +} + +std::string PathToSystem(const std::string& path) { + std::string ret(path); + ConvertPathToSystem(&ret); + return ret; +} + diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h new file mode 100644 index 0000000..19139f8 --- /dev/null +++ b/tools/gn/filesystem_utils.h @@ -0,0 +1,115 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_FILESYSTEM_UTILS_H_ +#define TOOLS_GN_FILESYSTEM_UTILS_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "base/strings/string_piece.h" +#include "tools/gn/settings.h" +#include "tools/gn/target.h" + +class Err; +class Location; +class Value; + +enum SourceFileType { + SOURCE_UNKNOWN, + SOURCE_ASM, + SOURCE_C, + SOURCE_CC, + SOURCE_H, + SOURCE_M, + SOURCE_MM, + //SOURCE_S, // TODO(brettw) what is this? + SOURCE_RC, +}; + +SourceFileType GetSourceFileType(const SourceFile& file, + Settings::TargetOS os); + +// Returns the extension, not including the dot, for the given file type on the +// given system. +// +// Some targets make multiple files (like a .dll and an import library). This +// function returns the name of the file other targets should depend on and +// link to (so in this example, the import library). +const char* GetExtensionForOutputType(Target::OutputType type, + Settings::TargetOS os); + +std::string FilePathToUTF8(const base::FilePath& path); +base::FilePath UTF8ToFilePath(const base::StringPiece& sp); + +// Extensions ----------------------------------------------------------------- + +// Returns the index of the extension (character after the last dot not after a +// slash). Returns std::string::npos if not found. Returns path.size() if the +// file ends with a dot. +size_t FindExtensionOffset(const std::string& path); + +// Returns a string piece pointing into the input string identifying the +// extension. Note that the input pointer must outlive the output. +base::StringPiece FindExtension(const std::string* path); + +// Filename parts ------------------------------------------------------------- + +// Returns the offset of the character following the last slash, or +// 0 if no slash was found. Returns path.size() if the path ends with a slash. +// Note that the input pointer must outlive the output. +size_t FindFilenameOffset(const std::string& path); + +// Returns a string piece pointing into the input string identifying the +// file name (following the last slash, including the extension). Note that the +// input pointer must outlive the output. +base::StringPiece FindFilename(const std::string* path); + +// Like FindFilename but does not include the extension. +base::StringPiece FindFilenameNoExtension(const std::string* path); + +// Removes everything after the last slash. The last slash, if any, will be +// preserved. +void RemoveFilename(std::string* path); + +// Returns true if the given path ends with a slash. +bool EndsWithSlash(const std::string& s); + +// Path parts ----------------------------------------------------------------- + +// Returns a string piece pointing into the input string identifying the +// directory name of the given path, including the last slash. Note that the +// input pointer must outlive the output. +base::StringPiece FindDir(const std::string* path); + +// Verifies that the given string references a file inside of the given +// directory. This is pretty stupid and doesn't handle "." and "..", etc., +// it is designed for a sanity check to keep people from writing output files +// to the source directory accidentally. +// +// The originating value will be blamed in the error. +// +// If the file isn't in the dir, returns false and sets the error. Otherwise +// returns true and leaves the error untouched. +bool EnsureStringIsInOutputDir(const SourceDir& dir, + const std::string& str, + const Value& originating, + Err* err); + +// ---------------------------------------------------------------------------- + +// Converts a directory to its inverse (e.g. "/foo/bar/" -> "../../"). +// This will be the empty string for the root directories ("/" and "//"), and +// in all other cases, this is guaranteed to end in a slash. +std::string InvertDir(const SourceDir& dir); + +// Collapses "." and sequential "/"s and evaluates "..". +void NormalizePath(std::string* path); + +// Converts slashes to backslashes for Windows. Keeps the string unchanged +// for other systems. +void ConvertPathToSystem(std::string* path); +std::string PathToSystem(const std::string& path); + +#endif // TOOLS_GN_FILESYSTEM_UTILS_H_ diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc new file mode 100644 index 0000000..75bf7cd --- /dev/null +++ b/tools/gn/filesystem_utils_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2013 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 "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "tools/gn/filesystem_utils.h" + +TEST(FilesystemUtils, FileExtensionOffset) { + EXPECT_EQ(std::string::npos, FindExtensionOffset("")); + EXPECT_EQ(std::string::npos, FindExtensionOffset("foo/bar/baz")); + EXPECT_EQ(4u, FindExtensionOffset("foo.")); + EXPECT_EQ(4u, FindExtensionOffset("f.o.bar")); + EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/")); + EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/baz")); +} + +TEST(FilesystemUtils, FindExtension) { + std::string input; + EXPECT_EQ("", FindExtension(&input).as_string()); + input = "foo/bar/baz"; + EXPECT_EQ("", FindExtension(&input).as_string()); + input = "foo."; + EXPECT_EQ("", FindExtension(&input).as_string()); + input = "f.o.bar"; + EXPECT_EQ("bar", FindExtension(&input).as_string()); + input = "foo.bar/"; + EXPECT_EQ("", FindExtension(&input).as_string()); + input = "foo.bar/baz"; + EXPECT_EQ("", FindExtension(&input).as_string()); +} + +TEST(FilesystemUtils, FindFilenameOffset) { + EXPECT_EQ(0u, FindFilenameOffset("")); + EXPECT_EQ(0u, FindFilenameOffset("foo")); + EXPECT_EQ(4u, FindFilenameOffset("foo/")); + EXPECT_EQ(4u, FindFilenameOffset("foo/bar")); +} + +TEST(FilesystemUtils, RemoveFilename) { + std::string s; + + RemoveFilename(&s); + EXPECT_STREQ("", s.c_str()); + + s = "foo"; + RemoveFilename(&s); + EXPECT_STREQ("", s.c_str()); + + s = "/"; + RemoveFilename(&s); + EXPECT_STREQ("/", s.c_str()); + + s = "foo/bar"; + RemoveFilename(&s); + EXPECT_STREQ("foo/", s.c_str()); + + s = "foo/bar/baz.cc"; + RemoveFilename(&s); + EXPECT_STREQ("foo/bar/", s.c_str()); +} + +TEST(FilesystemUtils, FindDir) { + std::string input; + EXPECT_EQ("", FindDir(&input)); + input = "/"; + EXPECT_EQ("/", FindDir(&input)); + input = "foo/"; + EXPECT_EQ("foo/", FindDir(&input)); + input = "foo/bar/baz"; + EXPECT_EQ("foo/bar/", FindDir(&input)); +} + +TEST(FilesystemUtils, InvertDir) { + EXPECT_TRUE(InvertDir(SourceDir()) == ""); + EXPECT_TRUE(InvertDir(SourceDir("/")) == ""); + EXPECT_TRUE(InvertDir(SourceDir("//")) == ""); + + EXPECT_TRUE(InvertDir(SourceDir("//foo/bar")) == "../../"); + EXPECT_TRUE(InvertDir(SourceDir("/foo/bar/")) == "../../"); +} + +TEST(FilesystemUtils, NormalizePath) { + std::string input; + + NormalizePath(&input); + EXPECT_EQ("", input); + + input = "foo/bar.txt"; + NormalizePath(&input); + EXPECT_EQ("foo/bar.txt", input); + + input = "."; + NormalizePath(&input); + EXPECT_EQ("", input); + + input = ".."; + NormalizePath(&input); + EXPECT_EQ("..", input); + + input = "foo//bar"; + NormalizePath(&input); + EXPECT_EQ("foo/bar", input); + + input = "//foo"; + NormalizePath(&input); + EXPECT_EQ("//foo", input); + + input = "foo/..//bar"; + NormalizePath(&input); + EXPECT_EQ("bar", input); + + input = "foo/../../bar"; + NormalizePath(&input); + EXPECT_EQ("../bar", input); + + input = "/../foo"; // Don't go aboe the root dir. + NormalizePath(&input); + EXPECT_EQ("/foo", input); + + input = "//../foo"; // Don't go aboe the root dir. + NormalizePath(&input); + EXPECT_EQ("//foo", input); + + input = "../foo"; + NormalizePath(&input); + EXPECT_EQ("../foo", input); + + input = ".."; + NormalizePath(&input); + EXPECT_EQ("..", input); + + input = "./././."; + NormalizePath(&input); + EXPECT_EQ("", input); + + input = "../../.."; + NormalizePath(&input); + EXPECT_EQ("../../..", input); + + input = "../"; + NormalizePath(&input); + EXPECT_EQ("../", input); +} diff --git a/tools/gn/function_define_rule.cc b/tools/gn/function_define_rule.cc new file mode 100644 index 0000000..afbed9e --- /dev/null +++ b/tools/gn/function_define_rule.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2013 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 "tools/gn/functions.h" + +#include "tools/gn/parse_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/value.h" + +Value ExecuteDefineRule(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + // TODO(brettw) determine if the function is built-in and throw an error if + // it is. + if (args.size() != 1) { + *err = Err(function->function(), + "Need exactly one string arg to define_rule."); + return Value(); + } + if (!args[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + std::string rule_name = args[0].string_value(); + + const FunctionCallNode* existing_rule = scope->GetRule(rule_name); + if (existing_rule) { + *err = Err(function, "Duplicate rule definition.", + "A rule with this name was already defined."); + err->AppendSubErr(Err(existing_rule->function(), "Previous definition.")); + return Value(); + } + + scope->AddRule(rule_name, function); + return Value(); +} diff --git a/tools/gn/function_exec_script.cc b/tools/gn/function_exec_script.cc new file mode 100644 index 0000000..a950909 --- /dev/null +++ b/tools/gn/function_exec_script.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2013 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 "base/command_line.h" +#include "base/file_util.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "tools/gn/err.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/functions.h" +#include "tools/gn/input_conversion.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/value.h" + +#if defined(OS_WIN) +#include <windows.h> + +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#endif + +/* +exec_script: Synchronously run a script and return the output. + + exec_script(filename, arguments, input_conversion, [file_dependencies]) + + Runs the given script, returning the stdout of the script. The build + generation will fail if the script does not exist or returns a nonzero + exit code. + +Arguments: + + filename: + File name of python script to execute, relative to the build file. + + arguments: + A list of strings to be passed to the scripe as arguments. + + input_conversion: + Controls how the file is read and parsed. See "help input_conversion". + + dependencies: + (Optional) A list of files that this script reads or otherwise depends + on. These dependencies will be added to the build result such that if + any of them change, the build will be regenerated and the script will + be re-run. + + The script itself will be an implicit dependency so you do not need to + list it. + +Example: + + all_lines = exec_script("myscript.py", [some_input], "list lines", + ["data_file.txt"]) +*/ + +namespace { + +#if defined(OS_WIN) +bool ExecProcess(const CommandLine& cmdline, + const base::FilePath& startup_dir, + std::string* std_out, + std::string* std_err, + int* exit_code) { + SECURITY_ATTRIBUTES sa_attr; + // Set the bInheritHandle flag so pipe handles are inherited. + sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sa_attr.bInheritHandle = TRUE; + sa_attr.lpSecurityDescriptor = NULL; + + // Create the pipe for the child process's STDOUT. + HANDLE out_read = NULL; + HANDLE out_write = NULL; + if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { + NOTREACHED() << "Failed to create pipe"; + return false; + } + base::win::ScopedHandle scoped_out_read(out_read); + base::win::ScopedHandle scoped_out_write(out_write); + + // Create the pipe for the child process's STDERR. + HANDLE err_read = NULL; + HANDLE err_write = NULL; + if (!CreatePipe(&err_read, &err_write, &sa_attr, 0)) { + NOTREACHED() << "Failed to create pipe"; + return false; + } + base::win::ScopedHandle scoped_err_read(err_read); + base::win::ScopedHandle scoped_err_write(err_write); + + // Ensure the read handle to the pipe for STDOUT/STDERR is not inherited. + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + if (!SetHandleInformation(err_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + + base::FilePath::StringType cmdline_str(cmdline.GetCommandLineString()); + + base::win::ScopedProcessInformation proc_info; + STARTUPINFO start_info = { 0 }; + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdOutput = out_write; + // Keep the normal stdin. + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + // FIXME(brettw) set stderr here when we actually read it below. + //start_info.hStdError = err_write; + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + if (!CreateProcess(NULL, + &cmdline_str[0], + NULL, NULL, + TRUE, // Handles are inherited. + 0, NULL, + startup_dir.value().c_str(), + &start_info, proc_info.Receive())) { + return false; + } + + // Close our writing end of pipes now. Otherwise later read would not be able + // to detect end of child's output. + scoped_out_write.Close(); + scoped_err_write.Close(); + + // Read output from the child process's pipe for STDOUT + const int kBufferSize = 1024; + char buffer[kBufferSize]; + + // FIXME(brettw) read from stderr here! This is complicated because we want + // to read both of them at the same time, probably need overlapped I/O. + // Also uncomment start_info code above. + for (;;) { + DWORD bytes_read = 0; + BOOL success = ReadFile(out_read, buffer, kBufferSize, &bytes_read, NULL); + if (!success || bytes_read == 0) + break; + std_out->append(buffer, bytes_read); + } + + // Let's wait for the process to finish. + WaitForSingleObject(proc_info.process_handle(), INFINITE); + + DWORD dw_exit_code; + GetExitCodeProcess(proc_info.process_handle(), &dw_exit_code); + *exit_code = static_cast<int>(dw_exit_code); + + return true; +} +#else +bool ExecProcess(const CommandLine& cmdline, + const base::FilePath& startup_dir, + std::string* std_out, + std::string* std_err, + int* exit_code) { + //NOTREACHED() << "Implement me."; + return false; +} +#endif + +} // namespace + +Value ExecuteExecScript(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 3 && args.size() != 4) { + *err = Err(function->function(), "Wrong number of args to write_file", + "I expected three or four arguments."); + return Value(); + } + + const Settings* settings = scope->settings(); + const BuildSettings* build_settings = settings->build_settings(); + const SourceDir& cur_dir = SourceDirForFunctionCall(function); + + // Find the python script to run. + if (!args[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + SourceFile script_source = + cur_dir.ResolveRelativeFile(args[0].string_value()); + base::FilePath script_path = build_settings->GetFullPath(script_source); + if (!build_settings->secondary_source_path().empty() && + !base::PathExists(script_path)) { + // Fall back to secondary source root when the file doesn't exist. + script_path = build_settings->GetFullPathSecondary(script_source); + } + + // Add all dependencies of this script, including the script itself, to the + // build deps. + g_scheduler->AddGenDependency(script_source); + if (args.size() == 4) { + const Value& deps_value = args[3]; + if (!deps_value.VerifyTypeIs(Value::LIST, err)) + return Value(); + + for (size_t i = 0; i < deps_value.list_value().size(); i++) { + if (!deps_value.list_value()[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + g_scheduler->AddGenDependency(cur_dir.ResolveRelativeFile( + deps_value.list_value()[0].string_value())); + } + } + + // Make the command line. + const base::FilePath& python_path = build_settings->python_path(); + CommandLine cmdline(python_path); + cmdline.AppendArgPath(script_path); + + const Value& script_args = args[1]; + if (!script_args.VerifyTypeIs(Value::LIST, err)) + return Value(); + for (size_t i = 0; i < script_args.list_value().size(); i++) { + if (!script_args.list_value()[i].VerifyTypeIs(Value::STRING, err)) + return Value(); + cmdline.AppendArg(script_args.list_value()[i].string_value()); + } + + // Execute the process. + // TODO(brettw) set the environment block. + std::string output; + std::string stderr_output; // TODO(brettw) not hooked up, see above. + int exit_code = 0; + if (!ExecProcess(cmdline, build_settings->GetFullPath(cur_dir), + &output, &stderr_output, &exit_code)) { + *err = Err(function->function(), "Could not execute python.", + "I was trying to execute \"" + FilePathToUTF8(python_path) + "\"."); + return Value(); + } + + // TODO(brettw) maybe we need stderr also for reasonable stack dumps. + if (exit_code != 0) { + std::string msg = + std::string("I was running \"") + FilePathToUTF8(script_path) + "\"\n" + "and it returned " + base::IntToString(exit_code); + if (!output.empty()) + msg += " and printed out:\n\n" + output; + else + msg += "."; + *err = Err(function->function(), "Script returned non-zero exit code.", + msg); + return Value(); + } + + return ConvertInputToValue(output, function, args[2], err); +} diff --git a/tools/gn/function_process_file_template.cc b/tools/gn/function_process_file_template.cc new file mode 100644 index 0000000..18e1425 --- /dev/null +++ b/tools/gn/function_process_file_template.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2013 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 "tools/gn/file_template.h" +#include "tools/gn/functions.h" +#include "tools/gn/parse_tree.h" + +/* +process_file_template: Do template expansion over a list of files. + + process_file_template(source_list, template) + + process_file_template applies a template list to a source file list, + returning the result of applying each template to each source. This is + typically used for computing output file names from input files. + +Arguments: + + The source_list is a list of file names. + + The template can be a string or a list. If it is a list, multiple output + strings are generated for each input. + + The following template substrings are used in the template arguments + and are replaced with the corresponding part of the input file name: + + "{{source}}": The entire source name. + + "{{source_name_part}}": The source name with no path or extension. + +Example: + + sources = [ + "foo.idl", + "bar.idl", + ] + myoutputs = process_file_template( + sources, + [ "$target_gen_dir/{{source_name_part}}.cc", + "$target_gen_dir/{{source_name_part}}.h" ]) + + The result in this case will be: + [ "/out/Debug/foo.cc" + "/out/Debug/foo.h" + "/out/Debug/bar.cc" + "/out/Debug/bar.h" ] +*/ +Value ExecuteProcessFileTemplate(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 2) { + *err = Err(function->function(), "Expected two arguments"); + return Value(); + } + + FileTemplate file_template(args[1], err); + if (err->has_error()) + return Value(); + + Value ret(function, Value::LIST); + file_template.Apply(args[0], function, &ret.list_value(), err); + return ret; +} diff --git a/tools/gn/function_read_file.cc b/tools/gn/function_read_file.cc new file mode 100644 index 0000000..db981ad --- /dev/null +++ b/tools/gn/function_read_file.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2013 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 "base/file_util.h" +#include "tools/gn/err.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/functions.h" +#include "tools/gn/input_conversion.h" +#include "tools/gn/input_file.h" +#include "tools/gn/scheduler.h" + +// TODO(brettw) consider removing this. I originally wrote it for making the +// WebKit bindings but misundersood what was required, and didn't need to +// use this. This seems to have a high potential for misuse. + +/* +read_file: Read a file into a variable. + + read_file(filename, how_to_read) + + Whitespace will be trimmed from the end of the file. Throws an error if the + file can not be opened. + +Arguments: + + filename: + Filename to read, relative to the build file. + + input_conversion: + Controls how the file is read and parsed. See "help input_conversion". + +Example: + + lines = read_file("foo.txt", "list lines") +*/ +Value ExecuteReadFile(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 2) { + *err = Err(function->function(), "Wrong number of args to read_file", + "I expected two arguments."); + return Value(); + } + if (!args[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + + // Compute the file name. + const SourceDir& cur_dir = SourceDirForFunctionCall(function); + SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value()); + base::FilePath file_path = + scope->settings()->build_settings()->GetFullPath(source_file); + + // Ensure that everything is recomputed if the read file changes. + g_scheduler->AddGenDependency(source_file); + + // Read contents. + std::string file_contents; + if (!file_util::ReadFileToString(file_path, &file_contents)) { + *err = Err(args[0], "Could not read file.", + "I resolved this to \"" + FilePathToUTF8(file_path) + "\"."); + return Value(); + } + + return ConvertInputToValue(file_contents, function, args[1], err); +} diff --git a/tools/gn/function_set_default_toolchain.cc b/tools/gn/function_set_default_toolchain.cc new file mode 100644 index 0000000..4939ac8 --- /dev/null +++ b/tools/gn/function_set_default_toolchain.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2013 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 "tools/gn/build_settings.h" +#include "tools/gn/functions.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/toolchain_manager.h" + +/* +set_default_toolchain: Sets the default toolchain name. + + set_default_toolchain(toolchain_label) + + The given label should identify a toolchain definition (see "toolchain"). + This toolchain will be used for all targets unless otherwise specified. + + This function is only valid to call during the processing of the build + configuration file. Since the build configuration file is processed + separately for each toolchain, this function will be a no-op when called + under any non-default toolchains. + + For example, the default toolchain should be appropriate for the current + environment. If the current environment is 32-bit and somebody references + a target with a 64-bit toolchain, we wouldn't want processing of the build + config file for the 64-bit toolchain to reset the default toolchain to + 64-bit, we want to keep it 32-bits. + +Argument: + + toolchain_label: + Toolchain name. + +Example: + + set_default_toolchain("//build/config/win:vs32") +*/ + +Value ExecuteSetDefaultToolchain(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (!scope->IsProcessingBuildConfig()) { + *err = Err(function->function(), "Must be called from build config.", + "set_default_toolchain can only be called from the build configuration " + "file."); + return Value(); + } + + // Ignore non-default-build-config invocations. + if (!scope->IsProcessingDefaultBuildConfig()) + return Value(); + + const SourceDir& current_dir = SourceDirForFunctionCall(function); + const Label& default_toolchain = ToolchainLabelForScope(scope); + + if (!EnsureSingleStringArg(function, args, err)) + return Value(); + Label toolchain_label( + Label::Resolve(current_dir, default_toolchain, args[0], err)); + if (toolchain_label.is_null()) + return Value(); + + ToolchainManager& mgr = + scope->settings()->build_settings()->toolchain_manager(); + mgr.SetDefaultToolchainUnlocked(toolchain_label, function->GetRange(), err); + return Value(); +} diff --git a/tools/gn/function_template.cc b/tools/gn/function_template.cc new file mode 100644 index 0000000..a85a40f --- /dev/null +++ b/tools/gn/function_template.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 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 "tools/gn/functions.h" + +#include "tools/gn/parse_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/value.h" + +Value ExecuteTemplate(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + // TODO(brettw) determine if the function is built-in and throw an error if + // it is. + if (args.size() != 1) { + *err = Err(function->function(), + "Need exactly one string arg to template."); + return Value(); + } + if (!args[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + std::string template_name = args[0].string_value(); + + const FunctionCallNode* existing_template = scope->GetTemplate(template_name); + if (existing_template) { + *err = Err(function, "Duplicate template definition.", + "A template with this name was already defined."); + err->AppendSubErr(Err(existing_template->function(), + "Previous definition.")); + return Value(); + } + + scope->AddTemplate(template_name, function); + return Value(); +} diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc new file mode 100644 index 0000000..b78c639 --- /dev/null +++ b/tools/gn/function_toolchain.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2013 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 "tools/gn/err.h" +#include "tools/gn/functions.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/toolchain.h" + +namespace { + +// This is jsut a unique value to take the address of to use as the key for +// the toolchain property on a scope. +const int kToolchainPropertyKey = 0; + +// Reads the given string from the scope (if present) and puts the result into +// dest. If the value is not a string, sets the error and returns false. +bool ReadString(Scope& scope, const char* var, std::string* dest, Err* err) { + const Value* v = scope.GetValue(var, true); + if (!v) + return true; // Not present is fine. + + if (!v->VerifyTypeIs(Value::STRING, err)) + return false; + *dest = v->string_value(); + return true; +} + +} // namespace + +Value ExecuteToolchain(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + if (!EnsureNotProcessingImport(function, scope, err) || + !EnsureNotProcessingBuildConfig(function, scope, err)) + return Value(); + + // Note that we don't want to use MakeLabelForScope since that will include + // the toolchain name in the label, and toolchain labels don't themselves + // have toolchain names. + const SourceDir& input_dir = SourceDirForFunctionCall(function); + Label label(input_dir, args[0].string_value(), SourceDir(), std::string()); + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Generating toolchain", label.GetUserVisibleName(true)); + + // This object will actually be copied into the one owned by the toolchain + // manager, but that has to be done in the lock. + Toolchain toolchain(label); + + Scope block_scope(scope); + block_scope.SetProperty(&kToolchainPropertyKey, &toolchain); + block->ExecuteBlockInScope(&block_scope, err); + block_scope.SetProperty(&kToolchainPropertyKey, NULL); + if (err->has_error()) + return Value(); + if (!block_scope.CheckForUnusedVars(err)) + return Value(); + + const BuildSettings* build_settings = scope->settings()->build_settings(); + { + // Save the toolchain definition in the toolchain manager and mark the + // corresponding item in the dependency tree resolved so that targets + // that depend on this toolchain know it's ready. + base::AutoLock lock(build_settings->item_tree().lock()); + build_settings->toolchain_manager().SetToolchainDefinitionLocked( + toolchain, function->GetRange(), err); + build_settings->item_tree().MarkItemGeneratedLocked(label); + } + return Value(); +} + +Value ExecuteTool(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + // Find the toolchain definition we're executing inside of. The toolchain + // function will set a property pointing to it that we'll pick up. + Toolchain* toolchain = reinterpret_cast<Toolchain*>( + scope->GetProperty(&kToolchainPropertyKey, NULL)); + if (!toolchain) { + *err = Err(function->function(), "tool() called outside of toolchain().", + "The tool() function can only be used inside a toolchain() " + "definition."); + return Value(); + } + + if (!EnsureSingleStringArg(function, args, err)) + return Value(); + const std::string& tool_name = args[0].string_value(); + Toolchain::ToolType tool_type = Toolchain::ToolNameToType(tool_name); + if (tool_type == Toolchain::TYPE_NONE) { + *err = Err(args[0], "Unknown tool type"); + return Value(); + } + + // Run the tool block. + Scope block_scope(scope); + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + // Extract the stuff we need. + Toolchain::Tool t; + if (!ReadString(block_scope, "command", &t.command, err) || + !ReadString(block_scope, "depfile", &t.depfile, err) || + !ReadString(block_scope, "deps", &t.deps, err) || + !ReadString(block_scope, "description", &t.description, err) || + !ReadString(block_scope, "pool", &t.pool, err) || + !ReadString(block_scope, "restat", &t.restat, err) || + !ReadString(block_scope, "rspfile", &t.rspfile, err) || + !ReadString(block_scope, "rspfile_content", &t.rspfile_content, err)) + return Value(); + + // Make sure there weren't any vars set in this tool that were unused. + if (!block_scope.CheckForUnusedVars(err)) + return Value(); + + toolchain->SetTool(tool_type, t); + return Value(); +} diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc new file mode 100644 index 0000000..9356feb --- /dev/null +++ b/tools/gn/function_write_file.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2013 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 <iostream> +#include <sstream> + +#include "base/file_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "tools/gn/err.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/functions.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" + +/* +write_file: Read a file into a variable. + + write_file(filename, data) + + If data is a list, the list will be written one-item-per-line with no + quoting or brackets. + + TODO(brettw) we probably need an optional third argument to control list + formatting. + +Arguments: + + filename: + Filename to write. This must be within the output directory. + + data: + The list or string to write. +*/ +Value ExecuteWriteFile(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 2) { + *err = Err(function->function(), "Wrong number of args to write_file", + "I expected two arguments."); + return Value(); + } + + // Compute the file name and make sure it's in the output dir. + if (!args[0].VerifyTypeIs(Value::STRING, err)) + return Value(); + const SourceDir& cur_dir = SourceDirForFunctionCall(function); + SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value()); + if (!EnsureStringIsInOutputDir( + scope->settings()->build_settings()->build_dir(), + source_file.value(), args[0], err)) + return Value(); + + // Compute output. + std::ostringstream contents; + if (args[1].type() == Value::LIST) { + const std::vector<Value>& list = args[1].list_value(); + for (size_t i = 0; i < list.size(); i++) + contents << list[i].ToString() << std::endl; + } else { + contents << args[1].ToString(); + } + + // Write file, creating the directory if necessary. + base::FilePath file_path = + scope->settings()->build_settings()->GetFullPath(source_file); + const std::string& contents_string = contents.str(); + if (!file_util::CreateDirectory(file_path.DirName())) { + *err = Err(function->function(), "Unable to create directory.", + "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\"."); + return Value(); + } + if (file_util::WriteFile(file_path, + contents_string.c_str(), contents_string.size()) + != static_cast<int>(contents_string.size())) { + *err = Err(function->function(), "Unable to write file.", + "I was writing \"" + FilePathToUTF8(file_path) + "\"."); + return Value(); + } + return Value(); +} diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc new file mode 100644 index 0000000..7f32db2 --- /dev/null +++ b/tools/gn/functions.cc @@ -0,0 +1,443 @@ +// Copyright (c) 2013 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 "tools/gn/functions.h" + +#include <iostream> + +#include "base/strings/string_util.h" +#include "tools/gn/config.h" +#include "tools/gn/config_values_generator.h" +#include "tools/gn/err.h" +#include "tools/gn/input_file.h" +#include "tools/gn/item_tree.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/target_manager.h" +#include "tools/gn/token.h" +#include "tools/gn/value.h" + +namespace { + +void FillNeedsBlockError(const FunctionCallNode* function, Err* err) { + *err = Err(function->function(), "This function call requires a block.", + "The block's \"{\" must be on the same line as the function " + "call's \")\"."); +} + +Value ExecuteAssert(const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 1) { + *err = Err(function->function(), "Wrong number of arguments.", + "assert() takes one argument, " + "were you expecting somethig else?"); + } else if (args[0].InterpretAsInt() == 0) { + *err = Err(function->function(), "Assertion failed."); + if (args[0].origin()) { + // If you do "assert(foo)" we'd ideally like to show you where foo was + // set, and in this case the origin of the args will tell us that. + // However, if you do "assert(foo && bar)" the source of the value will + // be the assert like, which isn't so helpful. + // + // So we try to see if the args are from the same line or not. This will + // break if you do "assert(\nfoo && bar)" and we may show the second line + // as the source, oh well. The way around this is to check to see if the + // origin node is inside our function call block. + Location origin_location = args[0].origin()->GetRange().begin(); + if (origin_location.file() != function->function().location().file() || + origin_location.line_number() != + function->function().location().line_number()) { + err->AppendSubErr(Err(args[0].origin()->GetRange(), "", + "This is where it was set.")); + } + } + } + return Value(); +} + +Value ExecuteConfig(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (!EnsureSingleStringArg(function, args, err) || + !EnsureNotProcessingImport(function, scope, err)) + return Value(); + + Label label(MakeLabelForScope(scope, function, args[0].string_value())); + + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Generating config", label.GetUserVisibleName(true)); + + // Create the empty config object. + ItemTree* tree = &scope->settings()->build_settings()->item_tree(); + Config* config = Config::GetConfig(scope->settings(), function->GetRange(), + label, NULL, err); + if (err->has_error()) + return Value(); + + // Fill it. + const SourceDir input_dir = SourceDirForFunctionCall(function); + ConfigValuesGenerator gen(&config->config_values(), scope, + function->function(), input_dir, err); + gen.Run(); + if (err->has_error()) + return Value(); + + // Mark as complete. + { + base::AutoLock lock(tree->lock()); + tree->MarkItemGeneratedLocked(label); + } + return Value(); +} + +Value ExecuteDeclareArgs(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + // Only allow this to be called once. We use a variable in the current scope + // with a name the parser will reject if the user tried to type it. + const char did_declare_args_var[] = "@@declared_args"; + if (scope->GetValue(did_declare_args_var)) { + *err = Err(function->function(), "Duplicate call to declared_args."); + err->AppendSubErr( + Err(scope->GetValue(did_declare_args_var)->origin()->GetRange(), + "See the original call.")); + return Value(); + } + + // Find the root scope where the values will be set. + Scope* root = scope->mutable_containing(); + if (!root || root->containing() || !scope->IsProcessingBuildConfig()) { + *err = Err(function->function(), "declare_args called incorrectly." + "It must be called only from the build config script and in the " + "root scope."); + return Value(); + } + + // Take all variables set in the current scope as default values and put + // them in the parent scope. The values in the current scope are the defaults, + // then we apply the external args to this list. + Scope::KeyValueVector values; + scope->GetCurrentScopeValues(&values); + for (size_t i = 0; i < values.size(); i++) { + // TODO(brettw) actually import the arguments from the command line rather + // than only using the defaults. + root->SetValue(values[i].first, values[i].second, + values[i].second.origin()); + } + + scope->SetValue(did_declare_args_var, Value(function, 1), NULL); + return Value(); +} + +Value ExecuteImport(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (!EnsureSingleStringArg(function, args, err) || + !EnsureNotProcessingImport(function, scope, err)) + return Value(); + + const SourceDir input_dir = SourceDirForFunctionCall(function); + SourceFile import_file = + input_dir.ResolveRelativeFile(args[0].string_value()); + scope->settings()->import_manager().DoImport(import_file, function, + scope, err); + return Value(); +} + +Value ExecuteTemplate(Scope* scope, + const FunctionCallNode* invocation, + const std::vector<Value>& args, + BlockNode* block, + const FunctionCallNode* rule, + Err* err) { + if (!EnsureNotProcessingImport(invocation, scope, err)) + return Value(); + Scope block_scope(scope); + if (!FillTargetBlockScope(scope, invocation, + invocation->function().value().data(), + block, args, &block_scope, err)) + return Value(); + + // Run the block for the rule invocation. + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + // Now run the rule itself with that block as the current scope. + rule->block()->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + return Value(); +} + +Value ExecuteSetDefaults(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + if (!EnsureSingleStringArg(function, args, err)) + return Value(); + const std::string& target_type(args[0].string_value()); + + // Ensure there aren't defaults already set. + if (scope->GetTargetDefaults(target_type)) { + *err = Err(function->function(), + "This target type defaults were already set."); + return Value(); + } + + // Execute the block in a new scope that has a parent of the containing + // scope. + Scope block_scope(scope); + if (!FillTargetBlockScope(scope, function, + function->function().value().data(), + block, args, &block_scope, err)) + return Value(); + + // Run the block for the rule invocation. + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + // Now copy the values set on the scope we made into the free-floating one + // (with no containing scope) used to hold the target defaults. + Scope* dest = scope->MakeTargetDefaults(target_type); + block_scope.NonRecursiveMergeTo(dest, function, "<SHOULD NOT FAIL>", err); + return Value(); +} + +Value ExecuteSetSourcesAssignmentFilter(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 1) { + *err = Err(function, "set_sources_assignment_filter takes one argument."); + } else { + scoped_ptr<PatternList> f(new PatternList); + f->SetFromValue(args[0], err); + if (!err->has_error()) + scope->set_sources_assignment_filter(f.Pass()); + } + return Value(); +} + +// void print(...) +// prints all arguments to the console separated by spaces. +Value ExecutePrint(const std::vector<Value>& args, Err* err) { + for (size_t i = 0; i < args.size(); i++) { + if (i != 0) + std::cout << " "; + std::cout << args[i].ToString(); + } + std::cout << std::endl; + return Value(); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +namespace functions { + +const char kAssert[] = "assert"; +const char kComponent[] = "component"; +const char kConfig[] = "config"; +const char kCopy[] = "copy"; +const char kCustom[] = "custom"; +const char kDeclareArgs[] = "declare_args"; +const char kExecScript[] = "exec_script"; +const char kExecutable[] = "executable"; +const char kGroup[] = "group"; +const char kImport[] = "import"; +const char kPrint[] = "print"; +const char kProcessFileTemplate[] = "process_file_template"; +const char kReadFile[] = "read_file"; +const char kSetDefaults[] = "set_defaults"; +const char kSetDefaultToolchain[] = "set_default_toolchain"; +const char kSetSourcesAssignmentFilter[] = "set_sources_assignment_filter"; +const char kSharedLibrary[] = "shared_library"; +const char kStaticLibrary[] = "static_library"; +const char kTemplate[] = "template"; +const char kTool[] = "tool"; +const char kToolchain[] = "toolchain"; +const char kTest[] = "test"; +const char kWriteFile[] = "write_file"; + +} // namespace functions + +// ---------------------------------------------------------------------------- + +bool EnsureNotProcessingImport(const ParseNode* node, + const Scope* scope, + Err* err) { + if (scope->IsProcessingImport()) { + *err = Err(node, "Not valid from an import.", + "We need to talk about this thing you are doing here. Doing this\n" + "kind of thing from an imported file makes me feel like you are\n" + "abusing me. Imports are for defining defaults, variables, and rules.\n" + "The appropriate place for this kind of thing is really in a normal\n" + "BUILD file."); + return false; + } + return true; +} + +bool EnsureNotProcessingBuildConfig(const ParseNode* node, + const Scope* scope, + Err* err) { + if (scope->IsProcessingBuildConfig()) { + *err = Err(node, "Not valid from the build config.", + "You can't do this kind of thing from the build config script, " + "silly!\nPut it in a regular BUILD file."); + return false; + } + return true; +} + +bool FillTargetBlockScope(const Scope* scope, + const FunctionCallNode* function, + const char* target_type, + const BlockNode* block, + const std::vector<Value>& args, + Scope* block_scope, + Err* err) { + if (!block) { + FillNeedsBlockError(function, err); + return false; + } + + // Copy the target defaults, if any, into the scope we're going to execute + // the block in. + const Scope* default_scope = scope->GetTargetDefaults(target_type); + if (default_scope) { + if (!default_scope->NonRecursiveMergeTo(block_scope, function, + "target defaults", err)) + return false; + } + + // The name is the single argument to the target function. + if (!EnsureSingleStringArg(function, args, err)) + return false; + + // Set the target name variable to the current target, and mark it used + // because we don't want to issue an error if the script ignores it. + const base::StringPiece target_name("target_name"); + block_scope->SetValue(target_name, Value(function, args[0].string_value()), + function); + block_scope->MarkUsed(target_name); + return true; +} + +bool EnsureSingleStringArg(const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (args.size() != 1) { + *err = Err(function->function(), "Incorrect arguments.", + "This function requires a single string argument."); + return false; + } + return args[0].VerifyTypeIs(Value::STRING, err); +} + +const SourceDir& SourceDirForFunctionCall(const FunctionCallNode* function) { + return function->function().location().file()->dir(); +} + +const Label& ToolchainLabelForScope(const Scope* scope) { + return scope->settings()->toolchain()->label(); +} + +Label MakeLabelForScope(const Scope* scope, + const FunctionCallNode* function, + const std::string& name) { + const SourceDir& input_dir = SourceDirForFunctionCall(function); + const Label& toolchain_label = ToolchainLabelForScope(scope); + return Label(input_dir, name, toolchain_label.dir(), toolchain_label.name()); +} + +Value ExecuteFunction(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + const Token& name = function->function(); + if (block) { + // These target generators need to execute the block themselves. + if (name.IsIdentifierEqualTo(functions::kComponent)) + return ExecuteComponent(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kCustom)) + return ExecuteCustom(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kExecutable)) + return ExecuteExecutable(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kSetDefaults)) + return ExecuteSetDefaults(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kSharedLibrary)) + return ExecuteSharedLibrary(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kStaticLibrary)) + return ExecuteStaticLibrary(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kGroup)) + return ExecuteGroup(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kTest)) + return ExecuteExecutable(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kTemplate)) + return ExecuteTemplate(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kTool)) + return ExecuteTool(scope, function, args, block, err); + if (name.IsIdentifierEqualTo(functions::kToolchain)) + return ExecuteToolchain(scope, function, args, block, err); + + const FunctionCallNode* rule = + scope->GetTemplate(function->function().value().as_string()); + if (rule) + return ExecuteTemplate(scope, function, args, block, rule, err); + + // FIXME(brettw) This is not right, what if you specify a function that + // doesn't take a block but specify one?!?!? + + // The rest of the functions can take a pre-executed block for simplicity. + Scope block_scope(scope); + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + if (name.IsIdentifierEqualTo(functions::kConfig)) + return ExecuteConfig(&block_scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kCopy)) + return ExecuteCopy(&block_scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kDeclareArgs)) + return ExecuteDeclareArgs(&block_scope, function, args, err); + + *err = Err(name, "Unknown function."); + return Value(); + } + + if (name.IsIdentifierEqualTo(functions::kAssert)) + return ExecuteAssert(function, args, err); + if (name.IsIdentifierEqualTo(functions::kExecScript)) + return ExecuteExecScript(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kImport)) + return ExecuteImport(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kPrint)) + return ExecutePrint(args, err); + if (name.IsIdentifierEqualTo(functions::kProcessFileTemplate)) + return ExecuteProcessFileTemplate(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kReadFile)) + return ExecuteReadFile(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kSetDefaultToolchain)) + return ExecuteSetDefaultToolchain(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kSetSourcesAssignmentFilter)) + return ExecuteSetSourcesAssignmentFilter(scope, function, args, err); + if (name.IsIdentifierEqualTo(functions::kWriteFile)) + return ExecuteWriteFile(scope, function, args, err); + + *err = Err(function, "Unknown function."); + return Value(); +} diff --git a/tools/gn/functions.h b/tools/gn/functions.h new file mode 100644 index 0000000..806de1d --- /dev/null +++ b/tools/gn/functions.h @@ -0,0 +1,182 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_FUNCTIONS_H_ +#define TOOLS_GN_FUNCTIONS_H_ + +#include <string> +#include <vector> + +class Err; +class BlockNode; +class FunctionCallNode; +class Label; +class ListNode; +class ParseNode; +class Scope; +class SourceDir; +class Token; +class Value; + +Value ExecuteFunction(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, // Optional. + Err* err); + +// Function executing functions ----------------------------------------------- + +Value ExecuteTemplate(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteExecScript(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); +Value ExecuteProcessFileTemplate(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); +Value ExecuteReadFile(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); +Value ExecuteSetDefaultToolchain(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); +Value ExecuteTool(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteToolchain(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteWriteFile(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); + +// Target-generating functions. +Value ExecuteComponent(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteCopy(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); +Value ExecuteCustom(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteExecutable(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteSharedLibrary(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteStaticLibrary(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); +Value ExecuteGroup(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err); + +// Helper functions ----------------------------------------------------------- + +// Verifies that the current scope is not processing an import. If it is, it +// will set the error, blame the given parse node for it, and return false. +bool EnsureNotProcessingImport(const ParseNode* node, + const Scope* scope, + Err* err); + +// Like EnsureNotProcessingImport but checks for running the build config. +bool EnsureNotProcessingBuildConfig(const ParseNode* node, + const Scope* scope, + Err* err); + +// Sets up the |block_scope| for executing a target (or something like it). +// The |scope| is the containing scope. It should have been already set as the +// parent for the |block_scope| when the |block_scope| was created. +// +// This will set up the target defaults and set the |target_name| variable in +// the block scope to the current target name, which is assumed to be the first +// argument to the function. +// +// On success, returns true. On failure, sets the error and returns false. +bool FillTargetBlockScope(const Scope* scope, + const FunctionCallNode* function, + const char* target_type, + const BlockNode* block, + const std::vector<Value>& args, + Scope* block_scope, + Err* err); + +// Validates that the given function call has one string argument. This is +// the most common function signature, so it saves space to have this helper. +// Returns false and sets the error on failure. +bool EnsureSingleStringArg(const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); + +// Returns the source directory for the file comtaining the given function +// invocation. +const SourceDir& SourceDirForFunctionCall(const FunctionCallNode* function); + +// Returns the name of the toolchain for the given scope. +const Label& ToolchainLabelForScope(const Scope* scope); + +// Generates a label for the given scope, using the current directory and +// toolchain, and the given name. +Label MakeLabelForScope(const Scope* scope, + const FunctionCallNode* function, + const std::string& name); + +// Function name constants ---------------------------------------------------- + +namespace functions { + +extern const char kAssert[]; +extern const char kComponent[]; +extern const char kConfig[]; +extern const char kCopy[]; +extern const char kCustom[]; +extern const char kDeclareArgs[]; +extern const char kExecScript[]; +extern const char kExecutable[]; +extern const char kGroup[]; +extern const char kImport[]; +extern const char kPrint[]; +extern const char kProcessFileTemplate[]; +extern const char kReadFile[]; +extern const char kSetDefaults[]; +extern const char kSetDefaultToolchain[]; +extern const char kSetSourcesAssignmentFilter[]; +extern const char kSharedLibrary[]; +extern const char kStaticLibrary[]; +extern const char kTemplate[]; +extern const char kTest[]; +extern const char kTool[]; +extern const char kToolchain[]; +extern const char kWriteFile[]; + +} // namespace functions + +#endif // TOOLS_GN_FUNCTIONS_H_ diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc new file mode 100644 index 0000000..a5991e77 --- /dev/null +++ b/tools/gn/functions_target.cc @@ -0,0 +1,221 @@ +// Copyright (c) 2013 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 "tools/gn/functions.h" + +#include "tools/gn/err.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/target_generator.h" +#include "tools/gn/value.h" + +namespace { + +Value ExecuteGenericTarget(const char* target_type, + Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + if (!EnsureNotProcessingImport(function, scope, err) || + !EnsureNotProcessingBuildConfig(function, scope, err)) + return Value(); + Scope block_scope(scope); + if (!FillTargetBlockScope(scope, function, target_type, block, + args, &block_scope, err)) + return Value(); + + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + TargetGenerator::GenerateTarget(&block_scope, function->function(), args, + target_type, err); + + block_scope.CheckForUnusedVars(err); + return Value(); +} + +} // namespace + +Value ExecuteComponent(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + // A component is either a shared or static library, depending on the value + // of |component_mode|. + const Value* component_mode_value = scope->GetValue("component_mode"); + + static const char helptext[] = + "You're declaring a component here but have not defined " + "\"component_mode\" to\neither \"shared_library\" or \"static_library\"."; + if (!component_mode_value) { + *err = Err(function->function(), "No component mode set.", helptext); + return Value(); + } + if (component_mode_value->type() != Value::STRING || + (component_mode_value->string_value() != functions::kSharedLibrary && + component_mode_value->string_value() != functions::kStaticLibrary)) { + *err = Err(function->function(), "Invalid component mode set.", helptext); + return Value(); + } + const std::string& component_mode = component_mode_value->string_value(); + + if (!EnsureNotProcessingImport(function, scope, err)) + return Value(); + Scope block_scope(scope); + if (!FillTargetBlockScope(scope, function, component_mode.c_str(), block, + args, &block_scope, err)) + return Value(); + + block->ExecuteBlockInScope(&block_scope, err); + if (err->has_error()) + return Value(); + + TargetGenerator::GenerateTarget(&block_scope, function->function(), args, + component_mode, err); + return Value(); +} + +Value ExecuteCopy(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + if (!EnsureNotProcessingImport(function, scope, err) || + !EnsureNotProcessingBuildConfig(function, scope, err)) + return Value(); + TargetGenerator::GenerateTarget(scope, function->function(), args, + functions::kCopy, err); + return Value(); +} + +/* +custom: Declare a script-generated target. + + This target type allows you to run a script over a set of sources files and + generate a set of output files. + + The script will be executed with the given arguments with the current + directory being that of the current BUILD file. + + There are two modes. The first mode is the "per-file" mode where you + specify a list of sources and the script is run once for each one as a build + rule. In this case, each file specified in the |outputs| variable must be + unique when applied to each source file (normally you would reference + "{{source_name_part}}" from within each one) or the build system will get + confused about how to build those files. You should use the |data| variable + to list all additional dependencies of your script: these will be added + as dependencies for each build step. + + The second mode is when you just want to run a script once rather than as a + general rule over a set of files. In this case you don't list any sources. + Dependencies of your script are specified only in the |data| variable and + your |outputs| variable should just list all outputs. + +Variables: + + args, data, deps, outputs, script*, sources + * = required + + There are some special substrings that will be searched for when processing + some variables: + + "{{source}}" + Expanded in |args|, this is the name of the source file relative to the + current directory when running the script. This is how you specify + the current input file to your script. + + "{{source_name_part}}" + Expanded in |args| and |outputs|, this is just the filename part of the + current source file with no directory or extension. This is how you + specify a name transoformation to the output. Normally you would + write an output as "$target_output_dir/{{source_name_part}}.o". + + All |outputs| files must be inside the output directory of the build. You + would generally use "$target_output_dir" or "$target_gen_dir" to reference + the output or generated intermediate file directories, respectively. + +Examples: + + custom("general_rule") { + script = "do_processing.py" + sources = [ "foo.idl" ] + data = [ "my_configuration.txt" ] + outputs = [ "$target_gen_dir/{{source_name_part}}.h" ] + args = [ "{{source}}", + "-o", "$relative_target_gen_dir/{{source_name_part}}.h" ] + } + + custom("just_run_this_guy_once") { + script = "doprocessing.py" + data = [ "my_configuration.txt" ] + outputs = [ "$target_gen_dir/insightful_output.txt" ] + args = [ "--output_dir", $target_gen_dir ] + } +*/ +Value ExecuteCustom(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + return ExecuteGenericTarget(functions::kCustom, scope, function, args, + block, err); +} + +Value ExecuteExecutable(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + return ExecuteGenericTarget(functions::kExecutable, scope, function, args, + block, err); +} + +Value ExecuteSharedLibrary(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + return ExecuteGenericTarget(functions::kSharedLibrary, scope, function, args, + block, err); +} + +Value ExecuteStaticLibrary(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + return ExecuteGenericTarget(functions::kStaticLibrary, scope, function, args, + block, err); +} + +/* +group: Declare a group of targets. + + This target type allows you to create meta-targets that just collect a set + of dependencies into one named target. + +Variables: + + deps + +Example: + + group("all") { + deps = [ + "//project:runner", + "//project:unit_tests", + ] + } +*/ +Value ExecuteGroup(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + BlockNode* block, + Err* err) { + return ExecuteGenericTarget(functions::kGroup, scope, function, args, + block, err); +} + diff --git a/tools/gn/generate_test_gn_data.cc b/tools/gn/generate_test_gn_data.cc new file mode 100644 index 0000000..017c4c4 --- /dev/null +++ b/tools/gn/generate_test_gn_data.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2013 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 <fstream> +#include <iostream> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" + +// Usage: just run in the directory where you want your test source root to be. + +int files_written = 0; +int targets_written = 0; + +base::FilePath UTF8ToFilePath(const std::string& s) { +#if defined(OS_WIN) + return base::FilePath(UTF8ToWide(s)); +#else + return base::FilePath(s); +#endif +} + +std::string FilePathToUTF8(const base::FilePath& path) { +#if defined(OS_WIN) + return WideToUTF8(path.value()); +#else + return path.value(); +#endif +} + +base::FilePath RepoPathToPathName(const std::vector<int>& repo_path) { + base::FilePath ret; + for (size_t i = 0; i < repo_path.size(); i++) { + ret = ret.Append(UTF8ToFilePath(base::IntToString(repo_path[i]))); + } + return ret; +} + +std::string TargetIndexToLetter(int target_index) { + char ret[2]; + ret[0] = 'a' + target_index; + ret[1] = 0; + return ret; +} + +std::string RepoPathToTargetName(const std::vector<int>& repo_path, + int target_index) { + std::string ret; + for (size_t i = 0; i < repo_path.size(); i++) { + if (i != 0) + ret.push_back('_'); + ret.append(base::IntToString(repo_path[i])); + } + ret += TargetIndexToLetter(target_index); + return ret; +} + +std::string RepoPathToFullTargetName(const std::vector<int>& repo_path, + int target_index) { + std::string ret; + for (size_t i = 0; i < repo_path.size(); i++) { + ret.push_back('/'); + ret.append(base::IntToString(repo_path[i])); + } + + ret += ":" + RepoPathToTargetName(repo_path, target_index); + return ret; +} + +void WriteLevel(const std::vector<int>& repo_path, + int spread, + int max_depth, + int targets_per_level, + int files_per_target) { + base::FilePath dirname = RepoPathToPathName(repo_path); + base::FilePath filename = dirname.AppendASCII("BUILD.gn"); + std::cout << "Writing " << FilePathToUTF8(filename) << "\n"; + + // Don't keep the file open while recursing. + { + file_util::CreateDirectory(dirname); + + std::ofstream file; + file.open(FilePathToUTF8(filename).c_str(), + std::ios_base::out | std::ios_base::binary); + files_written++; + + for (int i = 0; i < targets_per_level; i++) { + targets_written++; + file << "executable(\"" << RepoPathToTargetName(repo_path, i) + << "\") {\n"; + file << " sources = [\n"; + for (int f = 0; f < files_per_target; f++) + file << " \"" << base::IntToString(f) << ".cc\",\n"; + + if (repo_path.size() < (size_t)max_depth) { + file << " ]\n"; + file << " deps = [\n"; + for (int d = 0; d < spread; d++) { + std::vector<int> cur = repo_path; + cur.push_back(d); + for (int t = 0; t < targets_per_level; t++) + file << " \"" << RepoPathToFullTargetName(cur, t) << "\",\n"; + } + } + file << " ]\n}\n\n"; + } + } + if (repo_path.size() < (size_t)max_depth) { + // Recursively generate subdirs. + for (int i = 0; i < spread; i++) { + std::vector<int> cur = repo_path; + cur.push_back(i); + WriteLevel(cur, spread, max_depth, targets_per_level, files_per_target); + } + } +} + +int main() { + WriteLevel(std::vector<int>(), 5, 4, 3, 50); // 781 files, 2343 targets + //WriteLevel(std::vector<int>(), 6, 4, 2, 50); + std::cout << "Wrote " << files_written << " files and " + << targets_written << " targets.\n"; + return 0; +} diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp new file mode 100644 index 0000000..4904e3d --- /dev/null +++ b/tools/gn/gn.gyp @@ -0,0 +1,171 @@ +{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'gn_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ ],
+ 'sources': [
+ 'build_settings.cc',
+ 'build_settings.h',
+ 'command_desc.cc',
+ 'command_desc.h',
+ 'command_gen.cc',
+ 'command_gen.h',
+ 'commands.h',
+ 'config.cc',
+ 'config.h',
+ 'config_values.cc',
+ 'config_values.h',
+ 'config_values_extractors.cc',
+ 'config_values_extractors.h',
+ 'config_values_generator.cc',
+ 'config_values_generator.h',
+ 'err.cc',
+ 'err.h',
+ 'escape.cc',
+ 'escape.h',
+ 'file_template.cc',
+ 'file_template.h',
+ 'filesystem_utils.cc',
+ 'filesystem_utils.h',
+ 'functions_target.cc',
+ 'functions.cc',
+ 'functions.h',
+ 'function_exec_script.cc',
+ 'function_process_file_template.cc',
+ 'function_read_file.cc',
+ 'function_set_default_toolchain.cc',
+ 'function_template.cc',
+ 'function_toolchain.cc',
+ 'function_write_file.cc',
+ 'import_manager.cc',
+ 'import_manager.h',
+ 'input_conversion.cc',
+ 'input_conversion.h',
+ 'input_file.cc',
+ 'input_file.h',
+ 'input_file_manager.cc',
+ 'input_file_manager.h',
+ 'item.cc',
+ 'item.h',
+ 'item_node.cc',
+ 'item_node.h',
+ 'item_tree.cc',
+ 'item_tree.h',
+ 'label.cc',
+ 'label.h',
+ 'location.h',
+ 'ninja_build_writer.cc',
+ 'ninja_build_writer.h',
+ 'ninja_helper.cc',
+ 'ninja_helper.h',
+ 'ninja_target_writer.cc',
+ 'ninja_target_writer.h',
+ 'ninja_toolchain_writer.cc',
+ 'ninja_toolchain_writer.h',
+ 'ninja_writer.cc',
+ 'ninja_writer.h',
+ 'operators.cc',
+ 'operators.h',
+ 'output_file.h',
+ 'parse_tree.cc',
+ 'parse_tree.h',
+ 'parser.cc',
+ 'parser.h',
+ 'path_output.cc',
+ 'path_output.h',
+ 'pattern.cc',
+ 'pattern.h',
+ 'scheduler.cc',
+ 'scheduler.h',
+ 'scope.cc',
+ 'scope.h',
+ 'scope_per_file_provider.cc',
+ 'scope_per_file_provider.h',
+ 'settings.cc',
+ 'settings.h',
+ 'setup.cc',
+ 'setup.h',
+ 'source_dir.cc',
+ 'source_dir.h',
+ 'source_file.cc',
+ 'source_file.h',
+ 'standard_out.cc',
+ 'standard_out.h',
+ 'string_utils.cc',
+ 'string_utils.h',
+ 'target.cc',
+ 'target.h',
+ 'target_generator.cc',
+ 'target_generator.h',
+ 'target_manager.cc',
+ 'target_manager.h',
+ 'token.cc',
+ 'token.h',
+ 'tokenizer.cc',
+ 'tokenizer.h',
+ 'toolchain.cc',
+ 'toolchain.h',
+ 'toolchain_manager.cc',
+ 'toolchain_manager.h',
+ 'value.cc',
+ 'value.h',
+ 'value_extractors.cc',
+ 'value_extractors.h',
+ ],
+ },
+ {
+ 'target_name': 'gn',
+ 'type': 'executable',
+ 'sources': [
+ 'gn_main.cc',
+ ],
+ 'dependencies': [
+ 'gn_lib',
+ '../../base/base.gyp:base',
+ '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ ],
+ },
+ {
+ 'target_name': 'gn_unittests',
+ 'type': '<(gtest_target_type)',
+ 'sources': [
+ 'escape_unittest.cc',
+ 'file_template_unittest.cc',
+ 'filesystem_utils_unittest.cc',
+ 'input_conversion_unittest.cc',
+ 'label_unittest.cc',
+ 'ninja_helper_unittest.cc',
+ 'parser_unittest.cc',
+ 'path_output_unittest.cc',
+ 'pattern_unittest.cc',
+ 'source_dir_unittest.cc',
+ 'string_utils_unittest.cc',
+ 'target_generator_unittest.cc',
+ 'target_manager_unittest.cc',
+ 'tokenizer_unittest.cc',
+ ],
+ 'dependencies': [
+ 'gn_lib',
+ '../../base/base.gyp:run_all_unittests',
+ '../../base/base.gyp:test_support_base',
+ '../../testing/gtest.gyp:gtest',
+ ],
+ },
+ {
+ 'target_name': 'generate_test_gn_data',
+ 'type': 'executable',
+ 'sources': [
+ 'generate_test_gn_data.cc',
+ ],
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ ],
+ }
+ ],
+}
diff --git a/tools/gn/gn_main.cc b/tools/gn/gn_main.cc new file mode 100644 index 0000000..6f5b31e --- /dev/null +++ b/tools/gn/gn_main.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2013 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 "base/at_exit.h" +#include "base/command_line.h" +#include "base/strings/utf_string_conversions.h" +#include "tools/gn/commands.h" +#include "tools/gn/err.h" +#include "tools/gn/location.h" + +namespace { + +std::vector<std::string> GetArgs(const CommandLine& cmdline) { + CommandLine::StringVector in_args = cmdline.GetArgs(); +#if defined(OS_WIN) + std::vector<std::string> out_args; + for (size_t i = 0; i < in_args.size(); i++) + out_args.push_back(base::WideToUTF8(in_args[i])); + return out_args; +#else + return in_args; +#endif +} + +} // namespace + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + CommandLine::Init(argc, argv); + + std::vector<std::string> args = GetArgs(*CommandLine::ForCurrentProcess()); + + std::string command; + if (args.empty()) { + command = "gen"; + } else { + command = args[0]; + args.erase(args.begin()); + } + + int retval = 0; + if (command == "gen") { + retval = RunGenCommand(args); + } else if (command == "desc" || command == "wtf") { + retval = RunDescCommand(args); + } else if (command == "deps") { + retval = RunDepsCommand(args); + } else if (command == "tree") { + retval = RunTreeCommand(args); + } else { + Err(Location(), + "Command \"" + command + "\" unknown.").PrintToStdout(); + retval = 1; + } + + exit(retval); // Don't free memory, it can be really slow! + return retval; +} diff --git a/tools/gn/import_manager.cc b/tools/gn/import_manager.cc new file mode 100644 index 0000000..774f866 --- /dev/null +++ b/tools/gn/import_manager.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2013 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 "tools/gn/import_manager.h" + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" + +namespace { + +// Returns a newly-allocated scope on success, null on failure. +Scope* UncachedImport(const Settings* settings, + const SourceFile& file, + const ParseNode* node_for_err, + Err* err) { + const ParseNode* node = g_scheduler->input_file_manager()->SyncLoadFile( + node_for_err->GetRange(), settings->build_settings(), file, err); + if (!node) + return NULL; + const BlockNode* block = node->AsBlock(); + CHECK(block); + + scoped_ptr<Scope> scope(new Scope(settings->base_config())); + scope->SetProcessingImport(); + block->ExecuteBlockInScope(scope.get(), err); + if (err->has_error()) + return NULL; + scope->ClearProcessingImport(); + + return scope.release(); +} + +} // namesapce + +ImportManager::ImportManager() { +} + +ImportManager::~ImportManager() { + STLDeleteContainerPairSecondPointers(imports_.begin(), imports_.end()); +} + +bool ImportManager::DoImport(const SourceFile& file, + const ParseNode* node_for_err, + Scope* scope, + Err* err) { + // See if we have a cached import, but be careful to actually do the scope + // copying outside of the lock. + const Scope* imported_scope = NULL; + { + base::AutoLock lock(lock_); + ImportMap::const_iterator found = imports_.find(file); + if (found != imports_.end()) + imported_scope = found->second; + } + + if (!imported_scope) { + // Do a new import of the file. + imported_scope = UncachedImport(scope->settings(), file, + node_for_err, err); + if (!imported_scope) + return false; + + // We loaded the file outside the lock. This means that there could be a + // race and the file was already loaded on a background thread. Recover + // from this and use the existing one if that happens. + { + base::AutoLock lock(lock_); + ImportMap::const_iterator found = imports_.find(file); + if (found != imports_.end()) { + delete imported_scope; + imported_scope = found->second; + } else { + imports_[file] = imported_scope; + } + } + } + + return imported_scope->NonRecursiveMergeTo(scope, node_for_err, + "import", err); +} diff --git a/tools/gn/import_manager.h b/tools/gn/import_manager.h new file mode 100644 index 0000000..725a5b2 --- /dev/null +++ b/tools/gn/import_manager.h @@ -0,0 +1,41 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_IMPORT_MANAGER_H_ +#define TOOLS_GN_IMPORT_MANAGER_H_ + +#include <map> + +#include "base/synchronization/lock.h" + +class Err; +class ParseNode; +class Scope; +class SourceFile; + +// Provides a cache of the results of importing scopes so the results can +// be re-used rather than running the imported files multiple times. +class ImportManager { + public: + ImportManager(); + ~ImportManager(); + + // Does an import of the given file into the given scope. On error, sets the + // error and returns false. + bool DoImport(const SourceFile& file, + const ParseNode* node_for_err, + Scope* scope, + Err* err); + + private: + base::Lock lock_; + + // Owning pointers to the scopes. + typedef std::map<SourceFile, const Scope*> ImportMap; + ImportMap imports_; + + DISALLOW_COPY_AND_ASSIGN(ImportManager); +}; + +#endif // TOOLS_GN_IMPORT_MANAGER_H_ diff --git a/tools/gn/input_conversion.cc b/tools/gn/input_conversion.cc new file mode 100644 index 0000000..c0a0685 --- /dev/null +++ b/tools/gn/input_conversion.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2013 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 "input_conversion.h" + +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/err.h" +#include "tools/gn/input_file.h" +#include "tools/gn/label.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/parser.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/tokenizer.h" +#include "tools/gn/value.h" + +namespace { + +// Returns the "first bit" of some script output for writing to error messages. +std::string GetExampleOfBadInput(const std::string& input) { + std::string result(input); + + // Maybe the result starts with a blank line or something, which we don't + // want. + TrimWhitespaceASCII(result, TRIM_ALL, &result); + + // Now take the first line, or the first set of chars, whichever is shorter. + bool trimmed = false; + size_t newline_offset = result.find('\n'); + if (newline_offset != std::string::npos) { + trimmed = true; + result.resize(newline_offset); + } + TrimWhitespaceASCII(result, TRIM_ALL, &result); + + const int kMaxSize = 50; + if (result.size() > kMaxSize) { + trimmed = true; + result.resize(kMaxSize); + } + + if (trimmed) + result.append("..."); + return result; +} + +// When parsing the result as a value, we may get various types of errors. +// This creates an error message for this case with an optional nested error +// message to reference. If there is no nested err, pass Err(). +// +// This code also takes care to rewrite the original error which will reference +// the temporary InputFile which won't exist when the error is propogated +// out to a higher level. +Err MakeParseErr(const std::string& input, + const ParseNode* origin, + const Err& nested) { + std::string help_text = + "When parsing a result as a \"value\" it should look like a list:\n" + " [ \"a\", \"b\", 5 ]\n" + "or a single literal:\n" + " \"my result\"\n" + "but instead I got this, which I find very confusing:\n"; + help_text.append(input); + if (nested.has_error()) + help_text.append("\nThe exact error was:"); + + Err result(origin, "Script result wasn't a valid value.", help_text); + if (nested.has_error()) { + result.AppendSubErr(Err(LocationRange(), nested.message(), + nested.help_text())); + } + return result; +} + +// Sets the origin of the value and any nested values with the given node. +void RecursivelySetOrigin(Value* value, const ParseNode* origin) { + value->set_origin(origin); + if (value->type() == Value::LIST) { + std::vector<Value>& list_value = value->list_value(); + for (size_t i = 0; i < list_value.size(); i++) + RecursivelySetOrigin(&list_value[i], origin); + } +} + +Value ParseString(const std::string& input, + const ParseNode* origin, + Err* err) { + SourceFile empty_source_for_most_vexing_parse; + InputFile input_file(empty_source_for_most_vexing_parse); + input_file.SetContents(input); + + std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, err); + if (err->has_error()) { + *err = MakeParseErr(input, origin, *err); + return Value(); + } + + scoped_ptr<ParseNode> expression = Parser::ParseExpression(tokens, err); + if (err->has_error()) { + *err = MakeParseErr(input, origin, *err); + return Value(); + } + + // It's valid for the result to be a null pointer, this just means that the + // script returned nothing. + if (!expression) + return Value(); + + // The result should either be a list or a literal, anything else is + // invalid. + if (!expression->AsList() && !expression->AsLiteral()) { + *err = MakeParseErr(input, origin, Err()); + return Value(); + } + + BuildSettings build_settings; + Label empty_label; + Toolchain toolchain(empty_label); + Settings settings(&build_settings, &toolchain, std::string()); + Scope scope(&settings); + + Err nested_err; + Value result = expression->Execute(&scope, &nested_err); + if (nested_err.has_error()) { + *err = MakeParseErr(input, origin, nested_err); + return Value(); + } + + // The returned value will have references to the temporary parse nodes we + // made on the stack. If the values are used in an error message in the + // future, this will crash. Reset the origin of all values to be our + // containing origin. + RecursivelySetOrigin(&result, origin); + return result; +} + +Value ParseList(const std::string& input, + const ParseNode* origin, + Err* err) { + Value ret(origin, Value::LIST); + std::vector<std::string> as_lines; + base::SplitString(input, '\n', &as_lines); + + // Trim empty lines from the end. + // Do we want to make this configurable? + while (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) + as_lines.resize(as_lines.size() - 1); + + ret.list_value().reserve(as_lines.size()); + for (size_t i = 0; i < as_lines.size(); i++) + ret.list_value().push_back(Value(origin, as_lines[i])); + return ret; +} + +} // namespace + +/* +input_conversion: Specifies how to transform input to a variable. + + input_conversion is an argument to read_file and exec_script that specifies + how the result of the read operation should be converted into a variable. + + "list lines": + Return the file contents as a list, with a string for each line. The + newlines will not be present in the result. Empty newlines will be + trimmed from the trailing end of the returned list. + + "value": + Parse the input as if it was a literal rvalue in a buildfile. + Examples of typical program output using this mode: + [ "foo", "bar" ] (result will be a list) + or + "foo bar" (result will be a string) + or + 5 (result will be an integer) + + Note that if the input is empty, the result will be a null value which + will produce an error if assigned to a variable. + + "string": + Return the file contents into a single string. +*/ + +Value ConvertInputToValue(const std::string& input, + const ParseNode* origin, + const Value& input_conversion_value, + Err* err) { + if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) + return Value(); + const std::string& input_conversion = input_conversion_value.string_value(); + + if (input_conversion == "value") + return ParseString(input, origin, err); + if (input_conversion == "string") + return Value(origin, input); + if (input_conversion == "list lines") + return ParseList(input, origin, err); + + *err = Err(input_conversion_value, "Not a valid read file mode.", + "Have you considered a career in retail?"); + return Value(); +} diff --git a/tools/gn/input_conversion.h b/tools/gn/input_conversion.h new file mode 100644 index 0000000..d15e513 --- /dev/null +++ b/tools/gn/input_conversion.h @@ -0,0 +1,26 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_INPUT_CONVERSION_H_ +#define TOOLS_GN_INPUT_CONVERSION_H_ + +#include <string> + +class Err; +class ParseNode; +class Value; + +// Converts the given input string (is read from a file or output from a +// script) to a Value. Conversions as specified in the input_conversion string +// will be performed. The given origin will be used for constructing the +// resulting Value. +// +// If the conversion string is invalid, the error will be set and an empty +// value will be returned. +Value ConvertInputToValue(const std::string& input, + const ParseNode* origin, + const Value& input_conversion_value, + Err* err); + +#endif // TOOLS_GN_INPUT_CONVERSION_H_ diff --git a/tools/gn/input_conversion_unittest.cc b/tools/gn/input_conversion_unittest.cc new file mode 100644 index 0000000..59c0123 --- /dev/null +++ b/tools/gn/input_conversion_unittest.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2013 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/err.h" +#include "tools/gn/input_conversion.h" +#include "tools/gn/value.h" + +TEST(InputConversion, String) { + Err err; + std::string input("\nfoo bar \n"); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "string"), &err); + EXPECT_FALSE(err.has_error()); + EXPECT_EQ(Value::STRING, result.type()); + EXPECT_EQ(input, result.string_value()); +} + +TEST(InputConversion, ListLines) { + Err err; + std::string input("\nfoo\nbar \n"); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "list lines"), + &err); + EXPECT_FALSE(err.has_error()); + EXPECT_EQ(Value::LIST, result.type()); + ASSERT_EQ(3u, result.list_value().size()); + EXPECT_EQ("", result.list_value()[0].string_value()); + EXPECT_EQ("foo", result.list_value()[1].string_value()); + EXPECT_EQ("bar", result.list_value()[2].string_value()); +} + +TEST(InputConversion, ValueString) { + Err err; + std::string input("\"str\""); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_FALSE(err.has_error()); + EXPECT_EQ(Value::STRING, result.type()); + EXPECT_EQ("str", result.string_value()); +} + +TEST(InputConversion, ValueInt) { + Err err; + std::string input("\n\n 6 \n "); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_FALSE(err.has_error()); + EXPECT_EQ(Value::INTEGER, result.type()); + EXPECT_EQ(6, result.int_value()); +} + +TEST(InputConversion, ValueList) { + Err err; + std::string input("\n [ \"a\", 5]"); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_FALSE(err.has_error()); + ASSERT_EQ(Value::LIST, result.type()); + ASSERT_EQ(2u, result.list_value().size()); + EXPECT_EQ("a", result.list_value()[0].string_value()); + EXPECT_EQ(5, result.list_value()[1].int_value()); +} + +TEST(InputConversion, ValueEmpty) { + Err err; + ConvertInputToValue("", NULL, Value(NULL, "value"), &err); +} + +TEST(InputConversion, ValueError) { + Err err; + std::string input("\n [ \"a\", 5\nfoo bar"); + Value result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_TRUE(err.has_error()); + + // Blocks not allowed. + input = "{ foo = 5 }"; + result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_TRUE(err.has_error()); + + // Function calls not allowed. + input = "print(5)"; + result = ConvertInputToValue(input, NULL, Value(NULL, "value"), &err); + EXPECT_TRUE(err.has_error()); +} diff --git a/tools/gn/input_file.cc b/tools/gn/input_file.cc new file mode 100644 index 0000000..b2c3c0b --- /dev/null +++ b/tools/gn/input_file.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2013 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 "tools/gn/input_file.h" + +#include "base/file_util.h" + +InputFile::InputFile(const SourceFile& name) + : name_(name), + dir_(name_.GetDir()), + contents_loaded_(false) { +} + +InputFile::~InputFile() { +} + +void InputFile::SetContents(const std::string& c) { + contents_loaded_ = true; + contents_ = c; +} + +bool InputFile::Load(const base::FilePath& system_path) { + if (file_util::ReadFileToString(system_path, &contents_)) { + contents_loaded_ = true; + return true; + } + return false; +} + diff --git a/tools/gn/input_file.h b/tools/gn/input_file.h new file mode 100644 index 0000000..66cf55c --- /dev/null +++ b/tools/gn/input_file.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_INPUT_FILE_H_ +#define TOOLS_GN_INPUT_FILE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" + +class InputFile { + public: + InputFile(const SourceFile& name); + + // Constructor for testing. Uses an empty file path and a given contents. + //InputFile(const char* contents); + ~InputFile(); + + const SourceFile& name() const { return name_; } + + // The directory is just a cached version of name_->GetDir() but we get this + // a lot so computing it once up front saves a bunch of work. + const SourceDir& dir() const { return dir_; } + + const std::string& contents() const { + DCHECK(contents_loaded_); + return contents_; + } + + // For testing and in cases where this input doesn't actually refer to + // "a file". + void SetContents(const std::string& c); + + // Loads the given file synchronously, returning true on success. This + bool Load(const base::FilePath& system_path); + + private: + SourceFile name_; + SourceDir dir_; + + bool contents_loaded_; + std::string contents_; + + DISALLOW_COPY_AND_ASSIGN(InputFile); +}; + +#endif // TOOLS_GN_INPUT_FILE_H_ diff --git a/tools/gn/input_file_manager.cc b/tools/gn/input_file_manager.cc new file mode 100644 index 0000000..e840ba0 --- /dev/null +++ b/tools/gn/input_file_manager.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2013 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 "tools/gn/input_file_manager.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/parser.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope_per_file_provider.h" +#include "tools/gn/tokenizer.h" + +namespace { + +void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb, + const ParseNode* node) { + cb.Run(node); +} + +} // namespace + +InputFileManager::InputFileData::InputFileData(const SourceFile& file_name) + : file(file_name), + loaded(false), + sync_invocation(false) { +} + +InputFileManager::InputFileData::~InputFileData() { +} + +InputFileManager::InputFileManager() { +} + +InputFileManager::~InputFileManager() { + // Should be single-threaded by now. + STLDeleteContainerPairSecondPointers(input_files_.begin(), + input_files_.end()); +} + +bool InputFileManager::AsyncLoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& file_name, + const FileLoadCallback& callback, + Err* err) { + // Try not to schedule callbacks while holding the lock. All cases that don't + // want to schedule should return early. Otherwise, this will be scheduled + // after we leave the lock. + base::Closure schedule_this; + { + base::AutoLock lock(lock_); + + InputFileMap::const_iterator found = input_files_.find(file_name); + if (found == input_files_.end()) { + // New file, schedule load. + InputFileData* data = new InputFileData(file_name); + data->scheduled_callbacks.push_back(callback); + input_files_[file_name] = data; + + schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile, + this, + origin, + build_settings, + file_name, + &data->file); + } else { + InputFileData* data = found->second; + + // Prevent mixing async and sync loads. See SyncLoadFile for discussion. + if (data->sync_invocation) { + g_scheduler->FailWithError(Err( + origin, "Load type mismatch.", + "The file \"" + file_name.value() + "\" was previously loaded\n" + "synchronously (via an import) and now you're trying to load it " + "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: " + "a single input file must\nbe loaded the same way each time to " + "avoid blowing my tiny, tiny mind.")); + return false; + } + + if (data->loaded) { + // Can just directly issue the callback on the background thread. + schedule_this = base::Bind(&InvokeFileLoadCallback, callback, + data->parsed_root.get()); + } else { + // Load is pending on this file, schedule the invoke. + data->scheduled_callbacks.push_back(callback); + return true; + } + } + } + g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, schedule_this, + base::SequencedWorkerPool::BLOCK_SHUTDOWN); + return true; +} + +const ParseNode* InputFileManager::SyncLoadFile( + const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& file_name, + Err* err) { + base::AutoLock lock(lock_); + + InputFileData* data = NULL; + InputFileMap::iterator found = input_files_.find(file_name); + if (found == input_files_.end()) { + base::AutoUnlock unlock(lock_); + + // Haven't seen this file yet, start loading right now. + data = new InputFileData(file_name); + data->sync_invocation = true; + input_files_[file_name] = data; + + if (!LoadFile(origin, build_settings, file_name, &data->file, err)) + return NULL; + } else { + // This file has either been loaded or is pending loading. + data = found->second; + + if (!data->sync_invocation) { + // Don't allow mixing of sync and async loads. If an async load is + // scheduled and then a bunch of threads need to load it synchronously + // and block on it loading, it could deadlock or at least cause a lot + // of wasted CPU while those threads wait for the load to complete (which + // may be far back in the input queue). + // + // We could work around this by promoting the load to a sync load. This + // requires a bunch of extra code to either check flags and likely do + // extra locking (bad) or to just do both types of load on the file and + // deal with the race condition. + // + // I have no practical way to test this, and generally we should have + // all include files processed synchronously and all build files + // processed asynchronously, so it doesn't happen in practice. + *err = Err( + origin, "Load type mismatch.", + "The file \"" + file_name.value() + "\" was previously loaded\n" + "asynchronously (via a deps rule) and now you're trying to load it " + "synchronously.\nThis is a class 2 misdemeanor: a single input file " + "must be loaded the same way\neach time to avoid blowing my tiny, " + "tiny mind."); + return NULL; + } + + if (!data->loaded) { + // Wait for the already-pending sync load to complete. + if (!data->completion_event) + data->completion_event.reset(new base::WaitableEvent(false, false)); + { + base::AutoUnlock unlock(lock_); + data->completion_event->Wait(); + } + } + } + + // The other load could have failed. In this case that error will be printed + // to the console, but we need to return something here, so make up a + // dummy error. + if (!data->parsed_root) + *err = Err(origin, "File parse failed"); + return data->parsed_root.get(); +} + +int InputFileManager::GetInputFileCount() const { + base::AutoLock lock(lock_); + return input_files_.size(); +} + +void InputFileManager::GetAllInputFileNames( + std::vector<SourceFile>* result) const { + base::AutoLock lock(lock_); + result->reserve(input_files_.size()); + for (InputFileMap::const_iterator i = input_files_.begin(); + i != input_files_.end(); ++i) { + result->push_back(i->second->file.name()); + } +} + +void InputFileManager::BackgroundLoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& name, + InputFile* file) { + Err err; + if (!LoadFile(origin, build_settings, name, file, &err)) + g_scheduler->FailWithError(err); +} + +bool InputFileManager::LoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& name, + InputFile* file, + Err* err) { + // Do all of this stuff outside the lock. We should not give out file + // pointers until the read is complete. + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Loading", name.value()); + + // Read. + base::FilePath primary_path = build_settings->GetFullPath(name); + if (!file->Load(primary_path)) { + if (!build_settings->secondary_source_path().empty()) { + // Fall back to secondary source tree. + base::FilePath secondary_path = + build_settings->GetFullPathSecondary(name); + if (!file->Load(secondary_path)) { + *err = Err(origin, "Can't load input file.", + "Unable to load either \n" + + FilePathToUTF8(primary_path) + " or \n" + + FilePathToUTF8(secondary_path)); + return false; + } + } else { + *err = Err(origin, + "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); + return false; + } + } + + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Parsing", name.value()); + + // Tokenize. + std::vector<Token> tokens = Tokenizer::Tokenize(file, err); + if (err->has_error()) + return false; + + // Parse. + scoped_ptr<ParseNode> root = Parser::Parse(tokens, err); + if (err->has_error()) + return false; + ParseNode* unowned_root = root.get(); + + std::vector<FileLoadCallback> callbacks; + { + base::AutoLock lock(lock_); + DCHECK(input_files_.find(name) != input_files_.end()); + + InputFileData* data = input_files_[name]; + data->loaded = true; + data->tokens.swap(tokens); + data->parsed_root = root.Pass(); + + callbacks.swap(data->scheduled_callbacks); + } + + // Run pending invocations. Theoretically we could schedule each of these + // separately to get some parallelism. But normally there will only be one + // item in the list, so that's extra overhead and complexity for no gain. + for (size_t i = 0; i < callbacks.size(); i++) + callbacks[i].Run(unowned_root); + return true; +} diff --git a/tools/gn/input_file_manager.h b/tools/gn/input_file_manager.h new file mode 100644 index 0000000..0f708d5 --- /dev/null +++ b/tools/gn/input_file_manager.h @@ -0,0 +1,123 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_INPUT_FILE_MANAGER_H_ +#define TOOLS_GN_INPUT_FILE_MANAGER_H_ + +#include <set> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/settings.h" + +class Err; +class LocationRange; +class ParseNode; +class Token; + +// Manages loading and parsing files from disk. This doesn't actually have +// any context for executing the results, so potentially multiple configs +// could use the same input file (saving parsing). +// +// This class is threadsafe. +// +// InputFile objects must never be deleted while the program is running since +// various state points into them. +class InputFileManager : public base::RefCountedThreadSafe<InputFileManager> { + public: + // Callback issued when a file is laoded. On auccess, the parse node will + // refer to the root block of the file. On failure, this will be NULL. + typedef base::Callback<void(const ParseNode*)> FileLoadCallback; + + InputFileManager(); + + // Loads the given file and executes the callback on the worker pool. + // + // There are two types of errors. For errors known synchronously, the error + // will be set, it will return false, and no work will be scheduled. + // + // For parse errors and such that happen in the future, the error will be + // logged to the scheduler and the callback will be invoked with a null + // ParseNode pointer. The given |origin| will be blamed for the invocation. + bool AsyncLoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& file_name, + const FileLoadCallback& callback, + Err* err); + + // Loads and parses the given file synchronously, returning the root block + // corresponding to the parsed result. On error, return NULL and the given + // Err is set. + const ParseNode* SyncLoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& file_name, + Err* err); + + int GetInputFileCount() const; + + void GetAllInputFileNames(std::vector<SourceFile>* result) const; + + private: + friend class base::RefCountedThreadSafe<InputFileManager>; + + struct InputFileData { + InputFileData(const SourceFile& file_name); + ~InputFileData(); + + // Don't touch this outside the lock until it's marked loaded. + InputFile file; + + bool loaded; + + bool sync_invocation; + + // Lists all invocations that need to be executed when the file completes + // loading. + std::vector<FileLoadCallback> scheduled_callbacks; + + // Event to signal when the load is complete (or fails). This is lazily + // created only when a thread is synchronously waiting for this load (which + // only happens for imports). + scoped_ptr<base::WaitableEvent> completion_event; + + std::vector<Token> tokens; + + // Null before the file is loaded or if loading failed. + scoped_ptr<ParseNode> parsed_root; + }; + + virtual ~InputFileManager(); + + void BackgroundLoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& name, + InputFile* file); + + // Loads the given file. On error, sets the Err and return false. + bool LoadFile(const LocationRange& origin, + const BuildSettings* build_settings, + const SourceFile& name, + InputFile* file, + Err* err); + + mutable base::Lock lock_; + + // Maps repo-relative filenames to the corresponding owned pointer. + typedef base::hash_map<SourceFile, InputFileData*> InputFileMap; + InputFileMap input_files_; + + DISALLOW_COPY_AND_ASSIGN(InputFileManager); +}; + +#endif // TOOLS_GN_INPUT_FILE_MANAGER_H_ diff --git a/tools/gn/item.cc b/tools/gn/item.cc new file mode 100644 index 0000000..747183cb --- /dev/null +++ b/tools/gn/item.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2013 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 "tools/gn/item.h" + +#include "base/logging.h" + +Item::Item(const Label& label) : label_(label) { +} + +Item::~Item() { +} + +Config* Item::AsConfig() { return NULL; } +const Config* Item::AsConfig() const { return NULL; } +Target* Item::AsTarget() { return NULL; } +const Target* Item::AsTarget() const { return NULL; } +Toolchain* Item::AsToolchain() { return NULL; } +const Toolchain* Item::AsToolchain() const { return NULL; } + +std::string Item::GetItemTypeName() const { + if (AsConfig()) + return "config"; + if (AsTarget()) + return "target"; + if (AsToolchain()) + return "toolchain"; + NOTREACHED(); + return "this thing that I have no idea what it is"; +} diff --git a/tools/gn/item.h b/tools/gn/item.h new file mode 100644 index 0000000..aa71544 --- /dev/null +++ b/tools/gn/item.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_ITEM_H_ +#define TOOLS_GN_ITEM_H_ + +#include <string> + +#include "tools/gn/label.h" + +class Config; +class Target; +class Toolchain; + +// A named item (target, config, etc.) that participates in the dependency +// graph. +class Item { + public: + Item(const Label& label); + virtual ~Item(); + + const Label& label() const { return label_; } + + // Manual RTTI. + virtual Config* AsConfig(); + virtual const Config* AsConfig() const; + virtual Target* AsTarget(); + virtual const Target* AsTarget() const; + virtual Toolchain* AsToolchain(); + virtual const Toolchain* AsToolchain() const; + + // Returns a name like "target" or "config" for the type of item this is, to + // be used in logging and error messages. + std::string GetItemTypeName() const; + + // Called when this item is resolved, meaning it and all of its dependents + // have no unresolved deps. + virtual void OnResolved() {} + + private: + Label label_; +}; + +#endif // TOOLS_GN_ITEM_H_ diff --git a/tools/gn/item_node.cc b/tools/gn/item_node.cc new file mode 100644 index 0000000..776a126 --- /dev/null +++ b/tools/gn/item_node.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2013 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 "tools/gn/item_node.h" + +#include <algorithm> + +#include "base/callback.h" +#include "base/logging.h" +#include "tools/gn/item.h" + +ItemNode::ItemNode(Item* i) + : state_(REFERENCED), + item_(i) { +} + +ItemNode::~ItemNode() { +} + +void ItemNode::AddDependency(ItemNode* node) { + if (direct_dependencies_.find(node) != direct_dependencies_.end()) + return; // Already have this dep. + direct_dependencies_.insert(node); + + if (node->state() != RESOLVED) { + // Wire up the pending resolution info. + unresolved_dependencies_.insert(node); + node->waiting_on_resolution_.insert(this); + } +} + +void ItemNode::MarkDirectDependencyResolved(ItemNode* node) { + DCHECK(unresolved_dependencies_.find(node) != unresolved_dependencies_.end()); + unresolved_dependencies_.erase(node); +} + +void ItemNode::SwapOutWaitingDependencySet(ItemNodeSet* out_set) { + waiting_on_resolution_.swap(*out_set); +} + +void ItemNode::SetGenerated() { + state_ = GENERATED; +} + +void ItemNode::SetResolved() { + state_ = RESOLVED; + + if (!resolved_closure_.is_null()) + resolved_closure_.Run(); +} diff --git a/tools/gn/item_node.h b/tools/gn/item_node.h new file mode 100644 index 0000000..297010f --- /dev/null +++ b/tools/gn/item_node.h @@ -0,0 +1,119 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_ITEM_NODE_H_ +#define TOOLS_GN_ITEM_NODE_H_ + +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/location.h" + +class Item; + +// Represents a node in the depdency tree. It references an Item which is +// the actual thing. +// +// The items and nodes are split apart so that the ItemTree can manipulate +// the dependencies one one thread while the Item itself is been modified on +// another. +class ItemNode { + public: + enum State { + // Another item has referenced this one by name, but we have not yet + // encountered this item to know what it is. + REFERENCED, + + // This item has been defined but some of the dependencies it references + // have not been. + GENERATED, + + // All of this item's transitive dependencies have been found and + // resolved. + RESOLVED, + }; + + typedef std::set<ItemNode*> ItemNodeSet; + + // Takes ownership of the pointer. + // Initial state will be REFERENCED. + ItemNode(Item* i); + ~ItemNode(); + + State state() const { return state_; } + + // This closure will be executed when the item is resolved. + void set_resolved_closure(const base::Closure& closure) { + resolved_closure_ = closure; + } + + const Item* item() const { return item_.get(); } + Item* item() { return item_.get(); } + + // Where this was created from, which might be when it was generated or + // when it was first referenced from another target. + const LocationRange& originally_referenced_from_here() const { + return originally_referenced_from_here_; + } + void set_originally_referenced_from_here(const LocationRange& r) { + originally_referenced_from_here_ = r; + } + + // Where this was generated from. This will be empty for items that have + // been referenced but not generated. Note that this has to be one the + // ItemNode because it can be changing from multiple threads and we need + // to be sure that access is serialized. + const LocationRange& generated_from_here() const { + return generated_from_here_; + } + void set_generated_from_here(const LocationRange& r) { + generated_from_here_ = r; + } + + const ItemNodeSet& direct_dependencies() const { + return direct_dependencies_; + } + const ItemNodeSet& unresolved_dependencies() const { + return unresolved_dependencies_; + } + + void AddDependency(ItemNode* node); + + // Removes the given dependency from the unresolved list. Does not do + // anything else to update waiters. + void MarkDirectDependencyResolved(ItemNode* node); + + // Destructively retrieve the set of waiting nodes. + void SwapOutWaitingDependencySet(ItemNodeSet* out_set); + + void SetGenerated(); + void SetResolved(); + + private: + State state_; + scoped_ptr<Item> item_; + + LocationRange originally_referenced_from_here_; + LocationRange generated_from_here_; + + // What to run when this item is resolved. + base::Closure resolved_closure_; + + // Everything this item directly depends on. + ItemNodeSet direct_dependencies_; + + // Unresolved things this item directly depends on. + ItemNodeSet unresolved_dependencies_; + + // These items are waiting on us to be resolved before they can be + // resolved. This is the backpointer for unresolved_dependencies_. + ItemNodeSet waiting_on_resolution_; + + DISALLOW_COPY_AND_ASSIGN(ItemNode); +}; + +#endif // TOOLS_GN_ITEM_NODE_H_ diff --git a/tools/gn/item_tree.cc b/tools/gn/item_tree.cc new file mode 100644 index 0000000..a4d1181 --- /dev/null +++ b/tools/gn/item_tree.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2013 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 "tools/gn/item_tree.h" + +#include <algorithm> + +#include "base/stl_util.h" +#include "tools/gn/err.h" +#include "tools/gn/item.h" +#include "tools/gn/item_node.h" + +namespace { + +// Recursively looks in the tree for a given node, returning true if it +// was found in the dependecy graph. This is used to see if a given node +// participates in a cycle. +// +// Note that "look_for" and "search_in" will be the same node when starting the +// search, so we don't want to return true in that case. +// +// If a cycle is found, the return value will be true and the cycle vector will +// be filled with the path (in reverse order). +bool RecursiveFindCycle(const ItemNode* look_for, + const ItemNode* search_in, + std::vector<const ItemNode*>* cycle) { + const ItemNode::ItemNodeSet& unresolved = + search_in->unresolved_dependencies(); + for (ItemNode::ItemNodeSet::const_iterator i = unresolved.begin(); + i != unresolved.end(); ++i) { + if (*i == look_for) { + cycle->push_back(*i); + return true; + } + + if (RecursiveFindCycle(look_for, *i, cycle)) { + // Found a cycle inside this one, record our path and return. + cycle->push_back(*i); + return true; + } + } + return false; +} + +} // namespace + +ItemTree::ItemTree() { +} + +ItemTree::~ItemTree() { + STLDeleteContainerPairSecondPointers(items_.begin(), items_.end()); +} + +ItemNode* ItemTree::GetExistingNodeLocked(const Label& label) { + lock_.AssertAcquired(); + StringToNodeHash::iterator found = items_.find(label); + if (found == items_.end()) + return NULL; + return found->second; +} + +void ItemTree::AddNodeLocked(ItemNode* node) { + lock_.AssertAcquired(); + DCHECK(items_.find(node->item()->label()) == items_.end()); + items_[node->item()->label()] = node; +} + +Err ItemTree::MarkItemGeneratedLocked(const Label& label) { + lock_.AssertAcquired(); + DCHECK(items_.find(label) != items_.end()); + + ItemNode* node = items_[label]; + + if (!node->unresolved_dependencies().empty()) { + // Still some pending dependencies, wait for those to be resolved. + node->SetGenerated(); + return Err(); + } + return MarkItemResolvedLocked(node); +} + +void ItemTree::GetAllItemsLocked(std::vector<const Item*>* dest) const { + lock_.AssertAcquired(); + dest->reserve(items_.size()); + for (StringToNodeHash::const_iterator i = items_.begin(); + i != items_.end(); ++i) { + dest->push_back(i->second->item()); + } +} + +Err ItemTree::CheckForBadItems() const { + base::AutoLock lock(lock_); + + // Look for errors where we find a GENERATED node that refers to a REFERENCED + // one. There may be other nodes depending on the GENERATED one, but listing + // all of those isn't helpful, we want to find the broken link. + // + // This finds normal "missing dependency" errors but does not find circular + // dependencies because in this case all items in the cycle will be GENERATED + // but none will be resolved. If this happens, we'll check explicitly for + // that below. + std::vector<const ItemNode*> bad_nodes; + std::string depstring; + for (StringToNodeHash::const_iterator i = items_.begin(); + i != items_.end(); ++i) { + const ItemNode* src = i->second; + + if (src->state() == ItemNode::GENERATED) { + bad_nodes.push_back(src); + + // Check dependencies. + for (ItemNode::ItemNodeSet::const_iterator dest = + src->unresolved_dependencies().begin(); + dest != src->unresolved_dependencies().end(); + ++dest) { + if ((*dest)->state() == ItemNode::REFERENCED) { + depstring += "\"" + src->item()->label().GetUserVisibleName(false) + + "\" needs " + (*dest)->item()->GetItemTypeName() + + " \"" + (*dest)->item()->label().GetUserVisibleName(false) + + "\"\n"; + } + } + } + } + + if (!bad_nodes.empty() && depstring.empty()) { + // Our logic above found a bad node but didn't identify the problem. This + // normally means a circular dependency. + depstring = CheckForCircularDependenciesLocked(bad_nodes); + if (depstring.empty()) { + // Something's very wrong, just dump out the bad nodes. + depstring = "I have no idea what went wrong, but these are unresolved, " + "possible due to an\ninternal error:"; + for (size_t i = 0; i < bad_nodes.size(); i++) { + depstring += "\n\"" + + bad_nodes[i]->item()->label().GetUserVisibleName(false) + "\""; + } + } + } + + if (depstring.empty()) + return Err(); + return Err(Location(), "Unresolved dependencies.", depstring); +} + +Err ItemTree::MarkItemResolvedLocked(ItemNode* node) { + node->SetResolved(); + node->item()->OnResolved(); + + // Now update our waiters, pushing the "resolved" bit. + ItemNode::ItemNodeSet waiting; + node->SwapOutWaitingDependencySet(&waiting); + for (ItemNode::ItemNodeSet::iterator i = waiting.begin(); + i != waiting.end(); ++i) { + ItemNode* waiter = *i; + + // Our node should be unresolved in the waiter. + DCHECK(waiter->unresolved_dependencies().find(node) != + waiter->unresolved_dependencies().end()); + waiter->MarkDirectDependencyResolved(node); + + // Recursively mark nodes as resolved. + if (waiter->state() == ItemNode::GENERATED && + waiter->unresolved_dependencies().empty()) { + Err err = MarkItemResolvedLocked(waiter); + if (err.has_error()) + return err; + } + } + + return Err(); +} + +std::string ItemTree::CheckForCircularDependenciesLocked( + const std::vector<const ItemNode*>& bad_nodes) const { + std::vector<const ItemNode*> cycle; + if (!RecursiveFindCycle(bad_nodes[0], bad_nodes[0], &cycle)) + return std::string(); // Didn't find a cycle, something else is wrong. + + cycle.push_back(bad_nodes[0]); + std::string ret = "There is a dependency cycle:"; + + // Walk backwards since the dependency arrows point in the reverse direction. + for (int i = static_cast<int>(cycle.size()) - 1; i >= 0; i--) { + ret += "\n \"" + cycle[i]->item()->label().GetUserVisibleName(false) + + "\""; + if (i != 0) + ret += " ->"; + } + + return ret; +} diff --git a/tools/gn/item_tree.h b/tools/gn/item_tree.h new file mode 100644 index 0000000..f2ac4ae --- /dev/null +++ b/tools/gn/item_tree.h @@ -0,0 +1,69 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_ITEM_TREE_H_ +#define TOOLS_GN_ITEM_TREE_H_ + +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "tools/gn/label.h" + +class Err; +class Item; +class ItemNode; + +// Represents the full dependency tree if labeled items in the system. +// Generally you will interact with this through the target manager, etc. +class ItemTree { + public: + ItemTree(); + ~ItemTree(); + + // This lock must be held when calling the "Locked" functions below. + base::Lock& lock() { return lock_; } + + // Returns NULL if the item is not found. + // + // The lock must be held. + ItemNode* GetExistingNodeLocked(const Label& label); + + // There must not be an item with this label in the tree already. Takes + // ownership of the pointer. + // + // The lock must be held. + void AddNodeLocked(ItemNode* node); + + // Mark the given item as being generated. If it has no unresolved + // dependencies, it will be marked resolved, and the resolved state will be + // recursively pushed into the dependency tree. Returns an error if there was + // an error. + Err MarkItemGeneratedLocked(const Label& label); + + // Fills the given vector with all known items. + void GetAllItemsLocked(std::vector<const Item*>* dest) const; + + // Returns an error if there are unresolved dependencies, or no error if + // there aren't. + // + // The lock should not be held. + Err CheckForBadItems() const; + + private: + Err MarkItemResolvedLocked(ItemNode* node); + + // Given a set of unresolved nodes, looks for cycles and returns the error + // message describing any cycles it found. + std::string CheckForCircularDependenciesLocked( + const std::vector<const ItemNode*>& bad_nodes) const; + + mutable base::Lock lock_; + + typedef base::hash_map<Label, ItemNode*> StringToNodeHash; + StringToNodeHash items_; // Owning pointer. + + DISALLOW_COPY_AND_ASSIGN(ItemTree); +}; + +#endif // TOOLS_GN_ITEM_TREE_H_ diff --git a/tools/gn/label.cc b/tools/gn/label.cc new file mode 100644 index 0000000..f9b48da --- /dev/null +++ b/tools/gn/label.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2013 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 "tools/gn/label.h" + +#include "base/logging.h" +#include "tools/gn/err.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/value.h" + +namespace { + +// We print user visible label names with no trailing slash after the +// directory name. +std::string DirWithNoTrailingSlash(const SourceDir& dir) { + // Be careful not to trim if the input is just "/" or "//". + if (dir.value().size() > 2) + return dir.value().substr(0, dir.value().size() - 1); + return dir.value(); +} + +// Given the separate-out input (everything before the colon) in the dep rule, +// computes the final build rule. Sets err on failure. On success, +// |*used_implicit| will be set to whether the implicit current directory was +// used. The value is used only for generating error messages. +bool ComputeBuildLocationFromDep(const Value& input_value, + const SourceDir& current_dir, + const base::StringPiece& input, + SourceDir* result, + Err* err) { + // No rule, use the current locaton. + if (input.empty()) { + *result = current_dir; + return true; + } + + // Don't allow directories to start with a single slash. All labels must be + // in the source root. + if (input[0] == '/' && (input.size() == 1 || input[1] != '/')) { + *err = Err(input_value, "Label can't start with a single slash", + "Labels must be either relative (no slash at the beginning) or be " + "absolute\ninside the source root (two slashes at the beginning)."); + return false; + } + + *result = current_dir.ResolveRelativeDir(input); + return true; +} + +// Given the separated-out target name (after the colon) computes the final +// name, using the implicit name from the previously-generated +// computed_location if necessary. The input_value is used only for generating +// error messages. +bool ComputeTargetNameFromDep(const Value& input_value, + const SourceDir& computed_location, + const base::StringPiece& input, + std::string* result, + Err* err) { + if (!input.empty()) { + // Easy case: input is specified, just use it. + result->assign(input.data(), input.size()); + return true; + } + + const std::string& loc = computed_location.value(); + + // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc. + if (loc.size() <= 1) { + *err = Err(input_value, "This dependency name is empty"); + return false; + } + + size_t next_to_last_slash = loc.rfind('/', loc.size() - 2); + DCHECK(next_to_last_slash != std::string::npos); + result->assign(&loc[next_to_last_slash + 1], + loc.size() - next_to_last_slash - 2); + return true; +} + +// The original value is used only for error reporting, use the |input| as the +// input to this function (which may be a substring of the original value when +// we're parsing toolchains. +// +// If the output toolchain vars are NULL, then we'll report an error if we +// find a toolchain specified (this is used when recursively parsing toolchain +// labels which themselves can't have toolchain specs). +// +// We assume that the output variables are initialized to empty so we don't +// write them unless we need them to contain something. +// +// Returns true on success. On failure, the out* variables might be written to +// but shouldn't be used. +bool Resolve(const SourceDir& current_dir, + const Label& current_toolchain, + const Value& original_value, + const base::StringPiece& input, + SourceDir* out_dir, + std::string* out_name, + SourceDir* out_toolchain_dir, + std::string* out_toolchain_name, + Err* err) { + // To workaround the problem that StringPiece operator[] doesn't return a ref. + const char* input_str = input.data(); + + size_t path_separator = input.find_first_of(":("); + base::StringPiece location_piece; + base::StringPiece name_piece; + base::StringPiece toolchain_piece; + if (path_separator == std::string::npos) { + location_piece = input; + // Leave name & toolchain piece null. + } else { + location_piece = base::StringPiece(&input_str[0], path_separator); + + size_t toolchain_separator = input.find('(', path_separator); + if (toolchain_separator == std::string::npos) { + name_piece = base::StringPiece(&input_str[path_separator + 1], + input.size() - path_separator - 1); + // Leave location piece null. + } else if (!out_toolchain_dir) { + // Toolchain specified but not allows in this context. + *err = Err(original_value, "Toolchain has a toolchain.", + "Your toolchain definition (inside the parens) seems to itself " + "have a\ntoolchain. Don't do this."); + return false; + } else { + // Name piece is everything between the two separators. Note that the + // separators may be the same (e.g. "//foo(bar)" which means empty name. + if (toolchain_separator > path_separator) { + name_piece = base::StringPiece( + &input_str[path_separator + 1], + toolchain_separator - path_separator - 1); + } + + // Toolchain name should end in a ) and this should be the end of the + // string. + if (input[input.size() - 1] != ')') { + *err = Err(original_value, "Bad toolchain name.", + "Toolchain name must end in a \")\" at the end of the label."); + return false; + } + + // Subtract off the two parens to just get the toolchain name. + toolchain_piece = base::StringPiece( + &input_str[toolchain_separator + 1], + input.size() - toolchain_separator - 2); + } + } + + // Everything before the separator is the filename. + // We allow three cases: + // Absolute: "//foo:bar" -> /foo:bar + // Target in current file: ":foo" -> <currentdir>:foo + // Path with implicit name: "/foo" -> /foo:foo + if (location_piece.empty() && name_piece.empty()) { + // Can't use both implicit filename and name (":"). + *err = Err(original_value, "This doesn't specify a dependency."); + return false; + } + + if (!ComputeBuildLocationFromDep(original_value, current_dir, location_piece, + out_dir, err)) + return false; + + if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece, + out_name, err)) + return false; + + // Last, do the toolchains. + if (out_toolchain_dir) { + // Handle empty toolchain strings. We don't allow normal labels to be + // empty so we can't allow the recursive call of this function to do this + // check. + if (toolchain_piece.empty()) { + *out_toolchain_dir = current_toolchain.dir(); + *out_toolchain_name = current_toolchain.name(); + return true; + } else { + return Resolve(current_dir, current_toolchain, + original_value, toolchain_piece, + out_toolchain_dir, out_toolchain_name, NULL, NULL, err); + } + } + return true; +} + +} // namespace + +Label::Label() { +} + +Label::Label(const SourceDir& dir, + const base::StringPiece& name, + const SourceDir& toolchain_dir, + const base::StringPiece& toolchain_name) + : dir_(dir), + toolchain_dir_(toolchain_dir) { + name_.assign(name.data(), name.size()); + toolchain_name_.assign(toolchain_name.data(), toolchain_name.size()); +} + +Label::~Label() { +} + +// static +Label Label::Resolve(const SourceDir& current_dir, + const Label& current_toolchain, + const Value& input, + Err* err) { + Label ret; + if (input.type() != Value::STRING) { + *err = Err(input, "Dependency is not a string."); + return ret; + } + const std::string& input_string = input.string_value(); + if (input_string.empty()) { + *err = Err(input, "Dependency string is empty."); + return ret; + } + + if (!::Resolve(current_dir, current_toolchain, input, input_string, + &ret.dir_, &ret.name_, + &ret.toolchain_dir_, &ret.toolchain_name_, + err)) + return Label(); + return ret; +} + +Label Label::GetToolchainLabel() const { + return Label(toolchain_dir_, toolchain_name_, + SourceDir(), base::StringPiece()); +} + +std::string Label::GetUserVisibleName(bool include_toolchain) const { + std::string ret; + ret.reserve(dir_.value().size() + name_.size() + 1); + + if (dir_.is_null()) + return ret; + + ret = DirWithNoTrailingSlash(dir_); + ret.push_back(':'); + ret.append(name_); + + if (include_toolchain) { + ret.push_back('('); + if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) { + ret.append(DirWithNoTrailingSlash(toolchain_dir_)); + ret.push_back(':'); + ret.append(toolchain_name_); + } + ret.push_back(')'); + } + return ret; +} + +std::string Label::GetUserVisibleName(const Label& default_toolchain) const { + bool include_toolchain = + default_toolchain.dir() != toolchain_dir_ || + default_toolchain.name() != toolchain_name_; + return GetUserVisibleName(include_toolchain); +} diff --git a/tools/gn/label.h b/tools/gn/label.h new file mode 100644 index 0000000..b31117a --- /dev/null +++ b/tools/gn/label.h @@ -0,0 +1,116 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_LABEL_H_ +#define TOOLS_GN_LABEL_H_ + +#include "base/containers/hash_tables.h" +#include "build/build_config.h" +#include "tools/gn/source_dir.h" + +class Err; +class Value; + +// A label represents the name of a target or some other named thing in +// the source path. The label is always absolute and always includes a name +// part, so it starts with a slash, and has one colon. +class Label { + public: + Label(); + + // Makes a label given an already-separate out path and name. + // See also Resolve(). + Label(const SourceDir& dir, + const base::StringPiece& name, + const SourceDir& toolchain_dir, + const base::StringPiece& toolchain_name); + ~Label(); + + // Resolives a string from a build file that may be relative to the + // current directory into a fully qualified label. On failure returns an + // is_null() label and sets the error. + static Label Resolve(const SourceDir& current_dir, + const Label& current_toolchain, + const Value& input, + Err* err); + + bool is_null() const { return dir_.is_null(); } + + const SourceDir& dir() const { return dir_; } + const std::string& name() const { return name_; } + + const SourceDir& toolchain_dir() const { return toolchain_dir_; } + const std::string& toolchain_name() const { return toolchain_name_; } + Label GetToolchainLabel() const; + + // Formats this label in a way that we can present to the user or expose to + // other parts of the system. SourceDirs end in slashes, but the user + // expects names like "//chrome/renderer:renderer_config" when printed. The + // toolchain is optionally included. + std::string GetUserVisibleName(bool include_toolchain) const; + + // Like the above version, but automatically includes the toolchain if it's + // not the default one. Normally the user only cares about the toolchain for + // non-default ones, so this can make certain output more clear. + std::string GetUserVisibleName(const Label& default_toolchain) const; + + bool operator==(const Label& other) const { + return name_ == other.name_ && dir_ == other.dir_ && + toolchain_dir_ == other.toolchain_dir_ && + toolchain_name_ == other.toolchain_name_; + } + bool operator!=(const Label& other) const { + return !operator==(other); + } + bool operator<(const Label& other) const { + // TODO(brettw) could be optimized to avoid an extra full string check + // (one for operator==, one for <). + if (dir_ != other.dir_) + return dir_ < other.dir_; + if (name_ != other.name_) + return name_ < other.name_; + if (toolchain_dir_ != other.toolchain_dir_) + return toolchain_dir_ < other.toolchain_dir_; + return toolchain_name_ < other.toolchain_name_; + } + + // Returns true if the toolchain dir/name of this object matches some + // other object. + bool ToolchainsEqual(const Label& other) const { + return toolchain_dir_ == other.toolchain_dir_ && + toolchain_name_ == other.toolchain_name_; + } + + private: + SourceDir dir_; + std::string name_; + + SourceDir toolchain_dir_; + std::string toolchain_name_; +}; + +namespace BASE_HASH_NAMESPACE { + +#if defined(COMPILER_GCC) +template<> struct hash<Label> { + std::size_t operator()(const Label& v) const { + hash<std::string> stringhash; + return ((stringhash(v.dir().value()) * 131 + + stringhash(v.name())) * 131 + + stringhash(v.toolchain_dir().value())) * 131 + + stringhash(v.toolchain_name()); + } +}; +#elif defined(COMPILER_MSVC) +inline size_t hash_value(const Label& v) { + return ((hash_value(v.dir().value()) * 131 + + hash_value(v.name())) * 131 + + hash_value(v.toolchain_dir().value())) * 131 + + hash_value(v.toolchain_name()); +} +#endif // COMPILER... + +} // namespace BASE_HASH_NAMESPACE + +#endif // TOOLS_GN_LABEL_H_ diff --git a/tools/gn/label_unittest.cc b/tools/gn/label_unittest.cc new file mode 100644 index 0000000..74eb3da --- /dev/null +++ b/tools/gn/label_unittest.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2013 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/err.h" +#include "tools/gn/label.h" +#include "tools/gn/value.h" + +namespace { + +struct ParseDepStringCase { + const char* cur_dir; + const char* str; + bool success; + const char* expected_dir; + const char* expected_name; + const char* expected_toolchain_dir; + const char* expected_toolchain_name; +}; + +} // namespace + +TEST(Label, Resolve) { + ParseDepStringCase cases[] = { + // cur input succ expected dir name tc dir tc name + { "//chrome/", "", false, "", "", "", "" }, + { "//chrome/", "/", false, "", "", "", "" }, + { "//chrome/", ":", false, "", "", "", "" }, + { "//chrome/", "/:", false, "", "", "", "" }, + { "//chrome/", "blah", true, "//chrome/blah/", "blah", "//t/", "d" }, + { "//chrome/", "blah:bar", true, "//chrome/blah/", "bar", "//t/", "d" }, + // No single-leading slash. + { "//chrome/", "/chrome:bar", false, "", "", "", "" }, + // No trailing slash. + { "//chrome/", "/chrome/:bar", false, "", "", "", "" }, + // Refers to root dir. + { "//chrome/", "//:bar", true, "//", "bar", "//t/", "d" }, + // Implicit directory + { "//chrome/", ":bar", true, "//chrome/", "bar", "//t/", "d" }, + { "//chrome/renderer/", ":bar", true, "//chrome/renderer/", "bar", "//t/", "d" }, + // Implicit names. + { "//chrome/", "//base", true, "//base/", "base", "//t/", "d" }, + { "//chrome/", "//base/i18n", true, "//base/i18n/", "i18n", "//t/", "d" }, + { "//chrome/", "//base/i18n:foo", true, "//base/i18n/", "foo", "//t/", "d" }, + // Toolchain parsing. + { "//chrome/", "//chrome:bar(//t:n)", true, "//chrome/", "bar", "//t/", "n" }, + { "//chrome/", "//chrome:bar(//t)", true, "//chrome/", "bar", "//t/", "t" }, + { "//chrome/", "//chrome:bar(//t:)", true, "//chrome/", "bar", "//t/", "t" }, + { "//chrome/", "//chrome:bar()", true, "//chrome/", "bar", "//t/", "d" }, + { "//chrome/", "//chrome:bar(foo)", true, "//chrome/", "bar", "//chrome/foo/", "foo" }, + { "//chrome/", "//chrome:bar(:foo)", true, "//chrome/", "bar", "//chrome/", "foo" }, + // TODO(brettw) it might be nice to make this an error: + //{ "//chrome/", "//chrome:bar())", false, "", "", "", "" }, + { "//chrome/", "//chrome:bar(//t:bar(tc))", false, "", "", "", "" }, + { "//chrome/", "//chrome:bar(()", false, "", "", "", "" }, + { "//chrome/", "(t:b)", false, "", "", "", "" }, + { "//chrome/", ":bar(//t/b)", true, "//chrome/", "bar", "//t/b/", "b" }, + { "//chrome/", ":bar(/t/b)", false, "", "", "", "" }, + { "//chrome/", ":bar(t/b)", true, "//chrome/", "bar", "//chrome/t/b/", "b" }, + }; + + Label default_toolchain(SourceDir("//t/"), "d", + SourceDir(), std::string()); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { + const ParseDepStringCase& cur = cases[i]; + + std::string location, name; + Err err; + Value v(NULL, Value::STRING); + v.string_value() = cur.str; + Label result = + Label::Resolve(SourceDir(cur.cur_dir), default_toolchain, v, &err); + EXPECT_EQ(cur.success, !err.has_error()) << i << " " << cur.str; + if (!err.has_error() && cur.success) { + EXPECT_EQ(cur.expected_dir, result.dir().value()) + << i << " " << cur.str; + EXPECT_EQ(cur.expected_name, result.name()) + << i << " " << cur.str; + EXPECT_EQ(cur.expected_toolchain_dir, + result.toolchain_dir().value()) + << i << " " << cur.str; + EXPECT_EQ(cur.expected_toolchain_name, result.toolchain_name()) + << i << " " << cur.str; + } + } +} diff --git a/tools/gn/location.h b/tools/gn/location.h new file mode 100644 index 0000000..2055125 --- /dev/null +++ b/tools/gn/location.h @@ -0,0 +1,77 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_LOCATION_H_ +#define TOOLS_GN_LOCATION_H_ + +#include <algorithm> + +#include "base/logging.h" + +class InputFile; + +// Represents a place in a source file. Used for error reporting. +class Location { + public: + Location() + : file_(NULL), + line_number_(-1), + char_offset_(-1) { + } + Location(const InputFile* file, int line_number, int char_offset) + : file_(file), + line_number_(line_number), + char_offset_(char_offset) { + } + + const InputFile* file() const { return file_; } + int line_number() const { return line_number_; } + int char_offset() const { return char_offset_; } + + bool operator==(const Location& other) const { + return other.file_ == file_ && + other.line_number_ == line_number_ && + other.char_offset_ == char_offset_; + } + + bool operator<(const Location& other) const { + DCHECK(file_ == other.file_); + if (line_number_ != other.line_number_) + return line_number_ < other.line_number_; + return char_offset_ < other.char_offset_; + } + + private: + const InputFile* file_; // Null when unset. + int line_number_; // -1 when unset. + int char_offset_; // -1 when unset. +}; + +// Represents a range in a source file. Used for error reporting. +// The end is exclusive i.e. [begin, end) +class LocationRange { + public: + LocationRange() {} + LocationRange(const Location& begin, const Location& end) + : begin_(begin), + end_(end) { + DCHECK(begin_.file() == end_.file()); + } + + const Location& begin() const { return begin_; } + const Location& end() const { return end_; } + + LocationRange Union(const LocationRange& other) const { + DCHECK(begin_.file() == other.begin_.file()); + return LocationRange( + begin_ < other.begin_ ? begin_ : other.begin_, + end_ < other.end_ ? other.end_ : end_); + } + + private: + Location begin_; + Location end_; +}; + +#endif // TOOLS_GN_LOCATION_H_ diff --git a/tools/gn/ninja_build_writer.cc b/tools/gn/ninja_build_writer.cc new file mode 100644 index 0000000..a63765c --- /dev/null +++ b/tools/gn/ninja_build_writer.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2013 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 "tools/gn/ninja_build_writer.h" + +#include <fstream> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/process/process_handle.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/input_file_manager.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/target.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace { + +std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { +#if defined(OS_WIN) + wchar_t module[MAX_PATH]; + GetModuleFileName(NULL, module, MAX_PATH); + //result = "\"" + WideToUTF8(module) + "\""; + base::FilePath executable(module); +#elif defined(OS_MACOSX) + // FIXME(brettw) write this on Mac! + base::FilePath executable("gn"); +#else + base::FilePath executable = + base::GetProcessExecutablePath(base::GetCurrentProcessHandle()); +#endif + +/* + // Append the root path. + CommandLine* cmdline = CommandLine::ForCurrentProcess(); + result += " --root=\"" + FilePathToUTF8(settings->root_path()) + "\""; +*/ + + CommandLine cmdline(executable); + cmdline.AppendSwitchPath("--root", build_settings->root_path()); + + // TODO(brettw) append other parameters. + +#if defined(OS_WIN) + return WideToUTF8(cmdline.GetCommandLineString()); +#else + return cmdline.GetCommandLineString(); +#endif +} + +} // namespace + +NinjaBuildWriter::NinjaBuildWriter( + const BuildSettings* build_settings, + const std::vector<const Settings*>& all_settings, + const std::vector<const Target*>& default_toolchain_targets, + std::ostream& out) + : build_settings_(build_settings), + all_settings_(all_settings), + default_toolchain_targets_(default_toolchain_targets), + out_(out), + path_output_(build_settings->build_dir(), ESCAPE_NINJA, true), + helper_(build_settings) { +} + +NinjaBuildWriter::~NinjaBuildWriter() { +} + +void NinjaBuildWriter::Run() { + WriteNinjaRules(); + WriteSubninjas(); + WritePhonyAndAllRules(); +} + +// static +bool NinjaBuildWriter::RunAndWriteFile( + const BuildSettings* build_settings, + const std::vector<const Settings*>& all_settings, + const std::vector<const Target*>& default_toolchain_targets) { + base::FilePath ninja_file(build_settings->GetFullPath( + SourceFile(build_settings->build_dir().value() + "build.ninja"))); + file_util::CreateDirectory(ninja_file.DirName()); + + std::ofstream file; + file.open(FilePathToUTF8(ninja_file).c_str(), + std::ios_base::out | std::ios_base::binary); + if (file.fail()) + return false; + + NinjaBuildWriter gen(build_settings, all_settings, + default_toolchain_targets, file); + gen.Run(); + return true; +} + +void NinjaBuildWriter::WriteNinjaRules() { + out_ << "rule gn\n"; + out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; + out_ << " description = GN the world\n\n"; + + out_ << "build build.ninja: gn"; + + // Input build files. + std::vector<SourceFile> input_files; + g_scheduler->input_file_manager()->GetAllInputFileNames(&input_files); + for (size_t i = 0; i < input_files.size(); i++) { + out_ << " "; + path_output_.WriteFile(out_, input_files[i]); + } + + // Other files read by the build. + std::vector<SourceFile> other_files = g_scheduler->GetGenDependencies(); + for (size_t i = 0; i < other_files.size(); i++) { + out_ << " "; + path_output_.WriteFile(out_, other_files[i]); + } + + out_ << std::endl << std::endl; +} + +void NinjaBuildWriter::WriteSubninjas() { + for (size_t i = 0; i < all_settings_.size(); i++) { + out_ << "subninja "; + path_output_.WriteFile(out_, + helper_.GetNinjaFileForToolchain(all_settings_[i])); + out_ << std::endl; + } + out_ << std::endl; +} + +void NinjaBuildWriter::WritePhonyAndAllRules() { + std::string all_rules; + + // Write phony rules for the default toolchain (don't do other toolchains or + // we'll get naming conflicts). + for (size_t i = 0; i < default_toolchain_targets_.size(); i++) { + const Target* target = default_toolchain_targets_[i]; + if (target->output_type() == Target::NONE) + continue; // Nothing to generate. + + OutputFile target_file = helper_.GetTargetOutputFile(target); + if (target_file.value() != target->label().name()) { + out_ << "build " << target->label().name() << ": phony "; + path_output_.WriteFile(out_, target_file); + out_ << std::endl; + } + + if (!all_rules.empty()) + all_rules.append(" $\n "); + all_rules.append(target_file.value()); + } + + if (!all_rules.empty()) { + out_ << "\nbuild all: phony " << all_rules << std::endl; + out_ << "default all" << std::endl; + } +} + diff --git a/tools/gn/ninja_build_writer.h b/tools/gn/ninja_build_writer.h new file mode 100644 index 0000000..85548df --- /dev/null +++ b/tools/gn/ninja_build_writer.h @@ -0,0 +1,53 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_NINJA_BUILD_WRITER_H_ +#define TOOLS_GN_NINJA_BUILD_WRITER_H_ + +#include <iosfwd> +#include <vector> + +#include "tools/gn/ninja_helper.h" +#include "tools/gn/path_output.h" + +class BuildSettings; +class Settings; +class Target; + +// Generates the toplevel "build.ninja" file. This references the individual +// toolchain files and lists all input .gn files as dependencies of the +// build itself. +class NinjaBuildWriter { + public: + static bool RunAndWriteFile( + const BuildSettings* settings, + const std::vector<const Settings*>& all_settings, + const std::vector<const Target*>& default_toolchain_targets); + + private: + NinjaBuildWriter(const BuildSettings* settings, + const std::vector<const Settings*>& all_settings, + const std::vector<const Target*>& default_toolchain_targets, + std::ostream& out); + ~NinjaBuildWriter(); + + void Run(); + + void WriteNinjaRules(); + void WriteSubninjas(); + void WritePhonyAndAllRules(); + + const BuildSettings* build_settings_; + std::vector<const Settings*> all_settings_; + std::vector<const Target*> default_toolchain_targets_; + std::ostream& out_; + PathOutput path_output_; + + NinjaHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(NinjaBuildWriter); +}; + +#endif // TOOLS_GN_NINJA_BUILD_GENERATOR_H_ + diff --git a/tools/gn/ninja_helper.cc b/tools/gn/ninja_helper.cc new file mode 100644 index 0000000..c27c0e4 --- /dev/null +++ b/tools/gn/ninja_helper.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2013 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 "tools/gn/ninja_helper.h" + +#include "base/logging.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/string_utils.h" +#include "tools/gn/target.h" + +namespace { + +const char kLibDirWithSlash[] = "lib"; +const char kObjectDirNoSlash[] = "obj"; + +} // namespace + +NinjaHelper::NinjaHelper(const BuildSettings* build_settings) + : build_settings_(build_settings) { + build_to_src_no_last_slash_ = build_settings->build_to_source_dir_string(); + if (!build_to_src_no_last_slash_.empty() && + build_to_src_no_last_slash_[build_to_src_no_last_slash_.size() - 1] == + '/') + build_to_src_no_last_slash_.resize(build_to_src_no_last_slash_.size() - 1); + + build_to_src_system_no_last_slash_ = build_to_src_no_last_slash_; + ConvertPathToSystem(&build_to_src_system_no_last_slash_); +} + +NinjaHelper::~NinjaHelper() { +} + +std::string NinjaHelper::GetTopleveOutputDir() const { + return kObjectDirNoSlash; +} + +std::string NinjaHelper::GetTargetOutputDir(const Target* target) const { + return kObjectDirNoSlash + target->label().dir().SourceAbsoluteWithOneSlash(); +} + +OutputFile NinjaHelper::GetNinjaFileForTarget(const Target* target) const { + OutputFile ret(target->settings()->toolchain_output_subdir()); + ret.value().append(kObjectDirNoSlash); + AppendStringPiece(&ret.value(), + target->label().dir().SourceAbsoluteWithOneSlash()); + ret.value().append(target->label().name()); + ret.value().append(".ninja"); + return ret; +} + +OutputFile NinjaHelper::GetNinjaFileForToolchain( + const Settings* settings) const { + OutputFile ret; + ret.value().append(settings->toolchain_output_subdir().value()); + ret.value().append("toolchain.ninja"); + return ret; +} + +// In Python, GypPathToUniqueOutput does the qualification. The only case where +// the Python version doesn't qualify the name is for target outputs, which we +// handle in a separate function. +OutputFile NinjaHelper::GetOutputFileForSource( + const Target* target, + const SourceFile& source, + SourceFileType type) const { + // Extract the filename and remove the extension (keep the dot). + base::StringPiece filename = FindFilename(&source.value()); + std::string name(filename.data(), filename.size()); + size_t extension_offset = FindExtensionOffset(name); + CHECK(extension_offset != std::string::npos); + name.resize(extension_offset); + + // Append the new extension. + switch (type) { + case SOURCE_ASM: + case SOURCE_C: + case SOURCE_CC: + case SOURCE_M: + case SOURCE_MM: + name.append(target->settings()->IsWin() ? "obj" : "o"); + break; + + case SOURCE_RC: + name.append("res"); + break; + + case SOURCE_H: + case SOURCE_UNKNOWN: + NOTREACHED(); + return OutputFile(); + } + + // Use the scheme <path>/<target>.<name>.<extension> so that all output + // names are unique to different targets. + OutputFile ret(kObjectDirNoSlash); + + // Find the directory, assume it starts with two slashes, and trim to one. + base::StringPiece dir = FindDir(&source.value()); + CHECK(dir.size() >= 2 && dir[0] == '/' && dir[1] == '/') + << "Source file isn't in the source repo: " << dir; + AppendStringPiece(&ret.value(), dir.substr(1)); + + ret.value().append(target->label().name()); + ret.value().append("."); + ret.value().append(name); + return ret; +} + +OutputFile NinjaHelper::GetTargetOutputFile(const Target* target) const { + OutputFile ret; + if (target->output_type() == Target::NONE) { + NOTREACHED(); + return ret; + } + + const char* extension; + if (target->output_type() == Target::NONE || + target->output_type() == Target::COPY_FILES || + target->output_type() == Target::CUSTOM) { + extension = "stamp"; + } else { + extension = GetExtensionForOutputType(target->output_type(), + target->settings()->target_os()); + } + + // Everything goes into the toolchain directory (which will be empty for the + // default toolchain, and will end in a slash otherwise). + ret.value().append(target->settings()->toolchain_output_subdir().value()); + + // Binaries and loadable libraries go into the toolchain root. + if (target->output_type() == Target::EXECUTABLE || + target->output_type() == Target::LOADABLE_MODULE || + (target->settings()->IsMac() && + (target->output_type() == Target::SHARED_LIBRARY || + target->output_type() == Target::STATIC_LIBRARY)) || + (target->settings()->IsWin() && + target->output_type() == Target::SHARED_LIBRARY)) { + // Generate a name like "<toolchain>/<name>.<extension>". + ret.value().append(target->label().name()); + ret.value().push_back('.'); + ret.value().append(extension); + return ret; + } + + // Libraries go into the library subdirectory like + // "<toolchain>/lib/<name>.<extension>". + if (target->output_type() == Target::SHARED_LIBRARY) { + ret.value().append(kLibDirWithSlash); + ret.value().append(target->label().name()); + ret.value().push_back('.'); + ret.value().append(extension); + return ret; + } + + // Everything else goes next to the target's .ninja file like + // "<toolchain>/obj/<path>/<name>.<extension>". + ret.value().append(kObjectDirNoSlash); + AppendStringPiece(&ret.value(), + target->label().dir().SourceAbsoluteWithOneSlash()); + ret.value().append(target->label().name()); + ret.value().push_back('.'); + ret.value().append(extension); + return ret; +} diff --git a/tools/gn/ninja_helper.h b/tools/gn/ninja_helper.h new file mode 100644 index 0000000..5bea29e --- /dev/null +++ b/tools/gn/ninja_helper.h @@ -0,0 +1,71 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_NINJA_HELPER_H_ +#define TOOLS_GN_NINJA_HELPER_H_ + +#include <iosfwd> +#include <string> + +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/output_file.h" + +class BuildSettings; +class SourceDir; +class SourceFile; +class Target; + +// NinjaHelper ----------------------------------------------------------------- + +class NinjaHelper { + public: + NinjaHelper(const BuildSettings* build_settings); + ~NinjaHelper(); + + // Ends in a slash. + std::string GetTopleveOutputDir() const; + + // Ends in a slash. + std::string GetTargetOutputDir(const Target* target) const; + + // Example: "base/base.ninja". The string version will not be escaped, and + // will always have slashes for path separators. + OutputFile GetNinjaFileForTarget(const Target* target) const; + + // Returns the name of the root .ninja file for the given toolchain. + OutputFile GetNinjaFileForToolchain(const Settings* settings) const; + + // Given a source file relative to the source root, returns the output + // filename. + OutputFile GetOutputFileForSource(const Target* target, + const SourceFile& source, + SourceFileType type) const; + + // Returns the filename produced by the given output. + // + // Some targets make multiple files (like a .dll and an import library). This + // function returns the name of the file other targets should depend on and + // link to (so in this example, the import library). + OutputFile GetTargetOutputFile(const Target* target) const; + + // Returns the relative directory in either slashes or the system separator + // from the ninja directory (e.g. "out/Debug") to the source root (e.g. + // "../.."). It has no terminating slash. + const std::string& build_to_src_no_last_slash() const { + return build_to_src_no_last_slash_; + } + const std::string& build_to_src_system_no_last_slash() const { + return build_to_src_system_no_last_slash_; + } + + private: + const BuildSettings* build_settings_; + + std::string build_to_src_no_last_slash_; + std::string build_to_src_system_no_last_slash_; + + DISALLOW_COPY_AND_ASSIGN(NinjaHelper); +}; + +#endif // TOOLS_GN_NINJA_HELPER_H_ diff --git a/tools/gn/ninja_helper_unittest.cc b/tools/gn/ninja_helper_unittest.cc new file mode 100644 index 0000000..4aec032 --- /dev/null +++ b/tools/gn/ninja_helper_unittest.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2013 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/filesystem_utils.h" +#include "tools/gn/ninja_helper.h" +#include "tools/gn/settings.h" +#include "tools/gn/target.h" +#include "tools/gn/toolchain.h" + +namespace { + +class HelperSetterUpper { + public: + HelperSetterUpper() + : build_settings(), + toolchain(Label(SourceDir("//"), "tc", SourceDir(), std::string())), + settings(&build_settings, &toolchain, std::string()), + target(&settings, + Label(SourceDir("//tools/gn/"), "name", + SourceDir(), std::string())) { + settings.set_target_os(Settings::WIN); + + // Output going to "out/Debug". + build_settings.SetBuildDir(SourceDir("/out/Debug/")); + + // Our source target is in "tools/gn". + target.set_output_type(Target::EXECUTABLE); + } + + BuildSettings build_settings; + Toolchain toolchain; + Settings settings; + Target target; +}; + +} // namespace + +TEST(NinjaHelper, GetNinjaFileForTarget) { + HelperSetterUpper setup; + NinjaHelper helper(&setup.build_settings); + + // Default toolchain. + EXPECT_EQ(OutputFile("obj/tools/gn/name.ninja").value(), + helper.GetNinjaFileForTarget(&setup.target).value()); +} + +TEST(NinjaHelper, GetOutputFileForSource) { + HelperSetterUpper setup; + NinjaHelper helper(&setup.build_settings); + + // On Windows, expect ".obj" + EXPECT_EQ(OutputFile("obj/tools/gn/name.foo.obj").value(), + helper.GetOutputFileForSource(&setup.target, + SourceFile("//tools/gn/foo.cc"), + SOURCE_CC).value()); +} + +TEST(NinjaHelper, GetTargetOutputFile) { + HelperSetterUpper setup; + NinjaHelper helper(&setup.build_settings); + EXPECT_EQ(OutputFile("name.exe"), + helper.GetTargetOutputFile(&setup.target)); + + // Static library on Windows goes alongside the object files. + setup.target.set_output_type(Target::STATIC_LIBRARY); + EXPECT_EQ(OutputFile("obj/tools/gn/name.lib"), + helper.GetTargetOutputFile(&setup.target)); + + // TODO(brettw) test output to library and other OS types. +} diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc new file mode 100644 index 0000000..ed2d09d --- /dev/null +++ b/tools/gn/ninja_target_writer.cc @@ -0,0 +1,550 @@ +// Copyright (c) 2013 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 "tools/gn/ninja_target_writer.h" + +#include <fstream> +#include <sstream> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "tools/gn/config_values_extractors.h" +#include "tools/gn/err.h" +#include "tools/gn/escape.h" +#include "tools/gn/file_template.h" +#include "tools/gn/location.h" +#include "tools/gn/path_output.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/string_utils.h" +#include "tools/gn/target.h" + +namespace { + +static const char kCustomTargetSourceKey[] = "{{source}}"; +static const char kCustomTargetSourceNamePartKey[] = "{{source_name_part}}"; + +struct DefineWriter { + void operator()(const std::string& s, std::ostream& out) const { + out << " -D" << s; + } +}; + +struct IncludeWriter { + IncludeWriter(PathOutput& path_output, + const NinjaHelper& h) + : helper(h), + path_output_(path_output), + old_inhibit_quoting_(path_output.inhibit_quoting()) { + // Inhibit quoting since we'll put quotes around the whole thing ourselves. + // Since we're writing in NINJA escaping mode, this won't actually do + // anything, but I think we may need to change to shell-and-then-ninja + // escaping for this in the future. + path_output_.set_inhibit_quoting(true); + } + ~IncludeWriter() { + path_output_.set_inhibit_quoting(old_inhibit_quoting_); + } + + void operator()(const SourceDir& d, std::ostream& out) const { + out << " \"-I"; + // It's important not to include the trailing slash on directories or on + // Windows it will be a backslash and the compiler might think we're + // escaping the quote! + path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH); + out << "\""; + } + + const NinjaHelper& helper; + PathOutput& path_output_; + bool old_inhibit_quoting_; // So we can put the PathOutput back. +}; + +} // namespace + +NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out) + : settings_(target->settings()), + target_(target), + out_(out), + path_output_(settings_->build_settings()->build_dir(), + ESCAPE_NINJA, true), + helper_(settings_->build_settings()) { +} + +NinjaTargetWriter::~NinjaTargetWriter() { +} + +void NinjaTargetWriter::Run() { + out_ << "arch = environment.x86\n"; + + if (target_->output_type() == Target::COPY_FILES) { + WriteCopyRules(); + } else if (target_->output_type() == Target::CUSTOM) { + WriteCustomRules(); + } else { + WriteCompilerVars(); + + std::vector<OutputFile> obj_files; + WriteSources(&obj_files); + + WriteLinkerStuff(obj_files); + } +} + +// static +void NinjaTargetWriter::RunAndWriteFile(const Target* target) { + if (target->output_type() == Target::NONE) + return; + + const Settings* settings = target->settings(); + NinjaHelper helper(settings->build_settings()); + + base::FilePath ninja_file(settings->build_settings()->GetFullPath( + helper.GetNinjaFileForTarget(target).GetSourceFile( + settings->build_settings()))); + + file_util::CreateDirectory(ninja_file.DirName()); + + // It's rediculously faster to write to a string and then write that to + // disk in one operation than to use an fstream here. + std::stringstream file; + if (file.fail()) { + g_scheduler->FailWithError( + Err(Location(), "Error writing ninja file.", + "Unable to open \"" + FilePathToUTF8(ninja_file) + "\"\n" + "for writing.")); + return; + } + + NinjaTargetWriter gen(target, file); + gen.Run(); + + std::string contents = file.str(); + file_util::WriteFile(ninja_file, contents.c_str(), contents.size()); +} + +void NinjaTargetWriter::WriteCopyRules() { + // The dest dir should be inside the output dir so we can just remove the + // prefix and get ninja-relative paths. + const std::string& output_dir = + settings_->build_settings()->build_dir().value(); + const std::string& dest_dir = target_->destdir().value(); + DCHECK(StartsWithASCII(dest_dir, output_dir, true)); + std::string relative_dest_dir(&dest_dir[output_dir.size()], + dest_dir.size() - output_dir.size()); + + const Target::FileList& sources = target_->sources(); + std::vector<OutputFile> dest_files; + dest_files.reserve(sources.size()); + + // Write out rules for each file copied. + for (size_t i = 0; i < sources.size(); i++) { + const SourceFile& input_file = sources[i]; + + // The files should have the same name but in the dest dir. + base::StringPiece name_part = FindFilename(&input_file.value()); + OutputFile dest_file(relative_dest_dir); + AppendStringPiece(&dest_file.value(), name_part); + + dest_files.push_back(dest_file); + + out_ << "build "; + path_output_.WriteFile(out_, dest_file); + out_ << ": copy "; + path_output_.WriteFile(out_, input_file); + out_ << std::endl; + } + + // Write out the rule for the target to copy all of them. + out_ << std::endl << "build "; + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); + out_ << ": stamp"; + for (size_t i = 0; i < dest_files.size(); i++) { + out_ << " "; + path_output_.WriteFile(out_, dest_files[i]); + } + out_ << std::endl; + + // TODO(brettw) need some kind of stamp file for depending on this, as well + // as order_only=prebuild. +} + +void NinjaTargetWriter::WriteCustomRules() { + // Make a unique name for this rule. + std::string target_label = target_->label().GetUserVisibleName(true); + std::string custom_rule_name(target_label); + ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name); + custom_rule_name.append("_rule"); + + // Run the script from the dir of the BUILD file. This has no trailing + // slash. + const SourceDir& script_cd = target_->label().dir(); + std::string script_cd_to_root = InvertDir(script_cd); + if (script_cd_to_root.empty()) { + script_cd_to_root = "."; + } else { + // Remove trailing slash + DCHECK(script_cd_to_root[script_cd_to_root.size() - 1] == '/'); + script_cd_to_root.resize(script_cd_to_root.size() - 1); + } + + std::string script_relative_to_cd = + script_cd_to_root + target_->script().value(); + + bool no_sources = target_->sources().empty(); + + // Use a unique name for the response file when there are multiple build + // steps so that they don't stomp on each other. + std::string rspfile = custom_rule_name; + if (!no_sources) + rspfile += ".$unique_name"; + rspfile += ".rsp"; + + // First write the custom rule. + out_ << "rule " << custom_rule_name << std::endl; + out_ << " command = $pythonpath gyp-win-tool action-wrapper $arch " + << rspfile << " "; + path_output_.WriteDir(out_, script_cd, PathOutput::DIR_NO_LAST_SLASH); + out_ << std::endl; + out_ << " description = CUSTOM " << target_label << std::endl; + out_ << " restat = 1" << std::endl; + out_ << " rspfile = " << rspfile << std::endl; + + // The build command goes in the rsp file. + out_ << " rspfile_content = $pythonpath " << script_relative_to_cd; + for (size_t i = 0; i < target_->script_args().size(); i++) { + const std::string& arg = target_->script_args()[i]; + out_ << " "; + WriteCustomArg(arg); + } + out_ << std::endl << std::endl; + + // Precompute the common dependencies for each step. This includes the + // script itself (changing the script should force a rebuild) and any data + // files. + std::ostringstream common_deps_stream; + path_output_.WriteFile(common_deps_stream, target_->script()); + const Target::FileList& datas = target_->data(); + for (size_t i = 0; i < datas.size(); i++) { + common_deps_stream << " "; + path_output_.WriteFile(common_deps_stream, datas[i]); + } + const std::string& common_deps = common_deps_stream.str(); + + // Collects all output files for writing below. + std::vector<OutputFile> output_files; + + if (no_sources) { + // No sources, write a rule that invokes the script once with the + // outputs as outputs, and the data as inputs. + out_ << "build"; + const Target::FileList& outputs = target_->outputs(); + for (size_t i = 0; i < outputs.size(); i++) { + OutputFile output_path( + RemovePrefix(outputs[i].value(), + settings_->build_settings()->build_dir().value())); + output_files.push_back(output_path); + out_ << " "; + path_output_.WriteFile(out_, output_path); + } + out_ << ": " << custom_rule_name << " " << common_deps << std::endl; + } else { + // Write separate rules for each input source file. + WriteCustomSourceRules(custom_rule_name, common_deps, script_cd, + script_cd_to_root, &output_files); + } + out_ << std::endl; + + // Last write a stamp rule to collect all outputs. + out_ << "build "; + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_)); + out_ << ": stamp"; + for (size_t i = 0; i < output_files.size(); i++) { + out_ << " "; + path_output_.WriteFile(out_, output_files[i]); + } + out_ << std::endl; +} + +void NinjaTargetWriter::WriteCustomArg(const std::string& arg) { + // This can be optimized if it's called a lot. + EscapeOptions options; + options.mode = ESCAPE_NINJA; + std::string output_str = EscapeString(arg, options); + + // Do this substitution after escaping our our $ will be escaped (which we + // don't want). + ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource, + "${source}"); + ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart, + "${source_name_part}"); + out_ << output_str; +} + +void NinjaTargetWriter::WriteCustomSourceRules( + const std::string& custom_rule_name, + const std::string& common_deps, + const SourceDir& script_cd, + const std::string& script_cd_to_root, + std::vector<OutputFile>* output_files) { + // Construct the template for generating the output files from each source. + const Target::FileList& outputs = target_->outputs(); + std::vector<std::string> output_template_args; + for (size_t i = 0; i < outputs.size(); i++) { + // All outputs should be in the output dir. + output_template_args.push_back( + RemovePrefix(outputs[i].value(), + settings_->build_settings()->build_dir().value())); + } + FileTemplate output_template(output_template_args); + + // Prevent re-allocating each time by initializing outside the loop. + std::vector<std::string> output_template_result; + + // Path output formatter for wrigin source paths passed to the script. + PathOutput script_source_path_output(script_cd, ESCAPE_SHELL, true); + + const Target::FileList& sources = target_->sources(); + for (size_t i = 0; i < sources.size(); i++) { + // Write outputs for this source file computed by the template. + out_ << "build"; + output_template.ApplyString(sources[i].value(), &output_template_result); + for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) { + OutputFile output_path(output_template_result[out_i]); + output_files->push_back(output_path); + out_ << " "; + path_output_.WriteFile(out_, output_path); + } + + out_ << ": " << custom_rule_name + << " " << common_deps + << " "; + path_output_.WriteFile(out_, sources[i]); + out_ << std::endl; + + out_ << " unique_name = " << i << std::endl; + + // The source file here should be relative to the script directory since + // this is the variable passed to the script. Here we slightly abuse the + // OutputFile object by putting a non-output-relative path in it to signal + // that the PathWriter should not prepend directories. + out_ << " source = "; + script_source_path_output.WriteFile(out_, sources[i]); + out_ << std::endl; + + out_ << " source_name_part = " + << FindFilenameNoExtension(&sources[i].value()).as_string() + << std::endl; + } +} + +void NinjaTargetWriter::WriteCompilerVars() { + // Defines. + out_ << "defines ="; + RecursiveTargetConfigToStream(target_, &ConfigValues::defines, + DefineWriter(), out_); + out_ << std::endl; + + // Includes. + out_ << "includes ="; + RecursiveTargetConfigToStream(target_, &ConfigValues::includes, + IncludeWriter(path_output_, helper_), out_); + + out_ << std::endl; + + // C flags and friends. + out_ << "cflags ="; + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags, out_); + out_ << std::endl; + out_ << "cflags_c ="; + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, out_); + out_ << std::endl; + out_ << "cflags_cc ="; + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc, out_); + out_ << std::endl; + + out_ << std::endl; +} + +void NinjaTargetWriter::WriteSources( + std::vector<OutputFile>* object_files) { + const Target::FileList& sources = target_->sources(); + object_files->reserve(sources.size()); + + for (size_t i = 0; i < sources.size(); i++) { + const SourceFile& input_file = sources[i]; + + SourceFileType input_file_type = GetSourceFileType(input_file, + settings_->target_os()); + if (input_file_type == SOURCE_UNKNOWN) + continue; // Skip unknown file types. + const char* command = GetCommandForSourceType(input_file_type); + if (!command) + continue; // Skip files not needing compilation. + + OutputFile output_file = helper_.GetOutputFileForSource( + target_, input_file, input_file_type); + object_files->push_back(output_file); + + out_ << "build "; + path_output_.WriteFile(out_, output_file); + out_ << ": " << command << " "; + path_output_.WriteFile(out_, input_file); + out_ << std::endl; + } + out_ << std::endl; +} + +void NinjaTargetWriter::WriteLinkerStuff( + const std::vector<OutputFile>& object_files) { + // Manifest file on Windows. + // TODO(brettw) this seems not to be necessary for static libs, skip in + // that case? + OutputFile windows_manifest; + if (settings_->IsWin()) { + windows_manifest.value().assign(helper_.GetTargetOutputDir(target_)); + windows_manifest.value().append(target_->label().name()); + windows_manifest.value().append(".intermediate.manifest"); + out_ << "manifests = "; + path_output_.WriteFile(out_, windows_manifest); + out_ << std::endl; + } + + // Linker flags, append manifest flag on Windows to reference our file. + out_ << "ldflags ="; + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags, out_); + if (settings_->IsWin()) + out_ << " /MANIFEST /ManifestFile:"; + path_output_.WriteFile(out_, windows_manifest); + { // HACK ERASEME BRETTW FIXME + out_ << " /DEBUG /MACHINE:X86 /LIBPATH:\"C:\\Program Files (x86)\\Windows Kits\\8.0\\Lib\\win8\\um\\x86\" /DELAYLOAD:dbghelp.dll /DELAYLOAD:dwmapi.dll /DELAYLOAD:shell32.dll /DELAYLOAD:uxtheme.dll /safeseh /dynamicbase /ignore:4199 /ignore:4221 /nxcompat /SUBSYSTEM:CONSOLE /INCREMENTAL /FIXED:NO /DYNAMICBASE:NO wininet.lib dnsapi.lib version.lib msimg32.lib ws2_32.lib usp10.lib psapi.lib dbghelp.lib winmm.lib shlwapi.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib user32.lib uuid.lib odbc32.lib odbccp32.lib delayimp.lib /NXCOMPAT"; + } + out_ << std::endl; + + // Libraries to link. + out_ << "libs =" << std::endl; + + // The external output file is the one that other libs depend on. + OutputFile external_output_file = helper_.GetTargetOutputFile(target_); + + // The internal output file is the "main thing" we think we're making. In + // the case of shared libraries, this is the shared library and the external + // output file is the import library. In other cases, the internal one and + // the external one are the same. + OutputFile internal_output_file; + if (target_->output_type() == Target::SHARED_LIBRARY) { + if (settings_->IsWin()) { + internal_output_file = OutputFile(target_->label().name() + ".dll"); + } else { + NOTREACHED(); // TODO(brettw) write this. + } + } else { + internal_output_file = external_output_file; + } + + // TODO(brettw) should we append data files to this? + + // In Python see "self.ninja.build(output, command, input," + out_ << "build "; + path_output_.WriteFile(out_, internal_output_file); + if (external_output_file != internal_output_file) { + out_ << " "; + path_output_.WriteFile(out_, external_output_file); + } + out_ << ": " << GetCommandForTargetType(); + for (size_t i = 0; i < object_files.size(); i++) { + out_ << " "; + path_output_.WriteFile(out_, object_files[i]); + } + + if (target_->output_type() == Target::EXECUTABLE || + target_->output_type() == Target::SHARED_LIBRARY || + target_->output_type() == Target::LOADABLE_MODULE) { + const std::vector<const Target*>& deps = target_->deps(); + const std::set<const Target*>& inherited = target_->inherited_libraries(); + + // Now append linkable libraries to the linker command. + for (size_t i = 0; i < deps.size(); i++) { + if (deps[i]->IsLinkable() && + inherited.find(deps[i]) == inherited.end()) { + out_ << " "; + path_output_.WriteFile(out_, + helper_.GetTargetOutputFile(target_->deps()[i])); + } + } + for (std::set<const Target*>::const_iterator i = inherited.begin(); + i != inherited.end(); ++i) { + out_ << " "; + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(*i)); + } + } + out_ << std::endl; + + if (target_->output_type() == Target::SHARED_LIBRARY) { + out_ << " soname = "; + path_output_.WriteFile(out_, internal_output_file); + out_ << std::endl; + + out_ << " lib = "; + path_output_.WriteFile(out_, internal_output_file); + out_ << std::endl; + + out_ << " dll = "; + path_output_.WriteFile(out_, internal_output_file); + out_ << std::endl; + + if (settings_->IsWin()) { + out_ << " implibflag = /IMPLIB:"; + path_output_.WriteFile(out_, external_output_file); + out_ << std::endl; + } + } + + // TODO(brettw) postbuild steps here. + + out_ << std::endl; +} + +const char* NinjaTargetWriter::GetCommandForSourceType( + SourceFileType type) const { + if (type == SOURCE_C) + return "cc"; + if (type == SOURCE_CC) + return "cxx"; + + // TODO(brettw) asm files. + + if (settings_->IsMac()) { + if (type == SOURCE_M) + return "objc"; + if (type == SOURCE_MM) + return "objcxx"; + } + + if (settings_->IsWin()) { + if (type == SOURCE_RC) + return "rc"; + } + + // TODO(brettw) stuff about "S" files on non-Windows. + return NULL; +} + +const char* NinjaTargetWriter::GetCommandForTargetType() const { + if (target_->output_type() == Target::NONE) { + NOTREACHED(); + return ""; + } + + if (target_->output_type() == Target::STATIC_LIBRARY) { + // TODO(brettw) stuff about standalong static libraryes on Unix in + // WriteTarget in the Python one, and lots of postbuild steps. + return "alink"; + } + + if (target_->output_type() == Target::SHARED_LIBRARY) + return "solink"; + + return "link"; +} diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h new file mode 100644 index 0000000..5e6827d --- /dev/null +++ b/tools/gn/ninja_target_writer.h @@ -0,0 +1,67 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_NINJA_TARGET_WRITER_H_ +#define TOOLS_GN_NINJA_TARGET_WRITER_H_ + +#include <iosfwd> +#include <string> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/ninja_helper.h" +#include "tools/gn/path_output.h" +#include "tools/gn/settings.h" + +class Target; + +// Generates one target's ".ninja" file. The toplevel "build.ninja" file is +// generated by the NinjaBuildGenerator. +class NinjaTargetWriter { + public: + NinjaTargetWriter(const Target* target, std::ostream& out); + ~NinjaTargetWriter(); + + void Run(); + + static void RunAndWriteFile(const Target* target); + + private: + void WriteCopyRules(); + + void WriteCustomRules(); + void WriteCustomArg(const std::string& arg); + + // Writs the rules for compiling each source, writing all output files + // to the given vector. + // + // common_deps is a precomputed string of all ninja files that are common + // to each build step. This is added to each one. + void WriteCustomSourceRules(const std::string& custom_rule_name, + const std::string& common_deps, + const SourceDir& script_cd, + const std::string& script_cd_to_root, + std::vector<OutputFile>* output_files); + + void WriteCompilerVars(); + void WriteSources(std::vector<OutputFile>* object_files); + void WriteLinkerStuff(const std::vector<OutputFile>& object_files); + + // Returns NULL if the source type should not be compiled on this target. + const char* GetCommandForSourceType(SourceFileType type) const; + + const char* GetCommandForTargetType() const; + + const Settings* settings_; // Non-owning. + const Target* target_; // Non-owning. + std::ostream& out_; + PathOutput path_output_; + + NinjaHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(NinjaTargetWriter); +}; + +#endif // TOOLS_GN_NINJA_TARGET_WRITER_H_ diff --git a/tools/gn/ninja_toolchain_writer.cc b/tools/gn/ninja_toolchain_writer.cc new file mode 100644 index 0000000..0e38b8c --- /dev/null +++ b/tools/gn/ninja_toolchain_writer.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2013 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 "tools/gn/ninja_toolchain_writer.h" + +#include <fstream> + +#include "base/file_util.h" +#include "base/strings/stringize_macros.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/settings.h" +#include "tools/gn/target.h" +#include "tools/gn/toolchain.h" + +NinjaToolchainWriter::NinjaToolchainWriter( + const Settings* settings, + const std::vector<const Target*>& targets, + std::ostream& out) + : settings_(settings), + targets_(targets), + out_(out), + path_output_(settings_->build_settings()->build_dir(), + ESCAPE_NINJA, true), + helper_(settings->build_settings()) { +} + +NinjaToolchainWriter::~NinjaToolchainWriter() { +} + +void NinjaToolchainWriter::Run() { + WriteRules(); + WriteSubninjas(); +} + +// static +bool NinjaToolchainWriter::RunAndWriteFile( + const Settings* settings, + const std::vector<const Target*>& targets) { + NinjaHelper helper(settings->build_settings()); + base::FilePath ninja_file(settings->build_settings()->GetFullPath( + helper.GetNinjaFileForToolchain(settings).GetSourceFile( + settings->build_settings()))); + file_util::CreateDirectory(ninja_file.DirName()); + + std::ofstream file; + file.open(FilePathToUTF8(ninja_file).c_str(), + std::ios_base::out | std::ios_base::binary); + if (file.fail()) + return false; + + NinjaToolchainWriter gen(settings, targets, file); + gen.Run(); + return true; +} + +void NinjaToolchainWriter::WriteRules() { + const Toolchain* tc = settings_->toolchain(); + std::string indent(" "); + + for (int i = Toolchain::TYPE_NONE + 1; i < Toolchain::TYPE_NUMTYPES; i++) { + Toolchain::ToolType tool_type = static_cast<Toolchain::ToolType>(i); + const Toolchain::Tool& tool = tc->GetTool(tool_type); + if (tool.empty()) + continue; + + out_ << "rule " << Toolchain::ToolTypeToName(tool_type) << std::endl; + + #define WRITE_ARG(name) \ + if (!tool.name.empty()) \ + out_ << indent << " " STRINGIZE(name) " = " << tool.name << std::endl; + WRITE_ARG(command); + WRITE_ARG(depfile); + WRITE_ARG(deps); + WRITE_ARG(description); + WRITE_ARG(pool); + WRITE_ARG(restat); + WRITE_ARG(rspfile); + WRITE_ARG(rspfile_content); + #undef WRITE_ARG + } + out_ << std::endl; +} + +void NinjaToolchainWriter::WriteSubninjas() { + for (size_t i = 0; i < targets_.size(); i++) { + if (targets_[i]->output_type() != Target::NONE) { + out_ << "subninja "; + path_output_.WriteFile(out_, helper_.GetNinjaFileForTarget(targets_[i])); + out_ << std::endl; + } + } + out_ << std::endl; +} diff --git a/tools/gn/ninja_toolchain_writer.h b/tools/gn/ninja_toolchain_writer.h new file mode 100644 index 0000000..71759ef --- /dev/null +++ b/tools/gn/ninja_toolchain_writer.h @@ -0,0 +1,46 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_ +#define TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_ + +#include <iosfwd> +#include <vector> + +#include "tools/gn/ninja_helper.h" +#include "tools/gn/path_output.h" + +class BuildSettings; +class Settings; +class Target; + +class NinjaToolchainWriter { + public: + // Takes the settings for the toolchain, as well as the list of all targets + // assicoated with the toolchain. + static bool RunAndWriteFile(const Settings* settings, + const std::vector<const Target*>& targets); + + private: + NinjaToolchainWriter(const Settings* settings, + const std::vector<const Target*>& targets, + std::ostream& out); + ~NinjaToolchainWriter(); + + void Run(); + + void WriteRules(); + void WriteSubninjas(); + + const Settings* settings_; + std::vector<const Target*> targets_; + std::ostream& out_; + PathOutput path_output_; + + NinjaHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(NinjaToolchainWriter); +}; + +#endif // TOOLS_GN_NINJA_TOOLCHAIN_WRITER_H_ diff --git a/tools/gn/ninja_writer.cc b/tools/gn/ninja_writer.cc new file mode 100644 index 0000000..8b69a3f --- /dev/null +++ b/tools/gn/ninja_writer.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2013 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 "tools/gn/ninja_writer.h" + +#include "tools/gn/location.h" +#include "tools/gn/ninja_build_writer.h" +#include "tools/gn/ninja_toolchain_writer.h" + + +NinjaWriter::NinjaWriter(const BuildSettings* build_settings) + : build_settings_(build_settings) { +} + +NinjaWriter::~NinjaWriter() { +} + +// static +bool NinjaWriter::RunAndWriteFiles(const BuildSettings* build_settings) { + NinjaWriter writer(build_settings); + return writer.WriteRootBuildfiles(); +} + +bool NinjaWriter::WriteRootBuildfiles() { + // Categorize all targets by toolchain. + typedef std::map<Label, std::vector<const Target*> > CategorizedMap; + CategorizedMap categorized; + + std::vector<const Target*> all_targets; + build_settings_->target_manager().GetAllTargets(&all_targets); + for (size_t i = 0; i < all_targets.size(); i++) { + categorized[all_targets[i]->label().GetToolchainLabel()].push_back( + all_targets[i]); + } + + Label default_label = + build_settings_->toolchain_manager().GetDefaultToolchainUnlocked(); + + // Write out the toolchain buildfiles, and also accumulate the set of + // all settings and find the list of targets in the default toolchain. + std::vector<const Settings*> all_settings; + const std::vector<const Target*>* default_targets = NULL; + for (CategorizedMap::const_iterator i = categorized.begin(); + i != categorized.end(); ++i) { + const Settings* settings; + { + base::AutoLock lock(build_settings_->item_tree().lock()); + Err ignored; + settings = + build_settings_->toolchain_manager().GetSettingsForToolchainLocked( + LocationRange(), i->first, &ignored); + } + if (i->first == default_label) + default_targets = &i->second; + all_settings.push_back(settings); + if (!NinjaToolchainWriter::RunAndWriteFile(settings, i->second)) + return false; + } + + // Write the root buildfile. + return NinjaBuildWriter::RunAndWriteFile(build_settings_, all_settings, + *default_targets); +} diff --git a/tools/gn/ninja_writer.h b/tools/gn/ninja_writer.h new file mode 100644 index 0000000..9b6d2c1 --- /dev/null +++ b/tools/gn/ninja_writer.h @@ -0,0 +1,27 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_NINJA_WRITER_H_ +#define TOOLS_GN_NINJA_WRITER_H_ + +#include "base/basictypes.h" + +class BuildSettings; + +class NinjaWriter { + public: + static bool RunAndWriteFiles(const BuildSettings* build_settings); + + private: + NinjaWriter(const BuildSettings* build_settings); + ~NinjaWriter(); + + bool WriteRootBuildfiles(); + + const BuildSettings* build_settings_; + + DISALLOW_COPY_AND_ASSIGN(NinjaWriter); +}; + +#endif // TOOLS_GN_NINJA_WRITER_H_ diff --git a/tools/gn/operators.cc b/tools/gn/operators.cc new file mode 100644 index 0000000..afded9f --- /dev/null +++ b/tools/gn/operators.cc @@ -0,0 +1,573 @@ +// Copyright (c) 2013 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 "tools/gn/operators.h" + +#include "base/strings/string_number_conversions.h" +#include "tools/gn/err.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scope.h" +#include "tools/gn/token.h" +#include "tools/gn/value.h" + +namespace { + +const char kSourcesName[] = "sources"; + +// Applies the sources assignment filter from the given scope to each element +// of source (can be a list or a string), appending it to dest if it doesn't +// match. +void AppendFilteredSourcesToValue(const Scope* scope, + const Value& source, + Value* dest) { + const PatternList* filter = scope->GetSourcesAssignmentFilter(); + + const std::vector<Value>& source_list = source.list_value(); + + if (source.type() == Value::STRING) { + if (!filter || filter->is_empty() || + !filter->MatchesValue(source)) + dest->list_value().push_back(source); + return; + } + + // Otherwise source is a list. + DCHECK(source.type() == Value::LIST); + if (!filter || filter->is_empty()) { + // No filter, append everything. + for (size_t i = 0; i < source_list.size(); i++) + dest->list_value().push_back(source_list[i]); + return; + } + + // Note: don't reserve() the dest vector here since that actually hurts + // the allocation pattern when the build script is doing multiple small + // additions. + for (size_t i = 0; i < source_list.size(); i++) { + if (!filter->MatchesValue(source_list[i])) + dest->list_value().push_back(source_list[i]); + } +} + +void RemoveMatchesFromList(const BinaryOpNode* op_node, + Value* list, + const Value& to_remove, + Err* err) { + std::vector<Value>& v = list->list_value(); + switch (to_remove.type()) { + case Value::INTEGER: // Filter out the individual int/string. + case Value::STRING: { + bool found_match = false; + for (size_t i = 0; i < v.size(); /* nothing */) { + if (v[i] == to_remove) { + found_match = true; + v.erase(v.begin() + i); + } else { + i++; + } + } + if (!found_match) { + *err = Err(to_remove.origin()->GetRange(), "Item not found", + "You were trying to remove \"" + to_remove.ToString() + + "\"\nfrom the list but it wasn't there."); + } + break; + } + + case Value::LIST: // Filter out each individual thing. + for (size_t i = 0; i < to_remove.list_value().size(); i++) { + // TODO(brettw) if the nested item is a list, we may want to search + // for the literal list rather than remote the items in it. + RemoveMatchesFromList(op_node, list, to_remove.list_value()[i], err); + if (err->has_error()) + return; + } + break; + + default: + break; + } +} + +// Assignment ----------------------------------------------------------------- + +Value ExecuteEquals(Scope* scope, + const BinaryOpNode* op_node, + const Token& left, + const Value& right, + Err* err) { + const Value* old_value = scope->GetValue(left.value(), false); + if (old_value) { + if (scope->IsSetButUnused(left.value())) { + // Throw an error for re-assigning without using the value first. The + // exception is that you can overwrite an empty list with another list + // since this is the way to get around the "can't overwrite a nonempty + // list with another nonempty list" restriction. + if (old_value->type() != Value::LIST || + !old_value->list_value().empty()) { + *err = Err(op_node->left()->GetRange(), "Overwriting unused variable.", + "This overwrites a previous assignment to \"" + + left.value().as_string() + "\" that had no effect."); + err->AppendSubErr(Err(*scope->GetValue(left.value()), + "Previously set here.", + "Maybe you wanted \"+=\" to append instead?")); + return Value(); + } + } else { + // Throw an error when overwriting a nonempty list with another nonempty + // list item. This is to detect the case where you write + // defines = ["FOO"] + // and you overwrote inherited ones, when instead you mean to append: + // defines += ["FOO"] + if (old_value->type() == Value::LIST && + !old_value->list_value().empty() && + right.type() == Value::LIST && + !right.list_value().empty()) { + *err = Err(op_node->left()->GetRange(), "Replacing nonempty list.", + std::string("This overwrites a previously-defined nonempty list ") + + "(length " + base::IntToString(old_value->list_value().size()) + + ")."); + err->AppendSubErr(Err(*old_value, "for previous definition", + "with another one (length " + + base::IntToString(right.list_value().size()) + "). Did you mean " + + "\"+=\" to append instead? If you\nreally want to do this, do\n " + + left.value().as_string() + " = []\nbefore reassigning.")); + return Value(); + } + } + } + if (err->has_error()) + return Value(); + + if (right.type() == Value::LIST && left.value() == kSourcesName) { + // Assigning to sources, filter the list. Here we do the filtering and + // copying in one step to save an extra list copy (the lists may be + // long). + Value* set_value = scope->SetValue(left.value(), + Value(op_node, Value::LIST), op_node); + set_value->list_value().reserve(right.list_value().size()); + AppendFilteredSourcesToValue(scope, right, set_value); + } else { + // Normal value set, just copy it. + scope->SetValue(left.value(), right, op_node->right()); + } + return Value(); +} + +// allow_type_conversion indicates if we're allowed to change the type of the +// left value. This is set to true when doing +, and false when doing +=. +void ValuePlusEquals(const Scope* scope, + const BinaryOpNode* op_node, + const Token& left_token, + Value* left, + const Value& right, + bool allow_type_conversion, + Err* err) { + switch (left->type()) { + // Left-hand-side int. + case Value::INTEGER: + switch (right.type()) { + case Value::INTEGER: // int + int -> addition. + left->int_value() += right.int_value(); + return; + + case Value::STRING: // int + string -> string concat. + if (allow_type_conversion) { + *left = Value(op_node, + base::Int64ToString(left->int_value()) + right.string_value()); + return; + } + break; + + default: + break; + } + break; + + // Left-hand-side string. + case Value::STRING: + switch (right.type()) { + case Value::INTEGER: // string + int -> string concat. + left->string_value().append(base::Int64ToString(right.int_value())); + return; + + case Value::STRING: // string + string -> string contat. + left->string_value().append(right.string_value()); + return; + + default: + break; + } + break; + + // Left-hand-side list. + case Value::LIST: + switch (right.type()) { + case Value::INTEGER: // list + integer -> list append. + case Value::STRING: // list + string -> list append. + if (left_token.value() == kSourcesName) + AppendFilteredSourcesToValue(scope, right, left); + else + left->list_value().push_back(right); + return; + + case Value::LIST: // list + list -> list concat. + if (left_token.value() == kSourcesName) { + // Filter additions through the assignment filter. + AppendFilteredSourcesToValue(scope, right, left); + } else { + // Normal list concat. + for (size_t i = 0; i < right.list_value().size(); i++) + left->list_value().push_back(right.list_value()[i]); + } + return; + + default: + break; + } + + default: + break; + } + + *err = Err(op_node->op(), "Incompatible types to add.", + std::string("I see a ") + Value::DescribeType(left->type()) + " and a " + + Value::DescribeType(right.type()) + "."); +} + +Value ExecutePlusEquals(Scope* scope, + const BinaryOpNode* op_node, + const Token& left, + const Value& right, + Err* err) { + // We modify in-place rather than doing read-modify-write to avoid + // copying large lists. + Value* left_value = + scope->GetValueForcedToCurrentScope(left.value(), op_node); + if (!left_value) { + *err = Err(left, "Undefined variable for +=.", + "I don't have something with this name in scope now."); + return Value(); + } + ValuePlusEquals(scope, op_node, left, left_value, right, false, err); + left_value->set_origin(op_node); + scope->MarkUnused(left.value()); + return Value(); +} + +void ValueMinusEquals(const BinaryOpNode* op_node, + Value* left, + const Value& right, + bool allow_type_conversion, + Err* err) { + switch (left->type()) { + // Left-hand-side int. + case Value::INTEGER: + switch (right.type()) { + case Value::INTEGER: // int - int -> subtraction. + left->int_value() -= right.int_value(); + return; + + default: + break; + } + break; + + // Left-hand-side string. + case Value::STRING: + break; // All are errors. + + // Left-hand-side list. + case Value::LIST: + RemoveMatchesFromList(op_node, left, right, err); + return; + + default: + break; + } + + *err = Err(op_node->op(), "Incompatible types to add.", + std::string("I see a ") + Value::DescribeType(left->type()) + " and a " + + Value::DescribeType(right.type()) + "."); +} + +Value ExecuteMinusEquals(Scope* scope, + const BinaryOpNode* op_node, + const Token& left, + const Value& right, + Err* err) { + Value* left_value = + scope->GetValueForcedToCurrentScope(left.value(), op_node); + if (!left_value) { + *err = Err(left, "Undefined variable for -=.", + "I don't have something with this name in scope now."); + return Value(); + } + ValueMinusEquals(op_node, left_value, right, false, err); + left_value->set_origin(op_node); + scope->MarkUnused(left.value()); + return Value(); +} + +// Plus/Minus ----------------------------------------------------------------- + +Value ExecutePlus(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + Value ret = left; + ValuePlusEquals(scope, op_node, Token(), &ret, right, true, err); + ret.set_origin(op_node); + return ret; +} + +Value ExecuteMinus(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + Value ret = left; + ValueMinusEquals(op_node, &ret, right, true, err); + ret.set_origin(op_node); + return ret; +} + +// Comparison ----------------------------------------------------------------- + +Value ExecuteEqualsEquals(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + if (left == right) + return Value(op_node, 1); + return Value(op_node, 0); +} + +Value ExecuteNotEquals(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + // Evaluate in terms of ==. + Value result = ExecuteEqualsEquals(scope, op_node, left, right, err); + result.int_value() = static_cast<int64>(!result.int_value()); + return result; +} + +Value FillNeedsToIntegersError(const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + *err = Err(op_node, "Comparison requires two integers.", + "This operator can only compare two integers."); + err->AppendRange(left.origin()->GetRange()); + err->AppendRange(right.origin()->GetRange()); + return Value(); +} + +Value ExecuteLessEquals(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + if (left.type() != Value::INTEGER || right.type() != Value::INTEGER) + return FillNeedsToIntegersError(op_node, left, right, err); + return Value(op_node, left.int_value() <= right.int_value()); +} + +Value ExecuteGreaterEquals(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + if (left.type() != Value::INTEGER || right.type() != Value::INTEGER) + return FillNeedsToIntegersError(op_node, left, right, err); + return Value(op_node, left.int_value() >= right.int_value()); +} + +Value ExecuteGreater(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + if (left.type() != Value::INTEGER || right.type() != Value::INTEGER) + return FillNeedsToIntegersError(op_node, left, right, err); + return Value(op_node, left.int_value() > right.int_value()); +} + +Value ExecuteLess(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + if (left.type() != Value::INTEGER || right.type() != Value::INTEGER) + return FillNeedsToIntegersError(op_node, left, right, err); + return Value(op_node, left.int_value() < right.int_value()); +} + +// Binary ---------------------------------------------------------------------- + +Value ExecuteOr(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + return Value(op_node, + static_cast<int64>(left.InterpretAsInt() || right.InterpretAsInt())); +} + +Value ExecuteAnd(Scope* scope, + const BinaryOpNode* op_node, + const Value& left, + const Value& right, + Err* err) { + return Value(op_node, + static_cast<int64>(left.InterpretAsInt() && right.InterpretAsInt())); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +bool IsUnaryOperator(const Token& token) { + if (token.type() != Token::OPERATOR) + return false; + return token.value() == "!"; +} + +bool IsBinaryOperator(const Token& token) { + if (token.type() != Token::OPERATOR) + return false; + return token.value() == "=" || + token.value() == "+=" || + token.value() == "-=" || + token.value() == "+" || + token.value() == "-" || + token.value() == "==" || + token.value() == "!=" || + token.value() == "<=" || + token.value() == ">=" || + token.value() == "<" || + token.value() == ">" || + token.value() == "&&" || + token.value() == "||"; +} + +bool IsFunctionCallArgBeginScoper(const Token& token) { + return token.IsScoperEqualTo("("); +} + +bool IsFunctionCallArgEndScoper(const Token& token) { + return token.IsScoperEqualTo(")"); +} + +bool IsScopeBeginScoper(const Token& token) { + return token.IsScoperEqualTo("{"); +} + +bool IsScopeEndScoper(const Token& token) { + return token.IsScoperEqualTo("}"); +} + +Value ExecuteUnaryOperator(Scope* scope, + const UnaryOpNode* op_node, + const Value& expr, + Err* err) { + DCHECK(op_node->op().IsOperatorEqualTo("!")); + return Value(op_node, !expr.InterpretAsInt()); +} + +Value ExecuteBinaryOperator(Scope* scope, + const BinaryOpNode* op_node, + const ParseNode* left, + const ParseNode* right, + Err* err) { + const Token& op = op_node->op(); + + // First handle the ones that take an lvalue. + if (op.IsOperatorEqualTo("=") || + op.IsOperatorEqualTo("+=") || + op.IsOperatorEqualTo("-=")) { + const IdentifierNode* left_id = left->AsIdentifier(); + if (!left_id) { + *err = Err(op, "Operator requires an lvalue.", + "This thing on the left is not an idenfitier."); + err->AppendRange(left->GetRange()); + return Value(); + } + const Token& dest = left_id->value(); + + Value right_value = right->Execute(scope, err); + if (err->has_error()) + return Value(); + if (right_value.type() == Value::NONE) { + *err = Err(op, "Operator requires an rvalue.", + "This thing on the right does not evaluate to a value."); + err->AppendRange(right->GetRange()); + return Value(); + } + + if (op.IsOperatorEqualTo("=")) + return ExecuteEquals(scope, op_node, dest, right_value, err); + if (op.IsOperatorEqualTo("+=")) + return ExecutePlusEquals(scope, op_node, dest, right_value, err); + if (op.IsOperatorEqualTo("-=")) + return ExecuteMinusEquals(scope, op_node, dest, right_value, err); + NOTREACHED(); + return Value(); + } + + // Left value. + Value left_value = left->Execute(scope, err); + if (err->has_error()) + return Value(); + if (left_value.type() == Value::NONE) { + *err = Err(op, "Operator requires an value.", + "This thing on the left does not evaluate to a value."); + err->AppendRange(left->GetRange()); + return Value(); + } + + // Right value. Note: don't move this above to share code with the lvalue + // version since in this case we want to execute the left side first. + Value right_value = right->Execute(scope, err); + if (err->has_error()) + return Value(); + if (right_value.type() == Value::NONE) { + *err = Err(op, "Operator requires an value.", + "This thing on the right does not evaluate to a value."); + err->AppendRange(right->GetRange()); + return Value(); + } + + // +, -. + if (op.IsOperatorEqualTo("-")) + return ExecuteMinus(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo("+")) + return ExecutePlus(scope, op_node, left_value, right_value, err); + + // Comparisons. + if (op.IsOperatorEqualTo("==")) + return ExecuteEqualsEquals(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo("!=")) + return ExecuteNotEquals(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo(">=")) + return ExecuteGreaterEquals(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo("<=")) + return ExecuteLessEquals(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo(">")) + return ExecuteGreater(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo("<")) + return ExecuteLess(scope, op_node, left_value, right_value, err); + + // ||, &&. + if (op.IsOperatorEqualTo("||")) + return ExecuteOr(scope, op_node, left_value, right_value, err); + if (op.IsOperatorEqualTo("&&")) + return ExecuteAnd(scope, op_node, left_value, right_value, err); + + return Value(); +} diff --git a/tools/gn/operators.h b/tools/gn/operators.h new file mode 100644 index 0000000..3401c22 --- /dev/null +++ b/tools/gn/operators.h @@ -0,0 +1,35 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_OPERATORS_H_ +#define TOOLS_GN_OPERATORS_H_ + +class BinaryOpNode; +class Err; +class ParseNode; +class Scope; +class Token; +class UnaryOpNode; +class Value; + +bool IsUnaryOperator(const Token& token); +bool IsBinaryOperator(const Token& token); + +bool IsFunctionCallArgBeginScoper(const Token& token); // "(" +bool IsFunctionCallArgEndScoper(const Token& token); // ")" + +bool IsScopeBeginScoper(const Token& token); // "{" +bool IsScopeEndScoper(const Token& token); // "}" + +Value ExecuteUnaryOperator(Scope* scope, + const UnaryOpNode* op_node, + const Value& value, + Err* err); +Value ExecuteBinaryOperator(Scope* scope, + const BinaryOpNode* op_node, + const ParseNode* left, + const ParseNode* right, + Err* err); + +#endif // TOOLS_GN_OPERATORS_H_ diff --git a/tools/gn/output_file.h b/tools/gn/output_file.h new file mode 100644 index 0000000..9c5e4b2 --- /dev/null +++ b/tools/gn/output_file.h @@ -0,0 +1,41 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_OUTPUT_FILE_H_ +#define TOOLS_GN_OUTPUT_FILE_H_ + +#include <string> + +#include "tools/gn/build_settings.h" +#include "tools/gn/source_file.h" + +// A simple wrapper around a string that indicates the string is a path +// relative to the output directory. +class OutputFile { + public: + OutputFile() : value_() {} + explicit OutputFile(const base::StringPiece& str) + : value_(str.data(), str.size()) { + } + + std::string& value() { return value_; } + const std::string& value() const { return value_; } + + // Converts to a SourceFile by prepending the build directory to the file. + SourceFile GetSourceFile(const BuildSettings* build_settings) const { + return SourceFile(build_settings->build_dir().value() + value_); + } + + bool operator==(const OutputFile& other) const { + return value_ == other.value_; + } + bool operator!=(const OutputFile& other) const { + return value_ != other.value_; + } + + private: + std::string value_; +}; + +#endif diff --git a/tools/gn/output_stream.h b/tools/gn/output_stream.h new file mode 100644 index 0000000..6f39c86 --- /dev/null +++ b/tools/gn/output_stream.h @@ -0,0 +1,42 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_OUTPUT_STREAM_H_ +#define TOOLS_GN_OUTPUT_STREAM_H_ + +class OutputStream { + public: + + + + OutputStream& WriteBuffer(const char* buf, size_t len); + OutputStream& WriteInt(int i); + + // Write a literal. + // This template expansion prevents having to look for nulls. + template<size_t size> OutputStream& Write(const char (&buf)[size]) { + return WriteBuffer(buf, size); + } + + // Write a literal string. + OutputStream& Write(const std::string& str) { + return WriteBuffer(str.c_str(), str.size()); + } + + // Quotes if necessary, and does necessary escaping. If more than one + // input is provided, the results will be concatenated together (useful + // for constructing paths without a temporary buffer). + OutputStream& WritePath(const std::string& s); + OutputStream& WritePath(const std::string& s0, + const std::string& s1); + OutputStream& WritePath(const std::string& s0, + const std::string& s1, + const std::string& s2); + + OutputStream& EndLine() { + return WriteBuffer("\n", 1); + } +}; + +#endif // TOOLS_GN_OUTPUT_STREAM_H_ diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc new file mode 100644 index 0000000..0f84970 --- /dev/null +++ b/tools/gn/parse_tree.cc @@ -0,0 +1,472 @@ +// Copyright (c) 2013 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 "tools/gn/parse_tree.h" + +#include <string> + +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "tools/gn/functions.h" +#include "tools/gn/operators.h" +#include "tools/gn/scope.h" +#include "tools/gn/string_utils.h" + +namespace { + +std::string IndentFor(int value) { + std::string ret; + for (int i = 0; i < value; i++) + ret.append(" "); + return ret; +} + +} // namespace + +ParseNode::ParseNode() { +} + +ParseNode::~ParseNode() { +} + +const AccessorNode* ParseNode::AsAccessor() const { return NULL; } +const BinaryOpNode* ParseNode::AsBinaryOp() const { return NULL; } +const BlockNode* ParseNode::AsBlock() const { return NULL; } +const ConditionNode* ParseNode::AsConditionNode() const { return NULL; } +const FunctionCallNode* ParseNode::AsFunctionCall() const { return NULL; } +const IdentifierNode* ParseNode::AsIdentifier() const { return NULL; } +const ListNode* ParseNode::AsList() const { return NULL; } +const LiteralNode* ParseNode::AsLiteral() const { return NULL; } +const UnaryOpNode* ParseNode::AsUnaryOp() const { return NULL; } + +// AccessorNode --------------------------------------------------------------- + +AccessorNode::AccessorNode() { +} + +AccessorNode::~AccessorNode() { +} + +const AccessorNode* AccessorNode::AsAccessor() const { + return this; +} + +Value AccessorNode::Execute(Scope* scope, Err* err) const { + Value index_value = index_->Execute(scope, err); + if (err->has_error()) + return Value(); + if (!index_value.VerifyTypeIs(Value::INTEGER, err)) + return Value(); + + const Value* base_value = scope->GetValue(base_.value(), true); + if (!base_value) { + *err = MakeErrorDescribing("Undefined identifier."); + return Value(); + } + if (!base_value->VerifyTypeIs(Value::LIST, err)) + return Value(); + + int64 index_int = index_value.int_value(); + if (index_int < 0) { + *err = Err(index_->GetRange(), "Negative array subscript.", + "You gave me " + base::Int64ToString(index_int) + "."); + return Value(); + } + size_t index_sizet = static_cast<size_t>(index_int); + if (index_sizet >= base_value->list_value().size()) { + *err = Err(index_->GetRange(), "Array subscript out of range.", + "You gave me " + base::Int64ToString(index_int) + + " but I was expecting something from 0 to " + + base::Int64ToString( + static_cast<int64>(base_value->list_value().size()) - 1) + + ", inclusive."); + return Value(); + } + + // Doing this assumes that there's no way in the language to do anything + // between the time the reference is created and the time that the reference + // is used. If there is, this will crash! Currently, this is just used for + // array accesses where this "shouldn't" happen. + return base_value->list_value()[index_sizet]; +} + +LocationRange AccessorNode::GetRange() const { + return LocationRange(base_.location(), index_->GetRange().end()); +} + +Err AccessorNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(GetRange(), msg, help); +} + +void AccessorNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "ACCESSOR\n"; + out << IndentFor(indent + 1) << base_.value() << "\n"; + index_->Print(out, indent + 1); +} + +// BinaryOpNode --------------------------------------------------------------- + +BinaryOpNode::BinaryOpNode() { +} + +BinaryOpNode::~BinaryOpNode() { +} + +const BinaryOpNode* BinaryOpNode::AsBinaryOp() const { + return this; +} + +Value BinaryOpNode::Execute(Scope* scope, Err* err) const { + return ExecuteBinaryOperator(scope, this, left_.get(), right_.get(), err); +} + +LocationRange BinaryOpNode::GetRange() const { + return left_->GetRange().Union(right_->GetRange()); +} + +Err BinaryOpNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(op_, msg, help); +} + +void BinaryOpNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "BINARY(" << op_.value() << ")\n"; + left_->Print(out, indent + 1); + right_->Print(out, indent + 1); +} + +// BlockNode ------------------------------------------------------------------ + +BlockNode::BlockNode(bool has_scope) + : has_scope_(has_scope), + begin_token_(NULL), + end_token_(NULL) { +} + +BlockNode::~BlockNode() { + STLDeleteContainerPointers(statements_.begin(), statements_.end()); +} + +const BlockNode* BlockNode::AsBlock() const { + return this; +} + +Value BlockNode::Execute(Scope* containing_scope, Err* err) const { + if (has_scope_) { + Scope our_scope(containing_scope); + Value ret = ExecuteBlockInScope(&our_scope, err); + if (err->has_error()) + return Value(); + + // Check for unused vars in the scope. + //our_scope.CheckForUnusedVars(err); + return ret; + } + return ExecuteBlockInScope(containing_scope, err); +} + +LocationRange BlockNode::GetRange() const { + if (begin_token_ && end_token_) { + return begin_token_->range().Union(end_token_->range()); + } + return LocationRange(); // TODO(brettw) indicate the entire file somehow. +} + +Err BlockNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + if (begin_token_) + return Err(*begin_token_, msg, help); + // TODO(brettw) this should have the beginning of the file in it or something. + return Err(Location(NULL, 1, 1), msg, help); +} + +void BlockNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "BLOCK\n"; + for (size_t i = 0; i < statements_.size(); i++) + statements_[i]->Print(out, indent + 1); +} + +Value BlockNode::ExecuteBlockInScope(Scope* our_scope, Err* err) const { + for (size_t i = 0; i < statements_.size() && !err->has_error(); i++) { + // Check for trying to execute things with no side effects in a block. + const ParseNode* cur = statements_[i]; + if (cur->AsList() || cur->AsLiteral() || cur->AsUnaryOp() || + cur->AsIdentifier()) { + *err = cur->MakeErrorDescribing( + "This statment has no effect.", + "Either delete it or do something with the result."); + return Value(); + } + cur->Execute(our_scope, err); + } + return Value(); +} + +// ConditionNode -------------------------------------------------------------- + +ConditionNode::ConditionNode() { +} + +ConditionNode::~ConditionNode() { +} + +const ConditionNode* ConditionNode::AsConditionNode() const { + return this; +} + +Value ConditionNode::Execute(Scope* scope, Err* err) const { + Value condition_result = condition_->Execute(scope, err); + if (err->has_error()) + return Value(); + if (condition_result.type() == Value::NONE) { + *err = condition_->MakeErrorDescribing( + "This does not evaluate to a value.", + "Please give me something to work with for the if statement."); + err->AppendRange(if_token_.range()); + return Value(); + } + + if (condition_result.InterpretAsInt()) { + if_true_->ExecuteBlockInScope(scope, err); + } else if (if_false_) { + // The else block is optional. It's either another condition (for an + // "else if" and we can just Execute it and the condition will handle + // the scoping) or it's a block indicating an "else" in which ase we + // need to be sure it inherits our scope. + const BlockNode* if_false_block = if_false_->AsBlock(); + if (if_false_block) + if_false_block->ExecuteBlockInScope(scope, err); + else + if_false_->Execute(scope, err); + } + + return Value(); +} + +LocationRange ConditionNode::GetRange() const { + if (if_false_) + return if_token_.range().Union(if_false_->GetRange()); + return if_token_.range().Union(if_true_->GetRange()); +} + +Err ConditionNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(if_token_, msg, help); +} + +void ConditionNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "CONDITION\n"; + condition_->Print(out, indent + 1); + if_true_->Print(out, indent + 1); + if (if_false_) + if_false_->Print(out, indent + 1); +} + +// FunctionCallNode ----------------------------------------------------------- + +FunctionCallNode::FunctionCallNode() { +} + +FunctionCallNode::~FunctionCallNode() { +} + +const FunctionCallNode* FunctionCallNode::AsFunctionCall() const { + return this; +} + +Value FunctionCallNode::Execute(Scope* scope, Err* err) const { + Value args = args_->Execute(scope, err); + if (err->has_error()) + return Value(); + return ExecuteFunction(scope, this, args.list_value(), block_.get(), err); +} + +LocationRange FunctionCallNode::GetRange() const { + if (block_) + return function_.range().Union(block_->GetRange()); + return function_.range().Union(args_->GetRange()); +} + +Err FunctionCallNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(function_, msg, help); +} + +void FunctionCallNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "FUNCTION(" << function_.value() << ")\n"; + args_->Print(out, indent + 1); + if (block_) + block_->Print(out, indent + 1); +} + +// IdentifierNode -------------------------------------------------------------- + +IdentifierNode::IdentifierNode() { +} + +IdentifierNode::IdentifierNode(const Token& token) : value_(token) { +} + +IdentifierNode::~IdentifierNode() { +} + +const IdentifierNode* IdentifierNode::AsIdentifier() const { + return this; +} + +Value IdentifierNode::Execute(Scope* scope, Err* err) const { + const Value* result = scope->GetValue(value_.value(), true); + if (!result) { + *err = MakeErrorDescribing("Undefined identifier"); + return Value(); + } + return *result; +} + +LocationRange IdentifierNode::GetRange() const { + return value_.range(); +} + +Err IdentifierNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(value_, msg, help); +} + +void IdentifierNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "IDENTIFIER(" << value_.value() << ")\n"; +} + +// ListNode ------------------------------------------------------------------- + +ListNode::ListNode() { +} + +ListNode::~ListNode() { + STLDeleteContainerPointers(contents_.begin(), contents_.end()); +} + +const ListNode* ListNode::AsList() const { + return this; +} + +Value ListNode::Execute(Scope* scope, Err* err) const { + Value result_value(this, Value::LIST); + std::vector<Value>& results = result_value.list_value(); + results.resize(contents_.size()); + + for (size_t i = 0; i < contents_.size(); i++) { + const ParseNode* cur = contents_[i]; + results[i] = cur->Execute(scope, err); + if (err->has_error()) + return Value(); + if (results[i].type() == Value::NONE) { + *err = cur->MakeErrorDescribing( + "This does not evaluate to a value.", + "I can't do something with nothing."); + return Value(); + } + } + return result_value; +} + +LocationRange ListNode::GetRange() const { + return LocationRange(begin_token_.location(), end_token_.location()); +} + +Err ListNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(begin_token_, msg, help); +} + +void ListNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "LIST\n"; + for (size_t i = 0; i < contents_.size(); i++) + contents_[i]->Print(out, indent + 1); +} + +// LiteralNode ----------------------------------------------------------------- + +LiteralNode::LiteralNode() { +} + +LiteralNode::LiteralNode(const Token& token) : value_(token) { +} + +LiteralNode::~LiteralNode() { +} + +const LiteralNode* LiteralNode::AsLiteral() const { + return this; +} + +Value LiteralNode::Execute(Scope* scope, Err* err) const { + switch (value_.type()) { + case Token::INTEGER: { + int64 result_int; + if (!base::StringToInt64(value_.value(), &result_int)) { + *err = MakeErrorDescribing("This does not look like an integer"); + return Value(); + } + return Value(this, result_int); + } + case Token::STRING: { + // TODO(brettw) Unescaping probably needs to be moved & improved. + // The input value includes the quotes around the string, strip those + // off and unescape. + Value v(this, Value::STRING); + ExpandStringLiteral(scope, value_, &v, err); + return v; + } + default: + NOTREACHED(); + return Value(); + } +} + +LocationRange LiteralNode::GetRange() const { + return value_.range(); +} + +Err LiteralNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(value_, msg, help); +} + +void LiteralNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "LITERAL(" << value_.value() << ")\n"; +} + +// UnaryOpNode ---------------------------------------------------------------- + +UnaryOpNode::UnaryOpNode() { +} + +UnaryOpNode::~UnaryOpNode() { +} + +const UnaryOpNode* UnaryOpNode::AsUnaryOp() const { + return this; +} + +Value UnaryOpNode::Execute(Scope* scope, Err* err) const { + Value operand_value = operand_->Execute(scope, err); + if (err->has_error()) + return Value(); + return ExecuteUnaryOperator(scope, this, operand_value, err); +} + +LocationRange UnaryOpNode::GetRange() const { + return op_.range().Union(operand_->GetRange()); +} + +Err UnaryOpNode::MakeErrorDescribing(const std::string& msg, + const std::string& help) const { + return Err(op_, msg, help); +} + +void UnaryOpNode::Print(std::ostream& out, int indent) const { + out << IndentFor(indent) << "UNARY(" << op_.value() << ")\n"; + operand_->Print(out, indent + 1); +} diff --git a/tools/gn/parse_tree.h b/tools/gn/parse_tree.h new file mode 100644 index 0000000..09646e5 --- /dev/null +++ b/tools/gn/parse_tree.h @@ -0,0 +1,366 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_PARSE_TREE_H_ +#define TOOLS_GN_PARSE_TREE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/err.h" +#include "tools/gn/token.h" +#include "tools/gn/value.h" + +class AccessorNode; +class BinaryOpNode; +class BlockNode; +class ConditionNode; +class FunctionCallNode; +class IdentifierNode; +class ListNode; +class LiteralNode; +class Scope; +class UnaryOpNode; + +// ParseNode ------------------------------------------------------------------- + +// A node in the AST. +class ParseNode { + public: + ParseNode(); + virtual ~ParseNode(); + + virtual const AccessorNode* AsAccessor() const; + virtual const BinaryOpNode* AsBinaryOp() const; + virtual const BlockNode* AsBlock() const; + virtual const ConditionNode* AsConditionNode() const; + virtual const FunctionCallNode* AsFunctionCall() const; + virtual const IdentifierNode* AsIdentifier() const; + virtual const ListNode* AsList() const; + virtual const LiteralNode* AsLiteral() const; + virtual const UnaryOpNode* AsUnaryOp() const; + + virtual Value Execute(Scope* scope, Err* err) const = 0; + + virtual LocationRange GetRange() const = 0; + + // Returns an error with the given messages and the range set to something + // that indicates this node. + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const = 0; + + // Prints a representation of this node to the given string, indenting + // by the given number of spaces. + virtual void Print(std::ostream& out, int indent) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ParseNode); +}; + +// AccessorNode ---------------------------------------------------------------- + +// Access an array element. +// +// If we need to add support for member variables like "variable.len" I was +// thinking this would also handle that case. +class AccessorNode : public ParseNode { + public: + AccessorNode(); + virtual ~AccessorNode(); + + virtual const AccessorNode* AsAccessor() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + // Base is the thing on the left of the [], currently always required to be + // an identifier token. + const Token& base() const { return base_; } + void set_base(const Token& b) { base_ = b; } + + // Index is the expression inside the []. + const ParseNode* index() const { return index_.get(); } + void set_index(scoped_ptr<ParseNode> i) { index_ = i.Pass(); } + + private: + Token base_; + scoped_ptr<ParseNode> index_; + + DISALLOW_COPY_AND_ASSIGN(AccessorNode); +}; + +// BinaryOpNode ---------------------------------------------------------------- + +class BinaryOpNode : public ParseNode { + public: + BinaryOpNode(); + virtual ~BinaryOpNode(); + + virtual const BinaryOpNode* AsBinaryOp() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + const Token& op() const { return op_; } + void set_op(const Token& t) { op_ = t; } + + const ParseNode* left() const { return left_.get(); } + void set_left(scoped_ptr<ParseNode> left) { + left_ = left.Pass(); + } + + const ParseNode* right() const { return right_.get(); } + void set_right(scoped_ptr<ParseNode> right) { + right_ = right.Pass(); + } + + private: + scoped_ptr<ParseNode> left_; + Token op_; + scoped_ptr<ParseNode> right_; + + DISALLOW_COPY_AND_ASSIGN(BinaryOpNode); +}; + +// BlockNode ------------------------------------------------------------------- + +class BlockNode : public ParseNode { + public: + // Set has_scope if this block introduces a nested scope. + BlockNode(bool has_scope); + virtual ~BlockNode(); + + virtual const BlockNode* AsBlock() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + void set_begin_token(const Token* t) { begin_token_ = t; } + void set_end_token(const Token* t) { end_token_ = t; } + + const std::vector<ParseNode*>& statements() const { return statements_; } + void append_statement(scoped_ptr<ParseNode> s) { + statements_.push_back(s.release()); + } + + // Doesn't create a nested scope. + Value ExecuteBlockInScope(Scope* our_scope, Err* err) const; + + private: + bool has_scope_; + + // Tokens corresponding to { and }, if any (may be NULL). + const Token* begin_token_; + const Token* end_token_; + + // Owning pointers, use unique_ptr when we can use C++11. + std::vector<ParseNode*> statements_; + + DISALLOW_COPY_AND_ASSIGN(BlockNode); +}; + +// ConditionNode --------------------------------------------------------------- + +class ConditionNode : public ParseNode { + public: + ConditionNode(); + virtual ~ConditionNode(); + + virtual const ConditionNode* AsConditionNode() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + void set_if_token(const Token& token) { if_token_ = token; } + + const ParseNode* condition() const { return condition_.get(); } + void set_condition(scoped_ptr<ParseNode> c) { + condition_ = c.Pass(); + } + + const BlockNode* if_true() const { return if_true_.get(); } + void set_if_true(scoped_ptr<BlockNode> t) { + if_true_ = t.Pass(); + } + + // This is either empty, a block (for the else clause), or another + // condition. + const ParseNode* if_false() const { return if_false_.get(); } + void set_if_false(scoped_ptr<ParseNode> f) { + if_false_ = f.Pass(); + } + + private: + // Token corresponding to the "if" string. + Token if_token_; + + scoped_ptr<ParseNode> condition_; // Always non-null. + scoped_ptr<BlockNode> if_true_; // Always non-null. + scoped_ptr<ParseNode> if_false_; // May be null. + + DISALLOW_COPY_AND_ASSIGN(ConditionNode); +}; + +// FunctionCallNode ------------------------------------------------------------ + +class FunctionCallNode : public ParseNode { + public: + FunctionCallNode(); + virtual ~FunctionCallNode(); + + virtual const FunctionCallNode* AsFunctionCall() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + const Token& function() const { return function_; } + void set_function(Token t) { function_ = t; } + + const ListNode* args() const { return args_.get(); } + void set_args(scoped_ptr<ListNode> a) { args_ = a.Pass(); } + + const BlockNode* block() const { return block_.get(); } + void set_block(scoped_ptr<BlockNode> b) { block_ = b.Pass(); } + + private: + Token function_; + scoped_ptr<ListNode> args_; + scoped_ptr<BlockNode> block_; // May be null. + + DISALLOW_COPY_AND_ASSIGN(FunctionCallNode); +}; + +// IdentifierNode -------------------------------------------------------------- + +class IdentifierNode : public ParseNode { + public: + IdentifierNode(); + IdentifierNode(const Token& token); + virtual ~IdentifierNode(); + + virtual const IdentifierNode* AsIdentifier() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + const Token& value() const { return value_; } + void set_value(const Token& t) { value_ = t; } + + private: + Token value_; + + DISALLOW_COPY_AND_ASSIGN(IdentifierNode); +}; + +// ListNode -------------------------------------------------------------------- + +class ListNode : public ParseNode { + public: + ListNode(); + virtual ~ListNode(); + + virtual const ListNode* AsList() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + void set_begin_token(const Token& t) { begin_token_ = t; } + void set_end_token(const Token& t) { end_token_ = t; } + + void append_item(scoped_ptr<ParseNode> s) { + contents_.push_back(s.release()); + } + const std::vector<ParseNode*>& contents() const { return contents_; } + + private: + // Tokens corresponding to the [ and ]. + Token begin_token_; + Token end_token_; + + // Owning pointers, use unique_ptr when we can use C++11. + std::vector<ParseNode*> contents_; + + DISALLOW_COPY_AND_ASSIGN(ListNode); +}; + +// LiteralNode ----------------------------------------------------------------- + +class LiteralNode : public ParseNode { + public: + LiteralNode(); + LiteralNode(const Token& token); + virtual ~LiteralNode(); + + virtual const LiteralNode* AsLiteral() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + const Token& value() const { return value_; } + void set_value(const Token& t) { value_ = t; } + + private: + Token value_; + + DISALLOW_COPY_AND_ASSIGN(LiteralNode); +}; + +// UnaryOpNode ----------------------------------------------------------------- + +class UnaryOpNode : public ParseNode { + public: + UnaryOpNode(); + virtual ~UnaryOpNode(); + + virtual const UnaryOpNode* AsUnaryOp() const OVERRIDE; + virtual Value Execute(Scope* scope, Err* err) const OVERRIDE; + virtual LocationRange GetRange() const OVERRIDE; + virtual Err MakeErrorDescribing( + const std::string& msg, + const std::string& help = std::string()) const OVERRIDE; + virtual void Print(std::ostream& out, int indent) const OVERRIDE; + + const Token& op() const { return op_; } + void set_op(const Token& t) { op_ = t; } + + const ParseNode* operand() const { return operand_.get(); } + void set_operand(scoped_ptr<ParseNode> operand) { + operand_ = operand.Pass(); + } + + private: + Token op_; + scoped_ptr<ParseNode> operand_; + + DISALLOW_COPY_AND_ASSIGN(UnaryOpNode); +}; + +#endif // TOOLS_GN_PARSE_TREE_H_ diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc new file mode 100644 index 0000000..385aa34 --- /dev/null +++ b/tools/gn/parser.cc @@ -0,0 +1,470 @@ +// Copyright (c) 2013 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 "tools/gn/parser.h" + +#include "base/logging.h" +#include "tools/gn/functions.h" +#include "tools/gn/operators.h" +#include "tools/gn/token.h" + +namespace { + +// Returns true if the two tokens are on the same line. We assume they're in +// the same file. +bool IsSameLine(const Token& a, const Token& b) { + DCHECK(a.location().file() == b.location().file()); + return a.location().line_number() == b.location().line_number(); +} + +} // namespace + +Parser::Parser(const std::vector<Token>& tokens, Err* err) + : tokens_(tokens), + err_(err), + cur_(0) { +} + +Parser::~Parser() { +} + +// static +scoped_ptr<ParseNode> Parser::Parse(const std::vector<Token>& tokens, + Err* err) { + Parser p(tokens, err); + return p.ParseBlock(false).PassAs<ParseNode>(); +} + +// static +scoped_ptr<ParseNode> Parser::ParseExpression(const std::vector<Token>& tokens, + Err* err) { + Parser p(tokens, err); + return p.ParseExpression().Pass(); +} + +bool Parser::IsToken(Token::Type type, char* str) const { + if (at_end()) + return false; + return cur_token().type() == type || cur_token().value() == str; +} + +scoped_ptr<AccessorNode> Parser::ParseAccessor() { + scoped_ptr<AccessorNode> accessor(new AccessorNode); + + DCHECK(cur_token().type() == Token::IDENTIFIER); + accessor->set_base(cur_token()); + cur_++; // Skip identifier. + cur_++; // Skip "[" (we know this exists because the existance of this + // token is how the caller knows it's an accessor. + + if (at_end()) { + *err_ = MakeEOFError("Got EOF when looking for list index."); + return scoped_ptr<AccessorNode>(); + } + + // Get the expression. + scoped_ptr<ParseNode> expr = ParseExpression().Pass(); + if (has_error()) + return scoped_ptr<AccessorNode>(); + if (at_end()) { + *err_ = MakeEOFError("Got EOF when looking for list accessor ]"); + return scoped_ptr<AccessorNode>(); + } + accessor->set_index(expr.Pass()); + + // Skip over "]" + if (!cur_token().IsScoperEqualTo("]")) { + *err_ = Err(cur_token(), "Expecting ]", + "You started a list access but didn't terminate it, and instead " + "I fould this\nstupid thing."); + return scoped_ptr<AccessorNode>(); + } + cur_++; + + return accessor.Pass(); +} + +// Blocks at the file scope don't need {} so we have the option to ignore +// them. When need_braces is set, we'll expect a begin an end brace. +// +// block := "{" block_contents "}" +// block_contents := (expression | conditional | block)* +scoped_ptr<BlockNode> Parser::ParseBlock(bool need_braces) { + scoped_ptr<BlockNode> block(new BlockNode(true)); + + // Eat initial { if necessary. + const Token* opening_curly_brace; + if (need_braces) { + if (at_end()) { + *err_ = MakeEOFError("Got EOF when looking for { for block.", + "It should have been after here."); + return scoped_ptr<BlockNode>(); + } else if(!IsScopeBeginScoper(cur_token())) { + *err_ = Err(cur_token(), "Expecting { instead of this thing.", + "THOU SHALT USE CURLY BRACES FOR ALL BLOCKS."); + return scoped_ptr<BlockNode>(); + } + opening_curly_brace = &cur_token(); + block->set_begin_token(opening_curly_brace); + cur_++; + } + + // Loop until EOF or end brace found. + while (!at_end() && !IsScopeEndScoper(cur_token())) { + if (cur_token().IsIdentifierEqualTo("if")) { + // Conditional. + block->append_statement(ParseCondition().PassAs<ParseNode>()); + } else if (IsScopeBeginScoper(cur_token())) { + // Nested block. + block->append_statement(ParseBlock(true).PassAs<ParseNode>()); + } else { + // Everything else is an expression. + block->append_statement(ParseExpression().PassAs<ParseNode>()); + } + if (has_error()) + return scoped_ptr<BlockNode>(); + } + + // Eat the ending "}" if necessary. + if (need_braces) { + if (at_end() || !IsScopeEndScoper(cur_token())) { + *err_ = Err(*opening_curly_brace, "Expecting }", + "I ran headlong into the end of the file looking for the " + "closing brace\ncorresponding to this one."); + return scoped_ptr<BlockNode>(); + } + block->set_end_token(&cur_token()); + cur_++; // Skip past "}". + } + + return block.Pass(); +} + +// conditional := "if (" expression ")" block [else_conditional] +// else_conditional := ("else" block) | ("else" conditional) +scoped_ptr<ConditionNode> Parser::ParseCondition() { + scoped_ptr<ConditionNode> cond(new ConditionNode); + + // Skip past "if". + const Token& if_token = cur_token(); + cond->set_if_token(if_token); + DCHECK(if_token.IsIdentifierEqualTo("if")); + cur_++; + + if (at_end() || !IsFunctionCallArgBeginScoper(cur_token())) { + *err_ = Err(if_token, "Expecting \"(\" after \"if\"", + "Did you think this was Python or something?"); + return scoped_ptr<ConditionNode>(); + } + + // Skip over (. + const Token& open_paren_token = cur_token(); + cur_++; + if (at_end()) { + *err_ = Err(if_token, "Unexpected EOF inside if condition"); + return scoped_ptr<ConditionNode>(); + } + + // Condition inside (). + cond->set_condition(ParseExpression().Pass()); + if (has_error()) + return scoped_ptr<ConditionNode>(); + + if (at_end() || !IsFunctionCallArgEndScoper(cur_token())) { + *err_ = Err(open_paren_token, "Expecting \")\" for \"if\" condition", + "You didn't finish the thought you started here."); + return scoped_ptr<ConditionNode>(); + } + cur_++; // Skip over ) + + // Contents of {}. + cond->set_if_true(ParseBlock(true).Pass()); + if (has_error()) + return scoped_ptr<ConditionNode>(); + + // Optional "else" at the end. + if (!at_end() && cur_token().IsIdentifierEqualTo("else")) { + cur_++; + + // The else may be followed by an if or a block. + if (at_end()) { + *err_ = MakeEOFError("Ran into end of file after \"else\".", + "else, WHAT?!?!?"); + return scoped_ptr<ConditionNode>(); + } + if (cur_token().IsIdentifierEqualTo("if")) { + // "else if() {" + cond->set_if_false(ParseCondition().PassAs<ParseNode>()); + } else if (IsScopeBeginScoper(cur_token())) { + // "else {" + cond->set_if_false(ParseBlock(true).PassAs<ParseNode>()); + } else { + // else <anything else> + *err_ = Err(cur_token(), "Expected \"if\" or \"{\" after \"else\".", + "This is neither of those things."); + return scoped_ptr<ConditionNode>(); + } + } + + if (has_error()) + return scoped_ptr<ConditionNode>(); + return cond.Pass(); +} + +// expression := paren_expression | accessor | identifier | literal | +// funccall | unary_expression | binary_expression +// +// accessor := identifier <non-newline-whitespace>* "[" expression "]" +// +// The "non-newline-whitespace is used to differentiate between this case: +// a[1] +// and this one: +// a +// [1] +// The second one is kind of stupid (since it does nothing with the values) +// but is still legal. +scoped_ptr<ParseNode> Parser::ParseExpression() { + scoped_ptr<ParseNode> expr = ParseExpressionExceptBinaryOperators(); + if (has_error()) + return scoped_ptr<ParseNode>(); + + // That may have hit EOF, in which case we can't have any binary operators. + if (at_end()) + return expr.Pass(); + + // TODO(brettw) handle operator precidence! + // Gobble up all subsequent expressions as long as there are binary + // operators. + + if (IsBinaryOperator(cur_token())) { + scoped_ptr<BinaryOpNode> binary_op(new BinaryOpNode); + binary_op->set_left(expr.Pass()); + const Token& operator_token = cur_token(); + binary_op->set_op(operator_token); + cur_++; + if (at_end()) { + *err_ = Err(operator_token, "Unexpected EOF in expression.", + "I was looking for the right-hand-side of this operator."); + return scoped_ptr<ParseNode>(); + } + binary_op->set_right(ParseExpression().Pass()); + if (has_error()) + return scoped_ptr<ParseNode>(); + return binary_op.PassAs<ParseNode>(); + } + + return expr.Pass(); +} + + +// This internal one does not handle binary operators, since it requires +// looking at the "next" thing. The regular ParseExpression above handles it. +scoped_ptr<ParseNode> Parser::ParseExpressionExceptBinaryOperators() { + if (at_end()) + return scoped_ptr<ParseNode>(); + + const Token& token = cur_token(); + + // Unary expression. + if (IsUnaryOperator(token)) + return ParseUnaryOp().PassAs<ParseNode>(); + + // Parenthesized expressions. + if (token.IsScoperEqualTo("(")) + return ParseParenExpression(); + + // Function calls. + if (token.type() == Token::IDENTIFIER) { + if (has_next_token() && IsFunctionCallArgBeginScoper(next_token())) + return ParseFunctionCall().PassAs<ParseNode>(); + } + + // Lists. + if (token.IsScoperEqualTo("[")) { + return ParseList(Token(Location(), Token::SCOPER, "["), + Token(Location(), Token::SCOPER, "]")).PassAs<ParseNode>(); + } + + // Literals. + if (token.type() == Token::STRING || token.type() == Token::INTEGER) { + cur_++; + return scoped_ptr<ParseNode>(new LiteralNode(token)); + } + + // Accessors. + if (token.type() == Token::IDENTIFIER && + has_next_token() && next_token().IsScoperEqualTo("[") && + IsSameLine(token, next_token())) { + return ParseAccessor().PassAs<ParseNode>(); + } + + // Identifiers. + if (token.type() == Token::IDENTIFIER) { + cur_++; + return scoped_ptr<ParseNode>(new IdentifierNode(token)); + } + + // Handle errors. + if (token.type() == Token::SEPARATOR) { + *err_ = Err(token, "Unexpected comma.", + "You can't put a comma here, it must be in list separating " + "complete\nthoughts."); + } else if (IsScopeBeginScoper(token)) { + *err_ = Err(token, "Unexpected token.", + "You can't put a \"{\" scope here, it must be in a block."); + } else { + *err_ = Err(token, "Unexpected token.", + "I was really hoping for something else here and you let me down."); + } + return scoped_ptr<ParseNode>(); +} + +// function_call := identifier "(" list_contents ")" +// [<non-newline-whitespace>* block] +scoped_ptr<FunctionCallNode> Parser::ParseFunctionCall() { + scoped_ptr<FunctionCallNode> func(new FunctionCallNode); + + const Token& function_token = cur_token(); + func->set_function(function_token); + + // This function should only get called when we know we have a function, + // which only happens when there is a paren following the name. Skip past it. + DCHECK(has_next_token()); + cur_++; // Skip past function name to (. + const Token& open_paren_token = cur_token(); + DCHECK(IsFunctionCallArgBeginScoper(open_paren_token)); + + if (at_end()) { + *err_ = Err(open_paren_token, "Unexpected EOF for function call.", + "You didn't finish the thought you started here."); + return scoped_ptr<FunctionCallNode>(); + } + + // Arguments. + func->set_args(ParseList(Token(Location(), Token::SCOPER, "("), + Token(Location(), Token::SCOPER, ")"))); + if (has_error()) + return scoped_ptr<FunctionCallNode>(); + + // Optional {} after function call for certain functions. The "{" must be on + // the same line as the ")" to disambiguate the case of a function followed + // by a random block just used for scoping purposes. + if (!at_end() && IsScopeBeginScoper(cur_token())) { + const Token& args_end_token = tokens_[cur_ - 1]; + DCHECK(args_end_token.IsScoperEqualTo(")")); + if (IsSameLine(args_end_token, cur_token())) + func->set_block(ParseBlock(true).Pass()); + } + + if (has_error()) + return scoped_ptr<FunctionCallNode>(); + return func.Pass(); +} + +// list := "[" expression* "]" +// list_contents := [(expression ",")* expression [","]] +// +// The list_contents is also used in function calls surrounded by parens, so +// this function takes the tokens that are expected to surround the list. +scoped_ptr<ListNode> Parser::ParseList(const Token& expected_begin, + const Token& expected_end) { + scoped_ptr<ListNode> list(new ListNode); + + const Token& open_bracket_token = cur_token(); + list->set_begin_token(open_bracket_token); + cur_++; // Skip "[" or "(". + + bool need_separator = false; + while(true) { + if (at_end()) { + *err_ = Err(open_bracket_token, "EOF found when parsing list.", + "I expected a \"" + expected_end.value().as_string() + + "\" corresponding to this one."); + return scoped_ptr<ListNode>(); + } + if (cur_token().type() == expected_end.type() && + cur_token().value() == expected_end.value()) { + list->set_end_token(cur_token()); + cur_++; + break; + } + + if (need_separator) { + DCHECK(!list->contents().empty()); + LocationRange prev_item_range = + list->contents().at(list->contents().size() - 1)->GetRange(); + *err_ = Err(prev_item_range.end(), + "Need comma separating items in list.", + "You probably need a comma after this thingy."); + err_->AppendRange(prev_item_range); + return scoped_ptr<ListNode>(); + } + scoped_ptr<ParseNode> expr = ParseExpression().Pass(); + if (has_error()) + return scoped_ptr<ListNode>(); + list->append_item(expr.Pass()); + + need_separator = true; + if (!at_end()) { + // Skip over the separator, marking that we found it. + if (cur_token().type() == Token::SEPARATOR) { + cur_++; + need_separator = false; + } + } + } + return list.Pass(); +} + +// paren_expression := "(" expression ")" +scoped_ptr<ParseNode> Parser::ParseParenExpression() { + const Token& open_paren_token = cur_token(); + cur_++; // Skip over ( + + scoped_ptr<ParseNode> ret = ParseExpression(); + if (has_error()) + return scoped_ptr<ParseNode>(); + + if (at_end()) { + *err_ = Err(open_paren_token, "EOF found when parsing expression.", + "I was looking for a \")\" corresponding to this one."); + return scoped_ptr<ParseNode>(); + } + if (!cur_token().IsScoperEqualTo(")")) { + *err_ = Err(open_paren_token, "Expected \")\" for expression", + "I was looking for a \")\" corresponding to this one."); + return scoped_ptr<ParseNode>(); + } + cur_++; // Skip over ) + return ret.Pass(); +} + +// unary_expression := "!" expression +scoped_ptr<UnaryOpNode> Parser::ParseUnaryOp() { + scoped_ptr<UnaryOpNode> unary(new UnaryOpNode); + + DCHECK(!at_end() && IsUnaryOperator(cur_token())); + const Token& op_token = cur_token(); + unary->set_op(op_token); + cur_++; + + if (at_end()) { + *err_ = Err(op_token, "Expected expression.", + "This operator needs something to operate on."); + return scoped_ptr<UnaryOpNode>(); + } + unary->set_operand(ParseExpression().Pass()); + if (has_error()) + return scoped_ptr<UnaryOpNode>(); + return unary.Pass(); +} + +Err Parser::MakeEOFError(const std::string& message, + const std::string& help) const { + if (tokens_.empty()) + return Err(Location(NULL, 1, 1), message, help); + + const Token& last = tokens_[tokens_.size() - 1]; + return Err(last, message, help); +} diff --git a/tools/gn/parser.h b/tools/gn/parser.h new file mode 100644 index 0000000..252b801 --- /dev/null +++ b/tools/gn/parser.h @@ -0,0 +1,81 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_PARSER_H_ +#define TOOLS_GN_PARSER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/err.h" +#include "tools/gn/parse_tree.h" + +// Parses a series of tokens. The resulting AST will refer to the tokens passed +// to the input, so the tokens an the file data they refer to must outlive your +// use of the ParseNode. +class Parser { + public: + // Will return a null pointer and set the err on error. + static scoped_ptr<ParseNode> Parse(const std::vector<Token>& tokens, + Err* err); + + // Alternative to parsing that assumes the input is an expression. + static scoped_ptr<ParseNode> ParseExpression(const std::vector<Token>& tokens, + Err* err); + + private: + // Vector must be valid for lifetime of call. + Parser(const std::vector<Token>& tokens, Err* err); + ~Parser(); + + scoped_ptr<AccessorNode> ParseAccessor(); + scoped_ptr<BlockNode> ParseBlock(bool need_braces); + scoped_ptr<ConditionNode> ParseCondition(); + scoped_ptr<ParseNode> ParseExpression(); + scoped_ptr<ParseNode> ParseExpressionExceptBinaryOperators(); + scoped_ptr<FunctionCallNode> ParseFunctionCall(); + scoped_ptr<ListNode> ParseList(const Token& expected_begin, + const Token& expected_end); + scoped_ptr<ParseNode> ParseParenExpression(); + scoped_ptr<UnaryOpNode> ParseUnaryOp(); + + bool IsToken(Token::Type type, char* str) const; + + // Gets an error corresponding to the last token. When we hit an EOF + // usually we've already gone beyond the end (or maybe there are no tokens) + // so there is some tricky logic to report this. + Err MakeEOFError(const std::string& message, + const std::string& help = std::string()) const; + + const Token& cur_token() const { return tokens_[cur_]; } + + bool done() const { return at_end() || has_error(); } + bool at_end() const { return cur_ >= tokens_.size(); } + bool has_error() const { return err_->has_error(); } + + const Token& next_token() const { return tokens_[cur_ + 1]; } + bool has_next_token() const { return cur_ + 1 < tokens_.size(); } + + const std::vector<Token>& tokens_; + + Err* err_; + + // Current index into the tokens. + size_t cur_; + + FRIEND_TEST_ALL_PREFIXES(Parser, BinaryOp); + FRIEND_TEST_ALL_PREFIXES(Parser, Block); + FRIEND_TEST_ALL_PREFIXES(Parser, Condition); + FRIEND_TEST_ALL_PREFIXES(Parser, Expression); + FRIEND_TEST_ALL_PREFIXES(Parser, FunctionCall); + FRIEND_TEST_ALL_PREFIXES(Parser, List); + FRIEND_TEST_ALL_PREFIXES(Parser, ParenExpression); + FRIEND_TEST_ALL_PREFIXES(Parser, UnaryOp); + + DISALLOW_COPY_AND_ASSIGN(Parser); +}; + +#endif // TOOLS_GN_PARSER_H_ diff --git a/tools/gn/parser_unittest.cc b/tools/gn/parser_unittest.cc new file mode 100644 index 0000000..3fd8ebe --- /dev/null +++ b/tools/gn/parser_unittest.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2013 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 <iostream> +#include <sstream> + +#include "testing/gtest/include/gtest/gtest.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parser.h" +#include "tools/gn/tokenizer.h" + +namespace { + +bool GetTokens(const InputFile* input, std::vector<Token>* result) { + result->clear(); + Err err; + *result = Tokenizer::Tokenize(input, &err); + return !err.has_error(); +} + +bool IsIdentifierEqual(const ParseNode* node, const char* val) { + if (!node) + return false; + const IdentifierNode* ident = node->AsIdentifier(); + if (!ident) + return false; + return ident->value().value() == val; +} + +bool IsLiteralEqual(const ParseNode* node, const char* val) { + if (!node) + return false; + const LiteralNode* lit = node->AsLiteral(); + if (!lit) + return false; + return lit->value().value() == val; +} + +// Returns true if the given node as a simple assignment to a given value. +bool IsAssignment(const ParseNode* node, const char* ident, const char* value) { + if (!node) + return false; + const BinaryOpNode* binary = node->AsBinaryOp(); + if (!binary) + return false; + return binary->op().IsOperatorEqualTo("=") && + IsIdentifierEqual(binary->left(), ident) && + IsLiteralEqual(binary->right(), value); +} + +// Returns true if the given node is a block with one assignment statement. +bool IsBlockWithAssignment(const ParseNode* node, + const char* ident, const char* value) { + if (!node) + return false; + const BlockNode* block = node->AsBlock(); + if (!block) + return false; + if (block->statements().size() != 1) + return false; + return IsAssignment(block->statements()[0], ident, value); +} + +void DoParserPrintTest(const char* input, const char* expected) { + std::vector<Token> tokens; + InputFile input_file(SourceFile("/test")); + input_file.SetContents(input); + ASSERT_TRUE(GetTokens(&input_file, &tokens)); + + Err err; + scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err); + ASSERT_TRUE(result); + + std::ostringstream collector; + result->Print(collector, 0); + + EXPECT_EQ(expected, collector.str()); +} + +// Expects the tokenizer or parser to identify an error at the given line and +// character. +void DoParserErrorTest(const char* input, int err_line, int err_char) { + InputFile input_file(SourceFile("/test")); + input_file.SetContents(input); + + Err err; + std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err); + if (!err.has_error()) { + scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err); + ASSERT_FALSE(result); + ASSERT_TRUE(err.has_error()); + } + + EXPECT_EQ(err_line, err.location().line_number()); + EXPECT_EQ(err_char, err.location().char_offset()); +} + +} // namespace + +TEST(Parser, BinaryOp) { + std::vector<Token> tokens; + + // Simple set expression. + InputFile expr_input(SourceFile("/test")); + expr_input.SetContents("a=2"); + ASSERT_TRUE(GetTokens(&expr_input, &tokens)); + Err err; + Parser set(tokens, &err); + scoped_ptr<ParseNode> expr = set.ParseExpression(); + ASSERT_TRUE(expr); + + const BinaryOpNode* binary_op = expr->AsBinaryOp(); + ASSERT_TRUE(binary_op); + + EXPECT_TRUE(binary_op->left()->AsIdentifier()); + + EXPECT_TRUE(binary_op->op().type() == Token::OPERATOR); + EXPECT_TRUE(binary_op->op().value() == "="); + + EXPECT_TRUE(binary_op->right()->AsLiteral()); +} + +TEST(Parser, Condition) { + std::vector<Token> tokens; + + InputFile cond_input(SourceFile("/test")); + cond_input.SetContents("if(1) { a = 2 }"); + ASSERT_TRUE(GetTokens(&cond_input, &tokens)); + Err err; + Parser simple_if(tokens, &err); + scoped_ptr<ConditionNode> cond = simple_if.ParseCondition(); + ASSERT_TRUE(cond); + + EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1")); + EXPECT_FALSE(cond->if_false()); // No else block. + EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2")); + + // Now try a complicated if/else if/else one. + InputFile complex_if_input(SourceFile("/test")); + complex_if_input.SetContents( + "if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }"); + ASSERT_TRUE(GetTokens(&complex_if_input, &tokens)); + Parser complex_if(tokens, &err); + cond = complex_if.ParseCondition(); + ASSERT_TRUE(cond); + + EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1")); + EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2")); + + ASSERT_TRUE(cond->if_false()); + const ConditionNode* nested_cond = cond->if_false()->AsConditionNode(); + ASSERT_TRUE(nested_cond); + EXPECT_TRUE(IsLiteralEqual(nested_cond->condition(), "0")); + EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_true(), "a", "3")); + EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_false(), "a", "4")); +} + +TEST(Parser, FunctionCall) { + const char* input = "foo(a, 1, 2,) bar()"; + const char* expected = + "BLOCK\n" + " FUNCTION(foo)\n" + " LIST\n" + " IDENTIFIER(a)\n" + " LITERAL(1)\n" + " LITERAL(2)\n" + " FUNCTION(bar)\n" + " LIST\n"; + DoParserPrintTest(input, expected); +} + +TEST(Parser, ParenExpression) { + const char* input = "(foo(1)) + (a + b)"; + const char* expected = + "BLOCK\n" + " BINARY(+)\n" + " FUNCTION(foo)\n" + " LIST\n" + " LITERAL(1)\n" + " BINARY(+)\n" + " IDENTIFIER(a)\n" + " IDENTIFIER(b)\n"; + DoParserPrintTest(input, expected); + DoParserErrorTest("(a +", 1, 4); +} + +TEST(Parser, UnaryOp) { + std::vector<Token> tokens; + + InputFile ident_input(SourceFile("/test")); + ident_input.SetContents("!foo"); + ASSERT_TRUE(GetTokens(&ident_input, &tokens)); + Err err; + Parser ident(tokens, &err); + scoped_ptr<UnaryOpNode> op = ident.ParseUnaryOp(); + + ASSERT_TRUE(op); + EXPECT_TRUE(op->op().type() == Token::OPERATOR); + EXPECT_TRUE(op->op().value() == "!"); +} + +TEST(Parser, CompleteFunction) { + const char* input = + "cc_test(\"foo\") {\n" + " sources = [\n" + " \"foo.cc\",\n" + " \"foo.h\"\n" + " ]\n" + " dependencies = [\n" + " \"base\"\n" + " ]\n" + "}\n"; + const char* expected = + "BLOCK\n" + " FUNCTION(cc_test)\n" + " LIST\n" + " LITERAL(\"foo\")\n" + " BLOCK\n" + " BINARY(=)\n" + " IDENTIFIER(sources)\n" + " LIST\n" + " LITERAL(\"foo.cc\")\n" + " LITERAL(\"foo.h\")\n" + " BINARY(=)\n" + " IDENTIFIER(dependencies)\n" + " LIST\n" + " LITERAL(\"base\")\n"; + DoParserPrintTest(input, expected); +} + +TEST(Parser, FunctionWithConditional) { + const char* input = + "cc_test(\"foo\") {\n" + " sources = [\"foo.cc\"]\n" + " if (OS == \"mac\") {\n" + " sources += \"bar.cc\"\n" + " } else if (OS == \"win\") {\n" + " sources -= [\"asd.cc\", \"foo.cc\"]\n" + " } else {\n" + " dependencies += [\"bar.cc\"]\n" + " }\n" + "}\n"; + const char* expected = + "BLOCK\n" + " FUNCTION(cc_test)\n" + " LIST\n" + " LITERAL(\"foo\")\n" + " BLOCK\n" + " BINARY(=)\n" + " IDENTIFIER(sources)\n" + " LIST\n" + " LITERAL(\"foo.cc\")\n" + " CONDITION\n" + " BINARY(==)\n" + " IDENTIFIER(OS)\n" + " LITERAL(\"mac\")\n" + " BLOCK\n" + " BINARY(+=)\n" + " IDENTIFIER(sources)\n" + " LITERAL(\"bar.cc\")\n" + " CONDITION\n" + " BINARY(==)\n" + " IDENTIFIER(OS)\n" + " LITERAL(\"win\")\n" + " BLOCK\n" + " BINARY(-=)\n" + " IDENTIFIER(sources)\n" + " LIST\n" + " LITERAL(\"asd.cc\")\n" + " LITERAL(\"foo.cc\")\n" + " BLOCK\n" + " BINARY(+=)\n" + " IDENTIFIER(dependencies)\n" + " LIST\n" + " LITERAL(\"bar.cc\")\n"; + DoParserPrintTest(input, expected); +} + +TEST(Parser, NestedBlocks) { + const char* input = "{cc_test(\"foo\") {{foo=1}{}}}"; + const char* expected = + "BLOCK\n" + " BLOCK\n" + " FUNCTION(cc_test)\n" + " LIST\n" + " LITERAL(\"foo\")\n" + " BLOCK\n" + " BLOCK\n" + " BINARY(=)\n" + " IDENTIFIER(foo)\n" + " LITERAL(1)\n" + " BLOCK\n"; + DoParserPrintTest(input, expected); +} + +TEST(Parser, List) { + const char* input = "[] a = [1,asd,] b = [1, 2+3 - foo]"; + const char* expected = + "BLOCK\n" + " LIST\n" + " BINARY(=)\n" + " IDENTIFIER(a)\n" + " LIST\n" + " LITERAL(1)\n" + " IDENTIFIER(asd)\n" + " BINARY(=)\n" + " IDENTIFIER(b)\n" + " LIST\n" + " LITERAL(1)\n" + " BINARY(+)\n" + " LITERAL(2)\n" + " BINARY(-)\n" + " LITERAL(3)\n" + " IDENTIFIER(foo)\n"; + DoParserPrintTest(input, expected); + + DoParserErrorTest("[a, 2+,]", 1, 7); + DoParserErrorTest("[,]", 1, 2); + DoParserErrorTest("[a,,]", 1, 4); +} + +TEST(Parser, UnterminatedBlock) { + DoParserErrorTest("hello {", 1, 7); +} + +TEST(Parser, BadlyTerminatedNumber) { + DoParserErrorTest("1234z", 1, 5); +} diff --git a/tools/gn/path_output.cc b/tools/gn/path_output.cc new file mode 100644 index 0000000..f67b3d4 --- /dev/null +++ b/tools/gn/path_output.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2013 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 "tools/gn/path_output.h" + +#include "build/build_config.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/output_file.h" +#include "tools/gn/string_utils.h" + +PathOutput::PathOutput(const SourceDir& current_dir, + EscapingMode escaping, + bool convert_slashes) + : current_dir_(current_dir) { + inverse_current_dir_ = InvertDir(current_dir_); + + options_.mode = escaping; + options_.convert_slashes = convert_slashes; + options_.inhibit_quoting = false; + + if (convert_slashes) + ConvertPathToSystem(&inverse_current_dir_); +} + +PathOutput::~PathOutput() { +} + +void PathOutput::WriteFile(std::ostream& out, const SourceFile& file) const { + WritePathStr(out, file.value()); +} + +void PathOutput::WriteDir(std::ostream& out, + const SourceDir& dir, + DirSlashEnding slash_ending) const { + if (dir.value() == "/") { + // Writing system root is always a slash (this will normally only come up + // on Posix systems). + out << "/"; + } else if (dir.value() == "//") { + // Writing out the source root. + if (slash_ending == DIR_NO_LAST_SLASH) { + // The inverse_current_dir_ will contain a [back]slash at the end, so we + // can't just write it out. + if (inverse_current_dir_.empty()) { + out << "."; + } else { + out.write(inverse_current_dir_.c_str(), + inverse_current_dir_.size() - 1); + } + } else { + if (inverse_current_dir_.empty()) + out << "./"; + else + out << inverse_current_dir_; + } + } else if (slash_ending == DIR_INCLUDE_LAST_SLASH) { + WritePathStr(out, dir.value()); + } else { + // DIR_NO_LAST_SLASH mode, just trim the last char. + WritePathStr(out, base::StringPiece(dir.value().data(), + dir.value().size() - 1)); + } +} + +void PathOutput::WriteFile(std::ostream& out, const OutputFile& file) const { + // Here we assume that the path is already preprocessed. + EscapeStringToStream(out, file.value(), options_); +} + +void PathOutput::WriteSourceRelativeString( + std::ostream& out, + const base::StringPiece& str) const { + // Input begins with two slashes, is relative to source root. Strip off + // the two slashes when cat-ing it. + if (options_.mode == ESCAPE_SHELL) { + // Shell escaping needs an intermediate string since it may end up + // quoting the whole thing. On Windows, the slashes may already be + // converted to backslashes in inverse_current_dir_, but we assume that on + // Windows the escaper won't try to then escape the preconverted + // backslashes and will just pass them, so this is fine. + std::string intermediate; + intermediate.reserve(inverse_current_dir_.size() + str.size()); + intermediate.assign(inverse_current_dir_.c_str(), + inverse_current_dir_.size()); + intermediate.append(str.data(), str.size()); + + EscapeStringToStream(out, + base::StringPiece(intermediate.c_str(), intermediate.size()), + options_); + } else { + // Ninja (and none) escaping can avoid the intermediate string and + // reprocessing of the inverse_current_dir_. + out << inverse_current_dir_; + EscapeStringToStream(out, str, options_); + } +} + +void PathOutput::WritePathStr(std::ostream& out, + const base::StringPiece& str) const { + DCHECK(str.size() > 0 && str[0] == '/'); + + if (str.size() >= 2 && str[1] == '/') { + WriteSourceRelativeString(out, str.substr(2)); + } else { + // Input begins with one slash, don't write the current directory since + // it's system-absolute. +#if defined(OS_WIN) + // On Windows, trim the leading slash, since the input for absolute + // paths will look like "/C:/foo/bar.txt". + EscapeStringToStream(out, str.substr(1), options_); +#else + EscapeStringToStream(out, str, options_); +#endif + } +} diff --git a/tools/gn/path_output.h b/tools/gn/path_output.h new file mode 100644 index 0000000..00bdbee --- /dev/null +++ b/tools/gn/path_output.h @@ -0,0 +1,80 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_PATH_OUTPUT_H_ +#define TOOLS_GN_PATH_OUTPUT_H_ + +#include <iosfwd> +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "tools/gn/escape.h" +#include "tools/gn/source_dir.h" + +class OutputFile; +class SourceFile; + +// Writes file names to streams assuming a certain input directory and +// escaping rules. This gives us a central place for managing this state. +class PathOutput { + public: + // Controls whether writing directory names include the trailing slash. + // Often we don't want the trailing slash when writing out to a command line, + // especially on Windows where it's a backslash and might be interpreted as + // escaping the thing following it. + enum DirSlashEnding { + DIR_INCLUDE_LAST_SLASH, + DIR_NO_LAST_SLASH, + }; + + PathOutput(const SourceDir& current_dir, + EscapingMode escaping, + bool convert_slashes); + ~PathOutput(); + + // Read-only since inverse_current_dir_ is computed depending on this. + EscapingMode escaping_mode() const { return options_.mode; } + + // When true, converts slashes to the system-type path separators (on + // Windows, this is a backslash, this is a NOP otherwise). + // + // Read-only since inverse_current_dir_ is computed depending on this. + bool convert_slashes_to_system() const { return options_.convert_slashes; } + + // When the output escaping is ESCAPE_SHELL, the escaper will normally put + // quotes around suspect things. If this value is set to true, we'll disable + // the quoting feature. This means that in ESCAPE_SHELL mode, strings with + // spaces in them qon't be quoted. This mode is for when quoting is done at + // some higher-level. Defaults to false. + bool inhibit_quoting() const { return options_.inhibit_quoting; } + void set_inhibit_quoting(bool iq) { options_.inhibit_quoting = iq; } + + void WriteFile(std::ostream& out, const SourceFile& file) const; + void WriteFile(std::ostream& out, const OutputFile& file) const; + void WriteDir(std::ostream& out, + const SourceDir& dir, + DirSlashEnding slash_ending) const; + + // Backend for WriteFile and WriteDir. This appends the given file or + // directory string to the file. + void WritePathStr(std::ostream& out, const base::StringPiece& str) const; + + private: + // Takes the given string and writes it out, appending to the inverse + // current dir. This assumes leading slashes have been trimmed. + void WriteSourceRelativeString(std::ostream& out, + const base::StringPiece& str) const; + + SourceDir current_dir_; + + // Uses system slashes if convert_slashes_to_system_. + std::string inverse_current_dir_; + + // Since the inverse_current_dir_ depends on some of these, we don't expose + // this directly to modification. + EscapeOptions options_; +}; + +#endif // TOOLS_GN_PATH_OUTPUT_H_ diff --git a/tools/gn/path_output_unittest.cc b/tools/gn/path_output_unittest.cc new file mode 100644 index 0000000..5133b79 --- /dev/null +++ b/tools/gn/path_output_unittest.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2013 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 <sstream> + +#include "testing/gtest/include/gtest/gtest.h" +#include "tools/gn/path_output.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" + +TEST(PathOutput, Basic) { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_NONE, false); + { + // Normal source-root path. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/bar.cc")); + EXPECT_EQ("../../foo/bar.cc", out.str()); + } + { + // File in the root dir. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo.cc")); + EXPECT_EQ("../../foo.cc", out.str()); + } +#if defined(OS_WIN) + { + // System-absolute path. + std::ostringstream out; + writer.WriteFile(out, SourceFile("/C:/foo/bar.cc")); + EXPECT_EQ("C:/foo/bar.cc", out.str()); + } +#else + { + // System-absolute path. + std::ostringstream out; + writer.WriteFile(out, SourceFile("/foo/bar.cc")); + EXPECT_EQ("/foo/bar.cc", out.str()); + } +#endif +} + +// Same as basic but the output dir is the root. +TEST(PathOutput, BasicInRoot) { + SourceDir build_dir("//"); + PathOutput writer(build_dir, ESCAPE_NONE, false); + { + // Normal source-root path. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/bar.cc")); + EXPECT_EQ("foo/bar.cc", out.str()); + } + { + // File in the root dir. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo.cc")); + EXPECT_EQ("foo.cc", out.str()); + } +} + +TEST(PathOutput, NinjaEscaping) { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_NINJA, false); + { + // Spaces and $ in filenames. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/foo bar$.cc")); + EXPECT_EQ("../../foo/foo$ bar$$.cc", out.str()); + } + { + // Not other weird stuff + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/\"foo\\bar\".cc")); + EXPECT_EQ("../../foo/\"foo\\bar\".cc", out.str()); + } +} + +TEST(PathOutput, ShellEscaping) { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_SHELL, false); + { + // Spaces in filenames should get quoted. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/foo bar.cc")); + EXPECT_EQ("\"../../foo/foo bar.cc\"", out.str()); + } + { + // Quotes should get blackslash-escaped. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/\"foobar\".cc")); + EXPECT_EQ("../../foo/\\\"foobar\\\".cc", out.str()); + } + { + // Backslashes should get escaped on non-Windows and preserved on Windows. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo\\bar.cc")); +#if defined(OS_WIN) + EXPECT_EQ("../../foo\\bar.cc", out.str()); +#else + EXPECT_EQ("../../foo\\\\bar.cc", out.str()); +#endif + } +} + +TEST(PathOutput, SlashConversion) { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_NINJA, true); + { + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/bar.cc")); +#if defined(OS_WIN) + EXPECT_EQ("..\\..\\foo\\bar.cc", out.str()); +#else + EXPECT_EQ("../../foo/bar.cc", out.str()); +#endif + } +} + +TEST(PathOutput, InhibitQuoting) { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_SHELL, false); + writer.set_inhibit_quoting(true); + { + // We should get unescaped spaces in the output with no quotes. + std::ostringstream out; + writer.WriteFile(out, SourceFile("//foo/foo bar.cc")); + EXPECT_EQ("../../foo/foo bar.cc", out.str()); + } +} + +TEST(PathOutput, WriteDir) { + { + SourceDir build_dir("//out/Debug/"); + PathOutput writer(build_dir, ESCAPE_NINJA, false); + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("//foo/bar/"), + PathOutput::DIR_INCLUDE_LAST_SLASH); + EXPECT_EQ("../../foo/bar/", out.str()); + } + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("//foo/bar/"), + PathOutput::DIR_NO_LAST_SLASH); + EXPECT_EQ("../../foo/bar", out.str()); + } + + // Output source root dir. + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("//"), + PathOutput::DIR_INCLUDE_LAST_SLASH); + EXPECT_EQ("../../", out.str()); + } + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("//"), + PathOutput::DIR_NO_LAST_SLASH); + EXPECT_EQ("../..", out.str()); + } + + // Output system root dir. + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("/"), + PathOutput::DIR_INCLUDE_LAST_SLASH); + EXPECT_EQ("/", out.str()); + } + { + std::ostringstream out; + writer.WriteDir(out, SourceDir("/"), + PathOutput::DIR_NO_LAST_SLASH); + EXPECT_EQ("/", out.str()); + } + } + { + // Empty build dir writer. + PathOutput root_writer(SourceDir("//"), ESCAPE_NINJA, false); + { + std::ostringstream out; + root_writer.WriteDir(out, SourceDir("//"), + PathOutput::DIR_INCLUDE_LAST_SLASH); + EXPECT_EQ("./", out.str()); + } + { + std::ostringstream out; + root_writer.WriteDir(out, SourceDir("//"), + PathOutput::DIR_NO_LAST_SLASH); + EXPECT_EQ(".", out.str()); + } + } +} diff --git a/tools/gn/pattern.cc b/tools/gn/pattern.cc new file mode 100644 index 0000000..cc08b2c --- /dev/null +++ b/tools/gn/pattern.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2013 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 "tools/gn/pattern.h" + +#include "tools/gn/value.h" + +namespace { + +void ParsePattern(const std::string& s, std::vector<Pattern::Subrange>* out) { + // Set when the last subrange is a literal so we can just append when we + // find another literal. + Pattern::Subrange* last_literal = NULL; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '*') { + // Don't allow two **. + if (out->size() == 0 || + (*out)[out->size() - 1].type != Pattern::Subrange::ANYTHING) + out->push_back(Pattern::Subrange(Pattern::Subrange::ANYTHING)); + last_literal = NULL; + } else if (s[i] == '\\') { + if (i < s.size() - 1 && s[i + 1] == 'b') { + // "\b" means path boundary. + i++; + out->push_back(Pattern::Subrange(Pattern::Subrange::PATH_BOUNDARY)); + last_literal = NULL; + } else { + // Backslash + anything else means that literal char. + if (!last_literal) { + out->push_back(Pattern::Subrange(Pattern::Subrange::LITERAL)); + last_literal = &(*out)[out->size() - 1]; + } + if (i < s.size() - 1) { + i++; + last_literal->literal.push_back(s[i]); + } else { + // Single backslash at end, use literal backslash. + last_literal->literal.push_back('\\'); + } + } + } else { + if (!last_literal) { + out->push_back(Pattern::Subrange(Pattern::Subrange::LITERAL)); + last_literal = &(*out)[out->size() - 1]; + } + last_literal->literal.push_back(s[i]); + } + } +} + +} // namespace + +Pattern::Pattern(const std::string& s) { + ParsePattern(s, &subranges_); + is_suffix_ = + (subranges_.size() == 2 && + subranges_[0].type == Subrange::ANYTHING && + subranges_[1].type == Subrange::LITERAL); +} + +Pattern::~Pattern() { +} + +bool Pattern::MatchesString(const std::string& s) const { + // Empty pattern matches only empty string. + if (subranges_.empty()) + return s.empty(); + + if (is_suffix_) { + const std::string& suffix = subranges_[1].literal; + if (suffix.size() > s.size()) + return false; // Too short. + return s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + return RecursiveMatch(s, 0, 0, true); +} + +// We assume the number of ranges is small so recursive is always reasonable. +// Could be optimized to only be recursive for *. +bool Pattern::RecursiveMatch(const std::string& s, + size_t begin_char, + size_t subrange_index, + bool allow_implicit_path_boundary) const { + if (subrange_index >= subranges_.size()) { + // Hit the end of our subranges, the text should also be at the end for a + // match. + return begin_char == s.size(); + } + + const Subrange& sr = subranges_[subrange_index]; + switch (sr.type) { + case Subrange::LITERAL: { + if (s.size() - begin_char < sr.literal.size()) + return false; // Not enough room. + if (s.compare(begin_char, sr.literal.size(), sr.literal) != 0) + return false; // Literal doesn't match. + + // Recursively check the next one. + return RecursiveMatch(s, begin_char + sr.literal.size(), + subrange_index + 1, true); + } + + case Subrange::PATH_BOUNDARY: { + // When we can accept an implicit path boundary, we have to check both + // a match of the literal and the implicit one. + if (allow_implicit_path_boundary && + (begin_char == 0 || begin_char == s.size())) { + // At implicit path boundary, see if the rest of the pattern matches. + if (RecursiveMatch(s, begin_char, subrange_index + 1, false)) + return true; + } + + // Check for a literal "/". + if (begin_char < s.size() && s[begin_char] == '/') { + // At explicit boundary, see if the rest of the pattern matches. + if (RecursiveMatch(s, begin_char + 1, subrange_index + 1, true)) + return true; + } + return false; + } + + case Subrange::ANYTHING: { + if (subrange_index == subranges_.size() - 1) + return true; // * at the end, consider it matching. + + size_t min_next_size = sr.MinSize(); + + // We don't care about exactly what matched as long as there was a match, + // so we can do this front-to-back. If we needed the match, we would + // normally want "*" to be greedy so would work backwards. + for (size_t i = begin_char; i < s.size() - min_next_size; i++) { + // Note: this could probably be faster by detecting the type of the + // next match in advance and checking for a match in this loop rather + // than doing a full recursive call for each character. + if (RecursiveMatch(s, i, subrange_index + 1, true)) + return true; + } + return false; + } + + default: + NOTREACHED(); + } + + return false; +} + +PatternList::PatternList() { +} + +PatternList::~PatternList() { +} + +void PatternList::SetFromValue(const Value& v, Err* err) { + patterns_.clear(); + + if (v.type() != Value::LIST) { + *err = Err(v.origin(), "This value must be a list."); + return; + } + + const std::vector<Value>& list = v.list_value(); + for (size_t i = 0; i < list.size(); i++) { + if (!list[i].VerifyTypeIs(Value::STRING, err)) + return; + patterns_.push_back(Pattern(list[i].string_value())); + } +} + +bool PatternList::MatchesString(const std::string& s) const { + for (size_t i = 0; i < patterns_.size(); i++) { + if (patterns_[i].MatchesString(s)) + return true; + } + return false; +} + +bool PatternList::MatchesValue(const Value& v) const { + if (v.type() == Value::STRING) + return MatchesString(v.string_value()); + return false; +} diff --git a/tools/gn/pattern.h b/tools/gn/pattern.h new file mode 100644 index 0000000..582cfea --- /dev/null +++ b/tools/gn/pattern.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_PATTERN_H_ +#define TOOLS_GN_PATTERN_H_ + +#include <string> +#include <vector> + +#include "tools/gn/value.h" + +class Pattern { + public: + struct Subrange { + enum Type { + LITERAL, // Matches exactly the contents of the string. + ANYTHING, // * (zero or more chars). + PATH_BOUNDARY // '/' or beginning of string. + }; + + Subrange(Type t, const std::string& l = std::string()) + : type(t), + literal(l) { + } + + // Returns the minimum number of chars that this subrange requires. + size_t MinSize() const { + switch (type) { + case LITERAL: + return literal.size(); + case ANYTHING: + return 0; + case PATH_BOUNDARY: + return 0; // Can match beginning or end of string, which is 0 len. + default: + return 0; + } + } + + Type type; + + // When type == LITERAL this is the text to match. + std::string literal; + }; + + Pattern(const std::string& s); + ~Pattern(); + + // Returns true if the current pattern matches the given string. + bool MatchesString(const std::string& s) const; + + private: + // allow_implicit_path_boundary determines if a path boundary should accept + // matches at the beginning or end of the string. + bool RecursiveMatch(const std::string& s, + size_t begin_char, + size_t subrange_index, + bool allow_implicit_path_boundary) const; + + std::vector<Subrange> subranges_; + + // Set to true when the subranges are "*foo" ("ANYTHING" followed by a + // literal). This covers most patterns so we optimize for this. + bool is_suffix_; +}; + +class PatternList { + public: + PatternList(); + ~PatternList(); + + bool is_empty() const { return patterns_.empty(); } + + // Initializes the pattern list from a give list of pattern strings. Sets + // |*err| on failure. + void SetFromValue(const Value& v, Err* err); + + bool MatchesString(const std::string& s) const; + bool MatchesValue(const Value& v) const; + + private: + std::vector<Pattern> patterns_; +}; + +#endif // TOOLS_GN_PATTERN_H_ diff --git a/tools/gn/pattern_unittest.cc b/tools/gn/pattern_unittest.cc new file mode 100644 index 0000000..e9ffea6 --- /dev/null +++ b/tools/gn/pattern_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2013 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/pattern.h" + +namespace { + +struct Case { + const char* pattern; + const char* candidate; + bool expected_match; +}; + +} // namespace + +TEST(Pattern, Matches) { + Case pattern_cases[] = { + // Empty pattern matches only empty string. + { "", "", true }, + { "", "foo", false }, + // Exact matches. + { "foo", "foo", true }, + { "foo", "bar", false }, + // Path boundaries. + { "\\b", "", true }, + { "\\b", "/", true }, + { "\\b\\b", "/", true }, + { "\\b\\b\\b", "", false }, + { "\\b\\b\\b", "/", true }, + { "\\b", "//", false }, + { "\\bfoo\\b", "foo", true }, + { "\\bfoo\\b", "/foo/", true }, + { "\\b\\bfoo", "/foo", true }, + // * + { "*", "", true }, + { "*", "foo", true }, + { "*foo", "foo", true }, + { "*foo", "gagafoo", true }, + { "*foo", "gagafoob", false }, + { "foo*bar", "foobar", true }, + { "foo*bar", "foo-bar", true }, + { "foo*bar", "foolalalalabar", true }, + { "foo*bar", "foolalalalabaz", false }, + { "*a*b*c*d*", "abcd", true }, + { "*a*b*c*d*", "1a2b3c4d5", true }, + { "*a*b*c*d*", "1a2b3c45", false }, + { "*\\bfoo\\b*", "foo", true }, + { "*\\bfoo\\b*", "/foo/", true }, + { "*\\bfoo\\b*", "foob", false }, + { "*\\bfoo\\b*", "lala/foo/bar/baz", true }, + }; + for (size_t i = 0; i < arraysize(pattern_cases); i++) { + const Case& c = pattern_cases[i]; + Pattern pattern(c.pattern); + bool result = pattern.MatchesString(c.candidate); + EXPECT_EQ(c.expected_match, result) << i << ": \"" << c.pattern + << "\", \"" << c.candidate << "\""; + } +} diff --git a/tools/gn/scheduler.cc b/tools/gn/scheduler.cc new file mode 100644 index 0000000..33c8f1a --- /dev/null +++ b/tools/gn/scheduler.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2013 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 "tools/gn/scheduler.h" + +#include "base/bind.h" +#include "tools/gn/ninja_target_writer.h" +#include "tools/gn/standard_out.h" + +Scheduler* g_scheduler = NULL; + +Scheduler::Scheduler() + : pool_(new base::SequencedWorkerPool(32, "worker_")), + input_file_manager_(new InputFileManager), + verbose_logging_(false), + work_count_(0), + is_failed_(false) { + g_scheduler = this; +} + +Scheduler::~Scheduler() { + g_scheduler = NULL; +} + +bool Scheduler::Run() { + runner_.Run(); + pool_->Shutdown(); + return !is_failed(); +} + +void Scheduler::Log(const std::string& verb, const std::string& msg) { + if (base::MessageLoop::current() == &main_loop_) { + LogOnMainThread(verb, msg); + } else { + // The run loop always joins on the sub threads, so the lifetime of this + // object outlives the invocations of this function, hence "unretained". + main_loop_.PostTask(FROM_HERE, + base::Bind(&Scheduler::LogOnMainThread, + base::Unretained(this), verb, msg)); + } +} + +void Scheduler::FailWithError(const Err& err) { + DCHECK(err.has_error()); + { + base::AutoLock lock(lock_); + + if (is_failed_) + return; // Ignore errors once we see one. + is_failed_ = true; + } + + if (base::MessageLoop::current() == &main_loop_) { + FailWithErrorOnMainThread(err); + } else { + // The run loop always joins on the sub threads, so the lifetime of this + // object outlives the invocations of this function, hence "unretained". + main_loop_.PostTask(FROM_HERE, + base::Bind(&Scheduler::FailWithErrorOnMainThread, + base::Unretained(this), err)); + } +} + +void Scheduler::ScheduleWork(const base::Closure& work) { + IncrementWorkCount(); + pool_->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, base::Bind(&Scheduler::DoWork, + base::Unretained(this), work), + base::SequencedWorkerPool::BLOCK_SHUTDOWN); +} + +void Scheduler::ScheduleTargetFileWrite(const Target* target) { + pool_->PostWorkerTaskWithShutdownBehavior( + FROM_HERE, base::Bind(&Scheduler::DoTargetFileWrite, + base::Unretained(this), target), + base::SequencedWorkerPool::BLOCK_SHUTDOWN); +} + +void Scheduler::AddGenDependency(const SourceFile& source_file) { + base::AutoLock lock(lock_); + gen_dependencies_.push_back(source_file); +} + +std::vector<SourceFile> Scheduler::GetGenDependencies() const { + base::AutoLock lock(lock_); + return gen_dependencies_; +} + +void Scheduler::IncrementWorkCount() { + base::AtomicRefCountInc(&work_count_); +} + +void Scheduler::DecrementWorkCount() { + if (!base::AtomicRefCountDec(&work_count_)) { + if (base::MessageLoop::current() == &main_loop_) { + OnComplete(); + } else { + main_loop_.PostTask(FROM_HERE, + base::Bind(&Scheduler::OnComplete, + base::Unretained(this))); + } + } +} + +void Scheduler::LogOnMainThread(const std::string& verb, + const std::string& msg) { + OutputString(verb, DECORATION_YELLOW); + OutputString(" " + msg + "\n"); +} + +void Scheduler::FailWithErrorOnMainThread(const Err& err) { + err.PrintToStdout(); + runner_.Quit(); +} + +void Scheduler::DoTargetFileWrite(const Target* target) { + NinjaTargetWriter::RunAndWriteFile(target); +} + +void Scheduler::DoWork(const base::Closure& closure) { + closure.Run(); + DecrementWorkCount(); +} + +void Scheduler::OnComplete() { + // Should be called on the main thread. + DCHECK(base::MessageLoop::current() == main_loop()); + runner_.Quit(); +} diff --git a/tools/gn/scheduler.h b/tools/gn/scheduler.h new file mode 100644 index 0000000..eab996d --- /dev/null +++ b/tools/gn/scheduler.h @@ -0,0 +1,90 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SCHEDULER_H_ +#define TOOLS_GN_SCHEDULER_H_ + +#include "base/atomic_ref_count.h" +#include "base/basictypes.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/synchronization/lock.h" +#include "base/threading/sequenced_worker_pool.h" +#include "tools/gn/input_file_manager.h" + +class Target; + +// Maintains the thread pool and error state. +class Scheduler { + public: + Scheduler(); + ~Scheduler(); + + bool Run(); + + base::MessageLoop* main_loop() { return &main_loop_; } + base::SequencedWorkerPool* pool() { return pool_; } + + InputFileManager* input_file_manager() { return input_file_manager_; } + + bool verbose_logging() const { return verbose_logging_; } + void set_verbose_logging(bool v) { verbose_logging_ = v; } + + // TODO(brettw) data race on this access (benign?). + bool is_failed() const { return is_failed_; } + + void Log(const std::string& verb, const std::string& msg); + void FailWithError(const Err& err); + + void ScheduleWork(const base::Closure& work); + + void ScheduleTargetFileWrite(const Target* target); + + // Declares that the given file was read and affected the build output. + // + // TODO(brettw) this is global rather than per-BuildSettings. If we + // start using >1 build settings, then we probably want this to take a + // BuildSettings object so we know the depdency on a per-build basis. + void AddGenDependency(const SourceFile& source_file); + std::vector<SourceFile> GetGenDependencies() const; + + // We maintain a count of the things we need to do that works like a + // refcount. When this reaches 0, the program exits. + void IncrementWorkCount(); + void DecrementWorkCount(); + + private: + void LogOnMainThread(const std::string& verb, const std::string& msg); + void FailWithErrorOnMainThread(const Err& err); + + void DoTargetFileWrite(const Target* target); + + void DoWork(const base::Closure& closure); + + void OnComplete(); + + base::MessageLoop main_loop_; + scoped_refptr<base::SequencedWorkerPool> pool_; + + scoped_refptr<InputFileManager> input_file_manager_; + + base::RunLoop runner_; + + bool verbose_logging_; + + base::AtomicRefCount work_count_; + + mutable base::Lock lock_; + bool is_failed_; + + // Additional input dependencies. Protected by the lock. + std::vector<SourceFile> gen_dependencies_; + + DISALLOW_COPY_AND_ASSIGN(Scheduler); +}; + +extern Scheduler* g_scheduler; + +#endif // TOOLS_GN_SCHEDULER_H_ + diff --git a/tools/gn/scope.cc b/tools/gn/scope.cc new file mode 100644 index 0000000..72664d7 --- /dev/null +++ b/tools/gn/scope.cc @@ -0,0 +1,372 @@ +// Copyright (c) 2013 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 "tools/gn/scope.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "tools/gn/parse_tree.h" + +namespace { + +// FLags set in the mode_flags_ of a scope. If a bit is set, it applies +// recursively to all dependent scopes. +const unsigned kProcessingBuildConfigFlag = 1; +const unsigned kProcessingDefaultBuildConfigFlag = 2; +const unsigned kProcessingImportFlag = 4; + +} // namespace + +Scope::Scope(const Settings* settings) + : const_containing_(NULL), + mutable_containing_(NULL), + settings_(settings), + mode_flags_(0) { +} + +Scope::Scope(Scope* parent) + : const_containing_(NULL), + mutable_containing_(parent), + settings_(parent->settings()), + mode_flags_(0) { +} + +Scope::Scope(const Scope* parent) + : const_containing_(parent), + mutable_containing_(NULL), + settings_(parent->settings()), + mode_flags_(0) { +} + +Scope::~Scope() { + STLDeleteContainerPairSecondPointers(target_defaults_.begin(), + target_defaults_.end()); +} + +const Value* Scope::GetValue(const base::StringPiece& ident, + bool counts_as_used) { + // First check for programatically-provided values. + for (ProviderSet::const_iterator i = programmatic_providers_.begin(); + i != programmatic_providers_.end(); ++i) { + const Value* v = (*i)->GetProgrammaticValue(ident); + if (v) + return v; + } + + RecordMap::iterator found = values_.find(ident); + if (found != values_.end()) { + if (counts_as_used) + found->second.used = true; + return &found->second.value; + } + + // Search in the parent scope. + if (const_containing_) + return const_containing_->GetValue(ident); + if (mutable_containing_) + return mutable_containing_->GetValue(ident, counts_as_used); + return NULL; +} + +Value* Scope::GetValueForcedToCurrentScope(const base::StringPiece& ident, + const ParseNode* set_node) { + RecordMap::iterator found = values_.find(ident); + if (found != values_.end()) + return &found->second.value; // Already have in the current scope. + + // Search in the parent scope. + if (containing()) { + const Value* in_containing = containing()->GetValue(ident); + if (in_containing) { + // Promote to current scope. + return SetValue(ident, *in_containing, set_node); + } + } + return NULL; +} + +const Value* Scope::GetValue(const base::StringPiece& ident) const { + RecordMap::const_iterator found = values_.find(ident); + if (found != values_.end()) + return &found->second.value; + if (containing()) + return containing()->GetValue(ident); + return NULL; +} + +Value* Scope::SetValue(const base::StringPiece& ident, + const Value& v, + const ParseNode* set_node) { + Record& r = values_[ident]; // Clears any existing value. + r.value = v; + r.value.set_origin(set_node); + return &r.value; +} + +bool Scope::AddTemplate(const std::string& name, const FunctionCallNode* decl) { + if (GetTemplate(name)) + return false; + templates_[name] = decl; + return true; +} + +const FunctionCallNode* Scope::GetTemplate(const std::string& name) const { + TemplateMap::const_iterator found = templates_.find(name); + if (found != templates_.end()) + return found->second; + if (containing()) + return containing()->GetTemplate(name); + return NULL; +} + +void Scope::MarkUsed(const base::StringPiece& ident) { + RecordMap::iterator found = values_.find(ident); + if (found == values_.end()) { + NOTREACHED(); + return; + } + found->second.used = true; +} + +void Scope::MarkUnused(const base::StringPiece& ident) { + RecordMap::iterator found = values_.find(ident); + if (found == values_.end()) { + NOTREACHED(); + return; + } + found->second.used = false; +} + +bool Scope::IsSetButUnused(const base::StringPiece& ident) const { + RecordMap::const_iterator found = values_.find(ident); + if (found != values_.end()) { + if (!found->second.used) { + return true; + } + } + return false; +} + +bool Scope::CheckForUnusedVars(Err* err) const { + for (RecordMap::const_iterator i = values_.begin(); + i != values_.end(); ++i) { + if (!i->second.used) { + std::string help = "You set the variable \"" + i->first.as_string() + + "\" here and it was unused before it went\nout of scope."; + + const BinaryOpNode* binary = i->second.value.origin()->AsBinaryOp(); + if (binary) { + // Make a nicer error message for normal var sets. + *err = Err(binary->left()->GetRange(), "Assignment had no effect.", + help); + } else { + // This will happen for internally-generated variables. + *err = Err(i->second.value.origin(), "Assignment had no effect.", help); + } + return false; + } + } + return true; +} + +void Scope::GetCurrentScopeValues(KeyValueVector* output) const { + output->reserve(values_.size()); + for (RecordMap::const_iterator i = values_.begin(); i != values_.end(); ++i) { + output->push_back(std::make_pair(i->first, i->second.value)); + } +} + +bool Scope::NonRecursiveMergeTo(Scope* dest, + const ParseNode* node_for_err, + const char* desc_for_err, + Err* err) const { + // Values. + for (RecordMap::const_iterator i = values_.begin(); i != values_.end(); ++i) { + const Value* existing_value = dest->GetValue(i->first); + if (existing_value) { + // Value present in both the source and the dest. + std::string desc_string(desc_for_err); + *err = Err(node_for_err, "Value collision.", + "This " + desc_string + " contains \"" + i->first.as_string() + "\""); + err->AppendSubErr(Err(i->second.value, "defined here.", + "Which would clobber the one in your current scope")); + err->AppendSubErr(Err(*existing_value, "defined here.", + "Executing " + desc_string + " should not conflict with anything " + "in the current\nscope.")); + return false; + } + dest->values_[i->first] = i->second; + } + + // Target defaults are owning pointers. + for (NamedScopeMap::const_iterator i = target_defaults_.begin(); + i != target_defaults_.end(); ++i) { + if (dest->GetTargetDefaults(i->first)) { + // TODO(brettw) it would be nice to know the origin of a + // set_target_defaults so we can give locations for the colliding target + // defaults. + std::string desc_string(desc_for_err); + *err = Err(node_for_err, "Target defaults collision.", + "This " + desc_string + " contains target defaults for\n" + "\"" + i->first + "\" which would clobber one for the\n" + "same target type in your current scope. It's unfortunate that I'm " + "too stupid\nto tell you the location of where the target defaults " + "were set. Usually\nthis happens in the BUILDCONFIG.gn file."); + return false; + } + + Scope* s = new Scope(settings_); + i->second->NonRecursiveMergeTo(s, node_for_err, "<SHOULDN'T HAPPEN>", err); + dest->target_defaults_[i->first] = s; + } + + // Sources assignment filter. + if (sources_assignment_filter_) { + if (dest->GetSourcesAssignmentFilter()) { + // Sources assignment filter present in both the source and the dest. + std::string desc_string(desc_for_err); + *err = Err(node_for_err, "Assignment filter collision.", + "The " + desc_string + " contains a sources_assignment_filter which\n" + "would clobber the one in your current scope."); + return false; + } + dest->sources_assignment_filter_.reset( + new PatternList(*sources_assignment_filter_)); + } + + // Templates. + for (TemplateMap::const_iterator i = templates_.begin(); + i != templates_.end(); ++i) { + const FunctionCallNode* existing_template = dest->GetTemplate(i->first); + if (existing_template) { + // Rule present in both the source and the dest. + std::string desc_string(desc_for_err); + *err = Err(node_for_err, "Template collision.", + "This " + desc_string + " contains a template \"" + i->first + "\""); + err->AppendSubErr(Err(i->second->function(), "defined here.", + "Which would clobber the one in your current scope")); + err->AppendSubErr(Err(existing_template->function(), "defined here.", + "Executing " + desc_string + " should not conflict with anything " + "in the current\nscope.")); + return false; + } + dest->templates_.insert(*i); + } + + return true; +} + +Scope* Scope::MakeTargetDefaults(const std::string& target_type) { + if (GetTargetDefaults(target_type)) + return NULL; + + Scope** dest = &target_defaults_[target_type]; + if (*dest) { + NOTREACHED(); // Already set. + return *dest; + } + *dest = new Scope(settings_); + return *dest; +} + +const Scope* Scope::GetTargetDefaults(const std::string& target_type) const { + NamedScopeMap::const_iterator found = target_defaults_.find(target_type); + if (found != target_defaults_.end()) + return found->second; + if (containing()) + return containing()->GetTargetDefaults(target_type); + return NULL; +} + +const PatternList* Scope::GetSourcesAssignmentFilter() const { + if (sources_assignment_filter_) + return sources_assignment_filter_.get(); + if (containing()) + return containing()->GetSourcesAssignmentFilter(); + return NULL; +} + +void Scope::SetProcessingBuildConfig() { + DCHECK((mode_flags_ & kProcessingBuildConfigFlag) == 0); + mode_flags_ |= kProcessingBuildConfigFlag; +} + +void Scope::ClearProcessingBuildConfig() { + DCHECK(mode_flags_ & kProcessingBuildConfigFlag); + mode_flags_ &= ~(kProcessingBuildConfigFlag); +} + +bool Scope::IsProcessingBuildConfig() const { + if (mode_flags_ & kProcessingBuildConfigFlag) + return true; + if (containing()) + return containing()->IsProcessingBuildConfig(); + return false; +} + +void Scope::SetProcessingDefaultBuildConfig() { + DCHECK((mode_flags_ & kProcessingDefaultBuildConfigFlag) == 0); + mode_flags_ |= kProcessingDefaultBuildConfigFlag; +} + +void Scope::ClearProcessingDefaultBuildConfig() { + DCHECK(mode_flags_ & kProcessingDefaultBuildConfigFlag); + mode_flags_ &= ~(kProcessingDefaultBuildConfigFlag); +} + +bool Scope::IsProcessingDefaultBuildConfig() const { + if (mode_flags_ & kProcessingDefaultBuildConfigFlag) + return true; + if (containing()) + return containing()->IsProcessingDefaultBuildConfig(); + return false; +} + +void Scope::SetProcessingImport() { + DCHECK((mode_flags_ & kProcessingImportFlag) == 0); + mode_flags_ |= kProcessingImportFlag; +} + +void Scope::ClearProcessingImport() { + DCHECK(mode_flags_ & kProcessingImportFlag); + mode_flags_ &= ~(kProcessingImportFlag); +} + +bool Scope::IsProcessingImport() const { + if (mode_flags_ & kProcessingImportFlag) + return true; + if (containing()) + return containing()->IsProcessingImport(); + return false; +} + +void Scope::SetProperty(const void* key, void* value) { + if (!value) { + DCHECK(properties_.find(key) != properties_.end()); + properties_.erase(key); + } else { + properties_[key] = value; + } +} + +void* Scope::GetProperty(const void* key, const Scope** found_on_scope) const { + PropertyMap::const_iterator found = properties_.find(key); + if (found != properties_.end()) { + if (found_on_scope) + *found_on_scope = this; + return found->second; + } + if (containing()) + return containing()->GetProperty(key, found_on_scope); + return NULL; +} + +void Scope::AddProvider(ProgrammaticProvider* p) { + programmatic_providers_.insert(p); +} + +void Scope::RemoveProvider(ProgrammaticProvider* p) { + DCHECK(programmatic_providers_.find(p) != programmatic_providers_.end()); + programmatic_providers_.erase(p); +} diff --git a/tools/gn/scope.h b/tools/gn/scope.h new file mode 100644 index 0000000..7d0547e --- /dev/null +++ b/tools/gn/scope.h @@ -0,0 +1,260 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SCOPE_H_ +#define TOOLS_GN_SCOPE_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/err.h" +#include "tools/gn/pattern.h" +#include "tools/gn/value.h" + +class FunctionCallNode; +class ImportManager; +class ParseNode; +class Settings; +class TargetManager; + +// Scope for the script execution. +// +// Scopes are nested. Writing goes into the toplevel scope, reading checks +// values resursively down the stack until a match is found or there are no +// more containing scopes. +// +// A containing scope can be const or non-const. The const containing scope is +// used primarily to refer to the master build config which is shared across +// many invocations. A const containing scope, however, prevents us from +// marking variables "used" which prevents us from issuing errors on unused +// variables. So you should use a non-const containing scope whenever possible. +class Scope { + public: + typedef std::vector<std::pair<base::StringPiece, Value> > KeyValueVector; + + // Allows code to provide values for built-in variables. This class will + // automatically register itself on construction and deregister itself on + // destruction. + class ProgrammaticProvider { + public: + ProgrammaticProvider(Scope* scope) : scope_(scope) { + scope_->AddProvider(this); + } + ~ProgrammaticProvider() { + scope_->RemoveProvider(this); + } + + // Returns a non-null value if the given value can be programmatically + // generated, or NULL if there is none. + virtual const Value* GetProgrammaticValue( + const base::StringPiece& ident) = 0; + + protected: + Scope* scope_; + }; + + // Creates an empty toplevel scope. + Scope(const Settings* settings); + + // Creates a dependent scope. + Scope(Scope* parent); + Scope(const Scope* parent); + + ~Scope(); + + const Settings* settings() const { return settings_; } + + // See the const_/mutable_containing_ var declaraions below. Yes, it's a + // bit weird that we can have a const pointer to the "mutable" one. + Scope* mutable_containing() { return mutable_containing_; } + const Scope* mutable_containing() const { return mutable_containing_; } + const Scope* const_containing() const { return const_containing_; } + const Scope* containing() const { + return mutable_containing_ ? mutable_containing_ : const_containing_; + } + + // Returns NULL if there's no such value. + // + // counts_as_used should be set if the variable is being read in a way that + // should count for unused variable checking. + const Value* GetValue(const base::StringPiece& ident, + bool counts_as_used); + const Value* GetValue(const base::StringPiece& ident) const; + + // Same as GetValue, but if the value exists in a parent scope, we'll copy + // it to the current scope. If the return value is non-null, the value is + // guaranteed to be set in the current scope. Generatlly this will be used + // if the calling code is planning on modifying the value in-place. + // + // Since this is used when doing read-modifies, we never count this access + // as reading the variable, since we assume it will be written to. + Value* GetValueForcedToCurrentScope(const base::StringPiece& ident, + const ParseNode* set_node); + + // The set_node indicates the statement that caused the set, for displaying + // errors later. Returns a pointer to the value in the current scope (a copy + // is made for storage). + Value* SetValue(const base::StringPiece& ident, + const Value& v, + const ParseNode* set_node); + + // Templates associated with this scope. A template can only be set once, so + // AddTemplate will fail and return NULL if a rule with that name already + // exists. GetTemplate returns NULL if the rule doesn't exist, and it will + // check all containing scoped rescursively. + bool AddTemplate(const std::string& name, const FunctionCallNode* decl); + const FunctionCallNode* GetTemplate(const std::string& name) const; + + // Marks the given identifier as (un)used in the current scope. + void MarkUsed(const base::StringPiece& ident); + void MarkUnused(const base::StringPiece& ident); + + // Checks to see if the scope has a var set that hasn't been used. This is + // called before replacing the var with a different one. It does not check + // containing scopes. + // + // If the identifier is present but hasnn't been used, return true. + bool IsSetButUnused(const base::StringPiece& ident) const; + + // Checks the scope to see if any values were set but not used, and fills in + // the error and returns false if they were. + bool CheckForUnusedVars(Err* err) const; + + // Returns all values set in the current scope, without going to the parent + // scopes. + void GetCurrentScopeValues(KeyValueVector* output) const; + + // Copies this scope's values into the destination. Values from the + // containing scope(s) (normally shadowed into the current one) will not be + // copied, neither will the reference to the containing scope (this is why + // it's "non-recursive"). + // + // It is an error to merge a variable into a scope that already has something + // with that name in scope (meaning in that scope or in any of its containing + // scopes). If this happens, the error will be set and the function will + // return false. + // + // This is used in different contexts. When generating the error, the given + // parse node will be blamed, and the given desc will be used to describe + // the operation that doesn't support doing this. For example, desc_for_err + // would be "import" when doing an import, and the error string would say + // something like "The import contains...". + bool NonRecursiveMergeTo(Scope* dest, + const ParseNode* node_for_err, + const char* desc_for_err, + Err* err) const; + + // Makes an empty scope with the given name. Returns NULL if the name is + // already set. + Scope* MakeTargetDefaults(const std::string& target_type); + + // Gets the scope associated with the given target name, or null if it hasn't + // been set. + const Scope* GetTargetDefaults(const std::string& target_type) const; + + // Filter to apply when the sources variable is assigned. May return NULL. + const PatternList* GetSourcesAssignmentFilter() const; + void set_sources_assignment_filter( + scoped_ptr<PatternList> f) { + sources_assignment_filter_ = f.Pass(); + } + + // Indicates if we're currently processing the build configuration file. + // This is true when processing the config file for any toolchain. See also + // *ProcessingDefaultBuildConfig() below. + // + // To set or clear the flag, it must currently be in the opposite state in + // the current scope. Note that querying the state of the flag recursively + // checks all containing scopes until it reaches the top or finds the flag + // set. + void SetProcessingBuildConfig(); + void ClearProcessingBuildConfig(); + bool IsProcessingBuildConfig() const; + + // Indicates we're currently processing the default toolchain's build + // configuration file. + void SetProcessingDefaultBuildConfig(); + void ClearProcessingDefaultBuildConfig(); + bool IsProcessingDefaultBuildConfig() const; + + // Indicates if we're currently processing an import file. + // + // See SetProcessingBaseConfig for how flags work. + void SetProcessingImport(); + void ClearProcessingImport(); + bool IsProcessingImport() const; + + // Properties are opaque pointers that code can use to set state on a Scope + // that it can retrieve later. + // + // The key should be a pointer to some use-case-specific object (to avoid + // collisions, otherwise it doesn't matter). Memory management is up to the + // setter. Setting the value to NULL will delete the property. + // + // Getting a property recursively searches all scopes, and the optional + // |found_on_scope| variable will be filled with the actual scope containing + // the key (if the pointer is non-NULL). + void SetProperty(const void* key, void* value); + void* GetProperty(const void* key, const Scope** found_on_scope) const; + + private: + friend class ProgrammaticProvider; + + struct Record { + Record() : used(false) {} + Record(const Value& v) : used(false), value(v) {} + + bool used; // Set to true when the variable is used. + Value value; + }; + + void AddProvider(ProgrammaticProvider* p); + void RemoveProvider(ProgrammaticProvider* p); + + // Scopes can have no containing scope (both null), a mutable containing + // scope, or a const containing scope. The reason is that when we're doing + // a new target, we want to refer to the base_config scope which will be read + // by multiple threads at the same time, so we REALLY want it to be const. + // When you jsut do a nested {}, however, we sometimes want to be able to + // change things (especially marking unused vars). + const Scope* const_containing_; + Scope* mutable_containing_; + + const Settings* settings_; + + // Bits set for different modes. See the flag definitions in the .cc file + // for more. + unsigned mode_flags_; + + typedef base::hash_map<base::StringPiece, Record> RecordMap; + RecordMap values_; + + // Owning pointers. Note that this can't use string pieces since the names + // are constructed from Values which might be deallocated before this goes + // out of scope. + typedef base::hash_map<std::string, Scope*> NamedScopeMap; + NamedScopeMap target_defaults_; + + // Null indicates not set and that we should fallback to the containing + // scope's filter. + scoped_ptr<PatternList> sources_assignment_filter_; + + // Non-owning pointers, the function calls are owned by the input file which + // should be kept around by the input file manager. + typedef std::map<std::string, const FunctionCallNode*> TemplateMap; + TemplateMap templates_; + + typedef std::map<const void*, void*> PropertyMap; + PropertyMap properties_; + + typedef std::set<ProgrammaticProvider*> ProviderSet; + ProviderSet programmatic_providers_; + + DISALLOW_COPY_AND_ASSIGN(Scope); +}; + +#endif // TOOLS_GN_SCOPE_H_ diff --git a/tools/gn/scope_per_file_provider.cc b/tools/gn/scope_per_file_provider.cc new file mode 100644 index 0000000..1799d9e --- /dev/null +++ b/tools/gn/scope_per_file_provider.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2013 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 "tools/gn/scope_per_file_provider.h" + +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/settings.h" +#include "tools/gn/source_file.h" +#include "tools/gn/toolchain_manager.h" +#include "tools/gn/value.h" + +const char* ScopePerFileProvider::kDefaultToolchain = + "default_toolchain"; +const char* ScopePerFileProvider::kPythonPath = + "python_path"; +const char* ScopePerFileProvider::kToolchain = + "toolchain"; +const char* ScopePerFileProvider::kRootOutputDirName = + "root_output_dir"; +const char* ScopePerFileProvider::kRootGenDirName = + "root_gen_dir"; +const char* ScopePerFileProvider::kTargetOutputDirName = + "target_output_dir"; +const char* ScopePerFileProvider::kTargetGenDirName = + "target_gen_dir"; +const char* ScopePerFileProvider::kRelativeRootOutputDirName = + "relative_root_output_dir"; +const char* ScopePerFileProvider::kRelativeRootGenDirName = + "relative_root_gen_dir"; +const char* ScopePerFileProvider::kRelativeTargetOutputDirName = + "relative_target_output_dir"; +const char* ScopePerFileProvider::kRelativeTargetGenDirName = + "relative_target_gen_dir"; + +ScopePerFileProvider::ScopePerFileProvider(Scope* scope, + const SourceFile& source_file) + : ProgrammaticProvider(scope), + source_file_(source_file) { +} + +ScopePerFileProvider::~ScopePerFileProvider() { +} + +const Value* ScopePerFileProvider::GetProgrammaticValue( + const base::StringPiece& ident) { + if (ident == kDefaultToolchain) + return GetDefaultToolchain(); + if (ident == kPythonPath) + return GetPythonPath(); + + if (ident == kTargetOutputDirName) + return GetTargetOutputDir(); + if (ident == kTargetGenDirName) + return GetTargetGenDir(); + + if (ident == kRelativeRootOutputDirName) + return GetRelativeRootOutputDir(); + if (ident == kRelativeRootGenDirName) + return GetRelativeRootGenDir(); + if (ident == kRelativeTargetOutputDirName) + return GetRelativeTargetOutputDir(); + if (ident == kRelativeTargetGenDirName) + return GetRelativeTargetGenDir(); + return NULL; +} + +// static +Value ScopePerFileProvider::GetRootOutputDir(const Settings* settings) { + return Value(NULL, GetRootOutputDirWithNoLastSlash(settings)); +} + +// static +Value ScopePerFileProvider::GetRootGenDir(const Settings* settings) { + return Value(NULL, GetRootGenDirWithNoLastSlash(settings)); +} + +const Value* ScopePerFileProvider::GetDefaultToolchain() { + if (!default_toolchain_) { + const ToolchainManager& toolchain_manager = + scope_->settings()->build_settings()->toolchain_manager(); + default_toolchain_.reset(new Value(NULL, + toolchain_manager.GetDefaultToolchainUnlocked().GetUserVisibleName( + false))); + } + return default_toolchain_.get(); +} + +const Value* ScopePerFileProvider::GetPythonPath() { + if (!python_path_) { + python_path_.reset(new Value(NULL, + FilePathToUTF8(scope_->settings()->build_settings()->python_path()))); + } + return python_path_.get(); +} + +const Value* ScopePerFileProvider::GetToolchain() { + if (!toolchain_) { + toolchain_.reset(new Value(NULL, + scope_->settings()->toolchain()->label().GetUserVisibleName(false))); + } + return toolchain_.get(); +} + +const Value* ScopePerFileProvider::GetTargetOutputDir() { + if (!target_output_dir_) { + target_output_dir_.reset(new Value(NULL, + GetRootOutputDirWithNoLastSlash(scope_->settings()) + + GetFileDirWithNoLastSlash())); + } + return target_output_dir_.get(); +} + +const Value* ScopePerFileProvider::GetTargetGenDir() { + if (!target_output_dir_) { + target_gen_dir_.reset(new Value(NULL, + GetRootGenDirWithNoLastSlash(scope_->settings()) + + GetFileDirWithNoLastSlash())); + } + return target_gen_dir_.get(); +} + +const Value* ScopePerFileProvider::GetRelativeRootOutputDir() { + if (!relative_root_output_dir_) { + relative_root_output_dir_.reset(new Value(NULL, + GetRelativeRootWithNoLastSlash() + + GetRootOutputDirWithNoLastSlash(scope_->settings()))); + } + return relative_root_output_dir_.get(); +} + +const Value* ScopePerFileProvider::GetRelativeRootGenDir() { + if (!relative_root_gen_dir_) { + relative_root_gen_dir_.reset(new Value(NULL, + GetRelativeRootWithNoLastSlash() + + GetRootGenDirWithNoLastSlash(scope_->settings()))); + } + return relative_root_gen_dir_.get(); +} + +const Value* ScopePerFileProvider::GetRelativeTargetOutputDir() { + if (!relative_target_output_dir_) { + relative_target_output_dir_.reset(new Value(NULL, + GetRelativeRootWithNoLastSlash() + + GetRootOutputDirWithNoLastSlash(scope_->settings()) + "obj/" + + GetFileDirWithNoLastSlash())); + } + return relative_target_output_dir_.get(); +} + +const Value* ScopePerFileProvider::GetRelativeTargetGenDir() { + if (!relative_target_gen_dir_) { + relative_target_gen_dir_.reset(new Value(NULL, + GetRelativeRootWithNoLastSlash() + + GetRootGenDirWithNoLastSlash(scope_->settings()) + + GetFileDirWithNoLastSlash())); + } + return relative_target_gen_dir_.get(); +} + +// static +std::string ScopePerFileProvider::GetRootOutputDirWithNoLastSlash( + const Settings* settings) { + const std::string& output_dir = + settings->build_settings()->build_dir().value(); + CHECK(!output_dir.empty()); + return output_dir.substr(0, output_dir.size() - 1); +} + +// static +std::string ScopePerFileProvider::GetRootGenDirWithNoLastSlash( + const Settings* settings) { + return GetRootOutputDirWithNoLastSlash(settings) + "/gen"; +} + +std::string ScopePerFileProvider::GetFileDirWithNoLastSlash() const { + std::string dir_value = source_file_.GetDir().value(); + return dir_value.substr(0, dir_value.size() - 1); +} + +std::string ScopePerFileProvider::GetRelativeRootWithNoLastSlash() const { + std::string inverted = InvertDir(source_file_.GetDir()); + if (inverted.empty()) + return "."; + return inverted.substr(0, inverted.size() - 1); +} diff --git a/tools/gn/scope_per_file_provider.h b/tools/gn/scope_per_file_provider.h new file mode 100644 index 0000000..1edb769 --- /dev/null +++ b/tools/gn/scope_per_file_provider.h @@ -0,0 +1,79 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_ +#define TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/scope.h" +#include "tools/gn/source_file.h" + +// ProgrammaticProvider for a scope to provide it with per-file built-in +// variable support. +class ScopePerFileProvider : public Scope::ProgrammaticProvider { + public: + ScopePerFileProvider(Scope* scope, const SourceFile& source_file); + virtual ~ScopePerFileProvider(); + + // ProgrammaticProvider implementation. + virtual const Value* GetProgrammaticValue( + const base::StringPiece& ident) OVERRIDE; + + // Returns the value to expose to script for the given thing. These values + // are acually set globally, but are put here so we can keep all logic + // for converting paths to built-in values in this one file. + static Value GetRootOutputDir(const Settings* settings); + static Value GetRootGenDir(const Settings* settings); + + // Variable names. These two should be set globally independent of the file + // being processed. + static const char* kRootOutputDirName; + static const char* kRootGenDirName; + + // Variable names. These are provided by this class as needed. + static const char* kDefaultToolchain; + static const char* kPythonPath; + static const char* kToolchain; + static const char* kTargetOutputDirName; + static const char* kTargetGenDirName; + static const char* kRelativeRootOutputDirName; + static const char* kRelativeRootGenDirName; + static const char* kRelativeTargetOutputDirName; + static const char* kRelativeTargetGenDirName; + + private: + const Value* GetDefaultToolchain(); + const Value* GetPythonPath(); + const Value* GetToolchain(); + const Value* GetTargetOutputDir(); + const Value* GetTargetGenDir(); + const Value* GetRelativeRootOutputDir(); + const Value* GetRelativeRootGenDir(); + const Value* GetRelativeTargetOutputDir(); + const Value* GetRelativeTargetGenDir(); + + static std::string GetRootOutputDirWithNoLastSlash(const Settings* settings); + static std::string GetRootGenDirWithNoLastSlash(const Settings* settings); + + std::string GetFileDirWithNoLastSlash() const; + std::string GetRelativeRootWithNoLastSlash() const; + + SourceFile source_file_; + + // All values are lazily created. + scoped_ptr<Value> default_toolchain_; + scoped_ptr<Value> python_path_; + scoped_ptr<Value> toolchain_; + scoped_ptr<Value> target_output_dir_; + scoped_ptr<Value> target_gen_dir_; + scoped_ptr<Value> relative_root_output_dir_; + scoped_ptr<Value> relative_root_gen_dir_; + scoped_ptr<Value> relative_target_output_dir_; + scoped_ptr<Value> relative_target_gen_dir_; + + DISALLOW_COPY_AND_ASSIGN(ScopePerFileProvider); +}; + +#endif // TOOLS_GN_SCOPE_PER_FILE_PROVIDER_H_ diff --git a/tools/gn/secondary/base/BUILD.gn b/tools/gn/secondary/base/BUILD.gn new file mode 100644 index 0000000..2286b19 --- /dev/null +++ b/tools/gn/secondary/base/BUILD.gn @@ -0,0 +1,919 @@ +# found in the LICENSE file.
+
+component("base") {
+ sources = [
+ "../build/build_config.h",
+ "third_party/dmg_fp/dmg_fp.h",
+ "third_party/dmg_fp/g_fmt.cc",
+ "third_party/dmg_fp/dtoa_wrapper.cc",
+ "third_party/icu/icu_utf.cc",
+ "third_party/icu/icu_utf.h",
+ "third_party/nspr/prcpucfg.h",
+ "third_party/nspr/prcpucfg_freebsd.h",
+ "third_party/nspr/prcpucfg_linux.h",
+ "third_party/nspr/prcpucfg_mac.h",
+ "third_party/nspr/prcpucfg_nacl.h",
+ "third_party/nspr/prcpucfg_openbsd.h",
+ "third_party/nspr/prcpucfg_solaris.h",
+ "third_party/nspr/prcpucfg_win.h",
+ "third_party/nspr/prtime.cc",
+ "third_party/nspr/prtime.h",
+ "third_party/nspr/prtypes.h",
+ "third_party/xdg_mime/xdgmime.h",
+ "allocator/allocator_extension.cc",
+ "allocator/allocator_extension.h",
+ "allocator/type_profiler_control.cc",
+ "allocator/type_profiler_control.h",
+ "android/activity_status.cc",
+ "android/activity_status.h",
+ "android/base_jni_registrar.cc",
+ "android/base_jni_registrar.h",
+ "android/build_info.cc",
+ "android/build_info.h",
+ "android/cpu_features.cc",
+ "android/fifo_utils.cc",
+ "android/fifo_utils.h",
+ "android/important_file_writer_android.cc",
+ "android/important_file_writer_android.h",
+ "android/scoped_java_ref.cc",
+ "android/scoped_java_ref.h",
+ "android/jni_android.cc",
+ "android/jni_android.h",
+ "android/jni_array.cc",
+ "android/jni_array.h",
+ "android/jni_helper.cc",
+ "android/jni_helper.h",
+ "android/jni_registrar.cc",
+ "android/jni_registrar.h",
+ "android/jni_string.cc",
+ "android/jni_string.h",
+ "android/memory_pressure_listener_android.cc",
+ "android/memory_pressure_listener_android.h",
+ "android/path_service_android.cc",
+ "android/path_service_android.h",
+ "android/path_utils.cc",
+ "android/path_utils.h",
+ "android/sys_utils.cc",
+ "android/sys_utils.h",
+ "android/thread_utils.h",
+ "at_exit.cc",
+ "at_exit.h",
+ "atomic_ref_count.h",
+ "atomic_sequence_num.h",
+ "atomicops.h",
+ "atomicops_internals_gcc.h",
+ "atomicops_internals_mac.h",
+ "atomicops_internals_tsan.h",
+ "atomicops_internals_x86_gcc.cc",
+ "atomicops_internals_x86_gcc.h",
+ "atomicops_internals_x86_msvc.h",
+ "base_export.h",
+ "base_paths.cc",
+ "base_paths.h",
+ "base_paths_android.cc",
+ "base_paths_android.h",
+ "base_paths_mac.h",
+ "base_paths_mac.mm",
+ "base_paths_posix.cc",
+ "base_paths_posix.h",
+ "base_paths_win.cc",
+ "base_paths_win.h",
+ "base_switches.h",
+ "base64.cc",
+ "base64.h",
+ "basictypes.h",
+ "bind.h",
+ "bind_helpers.cc",
+ "bind_helpers.h",
+ "bind_internal.h",
+ "bind_internal_win.h",
+ "bits.h",
+ "build_time.cc",
+ "build_time.h",
+ "callback.h",
+ "callback_helpers.h",
+ "callback_internal.cc",
+ "callback_internal.h",
+ "cancelable_callback.h",
+ "chromeos/chromeos_version.cc",
+ "chromeos/chromeos_version.h",
+ "command_line.cc",
+ "command_line.h",
+ "compiler_specific.h",
+ "containers/hash_tables.h",
+ "containers/linked_list.h",
+ "containers/mru_cache.h",
+ "containers/small_map.h",
+ "containers/stack_container.h",
+ "cpu.cc",
+ "cpu.h",
+ "critical_closure.h",
+ "critical_closure_ios.mm",
+ "debug/alias.cc",
+ "debug/alias.h",
+ "debug/crash_logging.cc",
+ "debug/crash_logging.h",
+ "debug/debug_on_start_win.cc",
+ "debug/debug_on_start_win.h",
+ "debug/debugger.cc",
+ "debug/debugger.h",
+ "debug/debugger_posix.cc",
+ "debug/debugger_win.cc",
+ # This file depends on files from the "allocator" target,
+ # but this target does not depend on "allocator" (see
+ # allocator.gyp for details).
+ "debug/leak_annotations.h",
+ "debug/leak_tracker.h",
+ "debug/proc_maps_linux.cc",
+ "debug/proc_maps_linux.h",
+ "debug/profiler.cc",
+ "debug/profiler.h",
+ "debug/stack_trace.cc",
+ "debug/stack_trace.h",
+ "debug/stack_trace_android.cc",
+ "debug/stack_trace_ios.mm",
+ "debug/stack_trace_posix.cc",
+ "debug/stack_trace_win.cc",
+ "debug/trace_event.h",
+ "debug/trace_event_android.cc",
+ "debug/trace_event_impl.cc",
+ "debug/trace_event_impl.h",
+ "debug/trace_event_impl_constants.cc",
+ "debug/trace_event_win.cc",
+ "deferred_sequenced_task_runner.cc",
+ "deferred_sequenced_task_runner.h",
+ "environment.cc",
+ "environment.h",
+ "file_descriptor_posix.h",
+ "file_util.cc",
+ "file_util.h",
+ "file_util_android.cc",
+ "file_util_linux.cc",
+ "file_util_mac.mm",
+ "file_util_posix.cc",
+ "file_util_win.cc",
+ "file_version_info.h",
+ "file_version_info_mac.h",
+ "file_version_info_mac.mm",
+ "file_version_info_win.cc",
+ "file_version_info_win.h",
+ "files/dir_reader_fallback.h",
+ "files/dir_reader_linux.h",
+ "files/dir_reader_posix.h",
+ "files/file_enumerator.cc",
+ "files/file_enumerator.h",
+ "files/file_enumerator_posix.cc",
+ "files/file_enumerator_win.cc",
+ "files/file_path.cc",
+ "files/file_path.h",
+ "files/file_path_constants.cc",
+ "files/file_path_watcher.cc",
+ "files/file_path_watcher.h",
+ "files/file_path_watcher_kqueue.cc",
+ "files/file_path_watcher_linux.cc",
+ "files/file_path_watcher_stub.cc",
+ "files/file_path_watcher_win.cc",
+ "files/file_util_proxy.cc",
+ "files/file_util_proxy.h",
+ "files/important_file_writer.h",
+ "files/important_file_writer.cc",
+ "files/memory_mapped_file.cc",
+ "files/memory_mapped_file.h",
+ "files/memory_mapped_file_posix.cc",
+ "files/memory_mapped_file_win.cc",
+ "files/scoped_temp_dir.cc",
+ "files/scoped_temp_dir.h",
+ "float_util.h",
+ "format_macros.h",
+ "gtest_prod_util.h",
+ "guid.cc",
+ "guid.h",
+ "guid_posix.cc",
+ "guid_win.cc",
+ "hash.cc",
+ "hash.h",
+ "id_map.h",
+ "ini_parser.cc",
+ "ini_parser.h",
+ "ios/device_util.h",
+ "ios/device_util.mm",
+ "ios/ios_util.h",
+ "ios/ios_util.mm",
+ "ios/scoped_critical_action.h",
+ "ios/scoped_critical_action.mm",
+ "json/json_file_value_serializer.cc",
+ "json/json_file_value_serializer.h",
+ "json/json_parser.cc",
+ "json/json_parser.h",
+ "json/json_reader.cc",
+ "json/json_reader.h",
+ "json/json_string_value_serializer.cc",
+ "json/json_string_value_serializer.h",
+ "json/json_value_converter.h",
+ "json/json_writer.cc",
+ "json/json_writer.h",
+ "json/string_escape.cc",
+ "json/string_escape.h",
+ "lazy_instance.cc",
+ "lazy_instance.h",
+ "location.cc",
+ "location.h",
+ "logging.cc",
+ "logging.h",
+ "logging_win.cc",
+ "logging_win.h",
+ "mac/authorization_util.h",
+ "mac/authorization_util.mm",
+ "mac/bind_objc_block.h",
+ "mac/bundle_locations.h",
+ "mac/bundle_locations.mm",
+ "mac/cocoa_protocols.h",
+ "mac/foundation_util.h",
+ "mac/foundation_util.mm",
+ "mac/launch_services_util.cc",
+ "mac/launch_services_util.h",
+ "mac/launchd.cc",
+ "mac/launchd.h",
+ "mac/libdispatch_task_runner.cc",
+ "mac/libdispatch_task_runner.h",
+ "mac/mac_logging.h",
+ "mac/mac_logging.cc",
+ "mac/mac_util.h",
+ "mac/mac_util.mm",
+ "mac/objc_property_releaser.h",
+ "mac/objc_property_releaser.mm",
+ "mac/os_crash_dumps.cc",
+ "mac/os_crash_dumps.h",
+ "mac/scoped_aedesc.h",
+ "mac/scoped_authorizationref.h",
+ "mac/scoped_block.h",
+ "mac/scoped_cftyperef.h",
+ "mac/scoped_ioobject.h",
+ "mac/scoped_ioplugininterface.h",
+ "mac/scoped_launch_data.h",
+ "mac/scoped_mach_port.cc",
+ "mac/scoped_mach_port.h",
+ "mac/scoped_nsautorelease_pool.h",
+ "mac/scoped_nsautorelease_pool.mm",
+ "mac/scoped_nsexception_enabler.h",
+ "mac/scoped_nsexception_enabler.mm",
+ "mac/scoped_nsobject.h",
+ "mac/scoped_sending_event.h",
+ "mac/scoped_sending_event.mm",
+ "mac/sdk_forward_declarations.h",
+ "memory/aligned_memory.cc",
+ "memory/aligned_memory.h",
+ "memory/discardable_memory.cc",
+ "memory/discardable_memory.h",
+ "memory/discardable_memory_android.cc",
+ "memory/discardable_memory_mac.cc",
+ "memory/linked_ptr.h",
+ "memory/manual_constructor.h",
+ "memory/memory_pressure_listener.cc",
+ "memory/memory_pressure_listener.h",
+ "memory/raw_scoped_refptr_mismatch_checker.h",
+ "memory/ref_counted.cc",
+ "memory/ref_counted.h",
+ "memory/ref_counted_delete_on_message_loop.h",
+ "memory/ref_counted_memory.cc",
+ "memory/ref_counted_memory.h",
+ "memory/scoped_handle.h",
+ "memory/scoped_open_process.h",
+ "memory/scoped_policy.h",
+ "memory/scoped_ptr.h",
+ "memory/scoped_vector.h",
+ "memory/shared_memory.h",
+ "memory/shared_memory_android.cc",
+ "memory/shared_memory_nacl.cc",
+ "memory/shared_memory_posix.cc",
+ "memory/shared_memory_win.cc",
+ "memory/singleton.cc",
+ "memory/singleton.h",
+ "memory/weak_ptr.cc",
+ "memory/weak_ptr.h",
+ "message_loop/message_loop.cc",
+ "message_loop/message_loop.h",
+ "message_loop/message_loop_proxy.cc",
+ "message_loop/message_loop_proxy.h",
+ "message_loop/message_loop_proxy_impl.cc",
+ "message_loop/message_loop_proxy_impl.h",
+ "message_loop/message_pump.cc",
+ "message_loop/message_pump.h",
+ "message_loop/message_pump_android.cc",
+ "message_loop/message_pump_android.h",
+ "message_loop/message_pump_default.cc",
+ "message_loop/message_pump_default.h",
+ "message_loop/message_pump_ozone.cc",
+ "message_loop/message_pump_ozone.h",
+ "message_loop/message_pump_win.cc",
+ "message_loop/message_pump_win.h",
+ "metrics/sample_map.cc",
+ "metrics/sample_map.h",
+ "metrics/sample_vector.cc",
+ "metrics/sample_vector.h",
+ "metrics/bucket_ranges.cc",
+ "metrics/bucket_ranges.h",
+ "metrics/histogram.cc",
+ "metrics/histogram.h",
+ "metrics/histogram_base.cc",
+ "metrics/histogram_base.h",
+ "metrics/histogram_flattener.h",
+ "metrics/histogram_samples.cc",
+ "metrics/histogram_samples.h",
+ "metrics/histogram_snapshot_manager.cc",
+ "metrics/histogram_snapshot_manager.h",
+ "metrics/sparse_histogram.cc",
+ "metrics/sparse_histogram.h",
+ "metrics/statistics_recorder.cc",
+ "metrics/statistics_recorder.h",
+ "metrics/stats_counters.cc",
+ "metrics/stats_counters.h",
+ "metrics/stats_table.cc",
+ "metrics/stats_table.h",
+ "move.h",
+ "native_library.h",
+ "native_library_mac.mm",
+ "native_library_posix.cc",
+ "native_library_win.cc",
+ "nix/mime_util_xdg.cc",
+ "nix/mime_util_xdg.h",
+ "nix/xdg_util.cc",
+ "nix/xdg_util.h",
+ "observer_list.h",
+ "observer_list_threadsafe.h",
+ "os_compat_android.cc",
+ "os_compat_android.h",
+ "os_compat_nacl.cc",
+ "os_compat_nacl.h",
+ "path_service.cc",
+ "path_service.h",
+ "pending_task.cc",
+ "pending_task.h",
+ "pickle.cc",
+ "pickle.h",
+ "platform_file.cc",
+ "platform_file.h",
+ "platform_file_posix.cc",
+ "platform_file_win.cc",
+ "port.h",
+ "posix/eintr_wrapper.h",
+ "posix/global_descriptors.cc",
+ "posix/global_descriptors.h",
+ "posix/unix_domain_socket_linux.cc",
+ "posix/unix_domain_socket_linux.h",
+ "power_monitor/power_monitor.cc",
+ "power_monitor/power_monitor.h",
+ "power_monitor/power_monitor_android.cc",
+ "power_monitor/power_monitor_android.h",
+ "power_monitor/power_monitor_ios.mm",
+ "power_monitor/power_monitor_mac.mm",
+ "power_monitor/power_monitor_posix.cc",
+ "power_monitor/power_monitor_win.cc",
+ "power_monitor/power_observer.h",
+ "process.h",
+ "process_info.h",
+ "process_info_mac.cc",
+ "process_info_win.cc",
+ "process_linux.cc",
+ "process_posix.cc",
+ "process_util.h",
+ "process_win.cc",
+ "process/internal_linux.cc",
+ "process/internal_linux.h",
+ "process/kill.cc",
+ "process/kill.h",
+ "process/kill_mac.cc",
+ "process/kill_posix.cc",
+ "process/kill_win.cc",
+ "process/launch.h",
+ "process/launch_ios.cc",
+ "process/launch_mac.cc",
+ "process/launch_posix.cc",
+ "process/launch_win.cc",
+ "process/memory.h",
+ "process/memory_linux.cc",
+ "process/memory_mac.mm",
+ "process/memory_win.cc",
+ "process/process_handle_freebsd.cc",
+ "process/process_handle_linux.cc",
+ "process/process_handle_mac.cc",
+ "process/process_handle_openbsd.cc",
+ "process/process_handle_posix.cc",
+ "process/process_handle_win.cc",
+ "process/process_iterator.cc",
+ "process/process_iterator.h",
+ "process/process_iterator_freebsd.cc",
+ "process/process_iterator_linux.cc",
+ "process/process_iterator_mac.cc",
+ "process/process_iterator_openbsd.cc",
+ "process/process_iterator_win.cc",
+ "process/process_metrics.h",
+ "process/process_metrics_freebsd.cc",
+ "process/process_metrics_ios.cc",
+ "process/process_metrics_linux.cc",
+ "process/process_metrics_mac.cc",
+ "process/process_metrics_openbsd.cc",
+ "process/process_metrics_posix.cc",
+ "process/process_metrics_win.cc",
+ "profiler/scoped_profile.cc",
+ "profiler/scoped_profile.h",
+ "profiler/alternate_timer.cc",
+ "profiler/alternate_timer.h",
+ "profiler/tracked_time.cc",
+ "profiler/tracked_time.h",
+ "rand_util.cc",
+ "rand_util.h",
+ "rand_util_nacl.cc",
+ "rand_util_posix.cc",
+ "rand_util_win.cc",
+ "run_loop.cc",
+ "run_loop.h",
+ "safe_numerics.h",
+ "safe_strerror_posix.cc",
+ "safe_strerror_posix.h",
+ "scoped_native_library.cc",
+ "scoped_native_library.h",
+ "sequence_checker.h",
+ "sequence_checker_impl.cc",
+ "sequence_checker_impl.h",
+ "sequenced_task_runner.cc",
+ "sequenced_task_runner.h",
+ "sequenced_task_runner_helpers.h",
+ "sha1.h",
+ "sha1_portable.cc",
+ "sha1_win.cc",
+ "single_thread_task_runner.h",
+ "stl_util.h",
+ "strings/latin1_string_conversions.cc",
+ "strings/latin1_string_conversions.h",
+ "strings/nullable_string16.cc",
+ "strings/nullable_string16.h",
+ "strings/string16.cc",
+ "strings/string16.h",
+ "strings/string_number_conversions.cc",
+ "strings/string_split.cc",
+ "strings/string_split.h",
+ "strings/string_number_conversions.h",
+ "strings/string_piece.cc",
+ "strings/string_piece.h",
+ "strings/string_tokenizer.h",
+ "strings/string_util.cc",
+ "strings/string_util.h",
+ "strings/string_util_constants.cc",
+ "strings/string_util_posix.h",
+ "strings/string_util_win.h",
+ "strings/stringize_macros.h",
+ "strings/stringprintf.cc",
+ "strings/stringprintf.h",
+ "strings/sys_string_conversions.h",
+ "strings/sys_string_conversions_mac.mm",
+ "strings/sys_string_conversions_posix.cc",
+ "strings/sys_string_conversions_win.cc",
+ "strings/utf_offset_string_conversions.cc",
+ "strings/utf_offset_string_conversions.h",
+ "strings/utf_string_conversion_utils.cc",
+ "strings/utf_string_conversion_utils.h",
+ "strings/utf_string_conversions.cc",
+ "strings/utf_string_conversions.h",
+ "supports_user_data.cc",
+ "supports_user_data.h",
+ "synchronization/cancellation_flag.cc",
+ "synchronization/cancellation_flag.h",
+ "synchronization/condition_variable.h",
+ "synchronization/condition_variable_posix.cc",
+ "synchronization/condition_variable_win.cc",
+ "synchronization/lock.cc",
+ "synchronization/lock.h",
+ "synchronization/lock_impl.h",
+ "synchronization/lock_impl_posix.cc",
+ "synchronization/lock_impl_win.cc",
+ "synchronization/spin_wait.h",
+ "synchronization/waitable_event.h",
+ "synchronization/waitable_event_posix.cc",
+ "synchronization/waitable_event_watcher.h",
+ "synchronization/waitable_event_watcher_posix.cc",
+ "synchronization/waitable_event_watcher_win.cc",
+ "synchronization/waitable_event_win.cc",
+ "system_monitor/system_monitor.cc",
+ "system_monitor/system_monitor.h",
+ "sys_byteorder.h",
+ "sys_info.cc",
+ "sys_info.h",
+ "sys_info_android.cc",
+ "sys_info_chromeos.cc",
+ "sys_info_freebsd.cc",
+ "sys_info_ios.mm",
+ "sys_info_linux.cc",
+ "sys_info_mac.cc",
+ "sys_info_openbsd.cc",
+ "sys_info_posix.cc",
+ "sys_info_win.cc",
+ "task_runner.cc",
+ "task_runner.h",
+ "task_runner_util.h",
+ "template_util.h",
+ "thread_task_runner_handle.cc",
+ "thread_task_runner_handle.h",
+ "threading/non_thread_safe.h",
+ "threading/non_thread_safe_impl.cc",
+ "threading/non_thread_safe_impl.h",
+ "threading/platform_thread.h",
+ "threading/platform_thread_android.cc",
+ "threading/platform_thread_linux.cc",
+ "threading/platform_thread_mac.mm",
+ "threading/platform_thread_posix.cc",
+ "threading/platform_thread_win.cc",
+ "threading/post_task_and_reply_impl.cc",
+ "threading/post_task_and_reply_impl.h",
+ "threading/sequenced_worker_pool.cc",
+ "threading/sequenced_worker_pool.h",
+ "threading/simple_thread.cc",
+ "threading/simple_thread.h",
+ "threading/thread.cc",
+ "threading/thread.h",
+ "threading/thread_checker.h",
+ "threading/thread_checker_impl.cc",
+ "threading/thread_checker_impl.h",
+ "threading/thread_collision_warner.cc",
+ "threading/thread_collision_warner.h",
+ "threading/thread_id_name_manager.cc",
+ "threading/thread_id_name_manager.h",
+ "threading/thread_local.h",
+ "threading/thread_local_posix.cc",
+ "threading/thread_local_storage.h",
+ "threading/thread_local_storage_posix.cc",
+ "threading/thread_local_storage_win.cc",
+ "threading/thread_local_win.cc",
+ "threading/thread_restrictions.h",
+ "threading/thread_restrictions.cc",
+ "threading/watchdog.cc",
+ "threading/watchdog.h",
+ "threading/worker_pool.h",
+ "threading/worker_pool.cc",
+ "threading/worker_pool_posix.cc",
+ "threading/worker_pool_posix.h",
+ "threading/worker_pool_win.cc",
+ "time/clock.cc",
+ "time/clock.h",
+ "time/default_clock.cc",
+ "time/default_clock.h",
+ "time/default_tick_clock.cc",
+ "time/default_tick_clock.h",
+ "time/tick_clock.cc",
+ "time/tick_clock.h",
+ "time/time.cc",
+ "time/time.h",
+ "time/time_mac.cc",
+ "time/time_posix.cc",
+ "time/time_win.cc",
+ "timer/hi_res_timer_manager_posix.cc",
+ "timer/hi_res_timer_manager_win.cc",
+ "timer/hi_res_timer_manager.h",
+ "timer/timer.cc",
+ "timer/timer.h",
+ "tracked_objects.cc",
+ "tracked_objects.h",
+ "tracking_info.cc",
+ "tracking_info.h",
+ "tuple.h",
+ "values.cc",
+ "values.h",
+ "value_conversions.cc",
+ "value_conversions.h",
+ "version.cc",
+ "version.h",
+ "vlog.cc",
+ "vlog.h",
+ "win/enum_variant.cc",
+ "win/enum_variant.h",
+ "win/event_trace_consumer.h",
+ "win/event_trace_controller.cc",
+ "win/event_trace_controller.h",
+ "win/event_trace_provider.cc",
+ "win/event_trace_provider.h",
+ "win/i18n.cc",
+ "win/i18n.h",
+ "win/iat_patch_function.cc",
+ "win/iat_patch_function.h",
+ "win/iunknown_impl.cc",
+ "win/iunknown_impl.h",
+ "win/message_window.cc",
+ "win/message_window.h",
+ "win/metro.cc",
+ "win/metro.h",
+ "win/object_watcher.cc",
+ "win/object_watcher.h",
+ "win/registry.cc",
+ "win/registry.h",
+ "win/resource_util.cc",
+ "win/resource_util.h",
+ "win/sampling_profiler.cc",
+ "win/sampling_profiler.h",
+ "win/scoped_bstr.cc",
+ "win/scoped_bstr.h",
+ "win/scoped_co_mem.h",
+ "win/scoped_com_initializer.h",
+ "win/scoped_comptr.h",
+ "win/scoped_gdi_object.h",
+ "win/scoped_handle.cc",
+ "win/scoped_handle.h",
+ "win/scoped_hdc.h",
+ "win/scoped_hglobal.h",
+ "win/scoped_process_information.cc",
+ "win/scoped_process_information.h",
+ "win/scoped_propvariant.h",
+ "win/scoped_select_object.h",
+ "win/scoped_variant.cc",
+ "win/scoped_variant.h",
+ "win/shortcut.cc",
+ "win/shortcut.h",
+ "win/startup_information.cc",
+ "win/startup_information.h",
+ "win/text_services_message_filter.cc",
+ "win/text_services_message_filter.h",
+ "win/win_util.cc",
+ "win/win_util.h",
+ "win/windows_version.cc",
+ "win/windows_version.h",
+ "win/wrapped_window_proc.cc",
+ "win/wrapped_window_proc.h",
+ ]
+
+ # TODO(brettw) I don't understand the conditions this file is used.
+ sources -= "files/file_path_watcher_stub.cc"
+
+ sources -= [
+ # TODO(brettw) ozone
+ "message_loop/message_pump_ozone.cc",
+ "message_loop/message_pump_ozone.h",
+
+ "process/process_handle_freebsd.cc",
+ "process/process_handle_openbsd.cc",
+ "process/process_iterator_freebsd.cc",
+ "process/process_iterator_openbsd.cc",
+ "process/process_metrics_freebsd.cc",
+ "process/process_metrics_openbsd.cc",
+ "sys_info_freebsd.cc",
+ "sys_info_openbsd.cc",
+ ]
+
+ if (!is_chromeos) {
+ sources -= [
+ "sys_info_chromeos.cc",
+ ]
+ }
+ if (!is_mac) {
+ sources -= "files/file_path_watcher_kqueue.cc"
+ }
+
+ # Remove nacl stuff.
+ if (!is_nacl) {
+ sources -= [
+ "os_compat_nacl.cc",
+ "os_compat_nacl.h",
+ "rand_util_nacl.cc",
+ "third_party/nspr/prcpucfg_nacl.h",
+ "memory/shared_memory_nacl.cc",
+ ]
+ }
+
+ # Windows stuff.
+ if (is_win && !is_nacl) {
+ sources -= [
+ "strings/string16.cc",
+ # Not using sha1_win.cc because it may have caused a
+ # regression to page cycler moz.
+ "sha1_win.cc",
+ ]
+
+ if (is_component_build) {
+ sources -= "debug/debug_on_start_win.cc"
+ }
+ }
+
+ # Remove non-Mac Unix stuff.
+ if (!is_posix || is_mac) {
+ sources -= [
+ "nix/mime_util_xdg.cc",
+ "nix/mime_util_xdg.h",
+ "nix/xdg_util.cc",
+ "nix/xdg_util.h",
+ ]
+ }
+
+ defines = [
+ "BASE_IMPLEMENTATION",
+ ]
+
+ deps = [
+ ":base_static",
+ "//base/allocator:allocator_extension_thunks",
+ "//third_party/modp_b64",
+ "//base/third_party/dynamic_annotations",
+ ]
+}
+
+# This is the subset of files from base that should not be used with a dynamic
+# library. Note that this library cannot depend on base because base depends on
+# base_static.
+static_library("base_static") {
+ sources = [
+ "base_switches.cc",
+ "base_switches.h",
+ "win/pe_image.cc",
+ "win/pe_image.h",
+ ]
+}
+
+component("base_i18n") {
+ sources = [
+ "i18n/base_i18n_export.h",
+ "i18n/bidi_line_iterator.cc",
+ "i18n/bidi_line_iterator.h",
+ "i18n/break_iterator.cc",
+ "i18n/break_iterator.h",
+ "i18n/char_iterator.cc",
+ "i18n/char_iterator.h",
+ "i18n/case_conversion.cc",
+ "i18n/case_conversion.h",
+ "i18n/file_util_icu.cc",
+ "i18n/file_util_icu.h",
+ "i18n/icu_encoding_detection.cc",
+ "i18n/icu_encoding_detection.h",
+ "i18n/icu_string_conversions.cc",
+ "i18n/icu_string_conversions.h",
+ "i18n/icu_util.cc",
+ "i18n/icu_util.h",
+ "i18n/number_formatting.cc",
+ "i18n/number_formatting.h",
+ "i18n/rtl.cc",
+ "i18n/rtl.h",
+ "i18n/string_compare.cc",
+ "i18n/string_compare.h",
+ "i18n/string_search.cc",
+ "i18n/string_search.h",
+ "i18n/time_formatting.cc",
+ "i18n/time_formatting.h",
+ ]
+ deps = [
+ ":base",
+ "//base/third_party/dynamic_annotations",
+ "//third_party/icu:icui18n",
+ "//third_party/icu:icuuc",
+ ]
+ defines = [
+ "BASE_I18N_IMPLEMENTATION",
+ ]
+ #'conditions': [
+ # ['toolkit_uses_gtk==1', {
+ # 'deps': [
+ # # i18n/rtl.cc uses gtk
+ # '../build/linux/system.gyp:gtk',
+ # ],
+ # }],
+ # ['OS == "win"', {
+ # # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ # 'msvs_disabled_warnings': [
+ # 4267,
+ # ],
+ # }],
+ #],
+ #'export_dependent_settings': [
+ # 'base',
+ #],
+ #'variables': {
+ # 'enable_wexit_time_destructors': 1,
+ # 'optimize': 'max',
+ #},
+}
+
+static_library("test_support_base") {
+ sources = [
+ "perftimer.cc",
+ "test/expectations/expectation.cc",
+ "test/expectations/expectation.h",
+ "test/expectations/parser.cc",
+ "test/expectations/parser.h",
+ "test/mock_chrome_application_mac.h",
+ "test/mock_chrome_application_mac.mm",
+ "test/mock_devices_changed_observer.cc",
+ "test/mock_devices_changed_observer.h",
+ "test/mock_time_provider.cc",
+ "test/mock_time_provider.h",
+ "test/multiprocess_test.cc",
+ "test/multiprocess_test.h",
+ "test/multiprocess_test_android.cc",
+ "test/null_task_runner.cc",
+ "test/null_task_runner.h",
+ "test/perf_test_suite.cc",
+ "test/perf_test_suite.h",
+ "test/scoped_locale.cc",
+ "test/scoped_locale.h",
+ "test/scoped_path_override.cc",
+ "test/scoped_path_override.h",
+ "test/sequenced_task_runner_test_template.cc",
+ "test/sequenced_task_runner_test_template.h",
+ "test/sequenced_worker_pool_owner.cc",
+ "test/sequenced_worker_pool_owner.h",
+ "test/simple_test_clock.cc",
+ "test/simple_test_clock.h",
+ "test/simple_test_tick_clock.cc",
+ "test/simple_test_tick_clock.h",
+ "test/task_runner_test_template.cc",
+ "test/task_runner_test_template.h",
+ "test/test_file_util.h",
+ "test/test_file_util_linux.cc",
+ "test/test_file_util_mac.cc",
+ "test/test_file_util_posix.cc",
+ "test/test_file_util_win.cc",
+ "test/test_listener_ios.h",
+ "test/test_listener_ios.mm",
+ "test/test_pending_task.cc",
+ "test/test_pending_task.h",
+ "test/test_process_killer_win.cc",
+ "test/test_process_killer_win.h",
+ "test/test_reg_util_win.cc",
+ "test/test_reg_util_win.h",
+ "test/test_shortcut_win.cc",
+ "test/test_shortcut_win.h",
+ "test/test_simple_task_runner.cc",
+ "test/test_simple_task_runner.h",
+ "test/test_suite.cc",
+ "test/test_suite.h",
+ "test/test_support_android.cc",
+ "test/test_support_android.h",
+ "test/test_support_ios.h",
+ "test/test_support_ios.mm",
+ "test/test_switches.cc",
+ "test/test_switches.h",
+ "test/test_timeouts.cc",
+ "test/test_timeouts.h",
+ "test/thread_test_helper.cc",
+ "test/thread_test_helper.h",
+ "test/trace_event_analyzer.cc",
+ "test/trace_event_analyzer.h",
+ "test/values_test_util.cc",
+ "test/values_test_util.h",
+ ]
+ deps = [
+ ":base",
+ ":base_static",
+ ":base_i18n",
+ "//testing:gmock",
+ "//testing:gtest",
+ ]
+
+ if (!is_posix) {
+ sources -= [
+ "test/scoped_locale.cc",
+ "test/scoped_locale.h",
+ ]
+ }
+ if (is_ios) {
+ # Pull in specific Mac files for iOS (which have been filtered out
+ # by file name rules).
+ { # Temporarily override the assignment filter in a new scope.
+ set_sources_assignment_filter([])
+ sources += "test/test_file_util_mac.cc"
+ }
+ }
+ #if (!is_bsd) {
+ # sources -= "test/test_file_util_linux.cc"
+ #}
+ #if (use_gtk) {
+ # deps += "/build/linux/system:gtk"
+ #}
+ #export_dependent_settings [
+ # 'base',
+ #]
+}
+
+config("perf_test_config") {
+ defines = [ "PERF_TEST" ]
+}
+
+static_library("test_support_perf") {
+ sources = [
+ "perftimer.cc",
+ "test/run_all_perftests.cc",
+ ]
+ deps = [
+ ":base",
+ "//testing:gtest",
+ ]
+
+ direct_dependent_configs = [ ":perf_test_config" ]
+
+ #if (toolkit_uses_gtk) {
+ # deps += "/build/linux/system:gtk",
+ #}
+}
+
+static_library("run_all_unittests") {
+ sources = [
+ "test/run_all_unittests.cc",
+ ]
+ deps = [
+ ":test_support_base",
+ ]
+}
diff --git a/tools/gn/secondary/base/allocator/BUILD.gn b/tools/gn/secondary/base/allocator/BUILD.gn new file mode 100644 index 0000000..bbcfe65 --- /dev/null +++ b/tools/gn/secondary/base/allocator/BUILD.gn @@ -0,0 +1,6 @@ +static_library("allocator_extension_thunks") {
+ sources = [
+ "allocator_extension_thunks.cc",
+ "allocator_extension_thunks.h",
+ ]
+}
diff --git a/tools/gn/secondary/base/third_party/dynamic_annotations/BUILD.gn b/tools/gn/secondary/base/third_party/dynamic_annotations/BUILD.gn new file mode 100644 index 0000000..e3939c35 --- /dev/null +++ b/tools/gn/secondary/base/third_party/dynamic_annotations/BUILD.gn @@ -0,0 +1,7 @@ +static_library("dynamic_annotations") {
+ sources = [
+ "dynamic_annotations.c",
+ "dynamic_annotations.h",
+ "../valgrind/valgrind.h",
+ ]
+}
diff --git a/tools/gn/secondary/build/config/BUILD.gn b/tools/gn/secondary/build/config/BUILD.gn new file mode 100644 index 0000000..74c57af --- /dev/null +++ b/tools/gn/secondary/build/config/BUILD.gn @@ -0,0 +1,52 @@ +config("my_msvs") {
+ includes = [ "../.." ]
+ cflags = [
+ "/Od", "/WX", "/Zi", "/Gy", "/GS", "/RTC1", "/EHsc",
+ ]
+ defines = [
+ "CHROMIUM_BUILD",
+ "TOOLKIT_VIEWS=1",
+ "USE_LIBJPEG_TURBO=1",
+ "ENABLE_ONE_CLICK_SIGNIN",
+ "ENABLE_REMOTING=1",
+ "ENABLE_WEBRTC=1",
+ "ENABLE_CONFIGURATION_POLICY",
+ "ENABLE_INPUT_SPEECH",
+ "ENABLE_NOTIFICATIONS",
+ "ENABLE_GPU=1",
+ "ENABLE_EGLIMAGE=1",
+ "ENABLE_TASK_MANAGER=1",
+ "ENABLE_EXTENSIONS=1",
+ "ENABLE_PLUGIN_INSTALLATION=1",
+ "ENABLE_PLUGINS=1",
+ "ENABLE_SESSION_SERVICE=1",
+ "ENABLE_THEMES=1",
+ "ENABLE_AUTOFILL_DIALOG=1",
+ "ENABLE_BACKGROUND=1",
+ "ENABLE_AUTOMATION=1",
+ "ENABLE_GOOGLE_NOW=1",
+ "ENABLE_LANGUAGE_DETECTION=1",
+ "ENABLE_PRINTING=1",
+ "ENABLE_CAPTIVE_PORTAL_DETECTION=1",
+ "ENABLE_APP_LIST=1",
+ "ENABLE_MESSAGE_CENTER=1",
+ "ENABLE_SETTINGS_APP=1",
+ "ENABLE_MANAGED_USERS=1",
+ ]
+}
+
+config("feature_flags") {
+ #defines =
+}
+
+config("debug") {
+ defines = [
+ "_DEBUG",
+ "DYNAMIC_ANNOTATIONS_ENABLED=1",
+ "WTF_USE_DYNAMIC_ANNOTATIONS=1",
+ ]
+}
+
+config("release") {
+
+}
diff --git a/tools/gn/secondary/build/config/BUILDCONFIG.gn b/tools/gn/secondary/build/config/BUILDCONFIG.gn new file mode 100644 index 0000000..0245597 --- /dev/null +++ b/tools/gn/secondary/build/config/BUILDCONFIG.gn @@ -0,0 +1,187 @@ +# =============================================================================
+# BUILD FLAGS
+# =============================================================================
+#
+# This block lists input arguments to the build, along with their default
+# values. GN requires listing them explicitly so it can validate input and have
+# a central place to manage the build flags.
+#
+# If a value is specified on the command line, it will overwrite the defaults
+# given here, otherwise the default will be injected into the root scope.
+#
+# KEEP IN ALPHABETICAL ORDER and write a good description for everything.
+# Use "is_*" names for intrinsic platform descriptions and build modes, and
+# "use_*" names for optional features libraries, and configurations.
+declare_args() {
+ is_component_build = 1
+ is_chromeos = 0
+ is_debug = 1
+ use_ash = 0
+ use_aura = 0
+ use_ozone = 0
+}
+
+# =============================================================================
+# SOURCES FILTERS
+# =============================================================================
+#
+# These patterns filter out platform-specific files when assigning to the
+# sources variable. The magic variable |sources_assignment_filter| is applied
+# to each assignment or appending to the sources variable and matches are
+# automatcally removed.
+#
+# We define lists of filters for each platform for all builds so they can
+# be used by individual targets if necessary (a target can always change
+# sources_assignment_filter on itself if it needs something more specific).
+#
+# Note that the patterns are NOT regular expressions. Only "*" and "\b" (path
+# boundary = end of string or slash) are supported, and the entire string
+# muct match the pattern (so you need "*.cc" to match all .cc files, for
+# example).
+
+windows_sources_filters = [
+ "*_win.cc",
+ "*_win.h",
+ "*_win_unittest.cc",
+ "*\bwin/*",
+]
+mac_sources_filters = [
+ "*_mac.h",
+ "*_mac.cc",
+ "*_mac.mm",
+ "*_mac_unittest.h",
+ "*_mac_unittest.cc",
+ "*_mac_unittest.mm",
+ "*\bmac/*",
+ "*_cocoa.h",
+ "*_cocoa.cc",
+ "*_cocoa.mm",
+ "*_cocoa_unittest.h",
+ "*_cocoa_unittest.cc",
+ "*_cocoa_unittest.mm",
+ "*\bcocoa/*",
+]
+ios_sources_filters = [
+ "*_ios.h",
+ "*_ios.cc",
+ "*_ios.mm",
+ "*_ios_unittest.h",
+ "*_ios_unittest.cc",
+ "*_ios_unittest.mm",
+ "*\bios/*",
+]
+objective_c_sources_filters = [
+ "*.mm",
+]
+linux_sources_filters = [
+ "*_linux.h",
+ "*_linux.cc",
+ "*_linux_unittest.h",
+ "*_linux_unittest.cc",
+ "*\blinux/*",
+]
+android_sources_filters = [
+ "*_android.h",
+ "*_android.cc",
+ "*_android_unittest.h",
+ "*_android_unittest.cc",
+ "*\bandroid/*",
+]
+posix_sources_filters = [
+ "*_posix.h",
+ "*_posix.cc",
+ "*_posix_unittest.h",
+ "*_posix_unittest.cc",
+ "*\bposix/*",
+]
+
+# Construct the full list of sources we're using for this platform.
+sources_assignment_filter = []
+if (is_win) {
+ sources_assignment_filter += posix_sources_filters
+} else {
+ sources_assignment_filter += windows_sources_filters
+}
+if (!is_mac) {
+ sources_assignment_filter += mac_sources_filters
+}
+if (!is_ios) {
+ sources_assignment_filter += ios_sources_filters
+}
+if (!is_mac && !is_ios) {
+ sources_assignment_filter += objective_c_sources_filters
+}
+if (!is_linux) {
+ sources_assignment_filter += linux_sources_filters
+}
+if (!is_android) {
+ sources_assignment_filter += android_sources_filters
+}
+
+# This is the actual set.
+set_sources_assignment_filter(sources_assignment_filter)
+
+# =============================================================================
+# SYSTEM CONFIG
+# =============================================================================
+
+is_nacl = 0
+
+# =============================================================================
+# BUILD OPTIONS
+# =============================================================================
+
+if (is_component_build) {
+ component_mode = "shared_library"
+} else {
+ component_mode = "static_library"
+}
+
+# =============================================================================
+# TARGET DEFAULTS
+# =============================================================================
+#
+# Set up the default configuration for every build target of the given type.
+# The values configured here will be automatically set on the scope of the
+# corresponding target. Target definitions can add or remove to the settings
+# here as needed.
+
+# Holds all configs used for making native executables and libraries, to avoid
+# duplication in each target below.
+native_compiler_configs = [
+ "//build/config:my_msvs", # TODO(brettw) eraseme
+
+ "//build/config/compiler:chromium_code",
+ "//build/config/compiler:disable_annoying_warnings",
+ "//build/config/compiler:no_rtti",
+ "//build/config/compiler:runtime_library",
+]
+if (is_win) {
+ native_compiler_configs += "//build/config/win:sdk"
+}
+
+if (is_debug) {
+ native_compiler_configs += "//build/config:debug"
+} else {
+ native_compiler_configs += "//build/config::release"
+}
+
+set_defaults("executable") {
+ configs = native_compiler_configs
+}
+
+set_defaults("static_library") {
+ configs = native_compiler_configs
+}
+
+set_defaults("shared_library") {
+ configs = native_compiler_configs
+}
+
+# ==============================================================================
+# TOOLCHAIN SETUP
+# ==============================================================================
+
+if (is_win) {
+ set_default_toolchain("//build/config/win:32")
+}
diff --git a/tools/gn/secondary/build/config/compiler/BUILD.gn b/tools/gn/secondary/build/config/compiler/BUILD.gn new file mode 100644 index 0000000..0487828 --- /dev/null +++ b/tools/gn/secondary/build/config/compiler/BUILD.gn @@ -0,0 +1,134 @@ +# Copyright (c) 2013 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.
+
+# runtime_library -------------------------------------------------------------
+#
+# Sets the runtime library and associated options.
+#
+# We don't bother making multiple versions that are toggle-able since there
+# is more than one axis of control (which makes it complicated) and there's
+# no practical reason for anybody to change this since the CRT must agree.
+
+config("runtime_library") {
+ if (is_component_build) {
+ # Component mode: dynamic CRT.
+ defines = [ "COMPONENT_BUILD" ]
+ if (is_win) {
+ # Since the library is shared, it requires exceptions or will give errors
+ # about things not matching, so keep exceptions on.
+ if (is_debug) {
+ cflags = [ "/MDd" ]
+ } else {
+ cflags = [ "/MD" ]
+ }
+ }
+ } else {
+ # Static CRT.
+ if (is_win) {
+ # We don't use exceptions, and when we link statically we can just get
+ # rid of them entirely.
+ defines = [ "_HAS_EXCEPTIONS=0" ]
+ if (is_debug) {
+ cflags = [ "/MTd" ]
+ } else {
+ cflags = [ "/MT" ]
+ }
+ }
+ }
+
+ if (is_win) {
+ defines += [
+ "__STD_C",
+ "__STDC_CONSTANT_MACROS",
+ "__STDC_FORMAT_MACROS",
+ "_CRT_RAND_S",
+ "_CRT_SECURE_NO_DEPRECATE",
+ "_SCL_SECURE_NO_DEPRECATE",
+ "_UNICODE",
+ "UNICODE",
+ ]
+ }
+}
+
+# chromium_code ---------------------------------------------------------------
+#
+# Toggles between higher and lower warnings for code that is (or isn't)
+# part of Chromium.
+
+config("chromium_code") {
+ if (is_win) {
+ cflags = [
+ "/W4", # Warning level 4.
+ ]
+ }
+}
+config("no_chromium_code") {
+ if (is_win) {
+ cflags = [
+ "/W3", # Warning level 3.
+ "/wd4800", # Disable warning when forcing value to bool.
+ ]
+ defines = [
+ "_CRT_NONSTDC_NO_WARNINGS",
+ "_CRT_NONSTDC_NO_DEPRECATE",
+ ]
+ }
+}
+
+# rtti ------------------------------------------------------------------------
+#
+# Allows turning Run-Time Type Identification on or off.
+
+config("rtti") {
+ if (is_win) {
+ cflags = [ "/GR" ]
+ }
+}
+config("no_rtti") {
+ if (is_win) {
+ cflags = [ "/GR-" ]
+ }
+}
+
+# Warnings ---------------------------------------------------------------------
+
+config("disable_annoying_warnings") {
+ if (is_win) {
+ # Please keep ordered and add names if you add more.
+ cflags = [
+ "/wd4018", # Comparing signed and unsigned values.
+ "/wd4100", # Unreferenced formal function parameter.
+ "/wd4121", # Alignment of a member was sensitive to packing.
+ "/wd4125", # Decimal digit terminates octal escape sequence.
+ "/wd4127", # Conditional expression is constant.
+ "/wd4130", # Logical operation on address of string constant.
+ # TODO(brettw) is this necessary? If so, it should probably be on whoever
+ # is silly enough to be doing this rather than globally.
+ #"/wd4131", # Function uses old-style declarator.
+ "/wd4189", # A variable was declared and initialized but never used.
+ "/wd4201", # Nonstandard extension used: nameless struct/union.
+ "/wd4238", # Nonstandard extension used: class rvalue used as lvalue.
+ "/wd4244", # Conversion: possible loss of data.
+ "/wd4245", # Conversion: signed/unsigned mismatch,
+ "/wd4251", # Class needs to have dll-interface.
+ "/wd4310", # Cast truncates constant value.
+ "/wd4351", # Elements of array will be default initialized.
+ "/wd4355", # 'this' used in base member initializer list.
+ "/wd4396", # Inline friend template thing.
+ "/wd4428", # Universal character name encountered in source.
+ "/wd4481", # Nonstandard extension: override specifier.
+ "/wd4503", # Decorated name length exceeded, name was truncated.
+ "/wd4505", # Unreferenced local function has been removed.
+ "/wd4510", # Default constructor could not be generated.
+ "/wd4512", # Assignment operator could not be generated.
+ "/wd4530", # Exception handler used, but unwind semantics not enabled.
+ "/wd4610", # Class can never be instantiated, constructor required.
+ "/wd4611", # C++ object destruction and 'catch'.
+ "/wd4701", # Potentially uninitialized local variable name used.
+ "/wd4702", # Unreachable code.
+ "/wd4706", # Assignment within conditional expression.
+ "/wd4819", # Character not in the current code page.
+ ]
+ }
+}
diff --git a/tools/gn/secondary/build/config/win/BUILD.gn b/tools/gn/secondary/build/config/win/BUILD.gn new file mode 100644 index 0000000..955504a --- /dev/null +++ b/tools/gn/secondary/build/config/win/BUILD.gn @@ -0,0 +1,184 @@ +# Copyright (c) 2013 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.
+
+# Should only be running on Windows.
+assert(is_win)
+
+# Setup the Visual Studio state.
+#
+# Its argument is the location to write the environment files.
+# It will write "environment.x86" and "environment.x64" to this directory,
+# and return a list to us.
+#
+# The list contains the include path as its only element. (I'm expecting to
+# add more so it's currently a list inside a list.)
+msvc_config = [["foo"]]
+#exec_script("get_msvc_config.py",
+ # [relative_root_output_dir],
+ # "value")
+
+# 32-bit toolchain -------------------------------------------------------------
+
+toolchain("32") {
+ tool("cc") {
+ command = "ninja -t msvc -e \$arch -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
+ description = "CC \$out"
+ rspfile = "\$out.rsp"
+ rspfile_content = "\$defines \$includes \$cflags \$cflags_c"
+ deps = "msvc"
+ }
+ tool("cxx") {
+ command = "ninja -t msvc -e \$arch -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
+ description = "CXX \$out"
+ rspfile = "\$out.rsp"
+ rspfile_content = "\$defines \$includes \$cflags \$cflags_cc"
+ deps = "msvc"
+ }
+ #tool("idl") {
+ # command = $python_path gyp-win-tool midl-wrapper \$arch \$outdir \$tlb \$h \$dlldata \$iid \$
+ # \$proxy \$in \$idlflags
+ # description = IDL \$in
+ #}
+ #tool("rc") {
+ # command = $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$defines \$includes \$rcflags \$
+ # /fo\$out \$in
+ # description = RC \$in
+ #}
+ #tool("asm") {
+ # command = $python_path gyp-win-tool asm-wrapper \$arch ml.exe \$defines \$includes /c /Fo \$
+ # \$out \$in
+ # description = ASM \$in
+ #}
+ tool("alink") {
+ command = "$python_path gyp-win-tool link-wrapper \$arch lib.exe /nologo /ignore:4221 /OUT:\$out @\$out.rsp"
+ description = "LIB \$out"
+ rspfile = "\$out.rsp"
+ rspfile_content = "\$in_newline \$libflags"
+ }
+ #tool("solink_embed_inc") {
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+ # /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+ # $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+ # -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc \$arch \$dll.manifest \$
+ # \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$
+ # \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$
+ # \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp \$dll.manifest.res
+ # description = LINK_EMBED_INC(DLL) \$dll
+ # restat = 1
+ # rspfile = \$dll.rsp
+ # rspfile_content = \$libs \$in_newline \$ldflags
+ #}
+ #tool("solink_module_embed_inc") {
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+ # /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+ # $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+ # -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc \$arch \$dll.manifest \$
+ # \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$
+ # \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$
+ # \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp \$dll.manifest.res
+ # description = LINK_EMBED_INC(DLL) \$dll
+ # restat = 1
+ # rspfile = \$dll.rsp
+ # rspfile_content = \$libs \$in_newline \$ldflags
+ #}
+ #rule link_embed_inc
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out \$
+ # /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c \$
+ # if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$out.manifest && \$
+ # $python_path gyp-win-tool manifest-to-rc \$arch \$out.manifest \$out.manifest.rc 1 && \$
+ # $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$out.manifest.rc && \$
+ # $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out /PDB:\$out.pdb \$
+ # @\$out.rsp \$out.manifest.res
+ # description = LINK_EMBED_INC \$out
+ # rspfile = \$out.rsp
+ # rspfile_content = \$in_newline \$libs \$ldflags
+ #rule solink_embed
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+ # /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+ # $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+ # -outputresource:\$dll;2
+ # description = LINK_EMBED(DLL) \$dll
+ # restat = 1
+ # rspfile = \$dll.rsp
+ # rspfile_content = \$libs \$in_newline \$ldflags
+ #rule solink_module_embed
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+ # /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+ # $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+ # -outputresource:\$dll;2
+ # description = LINK_EMBED(DLL) \$dll
+ # restat = 1
+ # rspfile = \$dll.rsp
+ # rspfile_content = \$libs \$in_newline \$ldflags
+ #rule link_embed
+ # command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out \$
+ # /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c \$
+ # if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool \$
+ # manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -outputresource:\$out;1
+ # description = LINK_EMBED \$out
+ # rspfile = \$out.rsp
+ # rspfile_content = \$in_newline \$libs \$ldflags
+ tool("solink") {
+ command = "cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$dll.manifest"
+ description = "LINK(DLL) \$dll"
+ restat = "1"
+ rspfile = "\$dll.rsp"
+ rspfile_content = "\$libs \$in_newline \$ldflags"
+ }
+ tool("solink_module") {
+ command = "cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$dll.manifet"
+ description = "LINK(DLL) \$dll"
+ restat = "1"
+ rspfile = "\$dll.rsp"
+ rspfile_content = "\$libs \$in_newline \$ldflags"
+ }
+ tool("link") {
+ command = "cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$out.manifest"
+ description = "LINK \$out"
+ rspfile = "\$out.rsp"
+ rspfile_content = "\$in_newline \$libs \$ldflags"
+ }
+ tool("stamp") {
+ command = "$python_path gyp-win-tool stamp \$out"
+ description = "STAMP \$out"
+ }
+ tool("copy") {
+ command = "$python_path gyp-win-tool recursive-mirror \$in \$out"
+ description = "COPY \$in \$out"
+ }
+}
+
+# 64-bit toolchain -------------------------------------------------------------
+
+toolchain("64") {
+}
+
+# SDK setup --------------------------------------------------------------------
+
+config("sdk") {
+ # The include path is the stuff returned by the script plus out own WTL
+ # checkout.
+ # TODO(brettw) should adding WTL be at this level or should it be more on
+ # a per-project basis?
+ includes = msvc_config[0] + "../../third_party/wtl/include"
+
+ defines = [
+ "_ATL_NO_OPENGL",
+ "_SECURE_ATL",
+ "_WIN32_WINNT=0x0602",
+ "_WINDOWS",
+ "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+ "NOMINMAX",
+ "NTDDI_VERSION=0x06020000",
+ "PSAPI_VERSION=1",
+ "WIN32",
+ "WIN32_LEAN_AND_MEAN",
+ "WINVER=0x0602",
+ ]
+}
diff --git a/tools/gn/secondary/build/config/win/get_msvc_config.py b/tools/gn/secondary/build/config/win/get_msvc_config.py new file mode 100644 index 0000000..01380cd --- /dev/null +++ b/tools/gn/secondary/build/config/win/get_msvc_config.py @@ -0,0 +1,77 @@ +# Copyright (c) 2013 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. + +# This file returns the MSVC config used by the Windows build. +# It's a bit hardcoded right now. I suspect we want to build this functionality +# into GN itself in the future. + +import sys + +# This script expects one parameter: the path to the root output directory. + +# TODO(brettw): do escaping. +def FormatStringForGN(x): + return '"' + x + '"' + +def PrintListOfStrings(x): + print '[' + for i in x: + print FormatStringForGN(i) + ', ' + print ']' + +# GN wants system-absolutepaths to begin in slashes. +sdk_root = '/C:\\Program Files (x86)\\Windows Kits\\8.0\\' +vs_root = '/C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\' + +def GetIncludes(): + return [ + sdk_root + 'Include\\shared', + sdk_root + 'Include\\um', + sdk_root + 'Include\\winrt', + vs_root + 'VC\\atlmfc\\include' + ] + +def _FormatAsEnvironmentBlock(envvar_dict): + """Format as an 'environment block' directly suitable for CreateProcess. + Briefly this is a list of key=value\0, terminated by an additional \0. See + CreateProcess documentation for more details.""" + block = '' + nul = '\0' + for key, value in envvar_dict.iteritems(): + block += key + '=' + value + nul + block += nul + return block + +def WriteEnvFile(file_path, values): + f = open(file_path, "w") + f.write(_FormatAsEnvironmentBlock(values)) + +includes = GetIncludes() + +# Write the environment files. +WriteEnvFile(sys.argv[1] + '\\environment.x86', + { 'TMP': 'C:\\Users\\brettw\\AppData\\Local\\Temp', + 'SYSTEMROOT': 'C:\\Windows', + 'TEMP': 'C:\\Users\\brettw\\AppData\\Local\\Temp', + 'LIB': 'c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\lib;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\lib;', + 'LIBPATH': 'C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;', + 'PATH': 'C:\\apps\\depot_tools\\python_bin;c:\\Program Files (x86)\\Microsoft F#\\v4.0\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VSTSDB\\Deploy;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\BIN;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\Tools;C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\VCPackages;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin\\NETFX 4.0 Tools;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin;C:\\apps\\depot_tools\\python_bin;C:\\apps\\depot_tools\\;C:\\apps\\depot_tools\\;C:\\apps\\depot_tools\\;c:\\Program Files (x86)\\Microsoft F#\\v4.0\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VSTSDB\\Deploy;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\BIN;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\Tools;C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\VCPackages;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin\\NETFX 4.0 Tools;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\windows\\corpam;C:\\python_26_amd64\\files;C:\\Windows\\ccmsetup;c:\\Program Files (x86)\\Microsoft SQL Server\\100\\Tools\\Binn\\;c:\\Program Files\\Microsoft SQL Server\\100\\Tools\\Binn\\;c:\\Program Files\\Microsoft SQL Server\\100\\DTS\\Binn\\;c:\\cygwin\\bin;C:\\apps\\;C:\\apps\\depot_tools;C:\\Program Files (x86)\\Windows Kits\\8.0\\Windows Performance Toolkit\\;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Google\\Cert Installer;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Google\\google_appengine\\', + 'PATHEXT': '=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', + 'INCLUDE': 'c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\INCLUDE;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\INCLUDE;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\include;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\INCLUDE;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\INCLUDE;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\include;'}) + +WriteEnvFile(sys.argv[1] + '\\environment.x64', + { 'TMP': 'C:\\Users\\brettw\\AppData\\Local\\Temp', + 'SYSTEMROOT': 'C:\\Windows', + 'TEMP': 'C:\\Users\\brettw\\AppData\\Local\\Temp', + 'LIB': 'c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB\\amd64;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB\\amd64;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\lib\\x64;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\lib;', + 'LIBPATH': 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework64\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB\\amd64;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB\\amd64;C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\LIB;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\LIB;', + 'PATH': 'C:\\apps\\depot_tools\\python_bin;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\BIN\\amd64;C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework64\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\VCPackages;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\Tools;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin\\NETFX 4.0 Tools\\x64;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin\\x64;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin;C:\\apps\\depot_tools\\python_bin;C:\\apps\\depot_tools\\;C:\\apps\\depot_tools\\;C:\\apps\\depot_tools\\;c:\\Program Files (x86)\\Microsoft F#\\v4.0\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VSTSDB\\Deploy;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\IDE\\;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\BIN;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\Common7\\Tools;C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319;C:\\Windows\\Microsoft.NET\\Framework\\v3.5;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\VCPackages;C:\\Program Files (x86)\\HTML Help Workshop;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin\\NETFX 4.0 Tools;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\windows\\corpam;C:\\python_26_amd64\\files;C:\\Windows\\ccmsetup;c:\\Program Files (x86)\\Microsoft SQL Server\\100\\Tools\\Binn\\;c:\\Program Files\\Microsoft SQL Server\\100\\Tools\\Binn\\;c:\\Program Files\\Microsoft SQL Server\\100\\DTS\\Binn\\;c:\\cygwin\\bin;C:\\apps\\;C:\\apps\\depot_tools;C:\\Program Files (x86)\\Windows Kits\\8.0\\Windows Performance Toolkit\\;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Google\\Cert Installer;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Google\\google_appengine\\', + 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', + 'INCLUDE': 'c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\INCLUDE;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\INCLUDE;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\include;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\INCLUDE;c:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\ATLMFC\\INCLUDE;C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.0A\\include;'}) + +# Return the includes and such. +print '[' +PrintListOfStrings(includes) +print ']' + diff --git a/tools/gn/secondary/build/config/win/get_msvc_config_real.py b/tools/gn/secondary/build/config/win/get_msvc_config_real.py new file mode 100644 index 0000000..a209d7f --- /dev/null +++ b/tools/gn/secondary/build/config/win/get_msvc_config_real.py @@ -0,0 +1,575 @@ +# Copyright (c) 2013 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. + +# This file copies the logic from GYP to find the MSVC configuration. It's not +# currently used because it is too slow. We will probably build this +# functionality into the C++ code in the future. + +"""Handle version information related to Visual Stuio.""" + +import errno +import os +import re +import subprocess +import sys + +class VisualStudioVersion(object): + """Information regarding a version of Visual Studio.""" + + def __init__(self, short_name, description, + solution_version, project_version, flat_sln, uses_vcxproj, + path, sdk_based, default_toolset=None): + self.short_name = short_name + self.description = description + self.solution_version = solution_version + self.project_version = project_version + self.flat_sln = flat_sln + self.uses_vcxproj = uses_vcxproj + self.path = path + self.sdk_based = sdk_based + self.default_toolset = default_toolset + + def ShortName(self): + return self.short_name + + def Description(self): + """Get the full description of the version.""" + return self.description + + def SolutionVersion(self): + """Get the version number of the sln files.""" + return self.solution_version + + def ProjectVersion(self): + """Get the version number of the vcproj or vcxproj files.""" + return self.project_version + + def FlatSolution(self): + return self.flat_sln + + def UsesVcxproj(self): + """Returns true if this version uses a vcxproj file.""" + return self.uses_vcxproj + + def ProjectExtension(self): + """Returns the file extension for the project.""" + return self.uses_vcxproj and '.vcxproj' or '.vcproj' + + def Path(self): + """Returns the path to Visual Studio installation.""" + return self.path + + def ToolPath(self, tool): + """Returns the path to a given compiler tool. """ + return os.path.normpath(os.path.join(self.path, "VC/bin", tool)) + + def DefaultToolset(self): + """Returns the msbuild toolset version that will be used in the absence + of a user override.""" + return self.default_toolset + + def SetupScript(self, target_arch): + """Returns a command (with arguments) to be used to set up the + environment.""" + # Check if we are running in the SDK command line environment and use + # the setup script from the SDK if so. |target_arch| should be either + # 'x86' or 'x64'. + assert target_arch in ('x86', 'x64') + sdk_dir = os.environ.get('WindowsSDKDir') + if self.sdk_based and sdk_dir: + return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')), + '/' + target_arch] + else: + # We don't use VC/vcvarsall.bat for x86 because vcvarsall calls + # vcvars32, which it can only find if VS??COMNTOOLS is set, which it + # isn't always. + if target_arch == 'x86': + return [os.path.normpath( + os.path.join(self.path, 'Common7/Tools/vsvars32.bat'))] + else: + assert target_arch == 'x64' + arg = 'x86_amd64' + if (os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or + os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'): + # Use the 64-on-64 compiler if we can. + arg = 'amd64' + return [os.path.normpath( + os.path.join(self.path, 'VC/vcvarsall.bat')), arg] + + +def _RegistryQueryBase(sysdir, key, value): + """Use reg.exe to read a particular key. + + While ideally we might use the win32 module, we would like gyp to be + python neutral, so for instance cygwin python lacks this module. + + Arguments: + sysdir: The system subdirectory to attempt to launch reg.exe from. + key: The registry key to read from. + value: The particular value to read. + Return: + stdout from reg.exe, or None for failure. + """ + # Skip if not on Windows or Python Win32 setup issue + if sys.platform not in ('win32', 'cygwin'): + return None + # Setup params to pass to and attempt to launch reg.exe + cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'), + 'query', key] + if value: + cmd.extend(['/v', value]) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Obtain the stdout from reg.exe, reading to the end so p.returncode is valid + # Note that the error text may be in [1] in some cases + text = p.communicate()[0] + # Check return code from reg.exe; officially 0==success and 1==error + if p.returncode: + return None + return text + + +def _RegistryQuery(key, value=None): + """Use reg.exe to read a particular key through _RegistryQueryBase. + + First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If + that fails, it falls back to System32. Sysnative is available on Vista and + up and available on Windows Server 2003 and XP through KB patch 942589. Note + that Sysnative will always fail if using 64-bit python due to it being a + virtual directory and System32 will work correctly in the first place. + + KB 942589 - http://support.microsoft.com/kb/942589/en-us. + + Arguments: + key: The registry key. + value: The particular registry value to read (optional). + Return: + stdout from reg.exe, or None for failure. + """ + text = None + try: + text = _RegistryQueryBase('Sysnative', key, value) + except OSError, e: + if e.errno == errno.ENOENT: + text = _RegistryQueryBase('System32', key, value) + else: + raise + return text + + +def _RegistryGetValue(key, value): + """Use reg.exe to obtain the value of a registry key. + + Args: + key: The registry key. + value: The particular registry value to read. + Return: + contents of the registry key's value, or None on failure. + """ + text = _RegistryQuery(key, value) + if not text: + return None + # Extract value. + match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text) + if not match: + return None + return match.group(1) + + +def _RegistryKeyExists(key): + """Use reg.exe to see if a key exists. + + Args: + key: The registry key to check. + Return: + True if the key exists + """ + if not _RegistryQuery(key): + return False + return True + + +def _CreateVersion(name, path, sdk_based=False): + """Sets up MSVS project generation. + + Setup is based off the GYP_MSVS_VERSION environment variable or whatever is + autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is + passed in that doesn't match a value in versions python will throw a error. + """ + if path: + path = os.path.normpath(path) + versions = { + '2013': VisualStudioVersion('2013', + 'Visual Studio 2013', + solution_version='13.00', + project_version='4.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v110'), + '2013e': VisualStudioVersion('2013e', + 'Visual Studio 2013', + solution_version='13.00', + project_version='4.0', + flat_sln=True, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v110'), + '2012': VisualStudioVersion('2012', + 'Visual Studio 2012', + solution_version='12.00', + project_version='4.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v110'), + '2012e': VisualStudioVersion('2012e', + 'Visual Studio 2012', + solution_version='12.00', + project_version='4.0', + flat_sln=True, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based, + default_toolset='v110'), + '2010': VisualStudioVersion('2010', + 'Visual Studio 2010', + solution_version='11.00', + project_version='4.0', + flat_sln=False, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based), + '2010e': VisualStudioVersion('2010e', + 'Visual Studio 2010', + solution_version='11.00', + project_version='4.0', + flat_sln=True, + uses_vcxproj=True, + path=path, + sdk_based=sdk_based), + '2008': VisualStudioVersion('2008', + 'Visual Studio 2008', + solution_version='10.00', + project_version='9.00', + flat_sln=False, + uses_vcxproj=False, + path=path, + sdk_based=sdk_based), + '2008e': VisualStudioVersion('2008e', + 'Visual Studio 2008', + solution_version='10.00', + project_version='9.00', + flat_sln=True, + uses_vcxproj=False, + path=path, + sdk_based=sdk_based), + '2005': VisualStudioVersion('2005', + 'Visual Studio 2005', + solution_version='9.00', + project_version='8.00', + flat_sln=False, + uses_vcxproj=False, + path=path, + sdk_based=sdk_based), + '2005e': VisualStudioVersion('2005e', + 'Visual Studio 2005', + solution_version='9.00', + project_version='8.00', + flat_sln=True, + uses_vcxproj=False, + path=path, + sdk_based=sdk_based), + } + return versions[str(name)] + + +def _ConvertToCygpath(path): + """Convert to cygwin path if we are using cygwin.""" + if sys.platform == 'cygwin': + p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE) + path = p.communicate()[0].strip() + return path + + +def _DetectVisualStudioVersions(versions_to_check, force_express): + """Collect the list of installed visual studio versions. + + Returns: + A list of visual studio versions installed in descending order of + usage preference. + Base this on the registry and a quick check if devenv.exe exists. + Only versions 8-10 are considered. + Possibilities are: + 2005(e) - Visual Studio 2005 (8) + 2008(e) - Visual Studio 2008 (9) + 2010(e) - Visual Studio 2010 (10) + 2012(e) - Visual Studio 2012 (11) + 2013(e) - Visual Studio 2013 (11) + Where (e) is e for express editions of MSVS and blank otherwise. + """ + version_to_year = { + '8.0': '2005', + '9.0': '2008', + '10.0': '2010', + '11.0': '2012', + '12.0': '2013', + } + versions = [] + for version in versions_to_check: + # Old method of searching for which VS version is installed + # We don't use the 2010-encouraged-way because we also want to get the + # path to the binaries, which it doesn't offer. + keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version, + r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version, + r'HKLM\Software\Microsoft\VCExpress\%s' % version, + r'HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s' % version] + for index in range(len(keys)): + path = _RegistryGetValue(keys[index], 'InstallDir') + if not path: + continue + path = _ConvertToCygpath(path) + # Check for full. + full_path = os.path.join(path, 'devenv.exe') + express_path = os.path.join(path, 'vcexpress.exe') + if not force_express and os.path.exists(full_path): + # Add this one. + versions.append(_CreateVersion(version_to_year[version], + os.path.join(path, '..', '..'))) + # Check for express. + elif os.path.exists(express_path): + # Add this one. + versions.append(_CreateVersion(version_to_year[version] + 'e', + os.path.join(path, '..', '..'))) + + # The old method above does not work when only SDK is installed. + keys = [r'HKLM\Software\Microsoft\VisualStudio\SxS\VC7', + r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7'] + for index in range(len(keys)): + path = _RegistryGetValue(keys[index], version) + if not path: + continue + path = _ConvertToCygpath(path) + versions.append(_CreateVersion(version_to_year[version] + 'e', + os.path.join(path, '..'), sdk_based=True)) + + return versions + + +def SelectVisualStudioVersion(version='auto'): + """Select which version of Visual Studio projects to generate. + + Arguments: + version: Hook to allow caller to force a particular version (vs auto). + Returns: + An object representing a visual studio project format version. + """ + # In auto mode, check environment variable for override. + if version == 'auto': + version = os.environ.get('GYP_MSVS_VERSION', 'auto') + version_map = { + 'auto': ('10.0', '9.0', '8.0', '11.0'), + '2005': ('8.0',), + '2005e': ('8.0',), + '2008': ('9.0',), + '2008e': ('9.0',), + '2010': ('10.0',), + '2010e': ('10.0',), + '2012': ('11.0',), + '2012e': ('11.0',), + '2013': ('12.0',), + '2013e': ('12.0',), + } + override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH') + if override_path: + msvs_version = os.environ.get('GYP_MSVS_VERSION') + if not msvs_version or 'e' not in msvs_version: + raise ValueError('GYP_MSVS_OVERRIDE_PATH requires GYP_MSVS_VERSION to be ' + 'set to an "e" version (e.g. 2010e)') + return _CreateVersion(msvs_version, override_path, sdk_based=True) + version = str(version) + versions = _DetectVisualStudioVersions(version_map[version], 'e' in version) + if not versions: + if version == 'auto': + # Default to 2005 if we couldn't find anything + return _CreateVersion('2005', None) + else: + return _CreateVersion(version, None) + return versions[0] + +def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, open_out): + """It's not sufficient to have the absolute path to the compiler, linker, + etc. on Windows, as those tools rely on .dlls being in the PATH. We also + need to support both x86 and x64 compilers within the same build (to support + msvs_target_platform hackery). Different architectures require a different + compiler binary, and different supporting environment variables (INCLUDE, + LIB, LIBPATH). So, we extract the environment here, wrap all invocations + of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which + sets up the environment, and then we do not prefix the compiler with + an absolute path, instead preferring something like "cl.exe" in the rule + which will then run whichever the environment setup has put in the path. + When the following procedure to generate environment files does not + meet your requirement (e.g. for custom toolchains), you can pass + "-G ninja_use_custom_environment_files" to the gyp to suppress file + generation and use custom environment files prepared by yourself.""" + archs = ('x86', 'x64') + if generator_flags.get('ninja_use_custom_environment_files', 0): + cl_paths = {} + for arch in archs: + cl_paths[arch] = 'cl.exe' + return cl_paths + vs = GetVSVersion(generator_flags) + cl_paths = {} + for arch in archs: + # Extract environment variables for subprocesses. + args = vs.SetupScript(arch) + args.extend(('&&', 'set')) + popen = subprocess.Popen( + args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + variables, _ = popen.communicate() + env = _ExtractImportantEnvironment(variables) + env_block = _FormatAsEnvironmentBlock(env) + f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb') + f.write(env_block) + f.close() + + # Find cl.exe location for this architecture. + args = vs.SetupScript(arch) + args.extend(('&&', + 'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i')) + popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE) + output, _ = popen.communicate() + cl_paths[arch] = _ExtractCLPath(output) + return cl_paths + +def OpenOutput(path, mode='w'): + """Open |path| for writing, creating directories if necessary.""" + try: + os.makedirs(os.path.dirname(path)) + except OSError: + pass + return open(path, mode) + +vs_version = None +def GetVSVersion(generator_flags): + global vs_version + if not vs_version: + vs_version = SelectVisualStudioVersion( + generator_flags.get('msvs_version', 'auto')) + return vs_version + +def _ExtractImportantEnvironment(output_of_set): + """Extracts environment variables required for the toolchain to run from + a textual dump output by the cmd.exe 'set' command.""" + envvars_to_save = ( + 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma. + 'include', + 'lib', + 'libpath', + 'path', + 'pathext', + 'systemroot', + 'temp', + 'tmp', + ) + env = {} + for line in output_of_set.splitlines(): + for envvar in envvars_to_save: + if re.match(envvar + '=', line.lower()): + var, setting = line.split('=', 1) + if envvar == 'path': + # Our own rules (for running gyp-win-tool) and other actions in + # Chromium rely on python being in the path. Add the path to this + # python here so that if it's not in the path when ninja is run + # later, python will still be found. + setting = os.path.dirname(sys.executable) + os.pathsep + setting + env[var.upper()] = setting + break + for required in ('SYSTEMROOT', 'TEMP', 'TMP'): + if required not in env: + raise Exception('Environment variable "%s" ' + 'required to be set to valid path' % required) + return env + +def _FormatAsEnvironmentBlock(envvar_dict): + """Format as an 'environment block' directly suitable for CreateProcess. + Briefly this is a list of key=value\0, terminated by an additional \0. See + CreateProcess documentation for more details.""" + block = '' + nul = '\0' + for key, value in envvar_dict.iteritems(): + block += key + '=' + value + nul + block += nul + return block + + +def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags): + """It's not sufficient to have the absolute path to the compiler, linker, + etc. on Windows, as those tools rely on .dlls being in the PATH. We also + need to support both x86 and x64 compilers within the same build (to support + msvs_target_platform hackery). Different architectures require a different + compiler binary, and different supporting environment variables (INCLUDE, + LIB, LIBPATH). So, we extract the environment here, wrap all invocations + of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which + sets up the environment, and then we do not prefix the compiler with + an absolute path, instead preferring something like "cl.exe" in the rule + which will then run whichever the environment setup has put in the path. + When the following procedure to generate environment files does not + meet your requirement (e.g. for custom toolchains), you can pass + "-G ninja_use_custom_environment_files" to the gyp to suppress file + generation and use custom environment files prepared by yourself.""" + archs = ('x86', 'x64') + if generator_flags.get('ninja_use_custom_environment_files', 0): + cl_paths = {} + for arch in archs: + cl_paths[arch] = 'cl.exe' + return cl_paths + vs = GetVSVersion(generator_flags) + cl_paths = {} + for arch in archs: + # Extract environment variables for subprocesses. + args = vs.SetupScript(arch) + args.extend(('&&', 'set')) + popen = subprocess.Popen( + args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + variables, _ = popen.communicate() + env = _ExtractImportantEnvironment(variables) + env_block = _FormatAsEnvironmentBlock(env) + f = OpenOutput(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb') + f.write(env_block) + f.close() + + # Find cl.exe location for this architecture. + args = vs.SetupScript(arch) + args.extend(('&&', + 'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i')) + popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE) + output, _ = popen.communicate() + cl_paths[arch] = _ExtractCLPath(output) + return cl_paths + +def _ExtractCLPath(output_of_where): + """Gets the path to cl.exe based on the output of calling the environment + setup batch file, followed by the equivalent of `where`.""" + # Take the first line, as that's the first found in the PATH. + for line in output_of_where.strip().splitlines(): + if line.startswith('LOC:'): + return line[len('LOC:'):].strip() + +#print SelectVisualStudioVersion().DefaultToolset() +#GenerateEnvironmentFiles("D:\\src\\src1\\src\\out\\gn\\eraseme", {}) +#print '"', GetVSVersion({}).Path(), '"' +print '"', GetVSVersion({}).sdk_based, '"' + +#------------------------------------------------------------------------------- + +version_info = { + '2010': { + 'includes': [ + 'VC\\atlmfc\\include', + ], + }, +} diff --git a/tools/gn/secondary/ipc/BUILD.gn b/tools/gn/secondary/ipc/BUILD.gn new file mode 100644 index 0000000..5e55ee9 --- /dev/null +++ b/tools/gn/secondary/ipc/BUILD.gn @@ -0,0 +1,168 @@ +# Copyright (c) 2012 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.
+
+component("ipc") {
+ sources = [
+ "file_descriptor_set_posix.cc",
+ "file_descriptor_set_posix.h",
+ "ipc_channel.cc",
+ "ipc_channel.h",
+ "ipc_channel_factory.cc",
+ "ipc_channel_factory.h",
+ "ipc_channel_handle.h",
+ "ipc_channel_nacl.cc",
+ "ipc_channel_nacl.h",
+ "ipc_channel_posix.cc",
+ "ipc_channel_posix.h",
+ "ipc_channel_proxy.cc",
+ "ipc_channel_proxy.h",
+ "ipc_channel_reader.cc",
+ "ipc_channel_reader.h",
+ "ipc_channel_win.cc",
+ "ipc_channel_win.h",
+ "ipc_descriptors.h",
+ "ipc_export.h",
+ "ipc_forwarding_message_filter.cc",
+ "ipc_forwarding_message_filter.h",
+ "ipc_listener.h",
+ "ipc_logging.cc",
+ "ipc_logging.h",
+ "ipc_message.cc",
+ "ipc_message.h",
+ "ipc_message_macros.h",
+ "ipc_message_start.h",
+ "ipc_message_utils.cc",
+ "ipc_message_utils.h",
+ "ipc_param_traits.h",
+ "ipc_platform_file.cc",
+ "ipc_platform_file.h",
+ "ipc_sender.h",
+ "ipc_switches.cc",
+ "ipc_switches.h",
+ "ipc_sync_channel.cc",
+ "ipc_sync_channel.h",
+ "ipc_sync_message.cc",
+ "ipc_sync_message.h",
+ "ipc_sync_message_filter.cc",
+ "ipc_sync_message_filter.h",
+ "param_traits_log_macros.h",
+ "param_traits_macros.h",
+ "param_traits_read_macros.h",
+ "param_traits_write_macros.h",
+ "struct_constructor_macros.h",
+ "struct_destructor_macros.h",
+ "unix_domain_socket_util.cc",
+ "unix_domain_socket_util.h",
+ ]
+
+ #if (!is_untrusted_nacl) {
+ sources -= [
+ "ipc_channel_nacl.cc",
+ "ipc_channel_nacl.h",
+ ]
+
+ if (is_win || is_ios) {
+ sources -= [
+ "ipc_channel_factory.cc",
+ "unix_domain_socket_util.cc",
+ ]
+ }
+
+ defines = [ "IPC_IMPLEMENTATION" ]
+
+ deps = [
+ "//base",
+ # TODO(viettrungluu): Needed for base/lazy_instance.h, which is suspect.
+ "//base/third_party/dynamic_annotations",
+ ]
+}
+
+test("ipc_tests") {
+ sources = [
+ "file_descriptor_set_posix_unittest.cc",
+ "ipc_channel_posix_unittest.cc",
+ "ipc_channel_unittest.cc",
+ "ipc_fuzzing_tests.cc",
+ "ipc_message_unittest.cc",
+ "ipc_message_utils_unittest.cc",
+ "ipc_send_fds_test.cc",
+ "ipc_sync_channel_unittest.cc",
+ "ipc_sync_message_unittest.cc",
+ "ipc_sync_message_unittest.h",
+ "ipc_test_base.cc",
+ "ipc_test_base.h",
+ "sync_socket_unittest.cc",
+ "unix_domain_socket_util_unittest.cc",
+ ]
+
+ #if (toolkit_uses_gtk) {
+ # deps += "/build/linux/system:gtk"
+ #}
+ if (is_win || is_ios) {
+ sources -= "unix_domain_socket_util_unittest.cc"
+ }
+ #if (is_android && gtest_target_type == "shared_library") {
+ # deps += "/testing/android/native_test.gyp:native_testNative_code"
+ #}
+ #if (is_posix && !is_mac && !is_android) {
+ # if (linux_use_tcmalloc) {
+ # deps += "/base/allocator"
+ # }
+ #}
+
+ deps = [
+ ":ipc",
+ ":test_support_ipc",
+ "//base",
+ "//base:base_i18n",
+ "//base:run_all_unittests",
+ "//base:test_support_base",
+ "//testing:gtest",
+ ]
+}
+
+test("ipc_perftests") {
+ sources = [
+ "ipc_perftests.cc",
+ "ipc_test_base.cc",
+ "ipc_test_base.h",
+ ]
+
+ #if (toolkit_uses_gtk) {
+ # deps += "/build/linux/system:gtk"
+ #}
+ #if (is_android && gtest_target_type == "shared_library") {
+ # deps += "/testing/android/native_test.gyp:native_testNative_code"
+ #}
+ #if (is_posix && !is_mac && !is_android) {
+ # if (linux_use_tcmalloc) {
+ # deps += "/base/allocator"
+ # }
+ #}
+
+ deps = [
+ ":ipc",
+ ":test_support_ipc",
+ "//base",
+ "//base:base_i18n",
+ "//base:test_support_base",
+ "//base:test_support_perf",
+ "//testing:gtest",
+ ]
+}
+
+static_library("test_support_ipc") {
+ sources = [
+ "ipc_multiprocess_test.cc",
+ "ipc_multiprocess_test.h",
+ "ipc_test_sink.cc",
+ "ipc_test_sink.h",
+ ]
+ deps = [
+ ":ipc",
+ "//base",
+ "//testing:gtest",
+ ]
+}
+
diff --git a/tools/gn/secondary/testing/BUILD.gn b/tools/gn/secondary/testing/BUILD.gn new file mode 100644 index 0000000..b54ceba --- /dev/null +++ b/tools/gn/secondary/testing/BUILD.gn @@ -0,0 +1,44 @@ +# Copyright (c) 2013 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.
+
+static_library("gtest") {
+ sources = [
+ "gtest/include/gtest/gtest-death-test.h",
+ "gtest/include/gtest/gtest-message.h",
+ "gtest/include/gtest/gtest-param-test.h",
+ "gtest/include/gtest/gtest-printers.h",
+ "gtest/include/gtest/gtest-spi.h",
+ "gtest/include/gtest/gtest-test-part.h",
+ "gtest/include/gtest/gtest-typed-test.h",
+ "gtest/include/gtest/gtest.h",
+ "gtest/include/gtest/gtest_pred_impl.h",
+ "gtest/include/gtest/internal/gtest-death-test-internal.h",
+ "gtest/include/gtest/internal/gtest-filepath.h",
+ "gtest/include/gtest/internal/gtest-internal.h",
+ "gtest/include/gtest/internal/gtest-linked_ptr.h",
+ "gtest/include/gtest/internal/gtest-param-util-generated.h",
+ "gtest/include/gtest/internal/gtest-param-util.h",
+ "gtest/include/gtest/internal/gtest-port.h",
+ "gtest/include/gtest/internal/gtest-string.h",
+ "gtest/include/gtest/internal/gtest-tuple.h",
+ "gtest/include/gtest/internal/gtest-type-util.h",
+ #"gtest/src/gtest-all.cc", # Not needed by our build.
+ "gtest/src/gtest-death-test.cc",
+ "gtest/src/gtest-filepath.cc",
+ "gtest/src/gtest-internal-inl.h",
+ "gtest/src/gtest-port.cc",
+ "gtest/src/gtest-printers.cc",
+ "gtest/src/gtest-test-part.cc",
+ "gtest/src/gtest-typed-test.cc",
+ "gtest/src/gtest.cc",
+ "multiprocess_func_list.cc",
+ "multiprocess_func_list.h",
+ "platform_test.h",
+ ]
+
+}
+
+static_library("gmock") {
+
+}
diff --git a/tools/gn/secondary/third_party/modp_b64/BUILD.gn b/tools/gn/secondary/third_party/modp_b64/BUILD.gn new file mode 100644 index 0000000..5598f12 --- /dev/null +++ b/tools/gn/secondary/third_party/modp_b64/BUILD.gn @@ -0,0 +1,11 @@ +# Copyright (c) 2013 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.
+
+static_library("modp_b64") {
+ sources = [
+ "modp_b64.cc",
+ "modp_b64.h",
+ "modp_b64_data.h",
+ ]
+}
diff --git a/tools/gn/settings.cc b/tools/gn/settings.cc new file mode 100644 index 0000000..82014a7 --- /dev/null +++ b/tools/gn/settings.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2013 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 "tools/gn/settings.h" + +#include "base/logging.h" +#include "tools/gn/filesystem_utils.h" + +Settings::Settings(const BuildSettings* build_settings, + const Toolchain* toolchain, + const std::string& output_subdir_name) + : build_settings_(build_settings), + toolchain_(toolchain), + target_os_(WIN), // FIXME(brettw) set this properly. + import_manager_(), + base_config_(this) { + DCHECK(output_subdir_name.find('/') == std::string::npos); + if (output_subdir_name.empty()) { + toolchain_output_dir_ = build_settings->build_dir(); + } else { + // We guarantee this ends in a slash. + toolchain_output_subdir_.value().append(output_subdir_name); + toolchain_output_subdir_.value().push_back('/'); + + toolchain_output_dir_ = SourceDir(build_settings->build_dir().value() + + toolchain_output_subdir_.value()); + } + // The output dir will be null in some tests and when invoked to parsed + // one-off data without doing generation. + if (!toolchain_output_dir_.is_null()) + toolchain_gen_dir_ = SourceDir(toolchain_output_dir_.value() + "gen/"); +} + +Settings::~Settings() { +} + + diff --git a/tools/gn/settings.h b/tools/gn/settings.h new file mode 100644 index 0000000..93a1ae6 --- /dev/null +++ b/tools/gn/settings.h @@ -0,0 +1,107 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SETTINGS_H_ +#define TOOLS_GN_SETTINGS_H_ + +#include "base/files/file_path.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/import_manager.h" +#include "tools/gn/output_file.h" +#include "tools/gn/scope.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/toolchain.h" + +// Holds the settings for one toolchain invocation. There will be one +// Settings object for each toolchain type, each referring to the same +// BuildSettings object for shared stuff. +// +// The Settings object is const once it is constructed, which allows us to +// use it from multiple threads during target generation without locking (which +// is important, because it gets used a lot). +// +// The Toolchain object holds the set of stuff that is set by the toolchain +// declaration, which obviously needs to be set later when we actually parse +// the file with the toolchain declaration in it. +class Settings { + public: + enum TargetOS { + UNKNOWN, + LINUX, + MAC, + WIN + }; + + // Constructs a toolchain settings. The output_subdir_name is the name we + // should use for the subdirectory in the build output directory for this + // toolchain's outputs. It should have no slashes in it. The default + // toolchain should use an empty string. + Settings(const BuildSettings* build_settings, + const Toolchain* toolchain, + const std::string& output_subdir_name); + ~Settings(); + + const BuildSettings* build_settings() const { return build_settings_; } + + // Danger: this must only be used for getting the toolchain label until the + // toolchain has been resolved. Otherwise, it will be modified on an + // arbitrary thread when the toolchain invocation is found. Generally, you + // will only read this from the target generation where we know everything + // has been resolved and won't change. + const Toolchain* toolchain() const { return toolchain_; } + + bool IsMac() const { return target_os_ == MAC; } + bool IsWin() const { return target_os_ == WIN; } + + TargetOS target_os() const { return target_os_; } + void set_target_os(TargetOS t) { target_os_ = t; } + + const OutputFile& toolchain_output_subdir() const { + return toolchain_output_subdir_; + } + const SourceDir& toolchain_output_dir() const { + return toolchain_output_dir_; + } + + // Directory for generated files. + const SourceDir& toolchain_gen_dir() const { + return toolchain_gen_dir_; + } + + // The import manager caches the result of executing imported files in the + // context of a given settings object. + // + // See the ItemTree getter in GlobalSettings for why this doesn't return a + // const pointer. + ImportManager& import_manager() const { return import_manager_; } + + const Scope* base_config() const { return &base_config_; } + Scope* base_config() { return &base_config_; } + + private: + const BuildSettings* build_settings_; + + const Toolchain* toolchain_; + + TargetOS target_os_; + + mutable ImportManager import_manager_; + + // The subdirectory inside the build output for this toolchain. For the + // default toolchain, this will be empty (since the deafult toolchain's + // output directory is the same as the build directory). When nonempty, this + // is guaranteed to end in a slash. + OutputFile toolchain_output_subdir_; + + // Full source file path to the toolchain output directory. + SourceDir toolchain_output_dir_; + + SourceDir toolchain_gen_dir_; + + Scope base_config_; + + DISALLOW_COPY_AND_ASSIGN(Settings); +}; + +#endif // TOOLS_GN_SETTINGS_H_ diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc new file mode 100644 index 0000000..f93cd4d --- /dev/null +++ b/tools/gn/setup.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2013 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 "tools/gn/setup.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/parser.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" +#include "tools/gn/tokenizer.h" +#include "tools/gn/value.h" + +namespace { + +// More logging. +const char kSwitchVerbose[] = "v"; + +const char kSwitchRoot[] = "root"; +const char kSecondarySource[] = "secondary"; + +const base::FilePath::CharType kGnFile[] = FILE_PATH_LITERAL(".gn"); + +base::FilePath FindDotFile(const base::FilePath& current_dir) { + base::FilePath try_this_file = current_dir.Append(kGnFile); + if (base::PathExists(try_this_file)) + return try_this_file; + + base::FilePath with_no_slash = current_dir.StripTrailingSeparators(); + base::FilePath up_one_dir = with_no_slash.DirName(); + if (up_one_dir == current_dir) + return base::FilePath(); // Got to the top. + + return FindDotFile(up_one_dir); +} + +} // namespace + +Setup::Setup() + : dotfile_toolchain_(Label()), + dotfile_settings_(&dotfile_build_settings_, &dotfile_toolchain_, + std::string()), + dotfile_scope_(&dotfile_settings_) { +} + +Setup::~Setup() { +} + +bool Setup::DoSetup() { + CommandLine* cmdline = CommandLine::ForCurrentProcess(); + + scheduler_.set_verbose_logging(cmdline->HasSwitch(kSwitchVerbose)); + + if (!FillSourceDir(*cmdline)) + return false; + if (!RunConfigFile()) + return false; + if (!FillOtherConfig(*cmdline)) + return false; + + // FIXME(brettw) get python path! +/*#if defined(OS_WIN) + build_settings_.set_python_path(base::FilePath( + //L"P:\\depot_tools\\python_bin\\python.exe")); + L"C:\\apps\\depot_tools\\python_bin\\python.exe")); +#else*/ + build_settings_.set_python_path(base::FilePath("python")); +//#endif + + build_settings_.SetBuildDir(SourceDir("//out/gn/")); + + return true; +} + +bool Setup::Run() { + // Load the root build file and start runnung. + build_settings_.toolchain_manager().StartLoadingUnlocked( + SourceFile("//BUILD.gn")); + if (!scheduler_.Run()) + return false; + + Err err = build_settings_.item_tree().CheckForBadItems(); + if (err.has_error()) { + err.PrintToStdout(); + return false; + } + return true; +} + +bool Setup::FillSourceDir(const CommandLine& cmdline) { + // Find the .gn file. + base::FilePath root_path; + + // Prefer the command line args to the config file. + base::FilePath relative_root_path = cmdline.GetSwitchValuePath(kSwitchRoot); + if (!relative_root_path.empty()) { + root_path = base::MakeAbsoluteFilePath(relative_root_path); + dotfile_name_ = root_path.Append(kGnFile); + } else { + base::FilePath cur_dir; + file_util::GetCurrentDirectory(&cur_dir); + dotfile_name_ = FindDotFile(cur_dir); + if (dotfile_name_.empty()) { + Err(Location(), "Can't find source root.", + "I could not find a \".gn\" file in the current directory or any " + "parent,\nand the --root command-line argument was not specified.") + .PrintToStdout(); + return false; + } + root_path = dotfile_name_.DirName(); + } + + if (scheduler_.verbose_logging()) + scheduler_.Log("Using source root", FilePathToUTF8(root_path)); + build_settings_.set_root_path(root_path); + + return true; +} + +bool Setup::RunConfigFile() { + if (scheduler_.verbose_logging()) + scheduler_.Log("Got dotfile", FilePathToUTF8(dotfile_name_)); + + dotfile_input_file_.reset(new InputFile(SourceFile("//.gn"))); + if (!dotfile_input_file_->Load(dotfile_name_)) { + Err(Location(), "Could not load dotfile.", + "The file \"" + FilePathToUTF8(dotfile_name_) + "\" cound't be loaded") + .PrintToStdout(); + return false; + } + + Err err; + dotfile_tokens_ = Tokenizer::Tokenize(dotfile_input_file_.get(), &err); + if (err.has_error()) { + err.PrintToStdout(); + return false; + } + + dotfile_root_ = Parser::Parse(dotfile_tokens_, &err); + if (err.has_error()) { + err.PrintToStdout(); + return false; + } + + dotfile_root_->AsBlock()->ExecuteBlockInScope(&dotfile_scope_, &err); + if (err.has_error()) { + err.PrintToStdout(); + return false; + } + + return true; +} + +bool Setup::FillOtherConfig(const CommandLine& cmdline) { + Err err; + + // Secondary source path. + SourceDir secondary_source; + if (cmdline.HasSwitch(kSecondarySource)) { + // Prefer the command line over the config file. + secondary_source = + SourceDir(cmdline.GetSwitchValueASCII(kSecondarySource)); + } else { + // Read from the config file if present. + const Value* secondary_value = + dotfile_scope_.GetValue("secondary_source", true); + if (secondary_value) { + if (!secondary_value->VerifyTypeIs(Value::STRING, &err)) { + err.PrintToStdout(); + return false; + } + build_settings_.SetSecondarySourcePath( + SourceDir(secondary_value->string_value())); + } + } + + // Build config dir. + const Value* build_config_value = + dotfile_scope_.GetValue("buildconfig", true); + if (!build_config_value) { + Err(Location(), "No build config file.", + "Your .gn file (\"" + FilePathToUTF8(dotfile_name_) + "\")\n" + "didn't specify a \"buildconfig\" value.").PrintToStdout(); + return false; + } + build_settings_.set_build_config_file( + SourceFile("//build/config/BUILDCONFIG.gn")); + + return true; +} diff --git a/tools/gn/setup.h b/tools/gn/setup.h new file mode 100644 index 0000000..e698ac5 --- /dev/null +++ b/tools/gn/setup.h @@ -0,0 +1,69 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SETUP_H_ +#define TOOLS_GN_SETUP_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/token.h" +#include "tools/gn/toolchain.h" + +class CommandLine; +class InputFile; +class ParseNode; + +// Helper class to setup the build settings and environment for the various +// commands to run. +class Setup { + public: + Setup(); + ~Setup(); + + // Configures the build for the current command line. On success returns + // true. On failure, prints the error and returns false. + bool DoSetup(); + + // Runs the load, returning true on success. On failure, prints the error + // and returns false. + bool Run(); + + BuildSettings& build_settings() { return build_settings_; } + Scheduler& scheduler() { return scheduler_; } + + private: + // Fills the root directory into the settings. Returns true on success. + bool FillSourceDir(const CommandLine& cmdline); + + // Run config file. + bool RunConfigFile(); + + bool FillOtherConfig(const CommandLine& cmdline); + + BuildSettings build_settings_; + Scheduler scheduler_; + + // State for invoking the dotfile. + // TODO(brettw) this seems a bit excessive, maybe we can get this down + // somehow? + base::FilePath dotfile_name_; + scoped_ptr<InputFile> dotfile_input_file_; + std::vector<Token> dotfile_tokens_; + scoped_ptr<ParseNode> dotfile_root_; + BuildSettings dotfile_build_settings_; + Toolchain dotfile_toolchain_; + Settings dotfile_settings_; + Scope dotfile_scope_; + + DISALLOW_COPY_AND_ASSIGN(Setup); +}; + +#endif // TOOLS_GN_SETUP_H_ diff --git a/tools/gn/source_dir.cc b/tools/gn/source_dir.cc new file mode 100644 index 0000000..5739b52 --- /dev/null +++ b/tools/gn/source_dir.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2013 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 "tools/gn/source_dir.h" + +#include "base/logging.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/source_file.h" + +namespace { + +void AssertValueSourceDirString(const std::string& s) { + DCHECK(!s.empty()); + DCHECK(s[0] == '/'); + DCHECK(EndsWithSlash(s)); +} + +} // namespace + +SourceDir::SourceDir() { +} + +SourceDir::SourceDir(const base::StringPiece& p) + : value_(p.data(), p.size()) { + if (!EndsWithSlash(value_)) + value_.push_back('/'); + AssertValueSourceDirString(value_); +} + +SourceDir::~SourceDir() { +} + +SourceFile SourceDir::ResolveRelativeFile(const base::StringPiece& p) const { + SourceFile ret; + + // It's an error to resolve an empty string or one that is a directory + // (indicated by a trailing slash) because this is the function that expects + // to return a file. + if (p.empty() || (p.size() > 0 && p[p.size() - 1] == '/')) + return SourceFile(); + if (p[0] == '/') { + // Absolute path. + ret.value_.assign(p.data(), p.size()); + return ret; + } + + ret.value_.reserve(value_.size() + p.size()); + ret.value_.assign(value_); + ret.value_.append(p.data(), p.size()); + + NormalizePath(&ret.value_); + return ret; +} + +SourceDir SourceDir::ResolveRelativeDir(const base::StringPiece& p) const { + SourceDir ret; + + if (p.empty()) + return ret; + if (p[0] == '/') { + // Absolute path. + return SourceDir(p); + } + + ret.value_.reserve(value_.size() + p.size()); + ret.value_.assign(value_); + ret.value_.append(p.data(), p.size()); + + NormalizePath(&ret.value_); + if (!EndsWithSlash(ret.value_)) + ret.value_.push_back('/'); + AssertValueSourceDirString(ret.value_); + + return ret; +} + +base::FilePath SourceDir::Resolve(const base::FilePath& source_root) const { + if (is_null()) + return base::FilePath(); + + std::string converted; + if (is_system_absolute()) { + converted = value_; + ConvertPathToSystem(&converted); + return base::FilePath(UTF8ToFilePath(converted)); + } + + // String the double-leading slash for source-relative paths. + converted.assign(&value_[2], value_.size() - 2); + ConvertPathToSystem(&converted); + return source_root.Append(UTF8ToFilePath(converted)); +} + +void SourceDir::SwapInValue(std::string* v) { + value_.swap(*v); + AssertValueSourceDirString(value_); +} diff --git a/tools/gn/source_dir.h b/tools/gn/source_dir.h new file mode 100644 index 0000000..3b6caee --- /dev/null +++ b/tools/gn/source_dir.h @@ -0,0 +1,104 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SOURCE_DIR_H_ +#define TOOLS_GN_SOURCE_DIR_H_ + +#include <string> + +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" + +class SourceFile; + +// Represents a directory within the source tree. Source dirs begin and end in +// slashes. +// +// If there is one slash at the beginning, it will mean a system-absolute file +// path. On Windows, absolute system paths will be of the form "/C:/foo/bar". +// +// Two slashes at the beginning indicate a path relative to the source root. +class SourceDir { + public: + SourceDir(); + explicit SourceDir(const base::StringPiece& p); + ~SourceDir(); + + // Resolves a file or dir name relative to this source directory. Will return + // an empty SourceDir/File on error. Empty input is always an error (it's + // possible we should say ResolveRelativeDir vs. an empty string should be + // the source dir, but we require "." instead). + SourceFile ResolveRelativeFile(const base::StringPiece& p) const; + SourceDir ResolveRelativeDir(const base::StringPiece& p) const; + + // Resolves this source file relative to some given source root. Returns + // an empty file path on error. + base::FilePath Resolve(const base::FilePath& source_root) const; + + bool is_null() const { return value_.empty(); } + const std::string& value() const { return value_; } + + // Returns true if this path starts with a "//" which indicates a path + // from the source root. + bool is_source_absolute() const { + return value_.size() >= 2 && value_[0] == '/' && value_[1] == '/'; + } + + // Returns true if this path starts with a single slash which indicates a + // system-absolute path. + bool is_system_absolute() const { + return !is_source_absolute(); + } + + // Returns a source-absolute path starting with only one slash at the + // beginning (normally source-absolute paths start with two slashes to mark + // them as such). This is normally used when concatenating directories + // together. + // + // This function asserts that the directory is actually source-absolute. The + // return value points into our buffer. + base::StringPiece SourceAbsoluteWithOneSlash() const { + CHECK(is_source_absolute()); + return base::StringPiece(&value_[1], value_.size() - 1); + } + + void SwapInValue(std::string* v); + + bool operator==(const SourceDir& other) const { + return value_ == other.value_; + } + bool operator!=(const SourceDir& other) const { + return !operator==(other); + } + bool operator<(const SourceDir& other) const { + return value_ < other.value_; + } + + private: + friend class SourceFile; + std::string value_; + + // Copy & assign supported. +}; + +namespace BASE_HASH_NAMESPACE { + +#if defined(COMPILER_GCC) +template<> struct hash<SourceDir> { + std::size_t operator()(const SourceDir& v) const { + hash<std::string> h; + return h(v.value()); + } +}; +#elif defined(COMPILER_MSVC) +inline size_t hash_value(const SourceDir& v) { + return hash_value(v.value()); +} +#endif // COMPILER... + +} // namespace BASE_HASH_NAMESPACE + +#endif // TOOLS_GN_SOURCE_DIR_H_ diff --git a/tools/gn/source_dir_unittest.cc b/tools/gn/source_dir_unittest.cc new file mode 100644 index 0000000..745513d --- /dev/null +++ b/tools/gn/source_dir_unittest.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2013 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/source_dir.h" +#include "tools/gn/source_file.h" + +TEST(SourceDir, ResolveRelativeFile) { + SourceDir base("//base/"); + + // Empty input is an error. + EXPECT_TRUE(base.ResolveRelativeFile("") == SourceFile()); + + // These things are directories, so should be an error. + EXPECT_TRUE(base.ResolveRelativeFile("//foo/bar/") == SourceFile()); + EXPECT_TRUE(base.ResolveRelativeFile("bar/") == SourceFile()); + + // Absolute paths should be passed unchanged. + EXPECT_TRUE(base.ResolveRelativeFile("//foo") == SourceFile("//foo")); + EXPECT_TRUE(base.ResolveRelativeFile("/foo") == SourceFile("/foo")); + + // Basic relative stuff. + EXPECT_TRUE(base.ResolveRelativeFile("foo") == SourceFile("//base/foo")); + EXPECT_TRUE(base.ResolveRelativeFile("./foo") == SourceFile("//base/foo")); + EXPECT_TRUE(base.ResolveRelativeFile("../foo") == SourceFile("//foo")); + EXPECT_TRUE(base.ResolveRelativeFile("../../foo") == SourceFile("//foo")); +} + +TEST(SourceDir, ResolveRelativeDir) { + SourceDir base("//base/"); + + // Empty input is an error. + EXPECT_TRUE(base.ResolveRelativeDir("") == SourceDir()); + + // Absolute paths should be passed unchanged. + EXPECT_TRUE(base.ResolveRelativeDir("//foo") == SourceDir("//foo/")); + EXPECT_TRUE(base.ResolveRelativeDir("/foo") == SourceDir("/foo/")); + + // Basic relative stuff. + EXPECT_TRUE(base.ResolveRelativeDir("foo") == SourceDir("//base/foo/")); + EXPECT_TRUE(base.ResolveRelativeDir("./foo") == SourceDir("//base/foo/")); + EXPECT_TRUE(base.ResolveRelativeDir("../foo") == SourceDir("//foo/")); + EXPECT_TRUE(base.ResolveRelativeDir("../../foo/") == SourceDir("//foo/")); +} diff --git a/tools/gn/source_file.cc b/tools/gn/source_file.cc new file mode 100644 index 0000000..de07de1 --- /dev/null +++ b/tools/gn/source_file.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2013 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 "tools/gn/source_file.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/source_dir.h" + +SourceFile::SourceFile() { +} + +SourceFile::SourceFile(const base::StringPiece& p) + : value_(p.data(), p.size()) { + DCHECK(!value_.empty()); + DCHECK(value_[0] == '/'); + DCHECK(!EndsWithSlash(value_)); +} + +SourceFile::~SourceFile() { +} + +std::string SourceFile::GetName() const { + if (is_null()) + return std::string(); + + DCHECK(value_.find('/') != std::string::npos); + size_t last_slash = value_.rfind('/'); + return std::string(&value_[last_slash + 1], + value_.size() - last_slash - 1); +} + +SourceDir SourceFile::GetDir() const { + if (is_null()) + return SourceDir(); + + DCHECK(value_.find('/') != std::string::npos); + size_t last_slash = value_.rfind('/'); + return SourceDir(base::StringPiece(&value_[0], last_slash + 1)); +} + +base::FilePath SourceFile::Resolve(const base::FilePath& source_root) const { + if (is_null()) + return base::FilePath(); + + std::string converted; +#if defined(OS_WIN) + if (is_system_absolute()) { + converted.assign(&value_[1], value_.size() - 1); + DCHECK(converted.size() > 2 && converted[1] == ':') + << "Expecting Windows absolute file path with a drive letter: " + << value_; + return base::FilePath(UTF8ToFilePath(converted)); + } + + converted.assign(&value_[2], value_.size() - 2); + ConvertPathToSystem(&converted); + return root_path_.Append(UTF8ToFilePath(converted)); +#else + if (is_system_absolute()) + return base::FilePath(value_); + converted.assign(&value_[2], value_.size() - 2); + return source_root.Append(converted); +#endif +} diff --git a/tools/gn/source_file.h b/tools/gn/source_file.h new file mode 100644 index 0000000..d883bc0 --- /dev/null +++ b/tools/gn/source_file.h @@ -0,0 +1,97 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_SOURCE_FILE_H_ +#define TOOLS_GN_SOURCE_FILE_H_ + +#include <string> + +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" + +class SourceDir; + +// Represents a file within the source tree. Always begins in a slash, never +// ends in one. +class SourceFile { + public: + SourceFile(); + + // Takes a known absolute source file. Always begins in a slash. + explicit SourceFile(const base::StringPiece& p); + + ~SourceFile(); + + bool is_null() const { return value_.empty(); } + const std::string& value() const { return value_; } + + // Returns everythign after the last slash. + std::string GetName() const; + SourceDir GetDir() const; + + // Resolves this source file relative to some given source root. Returns + // an empty file path on error. + base::FilePath Resolve(const base::FilePath& source_root) const; + + // Returns true if this file starts with a "//" which indicates a path + // from the source root. + bool is_source_absolute() const { + return value_.size() >= 2 && value_[0] == '/' && value_[1] == '/'; + } + + // Returns true if this file starts with a single slash which indicates a + // system-absolute path. + bool is_system_absolute() const { + return !is_source_absolute(); + } + + // Returns a source-absolute path starting with only one slash at the + // beginning (normally source-absolute paths start with two slashes to mark + // them as such). This is normally used when concatenating names together. + // + // This function asserts that the file is actually source-absolute. The + // return value points into our buffer. + base::StringPiece SourceAbsoluteWithOneSlash() const { + CHECK(is_source_absolute()); + return base::StringPiece(&value_[1], value_.size() - 1); + } + + bool operator==(const SourceFile& other) const { + return value_ == other.value_; + } + bool operator!=(const SourceFile& other) const { + return !operator==(other); + } + bool operator<(const SourceFile& other) const { + return value_ < other.value_; + } + + private: + friend class SourceDir; + + std::string value_; + + // Copy & assign supported. +}; + +namespace BASE_HASH_NAMESPACE { + +#if defined(COMPILER_GCC) +template<> struct hash<SourceFile> { + std::size_t operator()(const SourceFile& v) const { + hash<std::string> h; + return h(v.value()); + } +}; +#elif defined(COMPILER_MSVC) +inline size_t hash_value(const SourceFile& v) { + return hash_value(v.value()); +} +#endif // COMPILER... + +} // namespace BASE_HASH_NAMESPACE + +#endif // TOOLS_GN_SOURCE_FILE_H_ diff --git a/tools/gn/standard_out.cc b/tools/gn/standard_out.cc new file mode 100644 index 0000000..f6a2031 --- /dev/null +++ b/tools/gn/standard_out.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2013 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 "tools/gn/standard_out.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#else +#include <stdio.h> +#endif + +namespace { + +bool initialized = false; + +#if defined(OS_WIN) +HANDLE hstdout; +WORD default_attributes; + +bool is_console = false; +#endif + +void EnsureInitialized() { + if (initialized) + return; + initialized = true; + +#if defined(OS_WIN) + hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO info; + is_console = !!::GetConsoleScreenBufferInfo(hstdout, &info); + default_attributes = info.wAttributes; +#endif +} + +} // namespace + +#if defined(OS_WIN) + +void OutputString(const std::string& output, TextDecoration dec) { + EnsureInitialized(); + if (is_console) { + switch (dec) { + case DECORATION_NONE: + break; + case DECORATION_BOLD: + ::SetConsoleTextAttribute(hstdout, FOREGROUND_INTENSITY); + break; + case DECORATION_RED: + ::SetConsoleTextAttribute(hstdout, + FOREGROUND_RED | FOREGROUND_INTENSITY); + break; + case DECORATION_GREEN: + // Keep green non-bold. + ::SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN); + break; + case DECORATION_BLUE: + ::SetConsoleTextAttribute(hstdout, + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + break; + case DECORATION_YELLOW: + ::SetConsoleTextAttribute(hstdout, + FOREGROUND_RED | FOREGROUND_GREEN); + break; + } + } + + DWORD written = 0; + ::WriteFile(hstdout, output.c_str(), output.size(), &written, NULL); + + if (is_console) + ::SetConsoleTextAttribute(hstdout, default_attributes); +} + +#else + +void OutputString(const std::string& output, TextDecoration dec) { + printf("%s", output.c_str()); +} + +#endif diff --git a/tools/gn/standard_out.h b/tools/gn/standard_out.h new file mode 100644 index 0000000..2eb525b --- /dev/null +++ b/tools/gn/standard_out.h @@ -0,0 +1,22 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_STANDARD_OUT_H_ +#define TOOLS_GN_STANDARD_OUT_H_ + +#include <string> + +enum TextDecoration { + DECORATION_NONE = 0, + DECORATION_BOLD, + DECORATION_RED, + DECORATION_GREEN, + DECORATION_BLUE, + DECORATION_YELLOW +}; + +void OutputString(const std::string& output, + TextDecoration dec = DECORATION_NONE); + +#endif // TOOLS_GN_STANDARD_OUT_H_ diff --git a/tools/gn/string_utils.cc b/tools/gn/string_utils.cc new file mode 100644 index 0000000..14d296a --- /dev/null +++ b/tools/gn/string_utils.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2013 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 "tools/gn/string_utils.h" + +#include "tools/gn/err.h" +#include "tools/gn/scope.h" +#include "tools/gn/token.h" +#include "tools/gn/tokenizer.h" +#include "tools/gn/value.h" + +namespace { + +// Constructs an Err indicating a range inside a string. We assume that the +// token has quotes around it that are not counted by the offset. +Err ErrInsideStringToken(const Token& token, size_t offset, size_t size, + const std::string& msg, + const std::string& help = std::string()) { + // The "+1" is skipping over the " at the beginning of the token. + Location begin_loc(token.location().file(), + token.location().line_number(), + token.location().char_offset() + offset + 1); + Location end_loc(token.location().file(), + token.location().line_number(), + token.location().char_offset() + offset + 1 + size); + return Err(LocationRange(begin_loc, end_loc), msg, help); +} + +// Given the character input[i] indicating the $ in a string, locates the +// identifier and places its range in |*identifier|, and updates |*i| to +// point to the last character consumed. +// +// On error returns false and sets the error. +bool LocateInlineIdenfitier(const Token& token, + const char* input, size_t size, + size_t* i, + base::StringPiece* identifier, + Err* err) { + size_t dollars_index = *i; + (*i)++; + if (*i == size) { + *err = ErrInsideStringToken(token, dollars_index, 1, "$ at end of string.", + "I was expecting an identifier after the $."); + return false; + } + + bool has_brackets; + if (input[*i] == '{') { + (*i)++; + if (*i == size) { + *err = ErrInsideStringToken(token, dollars_index, 2, + "${ at end of string.", + "I was expecting an identifier inside the ${...}."); + return false; + } + has_brackets = true; + } else { + has_brackets = false; + } + + // First char is special. + if (!Tokenizer::IsIdentifierFirstChar(input[*i])) { + *err = ErrInsideStringToken( + token, dollars_index, *i - dollars_index + 1, + "$ not followed by an identifier char.", + "It you want a literal $ use \"\\$\"."); + return false; + } + size_t begin_offset = *i; + (*i)++; + + // Find the first non-identifier char following the string. + while (*i < size && Tokenizer::IsIdentifierContinuingChar(input[*i])) + (*i)++; + size_t end_offset = *i; + + // If we started with a bracket, validate that there's an ending one. Leave + // *i pointing to the last char we consumed (backing up one). + if (has_brackets) { + if (*i == size) { + *err = ErrInsideStringToken(token, dollars_index, *i - dollars_index, + "Unterminated ${..."); + return false; + } else if (input[*i] != '}') { + *err = ErrInsideStringToken(token, *i, 1, "Not an identifier in string expansion.", + "The contents of ${...} should be an identifier. " + "This character is out of sorts."); + return false; + } + // We want to consume the bracket but also back up one, so *i is unchanged. + } else { + (*i)--; + } + + *identifier = base::StringPiece(&input[begin_offset], + end_offset - begin_offset); + return true; +} + +bool AppendIdentifierValue(Scope* scope, + const Token& token, + const base::StringPiece& identifier, + std::string* output, + Err* err) { + const Value* value = scope->GetValue(identifier, true); + if (!value) { + // We assume the identifier points inside the token. + *err = ErrInsideStringToken( + token, identifier.data() - token.value().data() - 1, identifier.size(), + "Undefined identifier in string expansion.", + std::string("\"") + identifier + "\" is not currently in scope."); + return false; + } + + output->append(value->ToString()); + return true; +} + +} // namespace + +bool ExpandStringLiteral(Scope* scope, + const Token& literal, + Value* result, + Err* err) { + DCHECK(literal.type() == Token::STRING); + DCHECK(literal.value().size() > 1); // Should include quotes. + DCHECK(result->type() == Value::STRING); // Should be already set. + + // The token includes the surrounding quotes, so strip those off. + const char* input = &literal.value().data()[1]; + size_t size = literal.value().size() - 2; + + std::string& output = result->string_value(); + output.reserve(size); + for (size_t i = 0; i < size; i++) { + if (input[i] == '\\') { + if (i < size - 1) { + switch (input[i + 1]) { + case '\\': + case '"': + case '$': + output.push_back(input[i + 1]); + i++; + continue; + default: // Everything else has no meaning: pass the literal. + break; + } + } + output.push_back(input[i]); + } else if (input[i] == '$') { + base::StringPiece identifier; + if (!LocateInlineIdenfitier(literal, input, size, &i, &identifier, err)) + return false; + if (!AppendIdentifierValue(scope, literal, identifier, &output, err)) + return false; + } else { + output.push_back(input[i]); + } + } + return true; +} + +std::string RemovePrefix(const std::string& str, const std::string& prefix) { + CHECK(str.size() >= prefix.size() && + str.compare(0, prefix.size(), prefix) == 0); + return str.substr(prefix.size()); +} diff --git a/tools/gn/string_utils.h b/tools/gn/string_utils.h new file mode 100644 index 0000000..7fff1d8 --- /dev/null +++ b/tools/gn/string_utils.h @@ -0,0 +1,51 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_STRING_UTILS_H_ +#define TOOLS_GN_STRING_UTILS_H_ + +#include "base/strings/string_piece.h" + +class Err; +class Scope; +class Token; +class Value; + +inline std::string operator+(const std::string& a, const base::StringPiece& b) { + std::string ret; + ret.reserve(a.size() + b.size()); + ret.assign(a); + ret.append(b.data(), b.size()); + return ret; +} + +inline std::string operator+(const base::StringPiece& a, const std::string& b) { + std::string ret; + ret.reserve(a.size() + b.size()); + ret.assign(a.data(), a.size()); + ret.append(b); + return ret; +} + +// Unescapes and expands variables in the given literal, writing the result +// to the given value. On error, sets |err| and returns false. +bool ExpandStringLiteral(Scope* scope, + const Token& literal, + Value* result, + Err* err); + +// Removes the given prefix from the string. Asserts if the string does +// not have the given prefix. +// +// Note: could potentially return a StringPiece into the str. +std::string RemovePrefix(const std::string& str, const std::string& prefix); + +// Appends the given string piece to the given string. This avoids an +// intermediate copy. +inline void AppendStringPiece(std::string* dest, + const base::StringPiece& piece) { + dest->append(piece.data(), piece.size()); +} + +#endif // TOOLS_GN_STRING_UTILS_H_ diff --git a/tools/gn/string_utils_unittest.cc b/tools/gn/string_utils_unittest.cc new file mode 100644 index 0000000..81181d2 --- /dev/null +++ b/tools/gn/string_utils_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2013 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/err.h" +#include "tools/gn/scope.h" +#include "tools/gn/settings.h" +#include "tools/gn/string_utils.h" +#include "tools/gn/token.h" +#include "tools/gn/value.h" + +namespace { + +bool CheckExpansionCase(const char* input, const char* expected, bool success) { + Scope scope(static_cast<const Settings*>(NULL)); + scope.SetValue("one", Value(NULL, 1), NULL); + scope.SetValue("onestring", Value(NULL, "one"), NULL); + + // Construct the string token, which includes the quotes. + std::string literal_string; + literal_string.push_back('"'); + literal_string.append(input); + literal_string.push_back('"'); + Token literal(Location(), Token::STRING, literal_string); + + Value result(NULL, Value::STRING); + Err err; + bool ret = ExpandStringLiteral(&scope, literal, &result, &err); + + // Err and return value should agree. + EXPECT_NE(ret, err.has_error()); + + if (ret != success) + return false; + + if (!success) + return true; // Don't check result on failure. + return result.string_value() == expected; +} + +} // namespace + +TEST(StringUtils, ExpandStringLiteral) { + EXPECT_TRUE(CheckExpansionCase("", "", true)); + EXPECT_TRUE(CheckExpansionCase("hello", "hello", true)); + EXPECT_TRUE(CheckExpansionCase("hello #$one", "hello #1", true)); + EXPECT_TRUE(CheckExpansionCase("hello #$one/two", "hello #1/two", true)); + EXPECT_TRUE(CheckExpansionCase("hello #${one}", "hello #1", true)); + EXPECT_TRUE(CheckExpansionCase("hello #${one}one", "hello #1one", true)); + EXPECT_TRUE(CheckExpansionCase("hello #${one}$one", "hello #11", true)); + EXPECT_TRUE(CheckExpansionCase("$onestring${one}$one", "one11", true)); + + // Errors + EXPECT_TRUE(CheckExpansionCase("hello #$", NULL, false)); + EXPECT_TRUE(CheckExpansionCase("hello #$%", NULL, false)); + EXPECT_TRUE(CheckExpansionCase("hello #${", NULL, false)); + EXPECT_TRUE(CheckExpansionCase("hello #${}", NULL, false)); + EXPECT_TRUE(CheckExpansionCase("hello #$nonexistant", NULL, false)); + EXPECT_TRUE(CheckExpansionCase("hello #${unterminated", NULL, false)); + + // Unknown backslash values aren't special. + EXPECT_TRUE(CheckExpansionCase("\\", "\\", true)); + EXPECT_TRUE(CheckExpansionCase("\\b", "\\b", true)); + + // Backslashes escape some special things. \"\$\\ -> "$\ Note that gtest + // doesn't like this escape sequence so we have to put it out-of-line. + const char* in = "\\\"\\$\\\\"; + const char* out = "\"$\\"; + EXPECT_TRUE(CheckExpansionCase(in, out, true)); +} diff --git a/tools/gn/target.cc b/tools/gn/target.cc new file mode 100644 index 0000000..0936f22 --- /dev/null +++ b/tools/gn/target.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2013 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 "tools/gn/target.h" + +#include "base/bind.h" +#include "tools/gn/scheduler.h" + +namespace { + +void TargetResolvedThunk(const base::Callback<void(const Target*)>& cb, + const Target* t) { + cb.Run(t); +} + +} // namespace + +Target::Target(const Settings* settings, const Label& label) + : Item(label), + settings_(settings), + output_type_(NONE), + generated_(false), + generator_function_(NULL) { +} + +Target::~Target() { +} + +Target* Target::AsTarget() { + return this; +} + +const Target* Target::AsTarget() const { + return this; +} + +void Target::OnResolved() { + // Gather info from our dependents we need. + for (size_t dep = 0; dep < deps_.size(); dep++) { + // All dependent configs get pulled to us, and to our dependents. + const std::vector<const Config*>& all = + deps_[dep]->all_dependent_configs(); + for (size_t i = 0; i < all.size(); i++) { + configs_.push_back(all[i]); + all_dependent_configs_.push_back(all[i]); + } + + // Direct dependent configs get pulled only to us. + const std::vector<const Config*>& direct = + deps_[dep]->direct_dependent_configs(); + for (size_t i = 0; i < direct.size(); i++) + configs_.push_back(direct[i]); + + // Direct dependent libraries. + if (deps_[dep]->output_type() == STATIC_LIBRARY || + deps_[dep]->output_type() == SHARED_LIBRARY || + deps_[dep]->output_type() == LOADABLE_MODULE) + inherited_libraries_.insert(deps_[dep]); + + // Inherited libraries. DOn't pull transitive libraries from shared + // libraries, since obviously those shouldn't be linked directly into + // later deps unless explicitly specified. + if (deps_[dep]->output_type() != SHARED_LIBRARY && + deps_[dep]->output_type() != LOADABLE_MODULE && + deps_[dep]->output_type() != EXECUTABLE) { + const std::set<const Target*> inherited = + deps_[dep]->inherited_libraries(); + for (std::set<const Target*>::const_iterator i = inherited.begin(); + i != inherited.end(); ++i) + inherited_libraries_.insert(*i); + } + } + + if (!settings_->build_settings()->target_resolved_callback().is_null()) { + g_scheduler->ScheduleWork(base::Bind(&TargetResolvedThunk, + settings_->build_settings()->target_resolved_callback(), + this)); + } +} + +bool Target::HasBeenGenerated() const { + return generated_; +} + +void Target::SetGenerated(const Token* token) { + DCHECK(!generated_); + generated_ = true; + generator_function_ = token; +} + +bool Target::IsLinkable() const { + return output_type_ == STATIC_LIBRARY || output_type_ == SHARED_LIBRARY; +} diff --git a/tools/gn/target.h b/tools/gn/target.h new file mode 100644 index 0000000..63d62ae --- /dev/null +++ b/tools/gn/target.h @@ -0,0 +1,145 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TARGET_H_ +#define TOOLS_GN_TARGET_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/synchronization/lock.h" +#include "tools/gn/config_values.h" +#include "tools/gn/item.h" +#include "tools/gn/source_file.h" + +class InputFile; +class Settings; +class Token; + +class Target : public Item { + public: + enum OutputType { + NONE, + EXECUTABLE, + SHARED_LIBRARY, + STATIC_LIBRARY, + LOADABLE_MODULE, + COPY_FILES, + CUSTOM, + }; + typedef std::vector<SourceFile> FileList; + typedef std::vector<std::string> StringVector; + + Target(const Settings* settings, const Label& label); + virtual ~Target(); + + // Item overrides. + virtual Target* AsTarget() OVERRIDE; + virtual const Target* AsTarget() const OVERRIDE; + virtual void OnResolved() OVERRIDE; + + // This flag indicates if we've run the TargetGenerator for this target to + // fill out the rest of the values. Once we've done this, we save the + // location of the function that started the generating so that we can detect + // duplicate declarations. + bool HasBeenGenerated() const; + void SetGenerated(const Token* token); + + const Settings* settings() const { return settings_; } + + OutputType output_type() const { return output_type_; } + void set_output_type(OutputType t) { output_type_ = t; } + + bool IsLinkable() const; + + const FileList& sources() const { return sources_; } + void swap_in_sources(FileList* s) { sources_.swap(*s); } + + const FileList& data() const { return data_; } + void swap_in_data(FileList* d) { data_.swap(*d); } + + const std::vector<const Target*>& deps() const { return deps_; } + void swap_in_deps(std::vector<const Target*>* d) { deps_.swap(*d); } + + // List of configs that this class inherits settings from. + const std::vector<const Config*>& configs() const { return configs_; } + void swap_in_configs(std::vector<const Config*>* c) { configs_.swap(*c); } + + // List of configs that all dependencies (direct and indirect) of this + // target get. These configs are not added to this target. + const std::vector<const Config*>& all_dependent_configs() const { + return all_dependent_configs_; + } + void swap_in_all_dependent_configs(std::vector<const Config*>* c) { + all_dependent_configs_.swap(*c); + } + + // List of configs that targets depending directly on this one get. These + // configs are not added to this target. + const std::vector<const Config*>& direct_dependent_configs() const { + return direct_dependent_configs_; + } + void swap_in_direct_dependent_configs(std::vector<const Config*>* c) { + direct_dependent_configs_.swap(*c); + } + + const std::set<const Target*>& inherited_libraries() const { + return inherited_libraries_; + } + + // This config represents the configuration set directly on this target. + ConfigValues& config_values() { return config_values_; } + const ConfigValues& config_values() const { return config_values_; } + + const SourceDir& destdir() const { return destdir_; } + void set_destdir(const SourceDir& d) { destdir_ = d; } + + const SourceFile& script() const { return script_; } + void set_script(const SourceFile& s) { script_ = s; } + + const std::vector<std::string>& script_args() const { return script_args_; } + void swap_in_script_args(std::vector<std::string>* sa) { + script_args_.swap(*sa); + } + + const FileList& outputs() const { return outputs_; } + void swap_in_outputs(FileList* s) { outputs_.swap(*s); } + + private: + const Settings* settings_; + OutputType output_type_; + + FileList sources_; + FileList data_; + std::vector<const Target*> deps_; + std::vector<const Config*> configs_; + std::vector<const Config*> all_dependent_configs_; + std::vector<const Config*> direct_dependent_configs_; + + // Libraries from transitive deps. Libraries need to be linked only + // with the end target (executable, shared library). These do not get + // pushed beyond shared library boundaries. + std::set<const Target*> inherited_libraries_; + + ConfigValues config_values_; + + SourceDir destdir_; + + // Script target stuff. + SourceFile script_; + std::vector<std::string> script_args_; + FileList outputs_; + + bool generated_; + const Token* generator_function_; // Who generated this: for error messages. + + DISALLOW_COPY_AND_ASSIGN(Target); +}; + +#endif // TOOLS_GN_TARGET_H_ diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc new file mode 100644 index 0000000..90c8fe6 --- /dev/null +++ b/tools/gn/target_generator.cc @@ -0,0 +1,334 @@ +// Copyright (c) 2013 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 "tools/gn/target_generator.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/config.h" +#include "tools/gn/config_values_generator.h" +#include "tools/gn/err.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/functions.h" +#include "tools/gn/input_file.h" +#include "tools/gn/item_node.h" +#include "tools/gn/ninja_target_writer.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope.h" +#include "tools/gn/target_manager.h" +#include "tools/gn/token.h" +#include "tools/gn/value.h" +#include "tools/gn/value_extractors.h" + +namespace { + +bool TypeHasConfigs(Target::OutputType type) { + return type == Target::EXECUTABLE || + type == Target::SHARED_LIBRARY || + type == Target::STATIC_LIBRARY || + type == Target::LOADABLE_MODULE; +} + +bool TypeHasConfigValues(Target::OutputType type) { + return type == Target::EXECUTABLE || + type == Target::SHARED_LIBRARY || + type == Target::STATIC_LIBRARY || + type == Target::LOADABLE_MODULE; +} + +bool TypeHasSources(Target::OutputType type) { + return type != Target::NONE; +} + +bool TypeHasData(Target::OutputType type) { + return type != Target::NONE; +} + +bool TypeHasDestDir(Target::OutputType type) { + return type == Target::COPY_FILES; +} + +bool TypeHasOutputs(Target::OutputType type) { + return type == Target::CUSTOM; +} + +} // namespace + +TargetGenerator::TargetGenerator(Target* target, + Scope* scope, + const Token& function_token, + const std::vector<Value>& args, + const std::string& output_type, + Err* err) + : target_(target), + scope_(scope), + function_token_(function_token), + args_(args), + output_type_(output_type), + err_(err), + input_directory_(function_token.location().file()->dir()) { +} + +TargetGenerator::~TargetGenerator() { +} + +void TargetGenerator::Run() { + // Output type. + Target::OutputType output_type = GetOutputType(); + target_->set_output_type(output_type); + if (err_->has_error()) + return; + + if (TypeHasConfigs(output_type)) { + FillConfigs(); + FillAllDependentConfigs(); + FillDirectDependentConfigs(); + } + if (TypeHasSources(output_type)) + FillSources(); + if (TypeHasData(output_type)) + FillData(); + if (output_type == Target::CUSTOM) { + FillScript(); + FillScriptArgs(); + } + if (TypeHasOutputs(output_type)) + FillOutputs(); + FillDependencies(); // All types have dependencies. + + if (TypeHasConfigValues(output_type)) { + ConfigValuesGenerator gen(&target_->config_values(), scope_, + function_token_, input_directory_, err_); + gen.Run(); + if (err_->has_error()) + return; + } + + if (TypeHasDestDir(output_type)) + FillDestDir(); + + // Set the toolchain as a dependency of the target. + // TODO(brettw) currently we lock separately for each config, dep, and + // toolchain we add which is bad! Do this in one lock. + { + ItemTree* tree = &GetBuildSettings()->item_tree(); + base::AutoLock lock(tree->lock()); + ItemNode* tc_node = + tree->GetExistingNodeLocked(ToolchainLabelForScope(scope_)); + tree->GetExistingNodeLocked(target_->label())->AddDependency(tc_node); + } + + target_->SetGenerated(&function_token_); + GetBuildSettings()->target_manager().TargetGenerationComplete( + target_->label()); +} + +// static +void TargetGenerator::GenerateTarget(Scope* scope, + const Token& function_token, + const std::vector<Value>& args, + const std::string& output_type, + Err* err) { + // Name is the argument to the function. + if (args.size() != 1u || args[0].type() != Value::STRING) { + *err = Err(function_token, + "Target generator requires one string argument.", + "Otherwise I'm not sure what to call this target."); + return; + } + + // The location of the target is the directory name with no slash at the end. + // FIXME(brettw) validate name. + const Label& toolchain_label = ToolchainLabelForScope(scope); + Label label(function_token.location().file()->dir(), + args[0].string_value(), + toolchain_label.dir(), toolchain_label.name()); + + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Generating target", label.GetUserVisibleName(true)); + + Target* t = scope->settings()->build_settings()->target_manager().GetTarget( + label, function_token.range(), NULL, err); + if (err->has_error()) + return; + + TargetGenerator gen(t, scope, function_token, args, output_type, err); + gen.Run(); +} + +Target::OutputType TargetGenerator::GetOutputType() const { + if (output_type_ == functions::kGroup) + return Target::NONE; + if (output_type_ == functions::kExecutable) + return Target::EXECUTABLE; + if (output_type_ == functions::kSharedLibrary) + return Target::SHARED_LIBRARY; + if (output_type_ == functions::kStaticLibrary) + return Target::STATIC_LIBRARY; + // TODO(brettw) what does loadable module mean? + //if (output_type_ == ???) + // return Target::LOADABLE_MODULE; + if (output_type_ == functions::kCopy) + return Target::COPY_FILES; + if (output_type_ == functions::kCustom) + return Target::CUSTOM; + + *err_ = Err(function_token_, "Not a known output type", + "I am very confused."); + return Target::NONE; +} + +void TargetGenerator::FillGenericConfigs( + const char* var_name, + void (Target::*setter)(std::vector<const Config*>*)) { + const Value* value = scope_->GetValue(var_name, true); + if (!value) + return; + + std::vector<Label> labels; + if (!ExtractListOfLabels(*value, input_directory_, + ToolchainLabelForScope(scope_), &labels, err_)) + return; + + std::vector<const Config*> dest_configs; + dest_configs.resize(labels.size()); + for (size_t i = 0; i < labels.size(); i++) { + dest_configs[i] = Config::GetConfig( + scope_->settings(), + value->list_value()[i].origin()->GetRange(), + labels[i], target_, err_); + if (err_->has_error()) + return; + } + (target_->*setter)(&dest_configs); +} + +void TargetGenerator::FillConfigs() { + FillGenericConfigs("configs", &Target::swap_in_configs); +} + +void TargetGenerator::FillAllDependentConfigs() { + FillGenericConfigs("all_dependent_configs", + &Target::swap_in_all_dependent_configs); +} + +void TargetGenerator::FillDirectDependentConfigs() { + FillGenericConfigs("direct_dependent_configs", + &Target::swap_in_direct_dependent_configs); +} + +void TargetGenerator::FillSources() { + const Value* value = scope_->GetValue("sources", true); + if (!value) + return; + + Target::FileList dest_sources; + if (!ExtractListOfRelativeFiles(*value, input_directory_, &dest_sources, + err_)) + return; + target_->swap_in_sources(&dest_sources); +} + +void TargetGenerator::FillData() { + const Value* value = scope_->GetValue("data", true); + if (!value) + return; + + Target::FileList dest_data; + if (!ExtractListOfRelativeFiles(*value, input_directory_, &dest_data, + err_)) + return; + target_->swap_in_data(&dest_data); +} + +void TargetGenerator::FillDependencies() { + const Value* value = scope_->GetValue("deps", true); + if (!value) + return; + + std::vector<Label> labels; + if (!ExtractListOfLabels(*value, input_directory_, + ToolchainLabelForScope(scope_), &labels, err_)) + return; + + std::vector<const Target*> dest_deps; + dest_deps.resize(labels.size()); + for (size_t i = 0; i < labels.size(); i++) { + dest_deps[i] = GetBuildSettings()->target_manager().GetTarget( + labels[i], value->list_value()[i].origin()->GetRange(), target_, err_); + if (err_->has_error()) + return; + } + + target_->swap_in_deps(&dest_deps); +} + +void TargetGenerator::FillDestDir() { + // Destdir is required for all targets that use it. + const Value* value = scope_->GetValue("destdir", true); + if (!value) { + *err_ = Err(function_token_, "This target type requires a \"destdir\"."); + return; + } + if (!value->VerifyTypeIs(Value::STRING, err_)) + return; + + if (!EnsureStringIsInOutputDir( + GetBuildSettings()->build_dir(), + value->string_value(), *value, err_)) + return; + target_->set_destdir(SourceDir(value->string_value())); +} + +void TargetGenerator::FillScript() { + // If this gets called, the target type requires a script, so error out + // if it doesn't have one. + const Value* value = scope_->GetValue("script", true); + if (!value) { + *err_ = Err(function_token_, "This target type requires a \"script\"."); + return; + } + if (!value->VerifyTypeIs(Value::STRING, err_)) + return; + + target_->set_script( + input_directory_.ResolveRelativeFile(value->string_value())); +} + +void TargetGenerator::FillScriptArgs() { + const Value* value = scope_->GetValue("args", true); + if (!value) + return; + + std::vector<std::string> args; + if (!ExtractListOfStringValues(*value, &args, err_)) + return; + target_->swap_in_script_args(&args); +} + +void TargetGenerator::FillOutputs() { + const Value* value = scope_->GetValue("outputs", true); + if (!value) + return; + + Target::FileList outputs; + if (!ExtractListOfRelativeFiles(*value, input_directory_, &outputs, err_)) + return; + + // Validate that outputs are in the output dir. + CHECK(outputs.size() == value->list_value().size()); + for (size_t i = 0; i < outputs.size(); i++) { + if (!EnsureStringIsInOutputDir( + GetBuildSettings()->build_dir(), + outputs[i].value(), value->list_value()[i], err_)) + return; + } + target_->swap_in_outputs(&outputs); +} + +const BuildSettings* TargetGenerator::GetBuildSettings() const { + return scope_->settings()->build_settings(); +} diff --git a/tools/gn/target_generator.h b/tools/gn/target_generator.h new file mode 100644 index 0000000..11e920c --- /dev/null +++ b/tools/gn/target_generator.h @@ -0,0 +1,83 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TARGET_GENERATOR_H_ +#define TOOLS_GN_TARGET_GENERATOR_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/target.h" + +class BuildSettings; +class Err; +class Location; +class Scope; +class Token; +class Value; + +// Creates Target objects from a Scope (the result of a script execution). +class TargetGenerator { + public: + TargetGenerator(Target* target, + Scope* scope, + const Token& function_token, + const std::vector<Value>& args, + const std::string& output_type, + Err* err); + ~TargetGenerator(); + + void Run(); + + // The function token is the token of the function name of the generator for + // this target. err() will be set on failure. + static void GenerateTarget(Scope* scope, + const Token& function_token, + const std::vector<Value>& args, + const std::string& output_type, + Err* err); + + private: + // Sets err_ on failure. + Target::OutputType GetOutputType() const; + + // Reads configs from the given var name, and uses the given setting on the + // target to save them + void FillGenericConfigs(const char* var_name, + void (Target::*setter)(std::vector<const Config*>*)); + + void FillConfigs(); + void FillAllDependentConfigs(); + void FillDirectDependentConfigs(); + void FillSources(); + void FillData(); + void FillDependencies(); + void FillDestDir(); + void FillScript(); + void FillScriptArgs(); + void FillOutputs(); + + const BuildSettings* GetBuildSettings() const; + + Target* target_; + Scope* scope_; + const Token& function_token_; + std::vector<Value> args_; + std::string output_type_; + Err* err_; + + // Sources are relative to this. This comes from the input file which doesn't + // get freed so we don't acautlly have to make a copy. + const SourceDir& input_directory_; + + FRIEND_TEST_ALL_PREFIXES(TargetGenerator, ResolveInputPath); + + DISALLOW_COPY_AND_ASSIGN(TargetGenerator); +}; + +#endif // TOOLS_GN_TARGET_GENERATOR_H_ diff --git a/tools/gn/target_generator_unittest.cc b/tools/gn/target_generator_unittest.cc new file mode 100644 index 0000000..feef6a9 --- /dev/null +++ b/tools/gn/target_generator_unittest.cc @@ -0,0 +1,8 @@ +// Copyright (c) 2013 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/filesystem_utils.h" +#include "tools/gn/target_generator.h" + diff --git a/tools/gn/target_manager.cc b/tools/gn/target_manager.cc new file mode 100644 index 0000000..ece9f08 --- /dev/null +++ b/tools/gn/target_manager.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2013 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 "tools/gn/target_manager.h" + +#include <deque> + +#include "base/bind.h" +#include "base/strings/string_piece.h" +#include "tools/gn/build_settings.h" +#include "tools/gn/err.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/item_node.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/toolchain_manager.h" +#include "tools/gn/value.h" + +TargetManager::TargetManager(const BuildSettings* build_settings) + : build_settings_(build_settings) { +} + +TargetManager::~TargetManager() { +} + +Target* TargetManager::GetTarget(const Label& label, + const LocationRange& specified_from_here, + Target* dep_from, + Err* err) { + DCHECK(!label.is_null()); + DCHECK(!label.toolchain_dir().value().empty()); + DCHECK(!label.toolchain_name().empty()); + + base::AutoLock lock(build_settings_->item_tree().lock()); + + ItemNode* target_node = + build_settings_->item_tree().GetExistingNodeLocked(label); + Target* target = NULL; + if (!target_node) { + // First time we've seen this, may need to load the file. + + // Compute the settings. The common case is that we have a dep_from and + // the toolchains match, so we can use the settings from there rather than + // querying the toolchain manager (which requires locking, etc.). + const Settings* settings; + if (dep_from && dep_from->label().ToolchainsEqual(label)) { + settings = dep_from->settings(); + } else { + settings = + build_settings_->toolchain_manager().GetSettingsForToolchainLocked( + specified_from_here, label.GetToolchainLabel(), err); + if (!settings) + return NULL; + } + + target = new Target(settings, label); + target_node = new ItemNode(target); + target_node->set_originally_referenced_from_here(specified_from_here); + build_settings_->item_tree().AddNodeLocked(target_node); + + // We're generating a node when there is no referencing one. + if (!dep_from) + target_node->set_generated_from_here(specified_from_here); + + // Only schedule loading the given target if somebody is depending on it + // (and we optimize by not re-asking it to run the current file). + // Otherwise, we're probably generating it right now. + if (dep_from && dep_from->label().dir() != label.dir()) { + if (!ScheduleInvocationLocked(specified_from_here, label, err)) + return NULL; + } + } else if ((target = target_node->item()->AsTarget())) { + // Previously saw this item as a target. + + // If we have no dep_from, we're generating it. + if (!dep_from) { + // In this case, it had better not already be generated. + if (target_node->state() != ItemNode::REFERENCED) { + *err = Err(specified_from_here, + "Duplicate target.", + "\"" + label.GetUserVisibleName(true) + + "\" being defined here."); + err->AppendSubErr(Err(target_node->generated_from_here(), + "Originally defined here.")); + return NULL; + } else { + target_node->set_generated_from_here(specified_from_here); + } + } + } else { + // Error, we previously saw this thing as a non-target. + *err = Err(specified_from_here, "Not previously a target.", + "The target being declared here was previously seen referenced as a\n" + "non-target (like a config)"); + err->AppendSubErr(Err(target_node->originally_referenced_from_here(), + "Originally referenced from here.")); + return NULL; + } + + // Keep a record of the guy asking us for this dependency. We know if + // somebody is adding a dependency, that guy it himself not resolved. + if (dep_from && target_node->state() != ItemNode::RESOLVED) { + build_settings_->item_tree().GetExistingNodeLocked( + dep_from->label())->AddDependency(target_node); + } + + return target; +} + +void TargetManager::TargetGenerationComplete(const Label& label) { + base::AutoLock lock(build_settings_->item_tree().lock()); + build_settings_->item_tree().MarkItemGeneratedLocked(label); +} + +void TargetManager::GetAllTargets( + std::vector<const Target*>* all_targets) const { + base::AutoLock lock(build_settings_->item_tree().lock()); + + std::vector<const Item*> all_items; + build_settings_->item_tree().GetAllItemsLocked(&all_items); + for (size_t i = 0; i < all_items.size(); i++) { + const Target* t = all_items[i]->AsTarget(); + if (t) + all_targets->push_back(t); + } +} + +bool TargetManager::ScheduleInvocationLocked( + const LocationRange& specified_from_here, + const Label& label, + Err* err) { + return build_settings_->toolchain_manager().ScheduleInvocationLocked( + specified_from_here, label.GetToolchainLabel(), label.dir(), err); +} diff --git a/tools/gn/target_manager.h b/tools/gn/target_manager.h new file mode 100644 index 0000000..2dd2aed --- /dev/null +++ b/tools/gn/target_manager.h @@ -0,0 +1,73 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TARGET_MANAGER_H_ +#define TOOLS_GN_TARGET_MANAGER_H_ + +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/synchronization/lock.h" +#include "tools/gn/target.h" + +class BuildSettings; +class Err; +class ItemTree; +class LocationRange; +class ToolchainManager; +class Value; + +// Manages all the targets in the system. This integrates with the ItemTree +// to manage the target-specific rules and creation. +// +// This class is threadsafe. +class TargetManager { + public: + explicit TargetManager(const BuildSettings* settings); + ~TargetManager(); + + // Gets a reference to a named target. The given target name is created if + // it doesn't exist. + // + // The label should be fully specified in that it should include an + // explicit toolchain. + // + // |specified_from_here| should indicate the dependency or the target + // generator causing this access for error message generation. + // + // |dep_from| should be set when a target is getting a dep that it depends + // on. |dep_from| indicates the target that specified the dependency. It + // will be used to track outstanding dependencies so we can know when the + // target and all of its dependencies are complete. It should be null when + // getting a target for other reasons. + // + // On failure, |err| will be set. + // + // The returned pointer must not be dereferenced until it's generated, since + // it could be being generated on another thread. + Target* GetTarget(const Label& label, + const LocationRange& specified_from_here, + Target* dep_from, + Err* err); + + // Called by a target when it has been loaded from the .gin file. Its + // dependencies may or may not be resolved yet. + void TargetGenerationComplete(const Label& label); + + // Returns a list of all targets. + void GetAllTargets(std::vector<const Target*>* all_targets) const; + + private: + bool ScheduleInvocationLocked(const LocationRange& specified_from_here, + const Label& label, + Err* err); + + const BuildSettings* build_settings_; + + DISALLOW_COPY_AND_ASSIGN(TargetManager); +}; + +#endif // TOOLS_GN_TARGET_MANAGER_H diff --git a/tools/gn/target_manager_unittest.cc b/tools/gn/target_manager_unittest.cc new file mode 100644 index 0000000..a1a3ad2 --- /dev/null +++ b/tools/gn/target_manager_unittest.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2013 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/err.h" +#include "tools/gn/settings.h" +#include "tools/gn/target_manager.h" +#include "tools/gn/value.h" + +/* TODO(brettw) make this compile again +namespace { + +class TestTargetManagerDelegate : public TargetManager::Delegate { + public: + TestTargetManagerDelegate() {} + + virtual bool ScheduleInvocation(ToolchainManager* toolchain_manager, + const LocationRange& origin, + const Label& toolchain_name, + const SourceDir& dir, + Err* err) OVERRIDE { + invokes.push_back(dir.value()); + return true; + } + virtual void ScheduleTargetFileWrite(const Target* target) { + writes.push_back(target); + } + + std::vector<std::string> invokes; + std::vector<const Target*> writes; +}; + +} // namespace + +TEST(TargetManager, ResolveDeps) { + TestTargetManagerDelegate ttmd; + BuildSettings build_settings(&ttmd); + + TargetManager& target_manager = build_settings.target_manager(); + + SourceDir tc_dir("/chrome/"); + std::string tc_name("toolchain"); + + // Get a root target. This should not invoke anything. + Err err; + Label chromelabel(SourceDir("/chrome/"), "chrome", tc_dir, tc_name); + Target* chrome = target_manager.GetTarget( + chromelabel, LocationRange(), NULL, &err); + EXPECT_EQ(0u, ttmd.invokes.size()); + + // Declare it has a dependency on content1 and 2. We should get one + // invocation of the content build file. + Label content1label(SourceDir("/content/"), "content1", tc_dir, tc_name); + Target* content1 = target_manager.GetTarget( + content1label, LocationRange(), chrome, &err); + EXPECT_EQ(1u, ttmd.invokes.size()); + + Label content2label(SourceDir("/content/"), "content2", tc_dir, tc_name); + Target* content2 = target_manager.GetTarget( + content2label, LocationRange(), chrome, &err); + EXPECT_EQ(2u, ttmd.invokes.size()); + + // Declare chrome has a depdency on base, this should load it. + Label baselabel(SourceDir("/base/"), "base", tc_dir, tc_name); + Target* base1 = target_manager.GetTarget( + baselabel, LocationRange(), chrome, &err); + EXPECT_EQ(3u, ttmd.invokes.size()); + + // Declare content1 has a dependency on base. + Target* base2 = target_manager.GetTarget( + baselabel, LocationRange(), content1, &err); + EXPECT_EQ(3u, ttmd.invokes.size()); + EXPECT_EQ(base1, base2); + + // Mark content1 and chrome as done. They have unresolved depdendencies so + // shouldn't be written out yet. + target_manager.TargetGenerationComplete(content1label); + target_manager.TargetGenerationComplete(chromelabel); + EXPECT_EQ(0u, ttmd.writes.size()); + + // Mark content2 as done. It has no dependencies so should be written. + target_manager.TargetGenerationComplete(content2label); + ASSERT_EQ(1u, ttmd.writes.size()); + EXPECT_EQ(content2label, ttmd.writes[0]->label()); + + // Mark base as complete. It should have caused itself, content1 and then + // chrome to be written. + target_manager.TargetGenerationComplete(baselabel); + ASSERT_EQ(4u, ttmd.writes.size()); + EXPECT_EQ(baselabel, ttmd.writes[1]->label()); + EXPECT_EQ(content1label, ttmd.writes[2]->label()); + EXPECT_EQ(chromelabel, ttmd.writes[3]->label()); +} +*/ diff --git a/tools/gn/token.cc b/tools/gn/token.cc new file mode 100644 index 0000000..cbce5e7 --- /dev/null +++ b/tools/gn/token.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2013 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 "tools/gn/token.h" + +#include "base/logging.h" + +namespace { + +std::string UnescapeString(const base::StringPiece& input) { + std::string result; + result.reserve(input.size()); + + for (size_t i = 0; i < input.size(); i++) { + if (input[i] == '\\') { + DCHECK(i < input.size() - 1); // Last char shouldn't be a backslash or + // it would have escaped the terminator. + i++; // Skip backslash, next char is a literal. + } + result.push_back(input[i]); + } + return result; +} + +} // namespace + +Token::Token() : type_(INVALID), value_() { +} + +Token::Token(const Location& location, + Type t, + const base::StringPiece& v) + : type_(t), + value_(v), + location_(location) { +} + +bool Token::IsIdentifierEqualTo(const char* v) const { + return type_ == IDENTIFIER && value_ == v; +} + +bool Token::IsOperatorEqualTo(const char* v) const { + return type_ == OPERATOR && value_ == v; +} + +bool Token::IsScoperEqualTo(const char* v) const { + return type_ == SCOPER && value_ == v; +} + +bool Token::IsStringEqualTo(const char* v) const { + return type_ == STRING && value_ == v; +} + +std::string Token::StringValue() const { + DCHECK(type() == STRING); + + // Trim off the string terminators at the end. + return UnescapeString(value_.substr(1, value_.size() - 2)); +} diff --git a/tools/gn/token.h b/tools/gn/token.h new file mode 100644 index 0000000..64b06ea --- /dev/null +++ b/tools/gn/token.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TOKEN_H_ +#define TOOLS_GN_TOKEN_H_ + +#include "base/strings/string_piece.h" +#include "tools/gn/location.h" + +class Token { + public: + enum Type { + INVALID, + INTEGER, // 123 + STRING, // "blah" + OPERATOR, // =, +=, -=, +, -, ==, !=, <=, >=, <, > + IDENTIFIER, // foo + SCOPER, // (, ), [, ], {, } + SEPARATOR, // , + COMMENT // #...\n + }; + + Token(); + Token(const Location& location, Type t, const base::StringPiece& v); + + Type type() const { return type_; } + const base::StringPiece& value() const { return value_; } + const Location& location() const { return location_; } + LocationRange range() const { + return LocationRange(location_, + Location(location_.file(), location_.line_number(), + location_.char_offset() + value_.size())); + } + + // Helper functions for comparing this token to something. + bool IsIdentifierEqualTo(const char* v) const; + bool IsOperatorEqualTo(const char* v) const; + bool IsScoperEqualTo(const char* v) const; + bool IsStringEqualTo(const char* v) const; + + // For STRING tokens, returns the string value (no quotes at end, does + // unescaping). + std::string StringValue() const; + + private: + Type type_; + base::StringPiece value_; + Location location_; +}; + +#endif // TOOLS_GN_TOKEN_H_ diff --git a/tools/gn/tokenizer.cc b/tools/gn/tokenizer.cc new file mode 100644 index 0000000..971f56b --- /dev/null +++ b/tools/gn/tokenizer.cc @@ -0,0 +1,309 @@ +// Copyright (c) 2013 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 "tools/gn/tokenizer.h" + +#include "base/logging.h" +#include "tools/gn/input_file.h" + +namespace { + +bool IsNumberChar(char c) { + return c == '-' || (c >= '0' && c <= '9'); +} + +bool CouldBeTwoCharOperatorBegin(char c) { + return c == '<' || c == '>' || c == '!' || c == '=' || c == '-' || + c == '+' || c == '|' || c == '&'; +} + +bool CouldBeTwoCharOperatorEnd(char c) { + return c == '=' || c == '|' || c == '&'; +} + +bool CouldBeOneCharOperator(char c) { + return c == '=' || c == '<' || c == '>' || c == '+' || c == '!' || + c == ':' || c == '|' || c == '&' || c == '-'; +} + +bool CouldBeOperator(char c) { + return CouldBeOneCharOperator(c) || CouldBeTwoCharOperatorBegin(c); +} + +bool IsSeparatorChar(char c) { + return c == ','; +} + +bool IsScoperChar(char c) { + return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}'; +} + +} // namespace + +Tokenizer::Tokenizer(const InputFile* input_file, Err* err) + : input_file_(input_file), + input_(input_file->contents()), + err_(err), + cur_(0), + line_number_(1), + char_in_line_(1) { +} + +Tokenizer::~Tokenizer() { +} + +// static +std::vector<Token> Tokenizer::Tokenize(const InputFile* input_file, Err* err) { + Tokenizer t(input_file, err); + return t.Run(); +} + +std::vector<Token> Tokenizer::Run() { + std::vector<Token> tokens; + while (!done()) { + AdvanceToNextToken(); + if (done()) + break; + Location location = GetCurrentLocation(); + + Token::Type type = ClassifyCurrent(); + if (type == Token::INVALID) { + *err_ = GetErrorForInvalidToken(location); + break; + } + size_t token_begin = cur_; + AdvanceToEndOfToken(location, type); + if (has_error()) + break; + size_t token_end = cur_; + + // TODO(brettw) This just strips comments from the token stream. This + // is probably wrong, they should be removed at a later stage so we can + // do things like rewrite the file. But this makes the parser simpler and + // is OK for now. + if (type != Token::COMMENT) { + tokens.push_back(Token( + location, + type, + base::StringPiece(&input_.data()[token_begin], + token_end - token_begin))); + } + } + if (err_->has_error()) + tokens.clear(); + return tokens; +} + +// static +size_t Tokenizer::ByteOffsetOfNthLine(const base::StringPiece& buf, int n) { + int cur_line = 1; + size_t cur_byte = 0; + + DCHECK(n > 0); + + if (n == 1) + return 0; + + while (cur_byte < buf.size()) { + if (IsNewline(buf, cur_byte)) { + cur_line++; + if (cur_line == n) + return cur_byte + 1; + } + cur_byte++; + } + return -1; +} + +// static +bool Tokenizer::IsNewline(const base::StringPiece& buffer, size_t offset) { + DCHECK(offset < buffer.size()); + // We may need more logic here to handle different line ending styles. + return buffer[offset] == '\n'; +} + + +void Tokenizer::AdvanceToNextToken() { + while (!at_end() && IsCurrentWhitespace()) + Advance(); +} + +Token::Type Tokenizer::ClassifyCurrent() const { + DCHECK(!at_end()); + char next_char = cur_char(); + if (next_char >= '0' && next_char <= '9') + return Token::INTEGER; + if (next_char == '"') + return Token::STRING; + + // Note: '-' handled specially below. + if (next_char != '-' && CouldBeOperator(next_char)) + return Token::OPERATOR; + + if (IsIdentifierFirstChar(next_char)) + return Token::IDENTIFIER; + + if (IsScoperChar(next_char)) + return Token::SCOPER; + + if (IsSeparatorChar(next_char)) + return Token::SEPARATOR; + + if (next_char == '#') + return Token::COMMENT; + + // For the case of '-' differentiate between a negative number and anything + // else. + if (next_char == '-') { + if (!CanIncrement()) + return Token::OPERATOR; // Just the minus before end of file. + char following_char = input_[cur_ + 1]; + if (following_char >= '0' && following_char <= '9') + return Token::INTEGER; + return Token::OPERATOR; + } + + return Token::INVALID; +} + +void Tokenizer::AdvanceToEndOfToken(const Location& location, + Token::Type type) { + switch (type) { + case Token::INTEGER: + do { + Advance(); + } while (!at_end() && IsNumberChar(cur_char())); + if (!at_end()) { + // Require the char after a number to be some kind of space, scope, + // or operator. + char c = cur_char(); + if (!IsCurrentWhitespace() && !CouldBeOperator(c) && + !IsScoperChar(c) && !IsSeparatorChar(c)) { + *err_ = Err(GetCurrentLocation(), + "This is not a valid number.", + "Learn to count."); + // Highlight the number. + err_->AppendRange(LocationRange(location, GetCurrentLocation())); + } + } + break; + + case Token::STRING: { + char initial = cur_char(); + Advance(); // Advance past initial " + for (;;) { + if (at_end()) { + *err_ = Err(LocationRange(location, + Location(input_file_, line_number_, char_in_line_)), + "Unterminated string literal.", + "Don't leave me hanging like this!"); + break; + } + if (IsCurrentStringTerminator(initial)) { + Advance(); // Skip past last " + break; + } else if (cur_char() == '\n') { + *err_ = Err(LocationRange(location, + GetCurrentLocation()), + "Newline in string constant."); + } + Advance(); + } + break; + } + + case Token::OPERATOR: + // Some operators are two characters, some are one. + if (CouldBeTwoCharOperatorBegin(cur_char())) { + if (CanIncrement() && CouldBeTwoCharOperatorEnd(input_[cur_ + 1])) + Advance(); + } + Advance(); + break; + + case Token::IDENTIFIER: + while (!at_end() && IsIdentifierContinuingChar(cur_char())) + Advance(); + break; + + case Token::SCOPER: + case Token::SEPARATOR: + Advance(); // All are one char. + break; + + case Token::COMMENT: + // Eat to EOL. + while (!at_end() && !IsCurrentNewline()) + Advance(); + break; + + case Token::INVALID: + *err_ = Err(location, "Everything is all messed up", + "Please insert system disk in drive A: and press any key."); + NOTREACHED(); + return; + } +} + +bool Tokenizer::IsCurrentWhitespace() const { + DCHECK(!at_end()); + char c = input_[cur_]; + // Note that tab (0x09) is illegal. + return c == 0x0A || c == 0x0B || c == 0x0C || c == 0x0D || c == 0x20; +} + +bool Tokenizer::IsCurrentStringTerminator(char quote_char) const { + DCHECK(!at_end()); + if (cur_char() != quote_char) + return false; + + // Check for escaping. \" is not a string terminator, but \\" is. Count + // the number of preceeding backslashes. + int num_backslashes = 0; + for (int i = static_cast<int>(cur_) - 1; i >= 0 && input_[i] == '\\'; i--) + num_backslashes++; + + // Even backslashes mean that they were escaping each other and don't count + // as escaping this quote. + return (num_backslashes % 2) == 0; +} + +bool Tokenizer::IsCurrentNewline() const { + return IsNewline(input_, cur_); +} + +void Tokenizer::Advance() { + DCHECK(cur_ < input_.size()); + if (IsCurrentNewline()) { + line_number_++; + char_in_line_ = 1; + } else { + char_in_line_++; + } + cur_++; +} + +Location Tokenizer::GetCurrentLocation() const { + return Location(input_file_, line_number_, char_in_line_); +} + +Err Tokenizer::GetErrorForInvalidToken(const Location& location) const { + std::string help; + if (cur_char() == ';') { + // Semicolon. + help = "Semicolons are not needed, delete this one."; + } else if (cur_char() == '\t') { + // Tab. + help = "You got a tab character in here. Tabs are evil. " + "Convert to spaces."; + } else if (cur_char() == '/' && cur_ + 1 < input_.size() && + (input_[cur_ + 1] == '/' || input_[cur_ + 1] == '*')) { + // Different types of comments. + help = "Comments should start with # instead"; + } else { + help = "I have no idea what this is."; + } + + return Err(location, "Invalid token.", help); +} diff --git a/tools/gn/tokenizer.h b/tools/gn/tokenizer.h new file mode 100644 index 0000000..5e00169 --- /dev/null +++ b/tools/gn/tokenizer.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TOKENIZER_H_ +#define TOOLS_GN_TOKENIZER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "tools/gn/err.h" +#include "tools/gn/token.h" + +class InputFile; + +class Tokenizer { + public: + static std::vector<Token> Tokenize(const InputFile* input_file, Err* err); + + // Counts lines in the given buffer (the first line is "1") and returns + // the byte offset of the beginning of that line, or (size_t)-1 if there + // aren't that many lines in the file. Note that this will return the byte + // one past the end of the input if the last character is a newline. + // + // This is a helper function for error output so that the tokenizer's + // notion of lines can be used elsewhere. + static size_t ByteOffsetOfNthLine(const base::StringPiece& buf, int n); + + // Returns true if the given offset of the string piece counts as a newline. + // The offset must be in the buffer. + static bool IsNewline(const base::StringPiece& buffer, size_t offset); + + static bool IsIdentifierFirstChar(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; + } + + static bool IsIdentifierContinuingChar(char c) { + // Also allow digits after the first char. + return IsIdentifierFirstChar(c) || (c >= '0' && c <= '9'); + } + + private: + // InputFile must outlive the tokenizer and all generated tokens. + explicit Tokenizer(const InputFile* input_file, Err* err); + ~Tokenizer(); + + std::vector<Token> Run(); + + void AdvanceToNextToken(); + Token::Type ClassifyCurrent() const; + void AdvanceToEndOfToken(const Location& location, Token::Type type); + + bool IsCurrentWhitespace() const; + bool IsCurrentNewline() const; + bool IsCurrentStringTerminator(char quote_char) const; + + bool CanIncrement() const { return cur_ < input_.size(); } + + // Increments the current location by one. + void Advance(); + + // Returns the current character in the file as a location. + Location GetCurrentLocation() const; + + Err GetErrorForInvalidToken(const Location& location) const; + + bool done() const { return at_end() || has_error(); } + + bool at_end() const { return cur_ == input_.size(); } + char cur_char() const { return input_[cur_]; } + + bool has_error() const { return err_->has_error(); } + + const InputFile* input_file_; + const base::StringPiece input_; + Err* err_; + size_t cur_; // Byte offset into input buffer. + + int line_number_; + int char_in_line_; + + DISALLOW_COPY_AND_ASSIGN(Tokenizer); +}; + +#endif // TOOLS_GN_TOKENIZER_H_ diff --git a/tools/gn/tokenizer_unittest.cc b/tools/gn/tokenizer_unittest.cc new file mode 100644 index 0000000..d1a6788 --- /dev/null +++ b/tools/gn/tokenizer_unittest.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2013 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/input_file.h" +#include "tools/gn/token.h" +#include "tools/gn/tokenizer.h" + +namespace { + +struct TokenExpectation { + Token::Type type; + const char* value; +}; + +template<size_t len> +bool CheckTokenizer(const char* input, const TokenExpectation (&expect)[len]) { + InputFile input_file(SourceFile("/test")); + input_file.SetContents(input); + + Err err; + std::vector<Token> results = Tokenizer::Tokenize(&input_file, &err); + + if (results.size() != len) + return false; + for (size_t i = 0; i < len; i++) { + if (expect[i].type != results[i].type()) + return false; + if (expect[i].value != results[i].value()) + return false; + } + return true; +} + +} // namespace + +TEST(Tokenizer, Empty) { + InputFile empty_string_input(SourceFile("/test")); + empty_string_input.SetContents(""); + + Err err; + std::vector<Token> results = Tokenizer::Tokenize(&empty_string_input, &err); + EXPECT_TRUE(results.empty()); + + InputFile whitespace_input(SourceFile("/test")); + whitespace_input.SetContents(" \n\r"); + + results = Tokenizer::Tokenize(&whitespace_input, &err); + EXPECT_TRUE(results.empty()); +} + +TEST(Tokenizer, Identifier) { + TokenExpectation one_ident[] = { + { Token::IDENTIFIER, "foo" } + }; + EXPECT_TRUE(CheckTokenizer(" foo ", one_ident)); +} + +TEST(Tokenizer, Integer) { + TokenExpectation integers[] = { + { Token::INTEGER, "123" }, + { Token::INTEGER, "-123" } + }; + EXPECT_TRUE(CheckTokenizer(" 123 -123 ", integers)); +} + +TEST(Tokenizer, String) { + TokenExpectation strings[] = { + { Token::STRING, "\"foo\"" }, + { Token::STRING, "\"bar\\\"baz\"" }, + { Token::STRING, "\"asdf\\\\\"" } + }; + EXPECT_TRUE(CheckTokenizer(" \"foo\" \"bar\\\"baz\" \"asdf\\\\\" ", + strings)); +} + +TEST(Tokenizer, Operator) { + TokenExpectation operators[] = { + { Token::OPERATOR, "-" }, + { Token::OPERATOR, "+" }, + { Token::OPERATOR, "=" }, + { Token::OPERATOR, "+=" }, + { Token::OPERATOR, "-=" }, + { Token::OPERATOR, "!=" }, + { Token::OPERATOR, "==" }, + { Token::OPERATOR, "<" }, + { Token::OPERATOR, ">" }, + { Token::OPERATOR, "<=" }, + { Token::OPERATOR, ">=" }, + }; + EXPECT_TRUE(CheckTokenizer("- + = += -= != == < > <= >=", + operators)); +} + +TEST(Tokenizer, Scoper) { + TokenExpectation scopers[] = { + { Token::SCOPER, "{" }, + { Token::SCOPER, "[" }, + { Token::SCOPER, "]" }, + { Token::SCOPER, "}" }, + { Token::SCOPER, "(" }, + { Token::SCOPER, ")" }, + }; + EXPECT_TRUE(CheckTokenizer("{[ ]} ()", scopers)); +} + +TEST(Tokenizer, FunctionCall) { + TokenExpectation fn[] = { + { Token::IDENTIFIER, "fun" }, + { Token::SCOPER, "(" }, + { Token::STRING, "\"foo\"" }, + { Token::SCOPER, ")" }, + { Token::SCOPER, "{" }, + { Token::IDENTIFIER, "foo" }, + { Token::OPERATOR, "=" }, + { Token::INTEGER, "12" }, + { Token::SCOPER, "}" }, + }; + EXPECT_TRUE(CheckTokenizer("fun(\"foo\") {\nfoo = 12}", fn)); +} + +TEST(Tokenizer, StringUnescaping) { + InputFile input(SourceFile("/test")); + input.SetContents("\"asd\\\"f\" \"\""); + Err err; + std::vector<Token> results = Tokenizer::Tokenize(&input, &err); + + ASSERT_EQ(2u, results.size()); + EXPECT_EQ("asd\"f", results[0].StringValue()); + EXPECT_EQ("", results[1].StringValue()); +} + +TEST(Tokenizer, Locations) { + InputFile input(SourceFile("/test")); + input.SetContents("1 2 \"three\"\n 4"); + Err err; + std::vector<Token> results = Tokenizer::Tokenize(&input, &err); + + ASSERT_EQ(4u, results.size()); + ASSERT_TRUE(results[0].location() == Location(&input, 1, 1)); + ASSERT_TRUE(results[1].location() == Location(&input, 1, 3)); + ASSERT_TRUE(results[2].location() == Location(&input, 1, 5)); + ASSERT_TRUE(results[3].location() == Location(&input, 2, 3)); +} + +TEST(Tokenizer, ByteOffsetOfNthLine) { + EXPECT_EQ(0u, Tokenizer::ByteOffsetOfNthLine("foo", 1)); + + // Windows and Posix have different line endings, so check the byte at the + // location rather than the offset. + char input1[] = "aaa\nxaa\n\nya"; + EXPECT_EQ('x', input1[Tokenizer::ByteOffsetOfNthLine(input1, 2)]); + EXPECT_EQ('y', input1[Tokenizer::ByteOffsetOfNthLine(input1, 4)]); + + char input2[3]; + input2[0] = 'a'; + input2[1] = '\n'; // Manually set to avoid Windows double-byte endings. + input2[2] = 0; + EXPECT_EQ(0u, Tokenizer::ByteOffsetOfNthLine(input2, 1)); + EXPECT_EQ(2u, Tokenizer::ByteOffsetOfNthLine(input2, 2)); +} diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc new file mode 100644 index 0000000..20b81f5 --- /dev/null +++ b/tools/gn/toolchain.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2013 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 "tools/gn/toolchain.h" + +#include "base/logging.h" + +const char* Toolchain::kToolCc = "cc"; +const char* Toolchain::kToolCxx = "cxx"; +const char* Toolchain::kToolAsm = "asm"; +const char* Toolchain::kToolAlink = "alink"; +const char* Toolchain::kToolSolink = "solink"; +const char* Toolchain::kToolSolinkModule = "solink_module"; +const char* Toolchain::kToolLink = "link"; +const char* Toolchain::kToolStamp = "stamp"; +const char* Toolchain::kToolCopy = "copy"; + +Toolchain::Tool::Tool() { +} + +Toolchain::Tool::~Tool() { +} + +Toolchain::Toolchain(const Label& label) : Item(label) { +} + +Toolchain::~Toolchain() { +} + +Toolchain* Toolchain::AsToolchain() { + return this; +} + +const Toolchain* Toolchain::AsToolchain() const { + return this; +} + +// static +Toolchain::ToolType Toolchain::ToolNameToType(const base::StringPiece& str) { + if (str == kToolCc) return TYPE_CC; + if (str == kToolCxx) return TYPE_CXX; + if (str == kToolAsm) return TYPE_ASM; + if (str == kToolAlink) return TYPE_ALINK; + if (str == kToolSolink) return TYPE_SOLINK; + if (str == kToolSolinkModule) return TYPE_SOLINK_MODULE; + if (str == kToolLink) return TYPE_LINK; + if (str == kToolStamp) return TYPE_STAMP; + if (str == kToolCopy) return TYPE_COPY; + return TYPE_NONE; +} + +// static +std::string Toolchain::ToolTypeToName(ToolType type) { + switch (type) { + case TYPE_CC: return kToolCc; + case TYPE_CXX: return kToolCxx; + case TYPE_ASM: return kToolAsm; + case TYPE_ALINK: return kToolAlink; + case TYPE_SOLINK: return kToolSolink; + case TYPE_SOLINK_MODULE: return kToolSolinkModule; + case TYPE_LINK: return kToolLink; + case TYPE_STAMP: return kToolStamp; + case TYPE_COPY: return kToolCopy; + default: + NOTREACHED(); + return std::string(); + } +} + +const Toolchain::Tool& Toolchain::GetTool(ToolType type) const { + DCHECK(type != TYPE_NONE); + return tools_[static_cast<size_t>(type)]; +} + +void Toolchain::SetTool(ToolType type, const Tool& t) { + DCHECK(type != TYPE_NONE); + tools_[static_cast<size_t>(type)] = t; +} diff --git a/tools/gn/toolchain.h b/tools/gn/toolchain.h new file mode 100644 index 0000000..22b8151 --- /dev/null +++ b/tools/gn/toolchain.h @@ -0,0 +1,94 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TOOLCHAIN_H_ +#define TOOLS_GN_TOOLCHAIN_H_ + +#include "base/compiler_specific.h" +#include "base/strings/string_piece.h" +#include "tools/gn/item.h" + +// Holds information on a specific toolchain. This data is filled in when we +// encounter a toolchain definition. +// +// This class is an Item so it can participate in dependency management. In +// particular, when a target uses a toolchain, it should have a dependency on +// that toolchain's object so that we can be sure we loaded the toolchain +// before generating the build for that target. +// +// Note on threadsafety: The label of the toolchain never changes so can +// safetly be accessed from any thread at any time (we do this when asking for +// the toolchain name). But the values in the toolchain do, so these can't +// be accessed until this Item is resolved. +class Toolchain : public Item { + public: + enum ToolType { + TYPE_NONE = 0, + TYPE_CC, + TYPE_CXX, + TYPE_ASM, + TYPE_ALINK, + TYPE_SOLINK, + TYPE_SOLINK_MODULE, + TYPE_LINK, + TYPE_STAMP, + TYPE_COPY, + + TYPE_NUMTYPES // Must be last. + }; + + static const char* kToolCc; + static const char* kToolCxx; + static const char* kToolAsm; + static const char* kToolAlink; + static const char* kToolSolink; + static const char* kToolSolinkModule; + static const char* kToolLink; + static const char* kToolStamp; + static const char* kToolCopy; + + struct Tool { + Tool(); + ~Tool(); + + bool empty() const { + return command.empty() && depfile.empty() && deps.empty() && + description.empty() && pool.empty() && restat.empty() && + rspfile.empty() && rspfile_content.empty(); + } + + std::string command; + std::string depfile; + std::string deps; + std::string description; + std::string pool; + std::string restat; + std::string rspfile; + std::string rspfile_content; + }; + + Toolchain(const Label& label); + virtual ~Toolchain(); + + // Item overrides. + virtual Toolchain* AsToolchain() OVERRIDE; + virtual const Toolchain* AsToolchain() const OVERRIDE; + + // Returns TYPE_NONE on failure. + static ToolType ToolNameToType(const base::StringPiece& str); + static std::string ToolTypeToName(ToolType type); + + const Tool& GetTool(ToolType type) const; + void SetTool(ToolType type, const Tool& t); + + const std::string& environment() const { return environment_; } + void set_environment(const std::string& env) { environment_ = env; } + + private: + Tool tools_[TYPE_NUMTYPES]; + + std::string environment_; +}; + +#endif // TOOLS_GN_TOOLCHAIN_H_ diff --git a/tools/gn/toolchain_manager.cc b/tools/gn/toolchain_manager.cc new file mode 100644 index 0000000..8a54163 --- /dev/null +++ b/tools/gn/toolchain_manager.cc @@ -0,0 +1,480 @@ +// Copyright (c) 2013 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 "tools/gn/toolchain_manager.h" + +#include <set> + +#include "base/bind.h" +#include "tools/gn/err.h" +#include "tools/gn/item.h" +#include "tools/gn/item_node.h" +#include "tools/gn/item_tree.h" +#include "tools/gn/parse_tree.h" +#include "tools/gn/scheduler.h" +#include "tools/gn/scope.h" +#include "tools/gn/scope_per_file_provider.h" + +namespace { + +SourceFile DirToBuildFile(const SourceDir& dir) { + return SourceFile(dir.value() + "BUILD.gn"); +} + +void SetSystemVars(const Settings& settings, Scope* scope) { + // FIXME(brettw) port. + scope->SetValue("is_win", Value(NULL, 1), NULL); + scope->SetValue("is_linux", Value(NULL, 0), NULL); + scope->SetValue("is_posix", Value(NULL, 0), NULL); + scope->SetValue("is_mac", Value(NULL, 0), NULL); + scope->SetValue("is_android", Value(NULL, 0), NULL); + scope->SetValue("is_ios", Value(NULL, 0), NULL); + + // Set this value without the terminating slash because the code expects + // $root_output_dir/foo to work. + scope->SetValue(ScopePerFileProvider::kRootOutputDirName, + ScopePerFileProvider::GetRootOutputDir(&settings), + NULL); + scope->SetValue(ScopePerFileProvider::kRootGenDirName, + ScopePerFileProvider::GetRootGenDir(&settings), + NULL); +} + +} // namespace + +struct ToolchainManager::Info { + Info(const BuildSettings* build_settings, + const Label& toolchain_name, + const std::string& output_subdir_name) + : state(TOOLCHAIN_SETTINGS_NOT_LOADED), + toolchain(toolchain_name), + toolchain_set(false), + settings(build_settings, &toolchain, output_subdir_name), + item_node(NULL) { + } + + // MAkes sure that an ItemNode is created for the toolchain, which lets + // targets depend on the (potentially future) loading of the toolchain. + // + // We can't always do this at the beginning since when doing the default + // build config, we don't know the toolchain name yet. We also need to go + // through some effort to avoid doing this inside the toolchain manager's + // lock (to avoid holding two locks at once). + void EnsureItemNode() { + if (!item_node) { + ItemTree& tree = settings.build_settings()->item_tree(); + item_node = new ItemNode(&toolchain); + tree.AddNodeLocked(item_node); + } + } + + SettingsState state; + + Toolchain toolchain; + bool toolchain_set; + LocationRange toolchain_definition_location; + + // When the state is TOOLCHAIN_SETTINGS_LOADED, the settings should be + // considered read-only and can be read without locking. Otherwise, they + // should not be accessed at all except to load them (which can therefore + // also be done outside of the lock). This works as long as the state flag + // is only ever read or written inside the lock. + Settings settings; + + // While state == TOOLCHAIN_SETTINGS_LOADING, this will collect all + // scheduled invocations using this toolchain. They'll be issued once the + // settings file has been interpreted. + // + // The map maps the source file to "some" location it was invoked from (so + // we can give good error messages. It does NOT map to the root of the + // file to be invoked (the file still needs loading). This will be NULL + // for internally invoked files. + typedef std::map<SourceFile, LocationRange> ScheduledInvocationMap; + ScheduledInvocationMap scheduled_invocations; + + // Tracks all scheduled and executed invocations for this toolchain. This + // is used to avoid invoking a file more than once for a toolchain. + std::set<SourceFile> all_invocations; + + // Filled in by EnsureItemNode, see that for more. + ItemNode* item_node; +}; + +ToolchainManager::ToolchainManager(const BuildSettings* build_settings) + : build_settings_(build_settings) { +} + +ToolchainManager::~ToolchainManager() { + for (ToolchainMap::iterator i = toolchains_.begin(); + i != toolchains_.end(); ++i) + delete i->second; + toolchains_.clear(); +} + +void ToolchainManager::StartLoadingUnlocked(const SourceFile& build_file_name) { + // How the default build config works: Initially we don't have a toolchain + // name to call the settings for the default build config. So we create one + // with an empty toolchain name and execute the default build config file. + // When that's done, we'll go and fix up the name to the default build config + // that the script set. + base::AutoLock lock(GetLock()); + Err err; + Info* info = LoadNewToolchainLocked(LocationRange(), Label(), &err); + if (err.has_error()) + g_scheduler->FailWithError(err); + CHECK(info); + info->scheduled_invocations[build_file_name] = LocationRange(); + info->all_invocations.insert(build_file_name); + + g_scheduler->IncrementWorkCount(); + if (!g_scheduler->input_file_manager()->AsyncLoadFile( + LocationRange(), build_settings_, + build_settings_->build_config_file(), + base::Bind(&ToolchainManager::BackgroundLoadBuildConfig, + base::Unretained(this), info, true), + &err)) { + g_scheduler->FailWithError(err); + g_scheduler->DecrementWorkCount(); + } +} + +const Settings* ToolchainManager::GetSettingsForToolchainLocked( + const LocationRange& from_here, + const Label& toolchain_name, + Err* err) { + GetLock().AssertAcquired(); + ToolchainMap::iterator found = toolchains_.find(toolchain_name); + Info* info = NULL; + if (found == toolchains_.end()) { + info = LoadNewToolchainLocked(from_here, toolchain_name, err); + if (!info) + return NULL; + } else { + info = found->second; + } + info->EnsureItemNode(); + + return &info->settings; +} + +const Toolchain* ToolchainManager::GetToolchainDefinitionUnlocked( + const Label& toolchain_name) { + base::AutoLock lock(GetLock()); + ToolchainMap::iterator found = toolchains_.find(toolchain_name); + if (found == toolchains_.end() || !found->second->toolchain_set) + return NULL; + + // Since we don't allow defining a toolchain more than once, we know that + // once it's set it won't be mutated, so we can safely return this pointer + // for reading outside the lock. + return &found->second->toolchain; +} + +bool ToolchainManager::SetDefaultToolchainUnlocked( + const Label& default_toolchain, + const LocationRange& defined_here, + Err* err) { + base::AutoLock lock(GetLock()); + if (!default_toolchain_.is_null()) { + *err = Err(defined_here, "Default toolchain already set."); + err->AppendSubErr(Err(default_toolchain_defined_here_, + "Previously defined here.", + "You can only set this once.")); + return false; + } + + if (default_toolchain.is_null()) { + *err = Err(defined_here, "Bad default toolchain name.", + "You can't set the default toolchain name to nothing."); + return false; + } + if (!default_toolchain.toolchain_dir().is_null() || + !default_toolchain.toolchain_name().empty()) { + *err = Err(defined_here, "Toolchain name has toolchain.", + "You can't specify a toolchain (inside the parens) for a toolchain " + "name. I got:\n" + default_toolchain.GetUserVisibleName(true)); + return false; + } + + default_toolchain_ = default_toolchain; + default_toolchain_defined_here_ = defined_here; + return true; +} + +Label ToolchainManager::GetDefaultToolchainUnlocked() const { + base::AutoLock lock(GetLock()); + return default_toolchain_; +} + +bool ToolchainManager::SetToolchainDefinitionLocked( + const Toolchain& tc, + const LocationRange& defined_from, + Err* err) { + GetLock().AssertAcquired(); + + ToolchainMap::iterator found = toolchains_.find(tc.label()); + Info* info = NULL; + if (found == toolchains_.end()) { + // New toolchain. + info = LoadNewToolchainLocked(defined_from, tc.label(), err); + if (!info) + return false; + } else { + // It's important to preserve the exact Toolchain object in our tree since + // it will be in the ItemTree and targets may have dependencies on it. + info = found->second; + } + + // The labels should match or else we're setting the wrong one! + CHECK(info->toolchain.label() == tc.label()); + + info->toolchain = tc; + if (info->toolchain_set) { + *err = Err(defined_from, "Duplicate toolchain definition."); + err->AppendSubErr(Err( + info->toolchain_definition_location, + "Previously defined here.", + "A toolchain can only be defined once. One tricky way that this could\n" + "happen is if your definition is itself in a file that's interpreted\n" + "under different toolchains, which would result in multiple\n" + "definitions as the file is loaded multiple times. So be sure your\n" + "toolchain definitions are in files that either don't define any\n" + "targets (probably best) or at least don't contain targets executed\n" + "with more than one toolchain.")); + return false; + } + + info->EnsureItemNode(); + + info->toolchain_set = true; + info->toolchain_definition_location = defined_from; + return true; +} + +bool ToolchainManager::ScheduleInvocationLocked( + const LocationRange& specified_from, + const Label& toolchain_name, + const SourceDir& dir, + Err* err) { + GetLock().AssertAcquired(); + SourceFile build_file(DirToBuildFile(dir)); + + ToolchainMap::iterator found = toolchains_.find(toolchain_name); + Info* info = NULL; + if (found == toolchains_.end()) { + // New toolchain. + info = LoadNewToolchainLocked(specified_from, toolchain_name, err); + if (!info) + return false; + } else { + // Use existing one. + info = found->second; + if (info->all_invocations.find(build_file) != + info->all_invocations.end()) { + // We've already seen this source file for this toolchain, don't need + // to do anything. + return true; + } + } + + info->all_invocations.insert(build_file); + + // True if the settings load needs to be scheduled. + bool info_needs_settings_load = false; + + // True if the settings load has completed. + bool info_settings_loaded = false; + + switch (info->state) { + case TOOLCHAIN_SETTINGS_NOT_LOADED: + info_needs_settings_load = true; + info->scheduled_invocations[build_file] = specified_from; + break; + + case TOOLCHAIN_SETTINGS_LOADING: + info->scheduled_invocations[build_file] = specified_from; + break; + + case TOOLCHAIN_SETTINGS_LOADED: + info_settings_loaded = true; + break; + } + + if (info_needs_settings_load) { + // Load the settings file. + g_scheduler->IncrementWorkCount(); + if (!g_scheduler->input_file_manager()->AsyncLoadFile( + specified_from, build_settings_, + build_settings_->build_config_file(), + base::Bind(&ToolchainManager::BackgroundLoadBuildConfig, + base::Unretained(this), info, false), + err)) { + g_scheduler->DecrementWorkCount(); + return false; + } + } else if (info_settings_loaded) { + // Settings are ready to go, load the target file. + g_scheduler->IncrementWorkCount(); + if (!g_scheduler->input_file_manager()->AsyncLoadFile( + specified_from, build_settings_, build_file, + base::Bind(&ToolchainManager::BackgroundInvoke, + base::Unretained(this), info, build_file), + err)) { + g_scheduler->DecrementWorkCount(); + return false; + } + } + // Else we should have added the infocations to the scheduled_invocations + // from within the lock above. + return true; +} + +// static +std::string ToolchainManager::ToolchainToOutputSubdir( + const Label& toolchain_name) { + // For now just assume the toolchain name is always a valid dir name. We may + // want to clean up the in the future. + return toolchain_name.name(); +} + +ToolchainManager::Info* ToolchainManager::LoadNewToolchainLocked( + const LocationRange& specified_from, + const Label& toolchain_name, + Err* err) { + GetLock().AssertAcquired(); + Info* info = new Info(build_settings_, + toolchain_name, + ToolchainToOutputSubdir(toolchain_name)); + + toolchains_[toolchain_name] = info; + + // Invoke the file containing the toolchain definition so that it gets + // defined. The default one (label is empty) will be done spearately. + if (!toolchain_name.is_null()) { + // The default toolchain should be specified whenever we're requesting + // another one. This is how we know under what context we should execute + // the invoke for the toolchain file. + CHECK(!default_toolchain_.is_null()); + ScheduleInvocationLocked(specified_from, default_toolchain_, + toolchain_name.dir(), err); + } + return info; +} + +void ToolchainManager::FixupDefaultToolchainLocked() { + // Now that we've run the default build config, we should know the + // default toolchain name. Fix up our reference. + // See Start() for more. + GetLock().AssertAcquired(); + if (default_toolchain_.is_null()) { + g_scheduler->FailWithError(Err(Location(), + "Default toolchain not set.", + "Your build config file \"" + + build_settings_->build_config_file().value() + + "\"\ndid not call set_default_toolchain(). This is needed so " + "I know how to actually\ncompile your code.")); + return; + } + + ToolchainMap::iterator old_default = toolchains_.find(Label()); + CHECK(old_default != toolchains_.end()); + Info* info = old_default->second; + toolchains_[default_toolchain_] = info; + toolchains_.erase(old_default); + + // Toolchain should not have been loaded in the build config file. + CHECK(!info->toolchain_set); + + // We need to set the toolchain label now that we know it. There's no way + // to set the label, but we can assign the toolchain to a new one. Loading + // the build config can not change the toolchain, so we won't be overwriting + // anything useful. + info->toolchain = Toolchain(default_toolchain_); + info->EnsureItemNode(); + + // Schedule a load of the toolchain build file. + Err err; + ScheduleInvocationLocked(LocationRange(), default_toolchain_, + default_toolchain_.dir(), &err); + if (err.has_error()) + g_scheduler->FailWithError(err); +} + +void ToolchainManager::BackgroundLoadBuildConfig(Info* info, + bool is_default, + const ParseNode* root) { + // Danger: No early returns without decrementing the work count. + if (root && !g_scheduler->is_failed()) { + // Nobody should be accessing settings at this point other than us since we + // haven't marked it loaded, so we can do it outside the lock. + Scope* base_config = info->settings.base_config(); + SetSystemVars(info->settings, base_config); + base_config->SetProcessingBuildConfig(); + if (is_default) + base_config->SetProcessingDefaultBuildConfig(); + + const BlockNode* root_block = root->AsBlock(); + Err err; + root_block->ExecuteBlockInScope(base_config, &err); + + base_config->ClearProcessingBuildConfig(); + if (is_default) + base_config->ClearProcessingDefaultBuildConfig(); + + if (err.has_error()) { + g_scheduler->FailWithError(err); + } else { + // Base config processing succeeded. + Info::ScheduledInvocationMap schedule_these; + { + base::AutoLock lock(GetLock()); + schedule_these.swap(info->scheduled_invocations); + info->state = TOOLCHAIN_SETTINGS_LOADED; + if (is_default) + FixupDefaultToolchainLocked(); + } + + // Schedule build files waiting on this settings. There can be many so we + // want to load them in parallel on the pool. + for (Info::ScheduledInvocationMap::iterator i = schedule_these.begin(); + i != schedule_these.end() && !g_scheduler->is_failed(); ++i) { + // Note i->second may be NULL, so don't dereference. + g_scheduler->IncrementWorkCount(); + if (!g_scheduler->input_file_manager()->AsyncLoadFile( + i->second, build_settings_, i->first, + base::Bind(&ToolchainManager::BackgroundInvoke, + base::Unretained(this), info, i->first), + &err)) { + g_scheduler->FailWithError(err); + g_scheduler->DecrementWorkCount(); + break; + } + } + } + } + g_scheduler->DecrementWorkCount(); +} + +void ToolchainManager::BackgroundInvoke(const Info* info, + const SourceFile& file_name, + const ParseNode* root) { + if (root && !g_scheduler->is_failed()) { + if (g_scheduler->verbose_logging()) + g_scheduler->Log("Running", file_name.value()); + + Scope our_scope(info->settings.base_config()); + ScopePerFileProvider per_file_provider(&our_scope, file_name); + + Err err; + root->Execute(&our_scope, &err); + if (err.has_error()) + g_scheduler->FailWithError(err); + } + + g_scheduler->DecrementWorkCount(); +} + +base::Lock& ToolchainManager::GetLock() const { + return build_settings_->item_tree().lock(); +} diff --git a/tools/gn/toolchain_manager.h b/tools/gn/toolchain_manager.h new file mode 100644 index 0000000..4c0be41 --- /dev/null +++ b/tools/gn/toolchain_manager.h @@ -0,0 +1,167 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_TOOLCHAIN_MANAGER_H_ +#define TOOLS_GN_TOOLCHAIN_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/synchronization/lock.h" +#include "tools/gn/label.h" +#include "tools/gn/location.h" +#include "tools/gn/source_file.h" +#include "tools/gn/toolchain.h" + +class Err; +class BuildSettings; +class ParseNode; +class Settings; + +// The toolchain manager manages the mapping of toolchain names to the +// settings and toolchain object. It also loads build files in the context of a +// toolchain context of a toolchain, and manages running the build config +// script when necessary. +// +// This class uses the lock from the item tree to manage threadsafety. The +// functions requiring this lock to be held are named "Locked" to make this +// more clear. The "Unlocked" versions will acquire the lock themselves so will +// break if you call it while locked. (The rationale behind which is which is +// just based on the needs of the callers, so it can be changed.) There are two +// reasons for this: +// +// The first is that when resolving a target, we do a bunch of script +// stuff (slow) and then lookup the target, config, and toolchain dependencies +// based on that. The options are to do a lock around each dependency lookup +// or do a lock around the entire operation. Given that there's not a huge +// amount of work, the "big lock" approach is likely a bit better since it +// avoids lots of locking overhead. +// +// The second reason is that if we had a separate lock here, we would need to +// lock around creating a new toolchain. But creating a new toolchain involves +// adding it to the item tree, and this needs to be done atomically to prevent +// other threads from seeing a partially initialized toolchain. This sets up +// having deadlock do to acquiring multiple locks, or recursive locking +// problems. +class ToolchainManager { + public: + ToolchainManager(const BuildSettings* build_settings); + ~ToolchainManager(); + + // At the very beginning of processing, this begins loading build files. + // This will scheduler loadin the default build config and the given build + // file in that context, going out from there. + // + // This returns immediately, you need to run the Scheduler to actually + // process anything. It's assumed this function is called on the main thread + // before doing anything, so it does not need locking. + void StartLoadingUnlocked(const SourceFile& build_file_name); + + // Returns the settings object for a given toolchain. This does not + // schedule loading the given toolchain if it's not loaded yet: you actually + // need to invoke a target with that toolchain to get that. + // + // On error, returns NULL and sets the error. + const Settings* GetSettingsForToolchainLocked(const LocationRange& from_here, + const Label& toolchain_name, + Err* err); + + // Returns the toolchain definition or NULL if the toolchain hasn't been + // defined yet. + const Toolchain* GetToolchainDefinitionUnlocked(const Label& toolchain_name); + + // Sets the default toolchain. If the default toolchain is already set, + // this function will return false and fill in the given err. + bool SetDefaultToolchainUnlocked(const Label& dt, + const LocationRange& defined_from, + Err* err); + + // Returns the default toolchain name. This will be empty if it hasn't been + // set. + Label GetDefaultToolchainUnlocked() const; + + // Saves the given named toolchain (the name will be taken from the toolchain + // parameter). This will fail and return false if the given toolchain was + // already defined. In this case, the given error will be set. + bool SetToolchainDefinitionLocked(const Toolchain& tc, + const LocationRange& defined_from, + Err* err); + + // Schedules an invocation of the given file under the given toolchain. The + // toolchain file will be loaded if necessary. + // + // The origin should be the node that will be blamed for this invocation if + // an error occurs. If a synchronous error occurs, the given error will be + // set and it will return false. If an async error occurs, the error will be + // sent to the scheduler. + bool ScheduleInvocationLocked(const LocationRange& origin, + const Label& toolchain_name, + const SourceDir& dir, + Err* err); + + private: + enum SettingsState { + // Toolchain settings have not requested to be loaded. This means we + // haven't seen any targets that require this toolchain yet. Not loading + // the settings automatically allows you to define a bunch of toolchains + // and potentially not use them without much overhead. + TOOLCHAIN_SETTINGS_NOT_LOADED, + + // The settings have been scheduled to be loaded but have not completed. + TOOLCHAIN_SETTINGS_LOADING, + + // The settings are done being loaded. + TOOLCHAIN_SETTINGS_LOADED + }; + + struct Info; + + static std::string ToolchainToOutputSubdir(const Label& toolchain_name); + + // Creates a new info struct and saves it in the map. A pointer to the + // struct is returned. No loads are scheduled. + // + // If the label is non-empty, the toolchain will be added to the ItemTree + // so that other nodes can depend on it. THe empty label case is for the + // default build config file (when the toolchain name isn't known yet). It + // will be added later. + // + // On error, will return NULL and the error will be set. + Info* LoadNewToolchainLocked(const LocationRange& specified_from, + const Label& toolchain_name, + Err* err); + + // Fixes up the default toolchain names once they're known when processing + // the default build config, or throw an error if the default toolchain + // hasn't been set. See the StartLoading() implementation for more. + void FixupDefaultToolchainLocked(); + + // Loads the base config for the given toolchain. Run on a background thread + // asynchronously. + void BackgroundLoadBuildConfig(Info* info, + bool is_default, + const ParseNode* root); + + // Invokes the given file for a toolchain with loaded settings. Run on a + // background thread asynchronously. + void BackgroundInvoke(const Info* info, + const SourceFile& file_name, + const ParseNode* root); + + // Returns the lock to use. + base::Lock& GetLock() const; + + const BuildSettings* build_settings_; + + // We own the info pointers. + typedef std::map<Label, Info*> ToolchainMap; + ToolchainMap toolchains_; + + Label default_toolchain_; + LocationRange default_toolchain_defined_here_; + + DISALLOW_COPY_AND_ASSIGN(ToolchainManager); +}; + +#endif // TOOLS_GN_TOOLCHAIN_MANAGER_H_ diff --git a/tools/gn/value.cc b/tools/gn/value.cc new file mode 100644 index 0000000..cb78faa --- /dev/null +++ b/tools/gn/value.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2013 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 "tools/gn/value.h" + +#include "base/strings/string_number_conversions.h" + +Value::Value() + : type_(NONE), + int_value_(0), + origin_(NULL) { +} + +Value::Value(const ParseNode* origin, Type t) + : type_(t), + int_value_(0), + origin_(origin) { +} + +Value::Value(const ParseNode* origin, int64 int_val) + : type_(INTEGER), + int_value_(int_val), + origin_(origin) { +} + +Value::Value(const ParseNode* origin, const base::StringPiece& str_val) + : type_(STRING), + string_value_(str_val.as_string()), + int_value_(0), + origin_(origin) { +} + +Value::~Value() { +} + +// static +const char* Value::DescribeType(Type t) { + switch (t) { + case NONE: + return "none"; + case INTEGER: + return "integer"; + case STRING: + return "string"; + case LIST: + return "list"; + default: + NOTREACHED(); + return "UNKNOWN"; + } +} + +int64 Value::InterpretAsInt() const { + switch (type_) { + case NONE: + return 0; + case INTEGER: + return int_value_; + case STRING: + return string_value_.empty() ? 0 : 1; + case LIST: + return list_value_.empty() ? 0 : 1; + } + return 0; +} + +std::string Value::ToString() const { + switch (type_) { + case NONE: + return "<void>"; + case INTEGER: + return base::Int64ToString(int_value_); + case STRING: + return string_value_; + case LIST: { + std::string result = "["; + for (size_t i = 0; i < list_value_.size(); i++) { + if (i > 0) + result += ", "; + // TODO(brettw) maybe also want to escape quotes in the string. + if (list_value_[i].type() == STRING) + result += std::string("\"") + list_value_[i].ToString() + "\""; + else + result += list_value_[i].ToString(); + } + result.push_back(']'); + return result; + } + } + return std::string(); +} + +bool Value::VerifyTypeIs(Type t, Err* err) const { + if (type_ == t) + return true; + + *err = Err(origin(), std::string("This is not a ") + DescribeType(t) + "."); + return false; +} + +bool Value::operator==(const Value& other) const { + if (type_ != other.type_) + return false; + + switch (type_) { + case Value::INTEGER: + return int_value() == other.int_value(); + case Value::STRING: + return string_value() == other.string_value(); + case Value::LIST: + if (list_value().size() != other.list_value().size()) + return false; + for (size_t i = 0; i < list_value().size(); i++) { + if (list_value()[i] != other.list_value()[i]) + return false; + } + return true; + default: + return false; + } +} + +bool Value::operator!=(const Value& other) const { + return !operator==(other); +} diff --git a/tools/gn/value.h b/tools/gn/value.h new file mode 100644 index 0000000..8802bff --- /dev/null +++ b/tools/gn/value.h @@ -0,0 +1,91 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_VALUE_H_ +#define TOOLS_GN_VALUE_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "tools/gn/err.h" + +class ParseNode; + +// Represents a variable value in the interpreter. +class Value { + public: + enum Type { + NONE = 0, + INTEGER, + STRING, + LIST + }; + + Value(); + Value(const ParseNode* origin, Type t); + Value(const ParseNode* origin, int64 int_val); + Value(const ParseNode* origin, const base::StringPiece& str_val); + ~Value(); + + Type type() const { return type_; } + + // Returns a string describing the given type. + static const char* DescribeType(Type t); + + // Returns the node that made this. May be NULL. + const ParseNode* origin() const { return origin_; } + void set_origin(const ParseNode* o) { origin_ = o; } + + int64& int_value() { + DCHECK(type_ == INTEGER); + return int_value_; + } + const int64& int_value() const { + DCHECK(type_ == INTEGER); + return int_value_; + } + + std::string& string_value() { + DCHECK(type_ == STRING); + return string_value_; + } + const std::string& string_value() const { + DCHECK(type_ == STRING); + return string_value_; + } + + std::vector<Value>& list_value() { + DCHECK(type_ == LIST); + return list_value_; + } + const std::vector<Value>& list_value() const { + DCHECK(type_ == LIST); + return list_value_; + } + + // Returns the current value converted to an int, normally used for + // boolean operations. Undefined variables, empty lists, and empty strings + // are all interpreted as 0, otherwise 1. + int64 InterpretAsInt() const; + + // Converts the given value to a string. + std::string ToString() const; + + // Verifies that the value is of the given type. If it isn't, returns + // false and sets the error. + bool VerifyTypeIs(Type t, Err* err) const; + + // Compares values. Only the "value" is compared, not the origin. + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + + private: + Type type_; + std::string string_value_; + int64 int_value_; + std::vector<Value> list_value_; + const ParseNode* origin_; +}; + +#endif // TOOLS_GN_VALUE_H_ diff --git a/tools/gn/value_extractors.cc b/tools/gn/value_extractors.cc new file mode 100644 index 0000000..56a2be2 --- /dev/null +++ b/tools/gn/value_extractors.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2013 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 "tools/gn/value_extractors.h" + +#include "tools/gn/err.h" +#include "tools/gn/label.h" +#include "tools/gn/source_dir.h" +#include "tools/gn/source_file.h" + +namespace { + +// This extractor rejects files with system-absolute file paths. If we need +// that in the future, we'll have to add some flag to control this. +struct RelativeFileConverter { + RelativeFileConverter(const SourceDir& current_dir_in) + : current_dir(current_dir_in) {} + bool operator()(const Value& v, SourceFile* out, Err* err) const { + if (!v.VerifyTypeIs(Value::STRING, err)) + return false; + *out = current_dir.ResolveRelativeFile(v.string_value()); + if (out->is_system_absolute()) { + *err = Err(v, "System-absolute file path.", + "You can't list a system-absolute file path here. Please include " + "only files in\nthe source tree. Maybe you meant to begin with two " + "slashes to indicate an\nabsolute path in the source tree?"); + return false; + } + return true; + } + const SourceDir& current_dir; +}; + +struct RelativeDirConverter { + RelativeDirConverter(const SourceDir& current_dir_in) + : current_dir(current_dir_in) {} + bool operator()(const Value& v, SourceDir* out, Err* err) const { + if (!v.VerifyTypeIs(Value::STRING, err)) + return false; + *out = current_dir.ResolveRelativeDir(v.string_value()); + return true; + } + const SourceDir& current_dir; +}; + +struct LabelResolver { + LabelResolver(const SourceDir& current_dir_in, + const Label& current_toolchain_in) + : current_dir(current_dir_in), + current_toolchain(current_toolchain_in) {} + bool operator()(const Value& v, Label* out, Err* err) const { + if (!v.VerifyTypeIs(Value::STRING, err)) + return false; + *out = Label::Resolve(current_dir, current_toolchain, v, err); + return !err->has_error(); + } + const SourceDir& current_dir; + const Label& current_toolchain; +}; + +} // namespace + +bool ExtractListOfStringValues(const Value& value, + std::vector<std::string>* dest, + Err* err) { + if (!value.VerifyTypeIs(Value::LIST, err)) + return false; + const std::vector<Value>& input_list = value.list_value(); + dest->reserve(input_list.size()); + for (size_t i = 0; i < input_list.size(); i++) { + if (!input_list[i].VerifyTypeIs(Value::STRING, err)) + return false; + dest->push_back(input_list[i].string_value()); + } + return true; +} + +bool ExtractListOfRelativeFiles(const Value& value, + const SourceDir& current_dir, + std::vector<SourceFile>* files, + Err* err) { + return ListValueExtractor(value, files, err, + RelativeFileConverter(current_dir)); +} + +bool ExtractListOfRelativeDirs(const Value& value, + const SourceDir& current_dir, + std::vector<SourceDir>* dest, + Err* err) { + return ListValueExtractor(value, dest, err, + RelativeDirConverter(current_dir)); +} + +bool ExtractListOfLabels(const Value& value, + const SourceDir& current_dir, + const Label& current_toolchain, + std::vector<Label>* dest, + Err* err) { + return ListValueExtractor(value, dest, err, + LabelResolver(current_dir, current_toolchain)); +} diff --git a/tools/gn/value_extractors.h b/tools/gn/value_extractors.h new file mode 100644 index 0000000..556f44f --- /dev/null +++ b/tools/gn/value_extractors.h @@ -0,0 +1,57 @@ +// Copyright (c) 2013 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. + +#ifndef TOOLS_GN_VALUE_EXTRACTORS_H_ +#define TOOLS_GN_VALUE_EXTRACTORS_H_ + +#include <string> +#include <vector> + +#include "tools/gn/value.h" + +class Err; +class Label; +class SourceDir; +class SourceFile; + +// Sets the error and returns false on failure. +template<typename T, class Converter> +bool ListValueExtractor(const Value& value, std::vector<T>* dest, + Err* err, + const Converter& converter) { + if (!value.VerifyTypeIs(Value::LIST, err)) + return false; + const std::vector<Value>& input_list = value.list_value(); + dest->resize(input_list.size()); + for (size_t i = 0; i < input_list.size(); i++) { + if (!converter(input_list[i], &(*dest)[i], err)) + return false; + } + return true; +} + +// On failure, returns false and sets the error. +bool ExtractListOfStringValues(const Value& value, + std::vector<std::string>* dest, + Err* err); + +// Looks for a list of source files relative to a given current dir. +bool ExtractListOfRelativeFiles(const Value& value, + const SourceDir& current_dir, + std::vector<SourceFile>* files, + Err* err); + +// Looks for a list of source directories relative to a given current dir. +bool ExtractListOfRelativeDirs(const Value& value, + const SourceDir& current_dir, + std::vector<SourceDir>* dest, + Err* err); + +bool ExtractListOfLabels(const Value& value, + const SourceDir& current_dir, + const Label& current_toolchain, + std::vector<Label>* dest, + Err* err); + +#endif // TOOLS_GN_VALUE_EXTRACTORS_H_ |