Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / gyp / pylib / gyp / xcode_ninja.py
1 # Copyright (c) 2014 Google Inc. 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 """Xcode-ninja wrapper project file generator.
6
7 This updates the data structures passed to the Xcode gyp generator to build
8 with ninja instead. The Xcode project itself is transformed into a list of
9 executable targets, each with a build step to build with ninja, and a target
10 with every source and resource file.  This appears to sidestep some of the
11 major performance headaches experienced using complex projects and large number
12 of targets within Xcode.
13 """
14
15 import errno
16 import gyp.generator.ninja
17 import os
18 import re
19 import xml.sax.saxutils
20
21
22 def _WriteWorkspace(main_gyp, sources_gyp):
23   """ Create a workspace to wrap main and sources gyp paths. """
24   (build_file_root, build_file_ext) = os.path.splitext(main_gyp)
25   workspace_path = build_file_root + '.xcworkspace'
26   try:
27     os.makedirs(workspace_path)
28   except OSError, e:
29     if e.errno != errno.EEXIST:
30       raise
31   output_string = '<?xml version="1.0" encoding="UTF-8"?>\n' + \
32                   '<Workspace version = "1.0">\n'
33   for gyp_name in [main_gyp, sources_gyp]:
34     name = os.path.splitext(os.path.basename(gyp_name))[0] + '.xcodeproj'
35     name = xml.sax.saxutils.quoteattr("group:" + name)
36     output_string += '  <FileRef location = %s></FileRef>\n' % name
37   output_string += '</Workspace>\n'
38
39   workspace_file = os.path.join(workspace_path, "contents.xcworkspacedata")
40
41   try:
42     with open(workspace_file, 'r') as input_file:
43       input_string = input_file.read()
44       if input_string == output_string:
45         return
46   except IOError:
47     # Ignore errors if the file doesn't exist.
48     pass
49
50   with open(workspace_file, 'w') as output_file:
51     output_file.write(output_string)
52
53 def _TargetFromSpec(old_spec, params):
54   """ Create fake target for xcode-ninja wrapper. """
55   # Determine ninja top level build dir (e.g. /path/to/out).
56   ninja_toplevel = None
57   jobs = 0
58   if params:
59     options = params['options']
60     ninja_toplevel = \
61         os.path.join(options.toplevel_dir,
62                      gyp.generator.ninja.ComputeOutputDir(params))
63     jobs = params.get('generator_flags', {}).get('xcode_ninja_jobs', 0)
64
65   target_name = old_spec.get('target_name')
66   product_name = old_spec.get('product_name', target_name)
67
68   ninja_target = {}
69   ninja_target['target_name'] = target_name
70   ninja_target['product_name'] = product_name
71   ninja_target['toolset'] = old_spec.get('toolset')
72   ninja_target['default_configuration'] = old_spec.get('default_configuration')
73   ninja_target['configurations'] = {}
74
75   # Tell Xcode to look in |ninja_toplevel| for build products.
76   new_xcode_settings = {}
77   if ninja_toplevel:
78     new_xcode_settings['CONFIGURATION_BUILD_DIR'] = \
79         "%s/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)" % ninja_toplevel
80
81   if 'configurations' in old_spec:
82     for config in old_spec['configurations'].iterkeys():
83       old_xcode_settings = old_spec['configurations'][config]['xcode_settings']
84       if 'IPHONEOS_DEPLOYMENT_TARGET' in old_xcode_settings:
85         new_xcode_settings['CODE_SIGNING_REQUIRED'] = "NO"
86         new_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] = \
87             old_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET']
88       ninja_target['configurations'][config] = {}
89       ninja_target['configurations'][config]['xcode_settings'] = \
90           new_xcode_settings
91
92   ninja_target['mac_bundle'] = old_spec.get('mac_bundle', 0)
93   ninja_target['type'] = old_spec['type']
94   if ninja_toplevel:
95     ninja_target['actions'] = [
96       {
97         'action_name': 'Compile and copy %s via ninja' % target_name,
98         'inputs': [],
99         'outputs': [],
100         'action': [
101           'env',
102           'PATH=%s' % os.environ['PATH'],
103           'ninja',
104           '-C',
105           new_xcode_settings['CONFIGURATION_BUILD_DIR'],
106           target_name,
107         ],
108         'message': 'Compile and copy %s via ninja' % target_name,
109       },
110     ]
111     if jobs > 0:
112       ninja_target['actions'][0]['action'].extend(('-j', jobs))
113   return ninja_target
114
115 def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
116   """Limit targets for Xcode wrapper.
117
118   Xcode sometimes performs poorly with too many targets, so only include
119   proper executable targets, with filters to customize.
120   Arguments:
121     target_extras: Regular expression to always add, matching any target.
122     executable_target_pattern: Regular expression limiting executable targets.
123     spec: Specifications for target.
124   """
125   target_name = spec.get('target_name')
126   # Always include targets matching target_extras.
127   if target_extras is not None and re.search(target_extras, target_name):
128     return True
129
130   # Otherwise just show executable targets.
131   if spec.get('type', '') == 'executable' and \
132      spec.get('product_extension', '') != 'bundle':
133
134     # If there is a filter and the target does not match, exclude the target.
135     if executable_target_pattern is not None:
136       if not re.search(executable_target_pattern, target_name):
137         return False
138     return True
139   return False
140
141 def CreateWrapper(target_list, target_dicts, data, params):
142   """Initialize targets for the ninja wrapper.
143
144   This sets up the necessary variables in the targets to generate Xcode projects
145   that use ninja as an external builder.
146   Arguments:
147     target_list: List of target pairs: 'base/base.gyp:base'.
148     target_dicts: Dict of target properties keyed on target pair.
149     data: Dict of flattened build files keyed on gyp path.
150     params: Dict of global options for gyp.
151   """
152   orig_gyp = params['build_files'][0]
153   for gyp_name, gyp_dict in data.iteritems():
154     if gyp_name == orig_gyp:
155       depth = gyp_dict['_DEPTH']
156
157   # Check for custom main gyp name, otherwise use the default CHROMIUM_GYP_FILE
158   # and prepend .ninja before the .gyp extension.
159   generator_flags = params.get('generator_flags', {})
160   main_gyp = generator_flags.get('xcode_ninja_main_gyp', None)
161   if main_gyp is None:
162     (build_file_root, build_file_ext) = os.path.splitext(orig_gyp)
163     main_gyp = build_file_root + ".ninja" + build_file_ext
164
165   # Create new |target_list|, |target_dicts| and |data| data structures.
166   new_target_list = []
167   new_target_dicts = {}
168   new_data = {}
169
170   # Set base keys needed for |data|.
171   new_data[main_gyp] = {}
172   new_data[main_gyp]['included_files'] = []
173   new_data[main_gyp]['targets'] = []
174   new_data[main_gyp]['xcode_settings'] = \
175       data[orig_gyp].get('xcode_settings', {})
176
177   # Normally the xcode-ninja generator includes only valid executable targets.
178   # If |xcode_ninja_executable_target_pattern| is set, that list is reduced to
179   # executable targets that match the pattern. (Default all)
180   executable_target_pattern = \
181       generator_flags.get('xcode_ninja_executable_target_pattern', None)
182
183   # For including other non-executable targets, add the matching target name
184   # to the |xcode_ninja_target_pattern| regular expression. (Default none)
185   target_extras = generator_flags.get('xcode_ninja_target_pattern', None)
186
187   for old_qualified_target in target_list:
188     spec = target_dicts[old_qualified_target]
189     if IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
190       # Add to new_target_list.
191       target_name = spec.get('target_name')
192       new_target_name = '%s:%s#target' % (main_gyp, target_name)
193       new_target_list.append(new_target_name)
194
195       # Add to new_target_dicts.
196       new_target_dicts[new_target_name] = _TargetFromSpec(spec, params)
197
198       # Add to new_data.
199       for old_target in data[old_qualified_target.split(':')[0]]['targets']:
200         if old_target['target_name'] == target_name:
201           new_data_target = {}
202           new_data_target['target_name'] = old_target['target_name']
203           new_data_target['toolset'] = old_target['toolset']
204           new_data[main_gyp]['targets'].append(new_data_target)
205
206   # Create sources target.
207   sources_target_name = 'sources_for_indexing'
208   sources_target = _TargetFromSpec(
209     { 'target_name' : sources_target_name,
210       'toolset': 'target',
211       'default_configuration': 'Default',
212       'mac_bundle': '0',
213       'type': 'executable'
214     }, None)
215
216   # Tell Xcode to look everywhere for headers.
217   sources_target['configurations'] = {'Default': { 'include_dirs': [ depth ] } }
218
219   sources = []
220   for target, target_dict in target_dicts.iteritems():
221     base =  os.path.dirname(target)
222     files = target_dict.get('sources', []) + \
223             target_dict.get('mac_bundle_resources', [])
224     # Remove files starting with $. These are mostly intermediate files for the
225     # build system.
226     files = [ file for file in files if not file.startswith('$')]
227
228     # Make sources relative to root build file.
229     relative_path = os.path.dirname(main_gyp)
230     sources += [ os.path.relpath(os.path.join(base, file), relative_path)
231                     for file in files ]
232
233   sources_target['sources'] = sorted(set(sources))
234
235   # Put sources_to_index in it's own gyp.
236   sources_gyp = \
237       os.path.join(os.path.dirname(main_gyp), sources_target_name + ".gyp")
238   fully_qualified_target_name = \
239       '%s:%s#target' % (sources_gyp, sources_target_name)
240
241   # Add to new_target_list, new_target_dicts and new_data.
242   new_target_list.append(fully_qualified_target_name)
243   new_target_dicts[fully_qualified_target_name] = sources_target
244   new_data_target = {}
245   new_data_target['target_name'] = sources_target['target_name']
246   new_data_target['_DEPTH'] = depth
247   new_data_target['toolset'] = "target"
248   new_data[sources_gyp] = {}
249   new_data[sources_gyp]['targets'] = []
250   new_data[sources_gyp]['included_files'] = []
251   new_data[sources_gyp]['xcode_settings'] = \
252       data[orig_gyp].get('xcode_settings', {})
253   new_data[sources_gyp]['targets'].append(new_data_target)
254
255   # Write workspace to file.
256   _WriteWorkspace(main_gyp, sources_gyp)
257   return (new_target_list, new_target_dicts, new_data)