blob: 0aff1a92ee241cb4b30ca765ce05510ef6b4c753 [file] [log] [blame]
// 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 <map>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/environment.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/err.h"
#include "tools/gn/gyp_helper.h"
#include "tools/gn/gyp_target_writer.h"
#include "tools/gn/item_node.h"
#include "tools/gn/location.h"
#include "tools/gn/setup.h"
#include "tools/gn/source_file.h"
#include "tools/gn/standard_out.h"
#include "tools/gn/target.h"
namespace commands {
namespace {
typedef GypTargetWriter::TargetGroup TargetGroup;
typedef std::map<Label, TargetGroup> CorrelatedTargetsMap;
typedef std::map<SourceFile, std::vector<TargetGroup> > GroupedTargetsMap;
typedef std::map<std::string, std::string> StringStringMap;
// Groups targets sharing the same label between debug and release.
void CorrelateTargets(const std::vector<const Target*>& debug_targets,
const std::vector<const Target*>& release_targets,
CorrelatedTargetsMap* correlated) {
for (size_t i = 0; i < debug_targets.size(); i++) {
const Target* target = debug_targets[i];
(*correlated)[target->label()].debug = target;
}
for (size_t i = 0; i < release_targets.size(); i++) {
const Target* target = release_targets[i];
(*correlated)[target->label()].release = target;
}
}
// Verifies that both debug and release variants match. They can differ only
// by flags.
bool EnsureTargetsMatch(const TargetGroup& group, Err* err) {
// Check that both debug and release made this target.
if (!group.debug || !group.release) {
const Target* non_null_one = group.debug ? group.debug : group.release;
*err = Err(Location(), "The debug and release builds did not both generate "
"a target with the name\n" +
non_null_one->label().GetUserVisibleName(true));
return false;
}
// Check the flags that determine if and where we write the GYP file.
if (group.debug->item_node()->should_generate() !=
group.release->item_node()->should_generate() ||
group.debug->external() != group.release->external() ||
group.debug->gyp_file() != group.release->gyp_file()) {
*err = Err(Location(), "The metadata for the target\n" +
group.debug->label().GetUserVisibleName(true) +
"\ndoesn't match between the debug and release builds.");
return false;
}
// Check that the sources match.
if (group.debug->sources().size() != group.release->sources().size()) {
*err = Err(Location(), "The source file count for the target\n" +
group.debug->label().GetUserVisibleName(true) +
"\ndoesn't have the same number of files between the debug and "
"release builds.");
return false;
}
for (size_t i = 0; i < group.debug->sources().size(); i++) {
if (group.debug->sources()[i] != group.release->sources()[i]) {
*err = Err(Location(), "The debug and release version of the target \n" +
group.debug->label().GetUserVisibleName(true) +
"\ndon't agree on the file\n" +
group.debug->sources()[i].value());
return false;
}
}
// Check that the deps match.
if (group.debug->deps().size() != group.release->deps().size()) {
*err = Err(Location(), "The source file count for the target\n" +
group.debug->label().GetUserVisibleName(true) +
"\ndoesn't have the same number of deps between the debug and "
"release builds.");
return false;
}
for (size_t i = 0; i < group.debug->deps().size(); i++) {
if (group.debug->deps()[i].label != group.release->deps()[i].label) {
*err = Err(Location(), "The debug and release version of the target \n" +
group.debug->label().GetUserVisibleName(true) +
"\ndon't agree on the dep\n" +
group.debug->deps()[i].label.GetUserVisibleName(true));
return false;
}
}
return true;
}
// Python uses shlex.split, which we partially emulate here.
//
// Advances to the next "word" in a GYP_DEFINES entry. This is something
// separated by whitespace or '='. We allow backslash escaping and quoting.
// The return value will be the index into the array immediately following the
// word, and the contents of the word will be placed into |*word|.
size_t GetNextGypDefinesWord(const std::string& defines,
size_t cur,
std::string* word) {
size_t i = cur;
bool is_quoted = false;
if (cur < defines.size() && defines[cur] == '"') {
i++;
is_quoted = true;
}
for (; i < defines.size(); i++) {
// Handle certain escape sequences: \\, \", \<space>.
if (defines[i] == '\\' && i < defines.size() - 1 &&
(defines[i + 1] == '\\' ||
defines[i + 1] == ' ' ||
defines[i + 1] == '=' ||
defines[i + 1] == '"')) {
i++;
word->push_back(defines[i]);
continue;
}
if (is_quoted && defines[i] == '"') {
// Got to the end of the quoted sequence.
return i + 1;
}
if (!is_quoted && (defines[i] == ' ' || defines[i] == '=')) {
return i;
}
word->push_back(defines[i]);
}
return i;
}
// Advances to the beginning of the next word, or the size of the string if
// the end was encountered.
size_t AdvanceToNextGypDefinesWord(const std::string& defines, size_t cur) {
while (cur < defines.size() && defines[cur] == ' ')
cur++;
return cur;
}
// The GYP defines looks like:
// component=shared_library
// component=shared_library foo=1
// component=shared_library foo=1 windows_sdk_dir="C:\Program Files\..."
StringStringMap GetGypDefines() {
StringStringMap result;
scoped_ptr<base::Environment> env(base::Environment::Create());
std::string defines;
if (!env->GetVar("GYP_DEFINES", &defines) || defines.empty())
return result;
size_t cur = 0;
while (cur < defines.size()) {
std::string key;
cur = AdvanceToNextGypDefinesWord(defines, cur);
cur = GetNextGypDefinesWord(defines, cur, &key);
// The words should be separated by an equals.
cur = AdvanceToNextGypDefinesWord(defines, cur);
if (cur == defines.size())
break;
if (defines[cur] != '=')
continue;
cur++; // Skip over '='.
std::string value;
cur = AdvanceToNextGypDefinesWord(defines, cur);
cur = GetNextGypDefinesWord(defines, cur, &value);
result[key] = value;
}
return result;
}
// Returns a set of args from known GYP define values.
Scope::KeyValueMap GetArgsFromGypDefines() {
StringStringMap gyp_defines = GetGypDefines();
Scope::KeyValueMap result;
static const char kIsComponentBuild[] = "is_component_build";
if (gyp_defines["component"] == "shared_library") {
result[kIsComponentBuild] = Value(NULL, true);
} else {
result[kIsComponentBuild] = Value(NULL, false);
}
// Windows SDK path. GYP and the GN build use the same name.
static const char kWinSdkPath[] = "windows_sdk_path";
if (!gyp_defines[kWinSdkPath].empty())
result[kWinSdkPath] = Value(NULL, gyp_defines[kWinSdkPath]);
return result;
}
// Returns the number of targets, number of GYP files.
std::pair<int, int> WriteGypFiles(
const BuildSettings& debug_settings,
const BuildSettings& release_settings,
Err* err) {
// Group all targets by output GYP file name.
std::vector<const Target*> debug_targets;
std::vector<const Target*> release_targets;
debug_settings.target_manager().GetAllTargets(&debug_targets);
release_settings.target_manager().GetAllTargets(&release_targets);
// Match up the debug and release version of each target by label.
CorrelatedTargetsMap correlated;
CorrelateTargets(debug_targets, release_targets, &correlated);
GypHelper helper;
GroupedTargetsMap grouped_targets;
int target_count = 0;
for (CorrelatedTargetsMap::iterator i = correlated.begin();
i != correlated.end(); ++i) {
const TargetGroup& group = i->second;
if (!group.debug->item_node()->should_generate())
continue; // Skip non-generated ones.
if (group.debug->external())
continue; // Skip external ones.
if (group.debug->gyp_file().is_null())
continue; // Skip ones without GYP files.
if (!EnsureTargetsMatch(group, err))
return std::make_pair(0, 0);
target_count++;
grouped_targets[helper.GetGypFileForTarget(group.debug, err)].push_back(
group);
if (err->has_error())
return std::make_pair(0, 0);
}
// Write each GYP file.
for (GroupedTargetsMap::iterator i = grouped_targets.begin();
i != grouped_targets.end(); ++i) {
GypTargetWriter::WriteFile(i->first, i->second, err);
if (err->has_error())
return std::make_pair(0, 0);
}
return std::make_pair(target_count,
static_cast<int>(grouped_targets.size()));
}
} // namespace
// Suppress output on success.
const char kSwitchQuiet[] = "q";
const char kGyp[] = "gyp";
const char kGyp_HelpShort[] =
"gyp: Make GYP files from GN.";
const char kGyp_Help[] =
"gyp: Make GYP files from GN.\n"
"\n"
" This command will generate GYP files from GN sources. You can then run\n"
" GYP over the result to produce a build. Native GYP targets can depend\n"
" on any GN target except source sets. GN targets can depend on native\n"
" GYP targets, but all/direct dependent settings will NOT be pushed\n"
" across the boundary.\n"
"\n"
" To make this work you first need to manually run GN, then GYP, then\n"
" do the build. Because GN doesn't generate the final .ninja files,\n"
" there will be no rules to regenerate the .ninja files if the inputs\n"
" change, so you will have to manually repeat these steps each time\n"
" something changes:\n"
"\n"
" out/Debug/gn gyp\n"
" python build/gyp_chromiunm\n"
" ninja -C out/Debug foo_target\n"
"\n"
" Two variables are used to control how a target relates to GYP:\n"
"\n"
" - \"external != true\" and \"gyp_file\" is set: This target will be\n"
" written to the named GYP file in the source tree (not restricted to\n"
" an output or generated files directory).\n"
"\n"
" - \"external == true\" and \"gyp_file\" is set: The target will not\n"
" be written to a GYP file. But other targets being written to GYP\n"
" files can depend on it, and they will reference the given GYP file\n"
" name for GYP to use. This allows you to specify how GN->GYP\n"
" dependencies and named, and provides a place to manually set the\n"
" dependent configs from GYP to GN.\n"
"\n"
" - \"gyp_file\" is unset: Like the previous case, but if a GN target is\n"
" being written to a GYP file that depends on this one, the default\n"
" GYP file name will be assumed. The default name will match the name\n"
" of the current directory, so \"//foo/bar:baz\" would be\n"
" \"<(DEPTH)/foo/bar/bar.gyp:baz\".\n"
"\n"
"Example:\n"
" # This target is assumed to be in the GYP build in the file\n"
" # \"foo/foo.gyp\". This declaration tells GN where to find the GYP\n"
" # equivalent, and gives it some direct dependent settings that targets\n"
" # depending on it should receive (since these don't flow from GYP to\n"
" # GN-generated targets).\n"
" shared_library(\"gyp_target\") {\n"
" gyp_file = \"//foo/foo.gyp\"\n"
" external = true\n"
" direct_dependen_configs = [ \":gyp_target_config\" ]\n"
" }\n"
"\n"
" executable(\"my_app\") {\n"
" deps = [ \":gyp_target\" ]\n"
" gyp_file = \"//foo/myapp.gyp\"\n"
" sources = ...\n"
" }\n";
int RunGyp(const std::vector<std::string>& args) {
const CommandLine* cmdline = CommandLine::ForCurrentProcess();
base::TimeTicks begin_time = base::TimeTicks::Now();
// Deliberately leaked to avoid expensive process teardown.
Setup* setup_debug = new Setup;
if (!setup_debug->DoSetup())
return 1;
const char kIsDebug[] = "is_debug";
setup_debug->build_settings().build_args().AddArgOverrides(
GetArgsFromGypDefines());
setup_debug->build_settings().build_args().AddArgOverride(
kIsDebug, Value(NULL, true));
// Make a release build based on the debug one. We use a new directory for
// the build output so that they don't stomp on each other.
DependentSetup* setup_release = new DependentSetup(*setup_debug);
setup_release->build_settings().build_args().AddArgOverride(
kIsDebug, Value(NULL, false));
setup_release->build_settings().SetBuildDir(
SourceDir(setup_release->build_settings().build_dir().value() +
"gn_release.tmp/"));
// Run both debug and release builds in parallel.
setup_release->RunPreMessageLoop();
if (!setup_debug->Run())
return 1;
if (!setup_release->RunPostMessageLoop())
return 1;
Err err;
std::pair<int, int> counts = WriteGypFiles(setup_debug->build_settings(),
setup_release->build_settings(),
&err);
if (err.has_error()) {
err.PrintToStdout();
return 1;
}
// Timing info.
base::TimeTicks end_time = base::TimeTicks::Now();
if (!cmdline->HasSwitch(kSwitchQuiet)) {
OutputString("Done. ", DECORATION_GREEN);
std::string stats = "Wrote " +
base::IntToString(counts.first) + " targets to " +
base::IntToString(counts.second) + " GYP files read from " +
base::IntToString(
setup_debug->scheduler().input_file_manager()->GetInputFileCount())
+ " GN files in " +
base::IntToString((end_time - begin_time).InMilliseconds()) + "ms\n";
OutputString(stats);
}
return 0;
}
} // namespace commands