Revert 214325 "Revert 214254 "Add initial prototype for the GN m..."
The issue was already fixed :) > Revert 214254 "Add initial prototype for the GN meta-buildsystem." > > It broke the check_licenses step on Android (see http://build.chromium.org/p/chromium.linux/builders/Android%20Builder%20%28dbg%29/builds/39904/steps/check_licenses/logs/stdio): > > @@@BUILD_STEP check_licenses@@@ > > /b/build/slave/Android_Builder__dbg_/build/src/android_webview/tools/webview_licenses.py scan > Got LicenseError "missing README.chromium or licenses.py SPECIAL_CASES entry" while scanning tools/gn/secondary/base/third_party/dynamic_annotations > Got LicenseError "missing README.chromium or licenses.py SPECIAL_CASES entry" while scanning tools/gn/secondary/third_party/modp_b64 > < /b/build/slave/Android_Builder__dbg_/build/src/android_webview/tools/webview_licenses.py scan > ERROR: process exited with code 2 > @@@STEP_FAILURE@@@ > > > > 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 > > TBR=brettw@chromium.org > > Review URL: https://codereview.chromium.org/21084010 TBR=bauerb@chromium.org Review URL: https://codereview.chromium.org/21204003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214333 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
.gn
tools/gn
BUILD.gnOWNERSREADME.txtbuild_settings.ccbuild_settings.hcommand_desc.cccommand_gen.cccommands.hconfig.ccconfig.hconfig_values.ccconfig_values.hconfig_values_extractors.ccconfig_values_extractors.hconfig_values_generator.ccconfig_values_generator.herr.ccerr.hescape.ccescape.hescape_unittest.ccfile_template.ccfile_template.hfile_template_unittest.ccfilesystem_utils.ccfilesystem_utils.hfilesystem_utils_unittest.ccfunction_define_rule.ccfunction_exec_script.ccfunction_process_file_template.ccfunction_read_file.ccfunction_set_default_toolchain.ccfunction_template.ccfunction_toolchain.ccfunction_write_file.ccfunctions.ccfunctions.hfunctions_target.ccgenerate_test_gn_data.ccgn.gypgn_main.ccimport_manager.ccimport_manager.hinput_conversion.ccinput_conversion.hinput_conversion_unittest.ccinput_file.ccinput_file.hinput_file_manager.ccinput_file_manager.hitem.ccitem.hitem_node.ccitem_node.hitem_tree.ccitem_tree.hlabel.cclabel.hlabel_unittest.cclocation.hninja_build_writer.ccninja_build_writer.hninja_helper.ccninja_helper.hninja_helper_unittest.ccninja_target_writer.ccninja_target_writer.hninja_toolchain_writer.ccninja_toolchain_writer.hninja_writer.ccninja_writer.hoperators.ccoperators.houtput_file.houtput_stream.hparse_tree.ccparse_tree.hparser.ccparser.hparser_unittest.ccpath_output.ccpath_output.hpath_output_unittest.ccpattern.ccpattern.hpattern_unittest.ccscheduler.ccscheduler.hscope.ccscope.hscope_per_file_provider.ccscope_per_file_provider.hsettings.ccsettings.hsetup.ccsetup.hsource_dir.ccsource_dir.hsource_dir_unittest.ccsource_file.ccsource_file.hstandard_out.ccstandard_out.hstring_utils.ccstring_utils.hstring_utils_unittest.cctarget.cctarget.htarget_generator.cctarget_generator.htarget_generator_unittest.cctarget_manager.cctarget_manager.htarget_manager_unittest.cctoken.cctoken.htokenizer.cctokenizer.htokenizer_unittest.cctoolchain.cctoolchain.htoolchain_manager.cctoolchain_manager.hvalue.ccvalue.hvalue_extractors.ccvalue_extractors.h
secondary
base
build
ipc
testing
third_party
modp_b64
8
.gn
Normal file
8
.gn
Normal file
@ -0,0 +1,8 @@
|
||||
# This file is used by the experimental meta-buildsystem in src/tools/gn to
|
||||
# find the root of the source tree and to set startup options.
|
||||
|
||||
# The location of the build configuration file.
|
||||
buildconfig = "//build/config/BUILDCONFIG.gn"
|
||||
|
||||
# The secondary source root is a parallel directory tree where
|
||||
secondary_source = "//tools/gn/secondary/"
|
153
tools/gn/BUILD.gn
Normal file
153
tools/gn/BUILD.gn
Normal file
@ -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" ]
|
||||
}
|
1
tools/gn/OWNERS
Normal file
1
tools/gn/OWNERS
Normal file
@ -0,0 +1 @@
|
||||
brettw@chromium.org
|
7
tools/gn/README.txt
Normal file
7
tools/gn/README.txt
Normal file
@ -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.
|
44
tools/gn/build_settings.cc
Normal file
44
tools/gn/build_settings.cc
Normal file
@ -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_);
|
||||
}
|
||||
|
110
tools/gn/build_settings.h
Normal file
110
tools/gn/build_settings.h
Normal file
@ -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_
|
201
tools/gn/command_desc.cc
Normal file
201
tools/gn/command_desc.cc
Normal file
@ -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;
|
||||
}
|
66
tools/gn/command_gen.cc
Normal file
66
tools/gn/command_gen.cc
Normal file
@ -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;
|
||||
}
|
18
tools/gn/commands.h
Normal file
18
tools/gn/commands.h
Normal file
@ -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_
|
78
tools/gn/config.cc
Normal file
78
tools/gn/config.cc
Normal file
@ -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;
|
||||
}
|
45
tools/gn/config.h
Normal file
45
tools/gn/config.h
Normal file
@ -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_
|
11
tools/gn/config_values.cc
Normal file
11
tools/gn/config_values.cc
Normal file
@ -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() {
|
||||
}
|
50
tools/gn/config_values.h
Normal file
50
tools/gn/config_values.h
Normal file
@ -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_
|
22
tools/gn/config_values_extractors.cc
Normal file
22
tools/gn/config_values_extractors.cc
Normal file
@ -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);
|
||||
}
|
50
tools/gn/config_values_extractors.h
Normal file
50
tools/gn/config_values_extractors.h
Normal file
@ -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_
|
89
tools/gn/config_values_generator.cc
Normal file
89
tools/gn/config_values_generator.cc
Normal file
@ -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_);
|
||||
}
|
48
tools/gn/config_values_generator.h
Normal file
48
tools/gn/config_values_generator.h
Normal file
@ -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_
|
196
tools/gn/err.cc
Normal file
196
tools/gn/err.cc
Normal file
@ -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);
|
||||
}
|
85
tools/gn/err.h
Normal file
85
tools/gn/err.h
Normal file
@ -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_
|
77
tools/gn/escape.cc
Normal file
77
tools/gn/escape.cc
Normal file
@ -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());
|
||||
}
|
53
tools/gn/escape.h
Normal file
53
tools/gn/escape.h
Normal file
@ -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_
|
4
tools/gn/escape_unittest.cc
Normal file
4
tools/gn/escape_unittest.cc
Normal file
@ -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.
|
||||
|
125
tools/gn/file_template.cc
Normal file
125
tools/gn/file_template.cc
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
74
tools/gn/file_template.h
Normal file
74
tools/gn/file_template.h
Normal file
@ -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_
|
45
tools/gn/file_template_unittest.cc
Normal file
45
tools/gn/file_template_unittest.cc
Normal file
@ -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]);
|
||||
}
|
350
tools/gn/filesystem_utils.cc
Normal file
350
tools/gn/filesystem_utils.cc
Normal file
@ -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;
|
||||
}
|
||||
|
115
tools/gn/filesystem_utils.h
Normal file
115
tools/gn/filesystem_utils.h
Normal file
@ -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_
|
146
tools/gn/filesystem_utils_unittest.cc
Normal file
146
tools/gn/filesystem_utils_unittest.cc
Normal file
@ -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);
|
||||
}
|
37
tools/gn/function_define_rule.cc
Normal file
37
tools/gn/function_define_rule.cc
Normal file
@ -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();
|
||||
}
|
254
tools/gn/function_exec_script.cc
Normal file
254
tools/gn/function_exec_script.cc
Normal file
@ -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);
|
||||
}
|
65
tools/gn/function_process_file_template.cc
Normal file
65
tools/gn/function_process_file_template.cc
Normal file
@ -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;
|
||||
}
|
67
tools/gn/function_read_file.cc
Normal file
67
tools/gn/function_read_file.cc
Normal file
@ -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);
|
||||
}
|
70
tools/gn/function_set_default_toolchain.cc
Normal file
70
tools/gn/function_set_default_toolchain.cc
Normal file
@ -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();
|
||||
}
|
38
tools/gn/function_template.cc
Normal file
38
tools/gn/function_template.cc
Normal file
@ -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();
|
||||
}
|
126
tools/gn/function_toolchain.cc
Normal file
126
tools/gn/function_toolchain.cc
Normal file
@ -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();
|
||||
}
|
84
tools/gn/function_write_file.cc
Normal file
84
tools/gn/function_write_file.cc
Normal file
@ -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();
|
||||
}
|
443
tools/gn/functions.cc
Normal file
443
tools/gn/functions.cc
Normal file
@ -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();
|
||||
}
|
182
tools/gn/functions.h
Normal file
182
tools/gn/functions.h
Normal file
@ -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_
|
221
tools/gn/functions_target.cc
Normal file
221
tools/gn/functions_target.cc
Normal file
@ -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);
|
||||
}
|
||||
|
129
tools/gn/generate_test_gn_data.cc
Normal file
129
tools/gn/generate_test_gn_data.cc
Normal file
@ -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;
|
||||
}
|
171
tools/gn/gn.gyp
Normal file
171
tools/gn/gn.gyp
Normal file
@ -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',
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
59
tools/gn/gn_main.cc
Normal file
59
tools/gn/gn_main.cc
Normal file
@ -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;
|
||||
}
|
83
tools/gn/import_manager.cc
Normal file
83
tools/gn/import_manager.cc
Normal file
@ -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);
|
||||
}
|
41
tools/gn/import_manager.h
Normal file
41
tools/gn/import_manager.h
Normal file
@ -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_
|
205
tools/gn/input_conversion.cc
Normal file
205
tools/gn/input_conversion.cc
Normal file
@ -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();
|
||||
}
|
26
tools/gn/input_conversion.h
Normal file
26
tools/gn/input_conversion.h
Normal file
@ -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_
|
81
tools/gn/input_conversion_unittest.cc
Normal file
81
tools/gn/input_conversion_unittest.cc
Normal file
@ -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());
|
||||
}
|
30
tools/gn/input_file.cc
Normal file
30
tools/gn/input_file.cc
Normal file
@ -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;
|
||||
}
|
||||
|
52
tools/gn/input_file.h
Normal file
52
tools/gn/input_file.h
Normal file
@ -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_
|
254
tools/gn/input_file_manager.cc
Normal file
254
tools/gn/input_file_manager.cc
Normal file
@ -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;
|
||||
}
|
123
tools/gn/input_file_manager.h
Normal file
123
tools/gn/input_file_manager.h
Normal file
@ -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_
|
31
tools/gn/item.cc
Normal file
31
tools/gn/item.cc
Normal file
@ -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";
|
||||
}
|
45
tools/gn/item.h
Normal file
45
tools/gn/item.h
Normal file
@ -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_
|
51
tools/gn/item_node.cc
Normal file
51
tools/gn/item_node.cc
Normal file
@ -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();
|
||||
}
|
119
tools/gn/item_node.h
Normal file
119
tools/gn/item_node.h
Normal file
@ -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_
|
193
tools/gn/item_tree.cc
Normal file
193
tools/gn/item_tree.cc
Normal file
@ -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;
|
||||
}
|
69
tools/gn/item_tree.h
Normal file
69
tools/gn/item_tree.h
Normal file
@ -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_
|
263
tools/gn/label.cc
Normal file
263
tools/gn/label.cc
Normal file
@ -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);
|
||||
}
|
116
tools/gn/label.h
Normal file
116
tools/gn/label.h
Normal file
@ -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_
|
88
tools/gn/label_unittest.cc
Normal file
88
tools/gn/label_unittest.cc
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
77
tools/gn/location.h
Normal file
77
tools/gn/location.h
Normal file
@ -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_
|
165
tools/gn/ninja_build_writer.cc
Normal file
165
tools/gn/ninja_build_writer.cc
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
53
tools/gn/ninja_build_writer.h
Normal file
53
tools/gn/ninja_build_writer.h
Normal file
@ -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_
|
||||
|
165
tools/gn/ninja_helper.cc
Normal file
165
tools/gn/ninja_helper.cc
Normal file
@ -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;
|
||||
}
|
71
tools/gn/ninja_helper.h
Normal file
71
tools/gn/ninja_helper.h
Normal file
@ -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_
|
73
tools/gn/ninja_helper_unittest.cc
Normal file
73
tools/gn/ninja_helper_unittest.cc
Normal file
@ -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.
|
||||
}
|
550
tools/gn/ninja_target_writer.cc
Normal file
550
tools/gn/ninja_target_writer.cc
Normal file
@ -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";
|
||||
}
|
67
tools/gn/ninja_target_writer.h
Normal file
67
tools/gn/ninja_target_writer.h
Normal file
@ -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_
|
94
tools/gn/ninja_toolchain_writer.cc
Normal file
94
tools/gn/ninja_toolchain_writer.cc
Normal file
@ -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;
|
||||
}
|
46
tools/gn/ninja_toolchain_writer.h
Normal file
46
tools/gn/ninja_toolchain_writer.h
Normal file
@ -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_
|
64
tools/gn/ninja_writer.cc
Normal file
64
tools/gn/ninja_writer.cc
Normal file
@ -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);
|
||||
}
|
27
tools/gn/ninja_writer.h
Normal file
27
tools/gn/ninja_writer.h
Normal file
@ -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_
|
573
tools/gn/operators.cc
Normal file
573
tools/gn/operators.cc
Normal file
@ -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();
|
||||
}
|
35
tools/gn/operators.h
Normal file
35
tools/gn/operators.h
Normal file
@ -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_
|
41
tools/gn/output_file.h
Normal file
41
tools/gn/output_file.h
Normal file
@ -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
|
42
tools/gn/output_stream.h
Normal file
42
tools/gn/output_stream.h
Normal file
@ -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_
|
472
tools/gn/parse_tree.cc
Normal file
472
tools/gn/parse_tree.cc
Normal file
@ -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);
|
||||
}
|
366
tools/gn/parse_tree.h
Normal file
366
tools/gn/parse_tree.h
Normal file
@ -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_
|
470
tools/gn/parser.cc
Normal file
470
tools/gn/parser.cc
Normal file
@ -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);
|
||||
}
|
81
tools/gn/parser.h
Normal file
81
tools/gn/parser.h
Normal file
@ -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_
|
329
tools/gn/parser_unittest.cc
Normal file
329
tools/gn/parser_unittest.cc
Normal file
@ -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);
|
||||
}
|
116
tools/gn/path_output.cc
Normal file
116
tools/gn/path_output.cc
Normal file
@ -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
|
||||
}
|
||||
}
|
80
tools/gn/path_output.h
Normal file
80
tools/gn/path_output.h
Normal file
@ -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_
|
193
tools/gn/path_output_unittest.cc
Normal file
193
tools/gn/path_output_unittest.cc
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
185
tools/gn/pattern.cc
Normal file
185
tools/gn/pattern.cc
Normal file
@ -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;
|
||||
}
|
86
tools/gn/pattern.h
Normal file
86
tools/gn/pattern.h
Normal file
@ -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_
|
61
tools/gn/pattern_unittest.cc
Normal file
61
tools/gn/pattern_unittest.cc
Normal file
@ -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 << "\"";
|
||||
}
|
||||
}
|
130
tools/gn/scheduler.cc
Normal file
130
tools/gn/scheduler.cc
Normal file
@ -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();
|
||||
}
|
90
tools/gn/scheduler.h
Normal file
90
tools/gn/scheduler.h
Normal file
@ -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_
|
||||
|
372
tools/gn/scope.cc
Normal file
372
tools/gn/scope.cc
Normal file
@ -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);
|
||||
}
|
260
tools/gn/scope.h
Normal file
260
tools/gn/scope.h
Normal file
@ -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_
|
186
tools/gn/scope_per_file_provider.cc
Normal file
186
tools/gn/scope_per_file_provider.cc
Normal file
@ -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);
|
||||
}
|
79
tools/gn/scope_per_file_provider.h
Normal file
79
tools/gn/scope_per_file_provider.h
Normal file
@ -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_
|
919
tools/gn/secondary/base/BUILD.gn
Normal file
919
tools/gn/secondary/base/BUILD.gn
Normal file
@ -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",
|
||||
]
|
||||
}
|
6
tools/gn/secondary/base/allocator/BUILD.gn
Normal file
6
tools/gn/secondary/base/allocator/BUILD.gn
Normal file
@ -0,0 +1,6 @@
|
||||
static_library("allocator_extension_thunks") {
|
||||
sources = [
|
||||
"allocator_extension_thunks.cc",
|
||||
"allocator_extension_thunks.h",
|
||||
]
|
||||
}
|
7
tools/gn/secondary/base/third_party/dynamic_annotations/BUILD.gn
vendored
Normal file
7
tools/gn/secondary/base/third_party/dynamic_annotations/BUILD.gn
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
static_library("dynamic_annotations") {
|
||||
sources = [
|
||||
"dynamic_annotations.c",
|
||||
"dynamic_annotations.h",
|
||||
"../valgrind/valgrind.h",
|
||||
]
|
||||
}
|
52
tools/gn/secondary/build/config/BUILD.gn
Normal file
52
tools/gn/secondary/build/config/BUILD.gn
Normal file
@ -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") {
|
||||
|
||||
}
|
187
tools/gn/secondary/build/config/BUILDCONFIG.gn
Normal file
187
tools/gn/secondary/build/config/BUILDCONFIG.gn
Normal file
@ -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")
|
||||
}
|
134
tools/gn/secondary/build/config/compiler/BUILD.gn
Normal file
134
tools/gn/secondary/build/config/compiler/BUILD.gn
Normal file
@ -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.
|
||||
]
|
||||
}
|
||||
}
|
184
tools/gn/secondary/build/config/win/BUILD.gn
Normal file
184
tools/gn/secondary/build/config/win/BUILD.gn
Normal file
@ -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",
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user