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

Unified Diff: ios/build/tools/setup-gn.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 | « ios/build/tools/setup-gn.config ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/build/tools/setup-gn.py
diff --git a/ios/build/tools/setup-gn.py b/ios/build/tools/setup-gn.py
new file mode 100755
index 0000000000000000000000000000000000000000..1453fa546bc4aa72e862bdd9d5ad3ebaa4865ba9
--- /dev/null
+++ b/ios/build/tools/setup-gn.py
@@ -0,0 +1,346 @@
+#!/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.
+
+import argparse
+import convert_gn_xcodeproj
+import errno
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import ConfigParser
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+
+SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator')
+SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official')
+
+
+class ConfigParserWithStringInterpolation(ConfigParser.SafeConfigParser):
+
+ '''A .ini file parser that supports strings and environment variables.'''
+
+ ENV_VAR_PATTERN = re.compile('\$([A-Za-z0-9_]+)')
+
+ def values(self, section):
+ return map(
+ lambda (k, v): self._UnquoteString(self._ExpandEnvVar(v)),
+ ConfigParser.SafeConfigParser.items(self, section))
+
+ def getstring(self, section, option):
+ return self._UnquoteString(self._ExpandEnvVar(self.get(section, option)))
+
+ def _UnquoteString(self, string):
+ if not string or string[0] != '"' or string[-1] != '"':
+ return string
+ return string[1:-1]
+
+ def _ExpandEnvVar(self, value):
+ match = self.ENV_VAR_PATTERN.search(value)
+ if not match:
+ return value
+ name, (begin, end) = match.group(1), match.span(0)
+ prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
+ return prefix + os.environ.get(name, '') + suffix
+
+class GnGenerator(object):
+
+ '''Holds configuration for a build and method to generate gn default files.'''
+
+ FAT_BUILD_DEFAULT_ARCH = '64-bit'
+
+ TARGET_CPU_VALUES = {
+ 'iphoneos': {
+ '32-bit': '"arm"',
+ '64-bit': '"arm64"',
+ },
+ 'iphonesimulator': {
+ '32-bit': '"x86"',
+ '64-bit': '"x64"',
+ }
+ }
+
+ def __init__(self, settings, config, target):
+ assert target in SUPPORTED_TARGETS
+ assert config in SUPPORTED_CONFIGS
+ self._settings = settings
+ self._config = config
+ self._target = target
+
+ def _GetGnArgs(self):
+ """Build the list of arguments to pass to gn.
+
+ Returns:
+ A list of tuple containing gn variable names and variable values (it
+ is not a dictionary as the order needs to be preserved).
+ """
+ args = []
+
+ if self._settings.getboolean('goma', 'enabled'):
+ args.append(('use_goma', True))
+ goma_dir = self._settings.getstring('goma', 'install')
+ if goma_dir:
+ args.append(('goma_dir', '"%s"' % os.path.expanduser(goma_dir)))
+
+ args.append(('is_debug', self._config == 'Debug'))
+ args.append(('enable_dsyms', self._config in ('Profile', 'Official')))
+ args.append(('enable_stripping', 'enable_dsyms'))
+ args.append(('is_official_build', self._config == 'Official'))
+ args.append(('is_chrome_branded', 'is_official_build'))
+ args.append(('use_xcode_clang', 'is_official_build'))
+
+ cpu_values = self.TARGET_CPU_VALUES[self._target]
+ build_arch = self._settings.getstring('build', 'arch')
+ if build_arch == 'fat':
+ target_cpu = cpu_values[self.FAT_BUILD_DEFAULT_ARCH]
+ args.append(('target_cpu', target_cpu))
+ args.append(('additional_target_cpus',
+ [cpu for cpu in cpu_values.itervalues() if cpu != target_cpu]))
+ else:
+ args.append(('target_cpu', cpu_values[build_arch]))
+
+ # Add user overrides after the other configurations so that they can
+ # refer to them and override them.
+ args.extend(self._settings.items('gn_args'))
+ return args
+
+
+ def Generate(self, gn_path, root_path, out_path):
+ buf = StringIO.StringIO()
+ self.WriteArgsGn(buf)
+ WriteToFileIfChanged(
+ os.path.join(out_path, 'args.gn'),
+ buf.getvalue(),
+ overwrite=True)
+
+ subprocess.check_call(
+ self.GetGnCommand(gn_path, root_path, out_path, True))
+
+ def CreateGnRules(self, gn_path, root_path, out_path):
+ buf = StringIO.StringIO()
+ self.WriteArgsGn(buf)
+ WriteToFileIfChanged(
+ os.path.join(out_path, 'args.gn'),
+ buf.getvalue(),
+ overwrite=True)
+
+ buf = StringIO.StringIO()
+ gn_command = self.GetGnCommand(gn_path, root_path, out_path, False)
+ self.WriteBuildNinja(buf, gn_command)
+ WriteToFileIfChanged(
+ os.path.join(out_path, 'build.ninja'),
+ buf.getvalue(),
+ overwrite=False)
+
+ buf = StringIO.StringIO()
+ self.WriteBuildNinjaDeps(buf)
+ WriteToFileIfChanged(
+ os.path.join(out_path, 'build.ninja.d'),
+ buf.getvalue(),
+ overwrite=False)
+
+ def WriteArgsGn(self, stream):
+ stream.write('# This file was generated by setup-gn.py. Do not edit\n')
+ stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
+ stream.write('# to configure settings.\n')
+ stream.write('\n')
+
+ if self._settings.has_section('$imports$'):
+ for import_rule in self._settings.values('$imports$'):
+ stream.write('import("%s")\n' % import_rule)
+ stream.write('\n')
+
+ gn_args = self._GetGnArgs()
+ for name, value in gn_args:
+ if isinstance(value, bool):
+ stream.write('%s = %s\n' % (name, str(value).lower()))
+ elif isinstance(value, list):
+ stream.write('%s = [%s' % (name, '\n' if len(value) else ''))
+ if len(value) == 1:
+ prefix = ' '
+ suffix = ' '
+ else:
+ prefix = ' '
+ suffix = ',\n'
+ for item in value:
+ if isinstance(item, bool):
+ stream.write('%s%s%s' % (prefix, str(item).lower(), suffix))
+ else:
+ stream.write('%s%s%s' % (prefix, item, suffix))
+ stream.write(']\n')
+ else:
+ stream.write('%s = %s\n' % (name, value))
+
+ def WriteBuildNinja(self, stream, gn_command):
+ stream.write('rule gn\n')
+ stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command))
+ stream.write(' description = Regenerating ninja files\n')
+ stream.write('\n')
+ stream.write('build build.ninja: gn\n')
+ stream.write(' generator = 1\n')
+ stream.write(' depfile = build.ninja.d\n')
+
+ def WriteBuildNinjaDeps(self, stream):
+ stream.write('build.ninja: nonexistant_file.gn\n')
+
+ def GetGnCommand(self, gn_path, src_path, out_path, generate_xcode_project):
+ gn_command = [ gn_path, '--root=%s' % os.path.realpath(src_path), '-q' ]
+ if generate_xcode_project:
+ gn_command.append('--ide=xcode')
+ gn_command.append('--root-target=gn_all')
+ if self._settings.getboolean('goma', 'enabled'):
+ ninja_jobs = self._settings.getint('xcode', 'jobs') or 200
+ gn_command.append('--ninja-extra-args=-j%s' % ninja_jobs)
+ if self._settings.has_section('filters'):
+ target_filters = self._settings.values('filters')
+ if target_filters:
+ gn_command.append('--filters=%s' % ';'.join(target_filters))
+ else:
+ gn_command.append('--check')
+ gn_command.append('gen')
+ gn_command.append('//%s' %
+ os.path.relpath(os.path.abspath(out_path), os.path.abspath(src_path)))
+ return gn_command
+
+
+def WriteToFileIfChanged(filename, content, overwrite):
+ '''Write |content| to |filename| if different. If |overwrite| is False
+ and the file already exists it is left untouched.'''
+ if os.path.exists(filename):
+ if not overwrite:
+ return
+ with open(filename) as file:
+ if file.read() == content:
+ return
+ if not os.path.isdir(os.path.dirname(filename)):
+ os.makedirs(os.path.dirname(filename))
+ with open(filename, 'w') as file:
+ file.write(content)
+
+
+def NinjaNeedEscape(arg):
+ '''Returns True if |arg| need to be escaped when writen to .ninja file.'''
+ return ':' in arg or '*' in arg or ';' in arg
+
+
+def NinjaEscapeCommand(command):
+ '''Escapes |command| in order to write it to .ninja file.'''
+ result = []
+ for arg in command:
+ if NinjaNeedEscape(arg):
+ arg = arg.replace(':', '$:')
+ arg = arg.replace(';', '\\;')
+ arg = arg.replace('*', '\\*')
+ else:
+ result.append(arg)
+ return ' '.join(result)
+
+
+def FindGn():
+ '''Returns absolute path to gn binary looking at the PATH env variable.'''
+ for path in os.environ['PATH'].split(os.path.pathsep):
+ gn_path = os.path.join(path, 'gn')
+ if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
+ return gn_path
+ return None
+
+
+def GenerateXcodeProject(gn_path, root_dir, out_dir, settings):
+ '''Convert GN generated Xcode project into multi-configuration Xcode
+ project.'''
+
+ temp_path = tempfile.mkdtemp(prefix=os.path.abspath(
+ os.path.join(out_dir, '_temp')))
+ try:
+ generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
+ generator.Generate(gn_path, root_dir, temp_path)
+ convert_gn_xcodeproj.ConvertGnXcodeProject(
+ os.path.join(temp_path),
+ os.path.join(out_dir, 'build'),
+ SUPPORTED_CONFIGS)
+ finally:
+ if os.path.exists(temp_path):
+ shutil.rmtree(temp_path)
+
+
+def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings):
+ '''Generates all template configurations for gn.'''
+ for config in SUPPORTED_CONFIGS:
+ for target in SUPPORTED_TARGETS:
+ build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
+ generator = GnGenerator(settings, config, target)
+ generator.CreateGnRules(gn_path, root_dir, build_dir)
+
+
+def Main(args):
+ default_root = os.path.normpath(os.path.join(
+ os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))
+
+ parser = argparse.ArgumentParser(
+ description='Generate build directories for use with gn.')
+ parser.add_argument(
+ 'root', default=default_root, nargs='?',
+ help='root directory where to generate multiple out configurations')
+ parser.add_argument(
+ '--import', action='append', dest='import_rules', default=[],
+ help='path to file defining default gn variables')
+ args = parser.parse_args(args)
+
+ # Load configuration (first global and then any user overrides).
+ settings = ConfigParserWithStringInterpolation()
+ settings.read([
+ os.path.splitext(__file__)[0] + '.config',
+ os.path.expanduser('~/.setup-gn'),
+ ])
+
+ # Add private sections corresponding to --import argument.
+ if args.import_rules:
+ settings.add_section('$imports$')
+ for i, import_rule in enumerate(args.import_rules):
+ if not import_rule.startswith('//'):
+ import_rule = '//%s' % os.path.relpath(
+ os.path.abspath(import_rule), os.path.abspath(args.root))
+ settings.set('$imports$', '$rule%d$' % i, import_rule)
+
+ # Validate settings.
+ if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
+ sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
+ settings.getstring('build', 'arch'))
+ sys.exit(1)
+
+ if settings.getboolean('goma', 'enabled'):
+ if settings.getint('xcode', 'jobs') < 0:
+ sys.stderr.write('ERROR: invalid value for xcode.jobs: %s\n' %
+ settings.get('xcode', 'jobs'))
+ sys.exit(1)
+ goma_install = os.path.expanduser(settings.getstring('goma', 'install'))
+ if not os.path.isdir(goma_install):
+ sys.stderr.write('WARNING: goma.install directory not found: %s\n' %
+ settings.get('goma', 'install'))
+ sys.stderr.write('WARNING: disabling goma\n')
+ settings.set('goma', 'enabled', 'false')
+
+ # Find gn binary in PATH.
+ gn_path = FindGn()
+ if gn_path is None:
+ sys.stderr.write('ERROR: cannot find gn in PATH\n')
+ sys.exit(1)
+
+ out_dir = os.path.join(args.root, 'out')
+ if not os.path.isdir(out_dir):
+ os.makedirs(out_dir)
+
+ GenerateXcodeProject(gn_path, args.root, out_dir, settings)
+ GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
« no previous file with comments | « ios/build/tools/setup-gn.config ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698