Add support for multiple configuration to Xcode project generator
Add two flags `--xcode-configs` and `--xcode-config-build-dir` that
allow generating an Xcode project supporting multiple configurations.
This will speed up the generation of the Xcode project used by the
Chromium project by removing the need to use a script that modify
the project generated by gn.
Bug: chromium:1331345
Change-Id: Ifb5e652ba60f1a5228e9eddda8b2cdd83b82aac9
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14142
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index 6ffec1d..084398d 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -828,6 +828,23 @@
"legacy" - Legacy Build system
"new" - New Build System
+ --xcode-configs=<config_name_list>
+ Configure the list of build configuration supported by the generated
+ project. If specified, must be a list of semicolon-separated strings.
+ If ommitted, a single configuration will be used in the generated
+ project derived from the build directory.
+
+ --xcode-config-build-dir=<string>
+ If present, must be a path relative to the source directory. It will
+ default to $root_out_dir if ommitted. The path is assumed to point to
+ the directory where ninja needs to be invoked. This variable can be
+ used to build for multiple configuration / platform / environment from
+ the same generated Xcode project (assuming that the user has created a
+ gn build directory with the correct args.gn for each).
+
+ One useful value is to use Xcode variables such as '${CONFIGURATION}'
+ or '${EFFECTIVE_PLATFORM}'.
+
--ninja-executable=<string>
Can be used to specify the ninja executable to use when building.
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index 23f15a0..fb68cf2 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -56,6 +56,8 @@
const char kSwitchXcodeBuildSystem[] = "xcode-build-system";
const char kSwitchXcodeBuildsystemValueLegacy[] = "legacy";
const char kSwitchXcodeBuildsystemValueNew[] = "new";
+const char kSwitchXcodeConfigurations[] = "xcode-configs";
+const char kSwitchXcodeConfigurationBuildPath[] = "xcode-config-build-dir";
const char kSwitchJsonFileName[] = "json-file-name";
const char kSwitchJsonIdeScript[] = "json-ide-script";
const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
@@ -257,6 +259,8 @@
command_line->GetSwitchValueASCII(kSwitchIdeRootTarget),
command_line->GetSwitchValueASCII(kSwitchNinjaExecutable),
command_line->GetSwitchValueASCII(kSwitchFilters),
+ command_line->GetSwitchValueASCII(kSwitchXcodeConfigurations),
+ command_line->GetSwitchValuePath(kSwitchXcodeConfigurationBuildPath),
XcodeBuildSystem::kLegacy,
};
@@ -516,6 +520,23 @@
"legacy" - Legacy Build system
"new" - New Build System
+ --xcode-configs=<config_name_list>
+ Configure the list of build configuration supported by the generated
+ project. If specified, must be a list of semicolon-separated strings.
+ If ommitted, a single configuration will be used in the generated
+ project derived from the build directory.
+
+ --xcode-config-build-dir=<string>
+ If present, must be a path relative to the source directory. It will
+ default to $root_out_dir if ommitted. The path is assumed to point to
+ the directory where ninja needs to be invoked. This variable can be
+ used to build for multiple configuration / platform / environment from
+ the same generated Xcode project (assuming that the user has created a
+ gn build directory with the correct args.gn for each).
+
+ One useful value is to use Xcode variables such as '${CONFIGURATION}'
+ or '${EFFECTIVE_PLATFORM}'.
+
--ninja-executable=<string>
Can be used to specify the ninja executable to use when building.
diff --git a/src/gn/xcode_object.cc b/src/gn/xcode_object.cc
index 2bd65e3..ab357bb 100644
--- a/src/gn/xcode_object.cc
+++ b/src/gn/xcode_object.cc
@@ -388,10 +388,10 @@
PBXTarget::PBXTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes)
: configurations_(
- std::make_unique<XCConfigurationList>(config_name, attributes, this)),
+ std::make_unique<XCConfigurationList>(configs, attributes, this)),
name_(name) {
if (!shell_script.empty()) {
build_phases_.push_back(
@@ -432,9 +432,9 @@
PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes)
- : PBXTarget(name, shell_script, config_name, attributes) {}
+ : PBXTarget(name, shell_script, configs, attributes) {}
PBXAggregateTarget::~PBXAggregateTarget() = default;
@@ -715,12 +715,12 @@
PBXNativeTarget::PBXNativeTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes,
const std::string& product_type,
const std::string& product_name,
const PBXFileReference* product_reference)
- : PBXTarget(name, shell_script, config_name, attributes),
+ : PBXTarget(name, shell_script, configs, attributes),
product_reference_(product_reference),
product_type_(product_type),
product_name_(product_name) {
@@ -773,15 +773,15 @@
// PBXProject -----------------------------------------------------------------
PBXProject::PBXProject(const std::string& name,
- const std::string& config_name,
+ std::vector<std::string> configs,
const std::string& source_path,
const PBXAttributes& attributes)
- : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
+ : name_(name), configs_(std::move(configs)), target_for_indexing_(nullptr) {
main_group_ = std::make_unique<PBXMainGroup>(source_path);
products_ = main_group_->CreateChild<PBXProductsGroup>();
configurations_ =
- std::make_unique<XCConfigurationList>(config_name, attributes, this);
+ std::make_unique<XCConfigurationList>(configs_, attributes, this);
}
PBXProject::~PBXProject() = default;
@@ -809,15 +809,16 @@
}
void PBXProject::AddAggregateTarget(const std::string& name,
+ const std::string& output_dir,
const std::string& shell_script) {
PBXAttributes attributes;
attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
attributes["CODE_SIGNING_REQUIRED"] = "NO";
- attributes["CONFIGURATION_BUILD_DIR"] = ".";
+ attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
attributes["PRODUCT_NAME"] = name;
targets_.push_back(std::make_unique<PBXAggregateTarget>(
- name, shell_script, config_name_, attributes));
+ name, shell_script, configs_, attributes));
}
void PBXProject::AddIndexingTarget() {
@@ -835,8 +836,8 @@
const char product_type[] = "com.apple.product-type.tool";
targets_.push_back(std::make_unique<PBXNativeTarget>(
- "sources", std::string(), config_name_, attributes, product_type,
- "sources", product_reference));
+ "sources", std::string(), configs_, attributes, product_type, "sources",
+ product_reference));
target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
}
@@ -873,7 +874,7 @@
attributes["EXCLUDED_SOURCE_FILE_NAMES"] = "*.*";
targets_.push_back(std::make_unique<PBXNativeTarget>(
- name, shell_script, config_name_, attributes, output_type, product_name,
+ name, shell_script, configs_, attributes, output_type, product_name,
product));
return static_cast<PBXNativeTarget*>(targets_.back().get());
}
@@ -1091,13 +1092,16 @@
// XCConfigurationList --------------------------------------------------------
-XCConfigurationList::XCConfigurationList(const std::string& name,
- const PBXAttributes& attributes,
- const PBXObject* owner_reference)
+XCConfigurationList::XCConfigurationList(
+ const std::vector<std::string>& configs,
+ const PBXAttributes& attributes,
+ const PBXObject* owner_reference)
: owner_reference_(owner_reference) {
DCHECK(owner_reference_);
- configurations_.push_back(
- std::make_unique<XCBuildConfiguration>(name, attributes));
+ for (const std::string& config_name : configs) {
+ configurations_.push_back(
+ std::make_unique<XCBuildConfiguration>(config_name, attributes));
+ }
}
XCConfigurationList::~XCConfigurationList() = default;
@@ -1134,7 +1138,7 @@
out << indent_str << Reference() << " = {\n";
PrintProperty(out, rules, "isa", ToString(Class()));
PrintProperty(out, rules, "buildConfigurations", configurations_);
- PrintProperty(out, rules, "defaultConfigurationIsVisible", 1u);
+ PrintProperty(out, rules, "defaultConfigurationIsVisible", 0u);
PrintProperty(out, rules, "defaultConfigurationName",
configurations_[0]->Name());
out << indent_str << "};\n";
diff --git a/src/gn/xcode_object.h b/src/gn/xcode_object.h
index 544c17c..076e993 100644
--- a/src/gn/xcode_object.h
+++ b/src/gn/xcode_object.h
@@ -144,7 +144,7 @@
public:
PBXTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes);
~PBXTarget() override;
@@ -174,7 +174,7 @@
public:
PBXAggregateTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes);
~PBXAggregateTarget() override;
@@ -340,7 +340,7 @@
public:
PBXNativeTarget(const std::string& name,
const std::string& shell_script,
- const std::string& config_name,
+ const std::vector<std::string>& configs,
const PBXAttributes& attributes,
const std::string& product_type,
const std::string& product_name,
@@ -369,7 +369,7 @@
class PBXProject : public PBXObject {
public:
PBXProject(const std::string& name,
- const std::string& config_name,
+ std::vector<std::string> configs,
const std::string& source_path,
const PBXAttributes& attributes);
~PBXProject() override;
@@ -380,6 +380,7 @@
const std::string& source_path,
PBXNativeTarget* target);
void AddAggregateTarget(const std::string& name,
+ const std::string& output_dir,
const std::string& shell_script);
void AddIndexingTarget();
PBXNativeTarget* AddNativeTarget(
@@ -411,7 +412,7 @@
std::string project_root_;
std::vector<std::unique_ptr<PBXTarget>> targets_;
std::string name_;
- std::string config_name_;
+ std::vector<std::string> configs_;
PBXGroup* products_ = nullptr;
PBXNativeTarget* target_for_indexing_ = nullptr;
@@ -523,7 +524,7 @@
class XCConfigurationList : public PBXObject {
public:
- XCConfigurationList(const std::string& name,
+ XCConfigurationList(const std::vector<std::string>& configs,
const PBXAttributes& attributes,
const PBXObject* owner_reference);
~XCConfigurationList() override;
diff --git a/src/gn/xcode_object_unittest.cc b/src/gn/xcode_object_unittest.cc
index 0498c3f..e20a1d0 100644
--- a/src/gn/xcode_object_unittest.cc
+++ b/src/gn/xcode_object_unittest.cc
@@ -38,7 +38,7 @@
// Instantiate a PBXProject object with arbitrary names.
std::unique_ptr<PBXProject> GetPBXProjectObject() {
std::unique_ptr<PBXProject> pbx_project(
- new PBXProject("project", "config", "out/build", PBXAttributes()));
+ new PBXProject("project", {"config"}, "out/build", PBXAttributes()));
return pbx_project;
}
@@ -61,7 +61,7 @@
// Instantiate a PBXAggregateTarget object with arbitrary names.
std::unique_ptr<PBXAggregateTarget> GetPBXAggregateTargetObject() {
std::unique_ptr<PBXAggregateTarget> pbx_aggregate_target(
- new PBXAggregateTarget("target_name", "shell_script", "config_name",
+ new PBXAggregateTarget("target_name", "shell_script", {"config_name"},
PBXAttributes()));
return pbx_aggregate_target;
}
@@ -70,7 +70,7 @@
std::unique_ptr<PBXNativeTarget> GetPBXNativeTargetObject(
const PBXFileReference* product_reference) {
std::unique_ptr<PBXNativeTarget> pbx_native_target(new PBXNativeTarget(
- "target_name", "ninja gn_unittests", "config_name", PBXAttributes(),
+ "target_name", "ninja gn_unittests", {"config_name"}, PBXAttributes(),
"com.apple.product-type.application", "product_name", product_reference));
return pbx_native_target;
}
@@ -104,7 +104,7 @@
std::unique_ptr<XCConfigurationList> GetXCConfigurationListObject(
const PBXObject* owner_reference) {
std::unique_ptr<XCConfigurationList> xc_configuration_list(
- new XCConfigurationList("config_list_name", PBXAttributes(),
+ new XCConfigurationList({"config_list_name"}, PBXAttributes(),
owner_reference));
return xc_configuration_list;
}
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index 809d6a7..0ec1fa8 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -11,6 +11,7 @@
#include <optional>
#include <sstream>
#include <string>
+#include <string_view>
#include <utility>
#include "base/environment.h"
@@ -18,6 +19,7 @@
#include "base/sha1.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "gn/args.h"
#include "gn/build_settings.h"
@@ -77,12 +79,17 @@
return WRITER_TARGET_OS_MACOS;
}
-std::string GetNinjaExecutable(const std::string& ninja_executable) {
- return ninja_executable.empty() ? "ninja" : ninja_executable;
-}
-
-std::string ComputeScriptEnviron(base::Environment* environment) {
+std::string GetBuildScript(const std::string& target_name,
+ const std::string& ninja_executable,
+ const std::string& build_dir,
+ base::Environment* environment) {
+ // Launch ninja with a sanitized environment (Xcode sets many environment
+ // variables overridding settings, including the SDK, thus breaking hermetic
+ // build).
std::stringstream buffer;
+ buffer << "exec env -i ";
+
+ // Write environment.
for (const auto& variable : kSafeEnvironmentVariables) {
buffer << variable.name << "=";
if (variable.capture_at_generation) {
@@ -94,18 +101,15 @@
}
buffer << " ";
}
- return buffer.str();
-}
-std::string GetBuildScript(const std::string& target_name,
- const std::string& ninja_executable,
- base::Environment* environment) {
- // Launch ninja with a sanitized environment (Xcode sets many environment
- // variables overridding settings, including the SDK, thus breaking hermetic
- // build).
- std::stringstream buffer;
- buffer << "exec env -i " << ComputeScriptEnviron(environment);
- buffer << GetNinjaExecutable(ninja_executable) << " -C .";
+ if (ninja_executable.empty()) {
+ buffer << "ninja";
+ } else {
+ buffer << ninja_executable;
+ }
+
+ buffer << " -C " << build_dir;
+
if (!target_name.empty()) {
buffer << " '" << target_name << "'";
}
@@ -323,21 +327,17 @@
project->Visit(visitor);
}
-// Returns a configuration name derived from the build directory. This gives
-// standard names if using the Xcode convention of naming the build directory
-// out/$configuration-$platform (e.g. out/Debug-iphonesimulator).
-std::string ConfigNameFromBuildSettings(const BuildSettings* build_settings) {
- std::string config_name = FilePathToUTF8(build_settings->build_dir()
- .Resolve(base::FilePath())
- .StripTrailingSeparators()
- .BaseName());
+// Returns a list of configuration names from the options passed to the
+// generator. If no configuration names have been passed, return default
+// value.
+std::vector<std::string> ConfigListFromOptions(const std::string& configs) {
+ std::vector<std::string> result = base::SplitString(
+ configs, ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
- std::string::size_type separator = config_name.find('-');
- if (separator != std::string::npos)
- config_name = config_name.substr(0, separator);
+ if (result.empty())
+ result.push_back(std::string("Release"));
- DCHECK(!config_name.empty());
- return config_name;
+ return result;
}
// Returns the path to root_src_dir from settings.
@@ -550,6 +550,10 @@
const std::map<const Target*, PBXNativeTarget*>& bundle_targets,
Err* err);
+ // Tweak `output_dir` to be relative to the configuration specific output
+ // directory (see --xcode-config-build-dir=... flag).
+ std::string GetConfigOutputDir(std::string_view output_dir);
+
// Generates the content of the .xcodeproj file into |out|.
void WriteFileContent(std::ostream& out) const;
@@ -566,7 +570,7 @@
: build_settings_(build_settings),
options_(options),
project_(options.project_name,
- ConfigNameFromBuildSettings(build_settings),
+ ConfigListFromOptions(options.configurations),
SourcePathFromBuildSettings(build_settings),
ProjectAttributesFromBuildSettings(build_settings)) {}
@@ -662,8 +666,9 @@
std::unique_ptr<base::Environment> env(base::Environment::Create());
project_.AddAggregateTarget(
- "All", GetBuildScript(options_.root_target_name,
- options_.ninja_executable, env.get()));
+ "All", GetConfigOutputDir("."),
+ GetBuildScript(options_.root_target_name, options_.ninja_executable,
+ GetConfigOutputDir("."), env.get()));
const std::optional<std::vector<const Target*>> targets =
GetTargetsFromBuilder(builder, err);
@@ -898,8 +903,9 @@
target->label().name(), "compiled.mach-o.executable",
target->output_name().empty() ? target->label().name()
: target->output_name(),
- "com.apple.product-type.tool", output_dir,
- GetBuildScript(target->label().name(), options_.ninja_executable, env));
+ "com.apple.product-type.tool", GetConfigOutputDir(output_dir),
+ GetBuildScript(target->label().name(), options_.ninja_executable,
+ GetConfigOutputDir("."), env));
}
PBXNativeTarget* XcodeProject::AddBundleTarget(const Target* target,
@@ -923,16 +929,33 @@
const std::string& target_output_name = RebasePath(
target->bundle_data().GetBundleRootDirOutput(target->settings()).value(),
build_settings_->build_dir());
+
const std::string output_dir =
RebasePath(target->bundle_data().GetBundleDir(target->settings()).value(),
build_settings_->build_dir());
+
return project_.AddNativeTarget(
pbxtarget_name, std::string(), target_output_name,
- target->bundle_data().product_type(), output_dir,
- GetBuildScript(pbxtarget_name, options_.ninja_executable, env),
+ target->bundle_data().product_type(), GetConfigOutputDir(output_dir),
+ GetBuildScript(pbxtarget_name, options_.ninja_executable,
+ GetConfigOutputDir("."), env),
xcode_extra_attributes);
}
+std::string XcodeProject::GetConfigOutputDir(std::string_view output_dir) {
+ if (options_.configuration_build_dir.empty())
+ return std::string(output_dir);
+
+ base::FilePath config_output_dir(options_.configuration_build_dir);
+ if (output_dir != ".") {
+ config_output_dir = config_output_dir.Append(UTF8ToFilePath(output_dir));
+ }
+
+ return RebasePath(FilePathToUTF8(config_output_dir.StripTrailingSeparators()),
+ build_settings_->build_dir(),
+ build_settings_->root_path_utf8());
+}
+
void XcodeProject::WriteFileContent(std::ostream& out) const {
out << "// !$*UTF8*$!\n"
<< "{\n"
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
index 96ed14f..7edd674 100644
--- a/src/gn/xcode_writer.h
+++ b/src/gn/xcode_writer.h
@@ -11,6 +11,8 @@
#include <string>
#include <vector>
+#include "base/files/file_path.h"
+
class Builder;
class BuildSettings;
class Err;
@@ -42,6 +44,18 @@
// files for those target will still be listed in the generated project).
std::string dir_filters_string;
+ // If specified, should be a semicolon-separated list of configuration
+ // names. It will be used to generate all the configuration variations
+ // in the project. If empty, the project is assumed to only use a single
+ // configuration "Release".
+ std::string configurations;
+
+ // If specified, should be the path for the configuration's build
+ // directory. It can use Xcode variables such as ${CONFIGURATION} or
+ // ${EFFECTIVE_PLATFORM_NAME}. If empty, it is assumed to be the same
+ // as the project directory.
+ base::FilePath configuration_build_dir;
+
// Control which version of the build system should be used for the
// generated Xcode project.
XcodeBuildSystem build_system = XcodeBuildSystem::kLegacy;