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;