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

Unified Diff: ios/build/tools/convert_gn_xcodeproj.py

Issue 2343853002: Upstream helper script to build Chromium on iOS and update instructions. (Closed)
Patch Set: Fix comment. 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « docs/ios_build_instructions.md ('k') | ios/build/tools/setup-gn.config » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/build/tools/convert_gn_xcodeproj.py
diff --git a/ios/build/tools/convert_gn_xcodeproj.py b/ios/build/tools/convert_gn_xcodeproj.py
new file mode 100755
index 0000000000000000000000000000000000000000..fa493565edad2f471a3bf4ffb166023adfe62d53
--- /dev/null
+++ b/ios/build/tools/convert_gn_xcodeproj.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Convert GN Xcode projects to platform and configuration independent targets.
+
+GN generates Xcode projects that build one configuration only. However, typical
+iOS development involves using the Xcode IDE to toggle the platform and
+configuration. This script replaces the 'gn' configuration with 'Debug',
+'Release' and 'Profile', and changes the ninja invokation to honor these
+configurations.
+"""
+
+import argparse
+import collections
+import copy
+import filecmp
+import json
+import hashlib
+import os
+import plistlib
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+
+XCTEST_PRODUCT_TYPE = 'com.apple.product-type.bundle.unit-test'
+
+
+class XcodeProject(object):
+
+ def __init__(self, objects, counter = 0):
+ self.objects = objects
+ self.counter = 0
+
+ def AddObject(self, parent_name, obj):
+ while True:
+ self.counter += 1
+ str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
+ new_id = hashlib.sha1(str_id).hexdigest()[:24].upper()
+
+ # Make sure ID is unique. It's possible there could be an id conflict
+ # since this is run after GN runs.
+ if new_id not in self.objects:
+ self.objects[new_id] = obj
+ return new_id
+
+
+def CopyFileIfChanged(source_path, target_path):
+ """Copy |source_path| to |target_path| is different."""
+ target_dir = os.path.dirname(target_path)
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+ if not os.path.exists(target_path) or \
+ not filecmp.cmp(source_path, target_path):
+ shutil.copyfile(source_path, target_path)
+
+
+def LoadXcodeProjectAsJSON(path):
+ """Return Xcode project at |path| as a JSON string."""
+ return subprocess.check_output([
+ 'plutil', '-convert', 'json', '-o', '-', path])
+
+
+def WriteXcodeProject(output_path, json_string):
+ """Save Xcode project to |output_path| as XML."""
+ with tempfile.NamedTemporaryFile() as temp_file:
+ temp_file.write(json_string)
+ temp_file.flush()
+ subprocess.check_call(['plutil', '-convert', 'xml1', temp_file.name])
+ CopyFileIfChanged(temp_file.name, output_path)
+
+
+def UpdateProductsProject(file_input, file_output, configurations):
+ """Update Xcode project to support multiple configurations.
+
+ Args:
+ file_input: path to the input Xcode project
+ file_output: path to the output file
+ configurations: list of string corresponding to the configurations that
+ need to be supported by the tweaked Xcode projects, must contains at
+ least one value.
+ """
+ json_data = json.loads(LoadXcodeProjectAsJSON(file_input))
+ project = XcodeProject(json_data['objects'])
+
+ objects_to_remove = []
+ for value in project.objects.values():
+ isa = value['isa']
+
+ # TODO(crbug.com/619072): gn does not write the min deployment target in the
+ # generated Xcode project, so add it while doing the conversion, only if it
+ # is not present. Remove this code and comment once the bug is fixed and gn
+ # has rolled past it.
+ if isa == 'XCBuildConfiguration':
+ build_settings = value['buildSettings']
+ if 'IPHONEOS_DEPLOYMENT_TARGET' not in build_settings:
+ build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
+
+ # Remove path name key and change path to basename.
+ if isa == 'PBXFileReference':
+ if 'name' in value:
+ del value['name']
+ value['path'] = os.path.basename(value['path'])
+
+ # Teach build shell script to look for the configuration and platform.
+ if isa == 'PBXShellScriptBuildPhase':
+ value['shellScript'] = value['shellScript'].replace(
+ 'ninja -C .',
+ 'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
+
+ # Configure BUNDLE_LOADER and TEST_HOST for xctest target (assuming that
+ # the host is named "${target}_host") unless gn has already configured
+ # them.
+ if isa == 'PBXNativeTarget' and value['productType'] == XCTEST_PRODUCT_TYPE:
+ configuration_list = project.objects[value['buildConfigurationList']]
+ for config_name in configuration_list['buildConfigurations']:
+ config = project.objects[config_name]
+ if not config['buildSettings'].get('BUNDLE_LOADER'):
+ config['buildSettings']['BUNDLE_LOADER'] = '$(TEST_HOST)'
+ config['buildSettings']['TEST_HOST'] = \
+ '${BUILT_PRODUCTS_DIR}/%(name)s_host.app/%(name)s' % value
+
+ # Add new configuration, using the first one as default.
+ if isa == 'XCConfigurationList':
+ value['defaultConfigurationName'] = configurations[0]
+ objects_to_remove.extend(value['buildConfigurations'])
+
+ build_config_template = project.objects[value['buildConfigurations'][0]]
+ build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
+ '../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
+
+ value['buildConfigurations'] = []
+ for configuration in configurations:
+ new_build_config = copy.copy(build_config_template)
+ new_build_config['name'] = configuration
+ value['buildConfigurations'].append(
+ project.AddObject('products', new_build_config))
+
+ for object_id in objects_to_remove:
+ del project.objects[object_id]
+
+ objects = collections.OrderedDict(sorted(project.objects.iteritems()))
+ WriteXcodeProject(file_output, json.dumps(json_data))
+
+
+def ConvertGnXcodeProject(input_dir, output_dir, configurations):
+ '''Tweak the Xcode project generated by gn to support multiple configurations.
+
+ The Xcode projects generated by "gn gen --ide" only supports a single
+ platform and configuration (as the platform and configuration are set
+ per output directory). This method takes as input such projects and
+ add support for multiple configurations and platforms (to allow devs
+ to select them in Xcode).
+
+ Args:
+ input_dir: directory containing the XCode projects created by "gn gen --ide"
+ output_dir: directory where the tweaked Xcode projects will be saved
+ configurations: list of string corresponding to the configurations that
+ need to be supported by the tweaked Xcode projects, must contains at
+ least one value.
+ '''
+ # Update products project.
+ products = os.path.join('products.xcodeproj', 'project.pbxproj')
+ product_input = os.path.join(input_dir, products)
+ product_output = os.path.join(output_dir, products)
+ UpdateProductsProject(product_input, product_output, configurations)
+
+ # Copy sources project and all workspace.
+ sources = os.path.join('sources.xcodeproj', 'project.pbxproj')
+ CopyFileIfChanged(os.path.join(input_dir, sources),
+ os.path.join(output_dir, sources))
+ xcwspace = os.path.join('all.xcworkspace', 'contents.xcworkspacedata')
+ CopyFileIfChanged(os.path.join(input_dir, xcwspace),
+ os.path.join(output_dir, xcwspace))
+
+
+def Main(args):
+ parser = argparse.ArgumentParser(
+ description='Convert GN Xcode projects for iOS.')
+ parser.add_argument(
+ 'input',
+ help='directory containing [product|sources|all] Xcode projects.')
+ parser.add_argument(
+ 'output',
+ help='directory where to generate the iOS configuration.')
+ parser.add_argument(
+ '--add-config', dest='configurations', default=[], action='append',
+ help='configuration to add to the Xcode project')
+ args = parser.parse_args(args)
+
+ if not os.path.isdir(args.input):
+ sys.stderr.write('Input directory does not exists.\n')
+ return 1
+
+ required = set(['products.xcodeproj', 'sources.xcodeproj', 'all.xcworkspace'])
+ if not required.issubset(os.listdir(args.input)):
+ sys.stderr.write(
+ 'Input directory does not contain all necessary Xcode projects.\n')
+ return 1
+
+ if not args.configurations:
+ sys.stderr.write('At least one configuration required, see --add-config.\n')
+ return 1
+
+ ConvertGnXcodeProject(args.input, args.output, args.configurations)
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
+
+
« no previous file with comments | « docs/ios_build_instructions.md ('k') | ios/build/tools/setup-gn.config » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698