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

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: Add documentation for new flags to "gn gen" 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
« tools/gn/xcode_object.cc ('K') | « 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 "third_party/re2/src/re2/re2.h"
20 #include "tools/gn/args.h"
21 #include "tools/gn/build_settings.h"
22 #include "tools/gn/builder.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 {
brettw 2016/04/28 21:16:17 Blank line after here.
sdefresne 2016/04/30 11:25:03 Done.
33 XcodeWriter::TargetOsType GetTargetOs(const Args& args) {
34 const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
35 if (target_os_value) {
36 if (target_os_value->type() == Value::STRING) {
37 if (target_os_value->string_value() == "ios")
38 return XcodeWriter::WRITER_TARGET_OS_IOS;
39 }
40 }
41 return XcodeWriter::WRITER_TARGET_OS_MACOS;
42 }
43
44 std::string GetArchs(const Args& args) {
45 const Value* target_cpu_value = args.GetArgOverride(variables::kTargetCpu);
46 if (target_cpu_value) {
47 if (target_cpu_value->type() == Value::STRING) {
48 if (target_cpu_value->string_value() == "x86")
49 return "i386";
50 if (target_cpu_value->string_value() == "x64")
51 return "x86_64";
52 if (target_cpu_value->string_value() == "arm")
53 return "armv7";
54 if (target_cpu_value->string_value() == "armv7")
55 return "armv7";
56 if (target_cpu_value->string_value() == "arm64")
57 return "armv64";
58 }
59 }
60 return "x86_64";
61 }
62
63 std::string GetBuildScript(const std::string& target_name,
64 const std::string& build_path,
65 const std::string& ninja_extra_args) {
66 std::stringstream script;
67 script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n"
68 << "exec ";
69 if (!build_path.empty())
70 script << "env PATH=\"" << build_path << "\" ";
71 script << "ninja -C .";
72 if (!ninja_extra_args.empty())
73 script << " " << ninja_extra_args;
74 if (!target_name.empty())
75 script << " " << target_name;
76 script << "\nexit 1\n";
77 return script.str();
78 }
79
80 class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor {
81 public:
82 CollectPBXObjectsPerClassHelper() {}
83
84 void Visit(PBXObject* object) override {
85 DCHECK(object);
86 objects_per_class_[object->Class()].push_back(object);
87 }
88
89 const std::map<PBXObjectClass, std::vector<const PBXObject*>>&
90 objects_per_class() const {
91 return objects_per_class_;
92 }
93
94 private:
95 std::map<PBXObjectClass, std::vector<const PBXObject*>> objects_per_class_;
96
97 DISALLOW_COPY_AND_ASSIGN(CollectPBXObjectsPerClassHelper);
98 };
99
100 std::map<PBXObjectClass, std::vector<const PBXObject*>>
101 CollectPBXObjectsPerClass(PBXProject* project) {
102 CollectPBXObjectsPerClassHelper visitor;
103 project->Visit(visitor);
104 return visitor.objects_per_class();
105 }
106
107 class RecursivelyAssignIdsHelper : public PBXObjectVisitor {
108 public:
109 RecursivelyAssignIdsHelper(const std::string& seed)
110 : seed_(seed), counter_(0) {}
111
112 void Visit(PBXObject* object) override {
113 std::stringstream buffer;
114 buffer << seed_ << " " << object->Name() << " " << counter_;
115 std::string hash = base::SHA1HashString(buffer.str());
116 DCHECK_EQ(hash.size() % 4, 0u);
117
118 uint32_t id[3] = {0, 0, 0};
119 const uint32_t* ptr = reinterpret_cast<const uint32_t*>(hash.data());
120 for (size_t i = 0; i < hash.size() / 4; i++) {
brettw 2016/04/28 21:16:17 No {}
sdefresne 2016/04/30 11:25:03 Done.
121 id[i % 3] ^= ptr[i];
122 }
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 } // namespace
brettw 2016/04/28 21:16:17 Blank line above here.
sdefresne 2016/04/30 11:25:03 Done.
140
141 // static
142 bool XcodeWriter::RunAndWriteFiles(const std::string& workspace_name,
143 const std::string& root_target_name,
144 const std::string& ninja_extra_args,
145 const std::string& target_filter_pattern,
146 const BuildSettings* build_settings,
147 Builder* builder,
148 Err* err) {
149 const XcodeWriter::TargetOsType target_os =
150 GetTargetOs(build_settings->build_args());
151
152 PBXAttributes attributes;
153 switch (target_os) {
154 case XcodeWriter::WRITER_TARGET_OS_IOS:
155 attributes["SDKROOT"] = "iphoneos";
156 attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
157 break;
158 case XcodeWriter::WRITER_TARGET_OS_MACOS:
159 attributes["ARCHS"] = GetArchs(build_settings->build_args());
160 attributes["SDKROOT"] = "macosx10.11";
161 break;
162 }
163
164 const std::string source_path =
165 base::FilePath::FromUTF8Unsafe(
166 RebasePath("//", build_settings->build_dir()))
167 .StripTrailingSeparators()
168 .AsUTF8Unsafe();
169
170 std::string config_name = build_settings->build_dir()
171 .Resolve(base::FilePath())
172 .StripTrailingSeparators()
173 .BaseName()
174 .AsUTF8Unsafe();
175 DCHECK(!config_name.empty());
176
177 std::string::size_type separator = config_name.find('-');
178 if (separator != std::string::npos)
179 config_name = config_name.substr(0, separator);
180
181 std::vector<const Target*> targets = builder->GetAllResolvedTargets();
182
183 XcodeWriter workspace(workspace_name);
184 workspace.CreateMainProject(targets, attributes, workspace.name_, source_path,
185 config_name, root_target_name, ninja_extra_args,
186 target_filter_pattern, target_os);
187
188 workspace.CreateSourceForIndexingProject(targets, build_settings->build_dir(),
189 attributes, "sources_for_indexing",
190 source_path, config_name, target_os);
191
192 return workspace.WriteFiles(build_settings, err);
193 }
194
195 XcodeWriter::XcodeWriter(const std::string& name) : name_(name) {
196 if (name_.empty())
197 name_.assign("all");
198 }
199
200 XcodeWriter::~XcodeWriter() {}
201
202 // static
203 std::vector<const Target*> XcodeWriter::FilterTargets(
204 const std::vector<const Target*>& all_targets,
205 const std::string& target_filter_pattern) {
206 std::vector<const Target*> targets;
207 targets.reserve(all_targets.size());
208
209 std::unique_ptr<RE2> pattern;
brettw 2016/04/28 21:16:17 Can you use GN "label_pattern"s here instead? This
sdefresne 2016/04/30 11:25:03 Done.
210 if (!target_filter_pattern.empty())
211 pattern.reset(new RE2(target_filter_pattern));
212
213 for (const Target* target : all_targets) {
214 if (!target->settings()->is_default())
215 continue;
216
217 if (target->output_type() != Target::CREATE_BUNDLE &&
218 target->output_type() != Target::EXECUTABLE) {
219 continue;
220 }
221
222 if (pattern && !RE2::PartialMatch(target->label().name(), *pattern.get()))
223 continue;
224
225 targets.push_back(target);
226 }
227
228 std::sort(targets.begin(), targets.end());
brettw 2016/04/28 21:16:17 Can you add a comment here about what you're doing
sdefresne 2016/04/30 11:25:03 Done.
229 for (const Target* target : all_targets) {
230 if (!target->settings()->is_default())
231 continue;
232
233 if (target->output_type() != Target::BUNDLE_DATA)
234 continue;
235
236 for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
237 if (pair.ptr->output_type() != Target::EXECUTABLE)
238 continue;
239
240 const auto& iter =
241 std::lower_bound(targets.begin(), targets.end(), pair.ptr);
242
243 if (iter != targets.end() && *iter == pair.ptr)
244 targets.erase(iter);
245 }
246 }
247
248 std::sort(targets.begin(), targets.end(),
249 [](const Target* a, const Target* b) {
250 return a->label().name() < b->label().name();
251 });
252
253 return targets;
254 }
255
256 void XcodeWriter::CreateMainProject(const std::vector<const Target*>& targets,
257 const PBXAttributes& attributes,
258 const std::string& project_name,
259 const std::string& source_path,
260 const std::string& config_name,
261 const std::string& root_target,
262 const std::string& ninja_extra_args,
263 const std::string& target_filter_pattern,
264 TargetOsType target_os) {
265 std::unique_ptr<PBXProject> main_project(
266 new PBXProject(project_name, config_name, source_path, attributes));
267
268 std::string build_path;
269 std::unique_ptr<base::Environment> env(base::Environment::Create());
270 env->GetVar("PATH", &build_path);
271
272 main_project->AddAggregateTarget(
273 "All", GetBuildScript(root_target, build_path, ninja_extra_args));
274
275 const std::vector<const Target*> filtered_targets =
276 XcodeWriter::FilterTargets(targets, target_filter_pattern);
277 for (const Target* target : filtered_targets) {
278 switch (target->output_type()) {
279 case Target::EXECUTABLE:
280 if (target_os == XcodeWriter::WRITER_TARGET_OS_IOS)
281 continue;
282
283 main_project->AddNativeTarget(
284 target->label().name(), "compiled.mach-o.executable",
285 target->output_name().empty() ? target->label().name()
286 : target->output_name(),
287 "com.apple.product-type.tool",
288 GetBuildScript(target->label().name(), build_path,
289 ninja_extra_args));
290 break;
291
292 case Target::CREATE_BUNDLE:
293 if (target->bundle_data().product_type().empty())
294 continue;
295
296 main_project->AddNativeTarget(
297 target->label().name(), std::string(),
298 target->bundle_data()
299 .GetBundleRootDirOutput(target->settings())
300 .Resolve(base::FilePath())
301 .AsUTF8Unsafe(),
302 target->bundle_data().product_type(),
303 GetBuildScript(target->label().name(), build_path,
304 ninja_extra_args));
305 break;
306
307 default:
308 break;
309 }
310 }
311
312 projects_.push_back(std::move(main_project));
313 }
314
315 void XcodeWriter::CreateSourceForIndexingProject(
316 const std::vector<const Target*>& targets,
317 const SourceDir& root_build_dir,
318 const PBXAttributes& attributes,
319 const std::string& project_name,
320 const std::string& source_path,
321 const std::string& config_name,
322 TargetOsType target_os) {
323 std::vector<SourceFile> sources;
324 for (const Target* target : targets) {
325 if (!target->settings()->is_default())
326 continue;
327
328 for (const SourceFile& source : target->sources()) {
329 if (source.is_system_absolute())
330 continue;
331
332 if (IsStringInOutputDir(root_build_dir, source.value()))
333 continue;
334
335 sources.push_back(source);
336 }
337 }
338
339 std::unique_ptr<PBXProject> sources_for_indexing(
340 new PBXProject(project_name, config_name, source_path, attributes));
341
342 std::sort(sources.begin(), sources.end());
343 for (const SourceFile& source : sources) {
344 base::FilePath source_path = source.Resolve(base::FilePath());
345 sources_for_indexing->AddSourceFile(source_path.AsUTF8Unsafe());
346 }
347
348 projects_.push_back(std::move(sources_for_indexing));
349 }
350
351 bool XcodeWriter::WriteFiles(const BuildSettings* build_settings, Err* err) {
352 for (const auto& project : projects_) {
353 if (!WriteProjectFile(build_settings, project.get(), err))
354 return false;
355 }
356
357 SourceFile xcworkspacedata_file =
358 build_settings->build_dir().ResolveRelativeFile(
359 Value(nullptr, name_ + ".xcworkspace/contents.xcworkspacedata"), err);
360 if (xcworkspacedata_file.is_null())
361 return false;
362
363 std::stringstream xcworkspacedata_string_out;
364 WriteWorkspaceContent(xcworkspacedata_string_out);
365
366 return WriteFileIfChanged(build_settings->GetFullPath(xcworkspacedata_file),
367 xcworkspacedata_string_out.str(), err);
368 }
369
370 bool XcodeWriter::WriteProjectFile(const BuildSettings* build_settings,
371 PBXProject* project,
372 Err* err) {
373 SourceFile pbxproj_file = build_settings->build_dir().ResolveRelativeFile(
374 Value(nullptr, project->Name() + ".xcodeproj/project.pbxproj"), err);
375 if (pbxproj_file.is_null())
376 return false;
377
378 std::stringstream pbxproj_string_out;
379 WriteProjectContent(pbxproj_string_out, project);
380
381 if (!WriteFileIfChanged(build_settings->GetFullPath(pbxproj_file),
382 pbxproj_string_out.str(), err))
383 return false;
384
385 return true;
386 }
387
388 void XcodeWriter::WriteWorkspaceContent(std::ostream& out) {
389 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
390 << "<Workspace version = \"1.0\">\n";
391 for (const auto& project : projects_) {
392 out << " <FileRef location = \"group:" << project->Name()
393 << ".xcodeproj\"></FileRef>\n";
394 }
395 out << "</Workspace>\n";
396 }
397
398 void XcodeWriter::WriteProjectContent(std::ostream& out, PBXProject* project) {
399 RecursivelyAssignIds(project);
400
401 out << "// !$*UTF8*$!\n"
402 << "{\n"
403 << "\tarchiveVersion = 1;\n"
404 << "\tclasses = {\n"
405 << "\t};\n"
406 << "\tobjectVersion = 46;\n"
407 << "\tobjects = {\n";
408
409 for (auto& pair : CollectPBXObjectsPerClass(project)) {
410 out << "\n"
411 << "/* Begin " << ToString(pair.first) << " section */\n";
412 std::sort(pair.second.begin(), pair.second.end(),
413 [](const PBXObject* a, const PBXObject* b) {
414 return a->id() < b->id();
415 });
416 for (const auto& object : pair.second) {
417 object->Print(out, 2);
418 }
419 out << "/* End " << ToString(pair.first) << " section */\n";
420 }
421
422 out << "\t};\n"
423 << "\trootObject = " << project->Reference() << ";\n"
424 << "}\n";
425 }
OLDNEW
« tools/gn/xcode_object.cc ('K') | « tools/gn/xcode_writer.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698