[xcode] Wrap ninja invocation for Xcode New Build System
The New Build System shipped with Xcode 10+ expects the paths
in error messages to be absolute even when invoking a custom
build script target.
Wrap the invocation of ninja in a python script that uses
regular expressions to convert the relative paths to absolute
paths.
Remove deprecated parameter --ninja-extra-args as it is hard
to support with python script (it would require parsing the
string and interpret it as a shell would do).
Bug: chromium:1094890
Change-Id: I893128f0c6744f98c8c89f1a807c83400be26545
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9080
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index 0180cd1..b3f0633 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -244,7 +244,6 @@
command_line->GetSwitchValueASCII(kSwitchXcodeProject),
command_line->GetSwitchValueASCII(kSwitchRootTarget),
command_line->GetSwitchValueASCII(kSwitchNinjaExecutable),
- command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs),
command_line->GetSwitchValueASCII(kSwitchFilters),
XcodeBuildSystem::kLegacy,
};
diff --git a/src/gn/xcode_object.cc b/src/gn/xcode_object.cc
index eb3bd64..b56f6a9 100644
--- a/src/gn/xcode_object.cc
+++ b/src/gn/xcode_object.cc
@@ -994,7 +994,7 @@
PrintProperty(out, rules, "name", name_);
PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
- PrintProperty(out, rules, "shellPath", "/bin/sh");
+ PrintProperty(out, rules, "shellPath", "/usr/bin/python3");
PrintProperty(out, rules, "shellScript", shell_script_);
PrintProperty(out, rules, "showEnvVarsInLog", 0u);
out << indent_str << "};\n";
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index 0daf1c5..0060545 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -19,6 +19,7 @@
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "gn/args.h"
#include "gn/build_settings.h"
#include "gn/builder.h"
@@ -40,6 +41,60 @@
namespace {
+// This is the template of the script used to build the target. It invokes
+// ninja (supporting --ninja-executable parameter), parsing ninja's output
+// using a regular expression looking for relative path to the source root
+// from root_build_dir that are at the start of a path and converting them
+// to absolute paths (use str.replace(rel_root_src, abs_root_src) would be
+// simpler but would fail if rel_root_src is present multiple time in the
+// path).
+const char kBuildScriptTemplate[] = R"(
+import re
+import os
+import subprocess
+import sys
+
+rel_root_src = '%s'
+abs_root_src = os.path.abspath(rel_root_src) + '/'
+
+build_target = '%s'
+ninja_binary = '%s'
+ninja_params = [ '-C', '.' ]
+
+%s
+
+if build_target:
+ ninja_params.append(build_target)
+ print('Compile "' + build_target + '" via ninja')
+else:
+ print('Compile "all" via ninja')
+
+process = subprocess.Popen(
+ [ ninja_binary ] + ninja_params,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ encoding='utf-8',
+ env=environ)
+
+pattern = re.compile('(?<!/)' + re.escape(rel_root_src))
+
+for line in iter(process.stdout.readline, ''):
+ while True:
+ match = pattern.search(line)
+ if not match:
+ break
+ span = match.span()
+ print(line[:span[0]], end='')
+ print(abs_root_src, end='')
+ line = line[span[1]:]
+ print(line, flush=True, end='')
+
+process.wait()
+
+sys.exit(process.returncode)
+)";
+
enum TargetOsType {
WRITER_TARGET_OS_IOS,
WRITER_TARGET_OS_MACOS,
@@ -84,39 +139,31 @@
return ninja_executable.empty() ? "ninja" : ninja_executable;
}
+std::string ComputeScriptEnviron(base::Environment* environment) {
+ std::stringstream buffer;
+ buffer << "environ = {}";
+ for (const auto& variable : kSafeEnvironmentVariables) {
+ buffer << "\nenviron['" << variable.name << "'] = ";
+ if (variable.capture_at_generation) {
+ std::string value;
+ environment->GetVar(variable.name, &value);
+ buffer << "'" << value << "'";
+ } else {
+ buffer << "os.environ.get('" << variable.name << "', '')";
+ }
+ }
+ return buffer.str();
+}
+
std::string GetBuildScript(const std::string& target_name,
const std::string& ninja_executable,
- const std::string& ninja_extra_args,
+ const std::string& root_src_dir,
base::Environment* environment) {
- std::stringstream script;
- script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n"
- << "exec ";
-
- // Launch ninja with a sanitized environment (Xcode sets many environment
- // variable overridding settings, including the SDK, thus breaking hermetic
- // build).
- script << "env -i ";
- for (const auto& variable : kSafeEnvironmentVariables) {
- script << variable.name << "=\"";
-
- std::string value;
- if (variable.capture_at_generation)
- environment->GetVar(variable.name, &value);
-
- if (!value.empty())
- script << value;
- else
- script << "$" << variable.name;
- script << "\" ";
- }
-
- script << GetNinjaExecutable(ninja_executable) << " -C .";
- if (!ninja_extra_args.empty())
- script << " " << ninja_extra_args;
- if (!target_name.empty())
- script << " " << target_name;
- script << "\nexit 1\n";
- return script.str();
+ std::string environ_script = ComputeScriptEnviron(environment);
+ std::string ninja = GetNinjaExecutable(ninja_executable);
+ return base::StringPrintf(kBuildScriptTemplate, root_src_dir.c_str(),
+ target_name.c_str(), ninja.c_str(),
+ environ_script.c_str());
}
bool IsApplicationTarget(const Target* target) {
@@ -663,10 +710,11 @@
bool XcodeProject::AddTargetsFromBuilder(const Builder& builder, Err* err) {
std::unique_ptr<base::Environment> env(base::Environment::Create());
- project_.AddAggregateTarget(
- "All",
- GetBuildScript(options_.root_target_name, options_.ninja_executable,
- options_.ninja_extra_args, env.get()));
+ const std::string root_src_dir =
+ RebasePath("//", build_settings_->build_dir());
+ project_.AddAggregateTarget("All", GetBuildScript(options_.root_target_name,
+ options_.ninja_executable,
+ root_src_dir, env.get()));
const std::optional<std::vector<const Target*>> targets =
GetTargetsFromBuilder(builder, err);
@@ -898,14 +946,15 @@
output_dir = RebasePath(output_dir, build_settings_->build_dir());
}
+ const std::string root_src_dir =
+ RebasePath("//", build_settings_->build_dir());
return project_.AddNativeTarget(
target->label().name(), "compiled.mach-o.executable",
target->output_name().empty() ? target->label().name()
: target->output_name(),
- "com.apple.product-type.tool",
- output_dir,
+ "com.apple.product-type.tool", output_dir,
GetBuildScript(target->label().name(), options_.ninja_executable,
- options_.ninja_extra_args, env));
+ root_src_dir, env));
}
PBXNativeTarget* XcodeProject::AddBundleTarget(const Target* target,
@@ -933,12 +982,13 @@
.GetBundleDir(target->settings())
.value(),
build_settings_->build_dir());
+ const std::string root_src_dir =
+ RebasePath("//", 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,
- options_.ninja_extra_args, env),
+ target->bundle_data().product_type(), output_dir,
+ GetBuildScript(pbxtarget_name, options_.ninja_executable, root_src_dir,
+ env),
xcode_extra_attributes);
}
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
index b45b606..f934275 100644
--- a/src/gn/xcode_writer.h
+++ b/src/gn/xcode_writer.h
@@ -38,9 +38,6 @@
// Name of the ninja executable. Defaults to "ninja" if empty.
std::string ninja_executable;
- // Extra parameters to pass to ninja. Deprecated.
- std::string ninja_extra_args;
-
// If specified, should be a semicolon-separated list of label patterns.
// It will be used to filter the list of targets generated in the project
// (in the same way that the other filtering is done, source and header