Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(102)

Side by Side Diff: tools/gn/xcode_writer.cc

Issue 1827103005: [GN] Add support for generating Xcode projects. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Improve comment about "assert(product_type != "")" Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/gn/xcode_writer.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "tools/gn/xcode_writer.h"
6
7 #include <iomanip>
8 #include <map>
9 #include <memory>
10 #include <sstream>
11 #include <string>
12 #include <utility>
13
14 #include "base/environment.h"
15 #include "base/logging.h"
16 #include "base/sha1.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "tools/gn/args.h"
20 #include "tools/gn/build_settings.h"
21 #include "tools/gn/builder.h"
22 #include "tools/gn/commands.h"
23 #include "tools/gn/deps_iterator.h"
24 #include "tools/gn/filesystem_utils.h"
25 #include "tools/gn/settings.h"
26 #include "tools/gn/source_file.h"
27 #include "tools/gn/target.h"
28 #include "tools/gn/value.h"
29 #include "tools/gn/variables.h"
30 #include "tools/gn/xcode_object.h"
31
32 namespace {
33
34 XcodeWriter::TargetOsType GetTargetOs(const Args& args) {
35 const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
36 if (target_os_value) {
37 if (target_os_value->type() == Value::STRING) {
38 if (target_os_value->string_value() == "ios")
39 return XcodeWriter::WRITER_TARGET_OS_IOS;
40 }
41 }
42 return XcodeWriter::WRITER_TARGET_OS_MACOS;
43 }
44
45 std::string GetArchs(const Args& args) {
46 const Value* target_cpu_value = args.GetArgOverride(variables::kTargetCpu);
47 if (target_cpu_value) {
48 if (target_cpu_value->type() == Value::STRING) {
49 if (target_cpu_value->string_value() == "x86")
50 return "i386";
51 if (target_cpu_value->string_value() == "x64")
52 return "x86_64";
53 if (target_cpu_value->string_value() == "arm")
54 return "armv7";
55 if (target_cpu_value->string_value() == "armv7")
56 return "armv7";
57 if (target_cpu_value->string_value() == "arm64")
58 return "armv64";
59 }
60 }
61 return "x86_64";
62 }
63
64 std::string GetBuildScript(const std::string& target_name,
65 const std::string& build_path,
66 const std::string& ninja_extra_args) {
67 std::stringstream script;
68 script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n"
69 << "exec ";
70 if (!build_path.empty())
71 script << "env PATH=\"" << build_path << "\" ";
72 script << "ninja -C .";
73 if (!ninja_extra_args.empty())
74 script << " " << ninja_extra_args;
75 if (!target_name.empty())
76 script << " " << target_name;
77 script << "\nexit 1\n";
78 return script.str();
79 }
80
81 class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor {
82 public:
83 CollectPBXObjectsPerClassHelper() {}
84
85 void Visit(PBXObject* object) override {
86 DCHECK(object);
87 objects_per_class_[object->Class()].push_back(object);
88 }
89
90 const std::map<PBXObjectClass, std::vector<const PBXObject*>>&
91 objects_per_class() const {
92 return objects_per_class_;
93 }
94
95 private:
96 std::map<PBXObjectClass, std::vector<const PBXObject*>> objects_per_class_;
97
98 DISALLOW_COPY_AND_ASSIGN(CollectPBXObjectsPerClassHelper);
99 };
100
101 std::map<PBXObjectClass, std::vector<const PBXObject*>>
102 CollectPBXObjectsPerClass(PBXProject* project) {
103 CollectPBXObjectsPerClassHelper visitor;
104 project->Visit(visitor);
105 return visitor.objects_per_class();
106 }
107
108 class RecursivelyAssignIdsHelper : public PBXObjectVisitor {
109 public:
110 RecursivelyAssignIdsHelper(const std::string& seed)
111 : seed_(seed), counter_(0) {}
112
113 void Visit(PBXObject* object) override {
114 std::stringstream buffer;
115 buffer << seed_ << " " << object->Name() << " " << counter_;
116 std::string hash = base::SHA1HashString(buffer.str());
117 DCHECK_EQ(hash.size() % 4, 0u);
118
119 uint32_t id[3] = {0, 0, 0};
120 const uint32_t* ptr = reinterpret_cast<const uint32_t*>(hash.data());
121 for (size_t i = 0; i < hash.size() / 4; i++)
122 id[i % 3] ^= ptr[i];
123
124 object->SetId(base::HexEncode(id, sizeof(id)));
125 ++counter_;
126 }
127
128 private:
129 std::string seed_;
130 int64_t counter_;
131
132 DISALLOW_COPY_AND_ASSIGN(RecursivelyAssignIdsHelper);
133 };
134
135 void RecursivelyAssignIds(PBXProject* project) {
136 RecursivelyAssignIdsHelper visitor(project->Name());
137 project->Visit(visitor);
138 }
139
140 } // namespace
141
142 // static
143 bool XcodeWriter::RunAndWriteFiles(const std::string& workspace_name,
144 const std::string& root_target_name,
145 const std::string& ninja_extra_args,
146 const std::string& dir_filters_string,
147 const BuildSettings* build_settings,
148 Builder* builder,
149 Err* err) {
150 const XcodeWriter::TargetOsType target_os =
151 GetTargetOs(build_settings->build_args());
152
153 PBXAttributes attributes;
154 switch (target_os) {
155 case XcodeWriter::WRITER_TARGET_OS_IOS:
156 attributes["SDKROOT"] = "iphoneos";
157 attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
158 break;
159 case XcodeWriter::WRITER_TARGET_OS_MACOS:
160 attributes["ARCHS"] = GetArchs(build_settings->build_args());
161 attributes["SDKROOT"] = "macosx10.11";
162 break;
163 }
164
165 const std::string source_path =
166 base::FilePath::FromUTF8Unsafe(
167 RebasePath("//", build_settings->build_dir()))
168 .StripTrailingSeparators()
169 .AsUTF8Unsafe();
170
171 std::string config_name = build_settings->build_dir()
172 .Resolve(base::FilePath())
173 .StripTrailingSeparators()
174 .BaseName()
175 .AsUTF8Unsafe();
176 DCHECK(!config_name.empty());
177
178 std::string::size_type separator = config_name.find('-');
179 if (separator != std::string::npos)
180 config_name = config_name.substr(0, separator);
181
182 std::vector<const Target*> targets;
183 std::vector<const Target*> all_targets = builder->GetAllResolvedTargets();
184 if (!XcodeWriter::FilterTargets(build_settings, all_targets,
185 dir_filters_string, &targets, err)) {
186 return false;
187 }
188
189 XcodeWriter workspace(workspace_name);
190 workspace.CreateProductsProject(targets, attributes, source_path, config_name,
191 root_target_name, ninja_extra_args,
192 target_os);
193
194 workspace.CreateSourcesProject(all_targets, build_settings->build_dir(),
195 attributes, source_path, config_name,
196 target_os);
197
198 return workspace.WriteFiles(build_settings, err);
199 }
200
201 XcodeWriter::XcodeWriter(const std::string& name) : name_(name) {
202 if (name_.empty())
203 name_.assign("all");
204 }
205
206 XcodeWriter::~XcodeWriter() {}
207
208 // static
209 bool XcodeWriter::FilterTargets(const BuildSettings* build_settings,
210 const std::vector<const Target*>& all_targets,
211 const std::string& dir_filters_string,
212 std::vector<const Target*>* targets,
213 Err* err) {
214 // Filter targets according to the semicolon-delimited list of label patterns,
215 // if defined, first.
216 targets->reserve(all_targets.size());
217 if (dir_filters_string.empty()) {
218 *targets = all_targets;
219 } else {
220 std::vector<LabelPattern> filters;
221 if (!commands::FilterPatternsFromString(build_settings, dir_filters_string,
222 &filters, err)) {
223 return false;
224 }
225
226 commands::FilterTargetsByPatterns(all_targets, filters, targets);
227 }
228
229 // Filter out all target of type EXECUTABLE that are direct dependency of
230 // a BUNDLE_DATA target (under the assumption that they will be part of a
231 // CREATE_BUNDLE target generating an application bundle). Sort the list
232 // of targets per pointer to use binary search for the removal.
233 std::sort(targets->begin(), targets->end());
234
235 for (const Target* target : all_targets) {
236 if (!target->settings()->is_default())
237 continue;
238
239 if (target->output_type() != Target::BUNDLE_DATA)
240 continue;
241
242 for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
243 if (pair.ptr->output_type() != Target::EXECUTABLE)
244 continue;
245
246 auto iter = std::lower_bound(targets->begin(), targets->end(), pair.ptr);
247 if (iter != targets->end() && *iter == pair.ptr)
248 targets->erase(iter);
249 }
250 }
251
252 // Sort the list of targets per-label to get a consistent ordering of them
253 // in the generated Xcode project (and thus stability of the file generated).
254 std::sort(targets->begin(), targets->end(),
255 [](const Target* a, const Target* b) {
256 return a->label().name() < b->label().name();
257 });
258
259 return true;
260 }
261
262 void XcodeWriter::CreateProductsProject(
263 const std::vector<const Target*>& targets,
264 const PBXAttributes& attributes,
265 const std::string& source_path,
266 const std::string& config_name,
267 const std::string& root_target,
268 const std::string& ninja_extra_args,
269 TargetOsType target_os) {
270 std::unique_ptr<PBXProject> main_project(
271 new PBXProject("products", config_name, source_path, attributes));
272
273 std::string build_path;
274 std::unique_ptr<base::Environment> env(base::Environment::Create());
275 env->GetVar("PATH", &build_path);
276
277 main_project->AddAggregateTarget(
278 "All", GetBuildScript(root_target, build_path, ninja_extra_args));
279
280 for (const Target* target : targets) {
281 switch (target->output_type()) {
282 case Target::EXECUTABLE:
283 if (target_os == XcodeWriter::WRITER_TARGET_OS_IOS)
284 continue;
285
286 main_project->AddNativeTarget(
287 target->label().name(), "compiled.mach-o.executable",
288 target->output_name().empty() ? target->label().name()
289 : target->output_name(),
290 "com.apple.product-type.tool",
291 GetBuildScript(target->label().name(), build_path,
292 ninja_extra_args));
293 break;
294
295 case Target::CREATE_BUNDLE:
296 if (target->bundle_data().product_type().empty())
297 continue;
298
299 main_project->AddNativeTarget(
300 target->label().name(), std::string(),
301 target->bundle_data()
302 .GetBundleRootDirOutput(target->settings())
303 .Resolve(base::FilePath())
304 .AsUTF8Unsafe(),
305 target->bundle_data().product_type(),
306 GetBuildScript(target->label().name(), build_path,
307 ninja_extra_args));
308 break;
309
310 default:
311 break;
312 }
313 }
314
315 projects_.push_back(std::move(main_project));
316 }
317
318 void XcodeWriter::CreateSourcesProject(
319 const std::vector<const Target*>& targets,
320 const SourceDir& root_build_dir,
321 const PBXAttributes& attributes,
322 const std::string& source_path,
323 const std::string& config_name,
324 TargetOsType target_os) {
325 std::vector<SourceFile> sources;
326 for (const Target* target : targets) {
327 if (!target->settings()->is_default())
328 continue;
329
330 for (const SourceFile& source : target->sources()) {
331 if (source.is_system_absolute())
332 continue;
333
334 if (IsStringInOutputDir(root_build_dir, source.value()))
335 continue;
336
337 sources.push_back(source);
338 }
339 }
340
341 std::unique_ptr<PBXProject> sources_for_indexing(
342 new PBXProject("sources", config_name, source_path, attributes));
343
344 // Sort sources to ensure determinisn of the project file generation and
345 // remove duplicate reference to the source files (can happen due to the
346 // bundle_data targets).
347 std::sort(sources.begin(), sources.end());
348 sources.erase(std::unique(sources.begin(), sources.end()), sources.end());
349
350 for (const SourceFile& source : sources) {
351 base::FilePath source_path = source.Resolve(base::FilePath());
352 sources_for_indexing->AddSourceFile(source_path.AsUTF8Unsafe());
353 }
354
355 projects_.push_back(std::move(sources_for_indexing));
356 }
357
358 bool XcodeWriter::WriteFiles(const BuildSettings* build_settings, Err* err) {
359 for (const auto& project : projects_) {
360 if (!WriteProjectFile(build_settings, project.get(), err))
361 return false;
362 }
363
364 SourceFile xcworkspacedata_file =
365 build_settings->build_dir().ResolveRelativeFile(
366 Value(nullptr, name_ + ".xcworkspace/contents.xcworkspacedata"), err);
367 if (xcworkspacedata_file.is_null())
368 return false;
369
370 std::stringstream xcworkspacedata_string_out;
371 WriteWorkspaceContent(xcworkspacedata_string_out);
372
373 return WriteFileIfChanged(build_settings->GetFullPath(xcworkspacedata_file),
374 xcworkspacedata_string_out.str(), err);
375 }
376
377 bool XcodeWriter::WriteProjectFile(const BuildSettings* build_settings,
378 PBXProject* project,
379 Err* err) {
380 SourceFile pbxproj_file = build_settings->build_dir().ResolveRelativeFile(
381 Value(nullptr, project->Name() + ".xcodeproj/project.pbxproj"), err);
382 if (pbxproj_file.is_null())
383 return false;
384
385 std::stringstream pbxproj_string_out;
386 WriteProjectContent(pbxproj_string_out, project);
387
388 if (!WriteFileIfChanged(build_settings->GetFullPath(pbxproj_file),
389 pbxproj_string_out.str(), err))
390 return false;
391
392 return true;
393 }
394
395 void XcodeWriter::WriteWorkspaceContent(std::ostream& out) {
396 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
397 << "<Workspace version = \"1.0\">\n";
398 for (const auto& project : projects_) {
399 out << " <FileRef location = \"group:" << project->Name()
400 << ".xcodeproj\"></FileRef>\n";
401 }
402 out << "</Workspace>\n";
403 }
404
405 void XcodeWriter::WriteProjectContent(std::ostream& out, PBXProject* project) {
406 RecursivelyAssignIds(project);
407
408 out << "// !$*UTF8*$!\n"
409 << "{\n"
410 << "\tarchiveVersion = 1;\n"
411 << "\tclasses = {\n"
412 << "\t};\n"
413 << "\tobjectVersion = 46;\n"
414 << "\tobjects = {\n";
415
416 for (auto& pair : CollectPBXObjectsPerClass(project)) {
417 out << "\n"
418 << "/* Begin " << ToString(pair.first) << " section */\n";
419 std::sort(pair.second.begin(), pair.second.end(),
420 [](const PBXObject* a, const PBXObject* b) {
421 return a->id() < b->id();
422 });
423 for (const auto& object : pair.second) {
424 object->Print(out, 2);
425 }
426 out << "/* End " << ToString(pair.first) << " section */\n";
427 }
428
429 out << "\t};\n"
430 << "\trootObject = " << project->Reference() << ";\n"
431 << "}\n";
432 }
OLDNEW
« no previous file with comments | « tools/gn/xcode_writer.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698