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

Side by Side Diff: ios/build/tools/convert_gn_xcodeproj.py

Issue 2343853002: Upstream helper script to build Chromium on iOS and update instructions. (Closed)
Patch Set: Created 4 years, 3 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
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2016 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Convert GN Xcode projects to platform and configuration independent targets.
7
8 GN generates Xcode projects that build one configuration only. However, typical
9 iOS development involves using the Xcode IDE to toggle the platform and
10 configuration. This script replaces the 'gn' configuration with 'Debug',
11 'Release' and 'Profile', and changes the ninja invokation to honor these
12 configurations.
13 """
14
15 import argparse
16 import collections
17 import copy
18 import filecmp
19 import json
20 import hashlib
21 import os
22 import plistlib
23 import random
24 import shutil
25 import subprocess
26 import sys
27 import tempfile
28
29
30 XCTEST_PRODUCT_TYPE = 'com.apple.product-type.bundle.unit-test'
31
32
33 class XcodeProject(object):
34
35 def __init__(self, objects, counter = 0):
36 self.objects = objects
37 self.counter = 0
38
39 def AddObject(self, parent_name, obj):
40 while True:
41 self.counter += 1
42 str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
43 new_id = hashlib.sha1(str_id).hexdigest()[:24].upper()
44
45 # Make sure ID is unique. It's possible there could be an id conflict
46 # since this is run after GN runs.
47 if new_id not in self.objects:
48 self.objects[new_id] = obj
49 return new_id
50
51
52 def CopyFileIfChanged(source_path, target_path):
53 """Copy |source_path| to |target_path| is different."""
54 target_dir = os.path.dirname(target_path)
55 if not os.path.isdir(target_dir):
56 os.makedirs(target_dir)
57 if not os.path.exists(target_path) or \
58 not filecmp.cmp(source_path, target_path):
59 shutil.copyfile(source_path, target_path)
60
61
62 def LoadXcodeProjectAsJSON(path):
63 """Return Xcode project at |path| as a JSON string."""
64 return subprocess.check_output([
65 'plutil', '-convert', 'json', '-o', '-', path])
66
67
68 def WriteXcodeProject(output_path, json_string):
69 """Save Xcode project to |output_path| as XML."""
70 with tempfile.NamedTemporaryFile() as temp_file:
71 temp_file.write(json_string)
72 temp_file.flush()
73 subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name])
74 CopyFileIfChanged(temp_file.name, output_path)
75
76
77 def UpdateProductsProject(file_input, file_output, configurations):
78 """Update Xcode project to support multiple configurations.
79
80 Args:
81 file_input: path to the input Xcode project
82 file_output: path to the output file
83 configurations: list of string corresponding to the configurations that
84 need to be supported by the tweaked Xcode projects, must contains at
85 least one value.
86 """
87 json_data = json.loads(LoadXcodeProjectAsJSON(file_input))
88 project = XcodeProject(json_data['objects'])
89
90 objects_to_remove = []
91 for value in project.objects.values():
92 isa = value['isa']
93
94 # TODO(crbug.com/619072): gn does not write the min deployment target in the
95 # generated Xcode project, so add it while doing the conversion, only if it
96 # is not present. Remove this code and comment once the bug is fixed and gn
97 # has rolled past it.
98 if isa == 'XCBuildConfiguration':
99 build_settings = value['buildSettings']
100 if 'IPHONEOS_DEPLOYMENT_TARGET' not in build_settings:
101 build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
102
103 # Remove path name key and change path to basename.
104 if isa == 'PBXFileReference':
105 if 'name' in value:
106 del value['name']
107 value['path'] = os.path.basename(value['path'])
108
109 # Teach build shell script to look for the configuration and platform.
110 if isa == 'PBXShellScriptBuildPhase':
111 value['shellScript'] = value['shellScript'].replace(
112 'ninja -C .',
113 'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
114
115 # Configure BUNDLE_LOADER and TEST_HOST for xctest target (assuming that
116 # the host is named "${target}_host") unless gn has already configured
117 # them.
118 if isa == 'PBXNativeTarget' and value['productType'] == XCTEST_PRODUCT_TYPE:
119 configuration_list = project.objects[value['buildConfigurationList']]
120 for config_name in configuration_list['buildConfigurations']:
121 config = project.objects[config_name]
122 if not config['buildSettings'].get('BUNDLE_LOADER'):
123 config['buildSettings']['BUNDLE_LOADER'] = '$(TEST_HOST)'
124 config['buildSettings']['TEST_HOST'] = \
125 '${BUILT_PRODUCTS_DIR}/%(name)s_host.app/%(name)s' % value
126
127 # Add new configuration, using the first one as default.
128 if isa == 'XCConfigurationList':
129 value['defaultConfigurationName'] = configurations[0]
130 objects_to_remove.extend(value['buildConfigurations'])
131
132 build_config_template = project.objects[value['buildConfigurations'][0]]
133 build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
134 '../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
135
136 value['buildConfigurations'] = []
137 for configuration in configurations:
138 new_build_config = copy.copy(build_config_template)
139 new_build_config['name'] = configuration
140 value['buildConfigurations'].append(
141 project.AddObject('products', new_build_config))
142
143 for object_id in objects_to_remove:
144 del project.objects[object_id]
145
146 objects = collections.OrderedDict(sorted(project.objects.iteritems()))
147 WriteXcodeProject(file_output, json.dumps(json_data))
148
149
150 def ConvertGnXcodeProject(input_dir, output_dir, configurations):
151 '''Tweak the Xcode project generated by gn to support multiple configurations.
152
153 The Xcode projects generated by "gn gen --ide" only supports a single
154 platform and configuration (as the platform and configuration are set
155 per output directory). This method takes as input such projects and
156 add support for multiple configurations and platforms (to allow devs
157 to select them in Xcode).
158
159 Args:
160 input_dir: directory containing the XCode projects created by "gn gen --ide"
161 output_dir: directory where the tweaked Xcode projects will be saved
162 configurations: list of string corresponding to the configurations that
163 need to be supported by the tweaked Xcode projects, must contains at
164 least one value.
165 '''
166 # Update products project.
167 products = os.path.join('products.xcodeproj', 'project.pbxproj')
168 product_input = os.path.join(input_dir, products)
169 product_output = os.path.join(output_dir, products)
170 UpdateProductsProject(product_input, product_output, configurations)
171
172 # Copy sources project and all workspace.
173 sources = os.path.join('sources.xcodeproj', 'project.pbxproj')
174 CopyFileIfChanged(os.path.join(input_dir, sources),
175 os.path.join(output_dir, sources))
176 xcwspace = os.path.join('all.xcworkspace', 'contents.xcworkspacedata')
177 CopyFileIfChanged(os.path.join(input_dir, xcwspace),
178 os.path.join(output_dir, xcwspace))
179
180
181 def Main(args):
182 parser = argparse.ArgumentParser(
183 description='Convert GN Xcode projects for iOS.')
184 parser.add_argument(
185 'input',
186 help='directory containing [product|sources|all] Xcode projects.')
187 parser.add_argument(
188 'output',
189 help='directory where to generate the iOS configuration.')
190 parser.add_argument(
191 '--add-config', dest='configurations', default=[], action='append',
192 help='configuration to add to the Xcode project')
193 args = parser.parse_args(args)
194
195 if not os.path.isdir(args.input):
196 sys.stderr.write('Input directory does not exists.\n')
197 return 1
198
199 required = set(['products.xcodeproj', 'sources.xcodeproj', 'all.xcworkspace'])
200 if not required.issubset(os.listdir(args.input)):
201 sys.stderr.write(
202 'Input directory does not contain all necessary Xcode projects.\n')
203 return 1
204
205 if not args.configurations:
206 sys.stderr.write('At least one configuration required, see --add-config.\n')
207 return 1
208
209 ConvertGnXcodeProject(args.input, args.output, args.configurations)
210
211 if __name__ == '__main__':
212 sys.exit(Main(sys.argv[1:]))
213
214
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698