OLD | NEW |
---|---|
(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 } | |
OLD | NEW |