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

Side by Side Diff: pylib/gyp/generator/analyzer.py

Issue 420383002: Changes analyzer to search for targets (Closed) Base URL: http://gyp.googlecode.com/svn/trunk
Patch Set: cleanup Created 6 years, 4 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | test/analyzer/gyptest-analyzer.new.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2014 Google Inc. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """ 5 """
6 This script is intended for use as a GYP_GENERATOR. It takes as input (by way of 6 This script is intended for use as a GYP_GENERATOR. It takes as input (by way of
7 the generator flag file_path) the list of relative file paths to consider. If 7 the generator flag config_path) the path of a json file that dictates the files
8 any target has at least one of the paths as a source (or input to an action or 8 and targets to search for. The following keys are supported:
9 rule) then 'Found dependency' is output, otherwise 'No dependencies' is output. 9 files: list of paths (relative) of the files to search for.
10 targets: list of targets to search for. The target names are unqualified.
11
12 The following (as JSON) is output:
13 error: only supplied if there is an error.
14 targets: the set of targets passed in via targets that either directly or
15 indirectly depend upon the set of paths supplied in files.
16 status: indicates if any of the supplied files matched at least one target.
10 """ 17 """
11 18
12 import gyp.common 19 import gyp.common
13 import gyp.ninja_syntax as ninja_syntax 20 import gyp.ninja_syntax as ninja_syntax
21 import json
14 import os 22 import os
15 import posixpath 23 import posixpath
24 import sys
16 25
17 debug = False 26 debug = False
18 27
28 found_dependency_string = 'Found dependency'
29 no_dependency_string = 'No dependencies'
30
31 # MatchStatus is used indicate if and how a target depends upon the supplied
32 # sources.
33 # The target's sources contain one of the supplied paths.
34 MATCH_STATUS_MATCHES = 1
35 # The target has a dependency on another target that contains one of the
36 # supplied paths.
37 MATCH_STATUS_MATCHES_BY_DEPENDENCY = 2
38 # The target's sources weren't in the supplied paths and none of the target's
39 # dependencies depend upon a target that matched.
40 MATCH_STATUS_DOESNT_MATCH = 3
41 # The target doesn't contain the source, but the dependent targets have not yet
42 # been visited to determine a more specific status yet.
43 MATCH_STATUS_TBD = 4
44
19 generator_supports_multiple_toolsets = True 45 generator_supports_multiple_toolsets = True
20 46
21 generator_wants_static_library_dependencies_adjusted = False 47 generator_wants_static_library_dependencies_adjusted = False
22 48
23 generator_default_variables = { 49 generator_default_variables = {
24 } 50 }
25 for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR', 51 for dirname in ['INTERMEDIATE_DIR', 'SHARED_INTERMEDIATE_DIR', 'PRODUCT_DIR',
26 'LIB_DIR', 'SHARED_LIB_DIR']: 52 'LIB_DIR', 'SHARED_LIB_DIR']:
27 generator_default_variables[dirname] = '!!!' 53 generator_default_variables[dirname] = '!!!'
28 54
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 __ExtractSourcesFromAction(action, base_path, base_path_components, 145 __ExtractSourcesFromAction(action, base_path, base_path_components,
120 results) 146 results)
121 if 'rules' in target_dict: 147 if 'rules' in target_dict:
122 for rule in target_dict['rules']: 148 for rule in target_dict['rules']:
123 __ExtractSourcesFromAction(rule, base_path, base_path_components, results) 149 __ExtractSourcesFromAction(rule, base_path, base_path_components, results)
124 150
125 return results 151 return results
126 152
127 class Target(object): 153 class Target(object):
128 """Holds information about a particular target: 154 """Holds information about a particular target:
129 sources: set of source files defined by this target. This includes inputs to 155 deps: set of the names of direct dependent targets.
130 actions and rules. 156 match_staus: one of the MatchStatus values"""
131 deps: list of direct dependencies."""
132 def __init__(self): 157 def __init__(self):
133 self.sources = [] 158 self.deps = set()
134 self.deps = [] 159 self.match_status = MATCH_STATUS_TBD
135 160
136 def __GenerateTargets(target_list, target_dicts, toplevel_dir): 161 class Config(object):
162 """Details what we're looking for
163 look_for_dependency_only: if true only search for a target listing any of
164 the files in files.
165 files: set of files to search for
166 targets: see file description for details"""
167 def __init__(self):
168 self.look_for_dependency_only = True
169 self.files = []
170 self.targets = []
171
172 def Init(self, params):
173 """Initializes Config. This is a separate method as it may raise an
174 exception if there is a parse error."""
175 generator_flags = params.get('generator_flags', {})
176 # TODO(sky): nuke file_path and look_for_dependency_only once migrate
177 # recipes.
178 file_path = generator_flags.get('file_path', None)
179 if file_path:
180 self._InitFromFilePath(file_path)
181 return
182
183 # If |file_path| wasn't specified then we look for config_path.
184 # TODO(sky): always look for config_path once migrated recipes.
185 config_path = generator_flags.get('config_path', None)
186 if not config_path:
187 return
188 self.look_for_dependency_only = False
189 try:
190 f = open(config_path, 'r')
191 config = json.load(f)
192 f.close()
193 except IOError:
194 raise Exception('Unable to open file ' + config_path)
195 except ValueError as e:
196 raise Exception('Unable to parse config file ' + config_path + str(e))
197 if not isinstance(config, dict):
198 raise Exception('config_path must be a JSON file containing a dictionary')
199 self.files = config.get('files', [])
200 # Coalesce duplicates
201 self.targets = list(set(config.get('targets', [])))
202
203 def _InitFromFilePath(self, file_path):
204 try:
205 f = open(file_path, 'r')
206 for file_name in f:
207 if file_name.endswith('\n'):
208 file_name = file_name[0:len(file_name) - 1]
209 if len(file_name):
210 self.files.append(file_name)
211 f.close()
212 except IOError:
213 raise Exception('Unable to open file', file_path)
214
215 def __GenerateTargets(target_list, target_dicts, toplevel_dir, files):
137 """Generates a dictionary with the key the name of a target and the value a 216 """Generates a dictionary with the key the name of a target and the value a
138 Target. |toplevel_dir| is the root of the source tree.""" 217 Target. |toplevel_dir| is the root of the source tree. If the sources of
218 a target match that of |files|, then |target.matched| is set to True.
219 This returns a tuple of the dictionary and whether at least one target's
220 sources listed one of the paths in |files|."""
139 targets = {} 221 targets = {}
140 222
141 # Queue of targets to visit. 223 # Queue of targets to visit.
142 targets_to_visit = target_list[:] 224 targets_to_visit = target_list[:]
143 225
226 matched = False
227
144 while len(targets_to_visit) > 0: 228 while len(targets_to_visit) > 0:
145 target_name = targets_to_visit.pop() 229 target_name = targets_to_visit.pop()
146 if target_name in targets: 230 if target_name in targets:
147 continue 231 continue
148 232
149 target = Target() 233 target = Target()
150 targets[target_name] = target 234 targets[target_name] = target
151 target.sources.extend(__ExtractSources(target_name, 235 sources = __ExtractSources(target_name, target_dicts[target_name],
152 target_dicts[target_name], 236 toplevel_dir)
153 toplevel_dir)) 237 for source in sources:
238 if source in files:
239 target.match_status = MATCH_STATUS_MATCHES
240 matched = True
241 break
154 242
155 for dep in target_dicts[target_name].get('dependencies', []): 243 for dep in target_dicts[target_name].get('dependencies', []):
156 targets[target_name].deps.append(dep) 244 targets[target_name].deps.add(dep)
157 targets_to_visit.append(dep) 245 targets_to_visit.append(dep)
158 246
159 return targets 247 return targets, matched
160 248
161 def __GetFiles(params): 249 def _GetUnqualifiedToQualifiedMapping(all_targets, to_find):
162 """Returns the list of files to analyze, or None if none specified.""" 250 """Returns a mapping (dictionary) from unqualified name to qualified name for
163 generator_flags = params.get('generator_flags', {}) 251 all the targets in |to_find|."""
164 file_path = generator_flags.get('file_path', None) 252 result = {}
165 if not file_path: 253 if not to_find:
166 return None
167 try:
168 f = open(file_path, 'r')
169 result = []
170 for file_name in f:
171 if file_name.endswith('\n'):
172 file_name = file_name[0:len(file_name) - 1]
173 if len(file_name):
174 result.append(file_name)
175 f.close()
176 return result 254 return result
177 except IOError: 255 to_find = set(to_find)
178 print 'Unable to open file', file_path 256 for target_name in all_targets.keys():
179 return None 257 extracted = gyp.common.ParseQualifiedTarget(target_name)
258 if len(extracted) > 1 and extracted[1] in to_find:
259 to_find.remove(extracted[1])
260 result[extracted[1]] = target_name
261 if not to_find:
262 return result
263 return result
264
265 def _DoesTargetDependOn(target, all_targets):
266 """Returns true if |target| or any of its dependencies matches the supplied
267 set of paths. This updates |matches| of the Targets as it recurses.
268 target: the Target to look for.
269 all_targets: mapping from target name to Target.
270 matching_targets: set of targets looking for."""
271 if target.match_status == MATCH_STATUS_DOESNT_MATCH:
272 return False
273 if target.match_status == MATCH_STATUS_MATCHES or \
274 target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY:
275 return True
276 for dep_name in target.deps:
277 dep_target = all_targets[dep_name]
278 if _DoesTargetDependOn(dep_target, all_targets):
279 dep_target.match_status = MATCH_STATUS_MATCHES_BY_DEPENDENCY
280 return True
281 dep_target.match_status = MATCH_STATUS_DOESNT_MATCH
282 return False
283
284 def _GetTargetsDependingOn(all_targets, possible_targets):
285 """Returns the list of targets in |possible_targets| that depend (either
286 directly on indirectly) on the matched files.
287 all_targets: mapping from target name to Target.
288 possible_targets: targets to search from."""
289 found = []
290 for target in possible_targets:
291 if _DoesTargetDependOn(all_targets[target], all_targets):
292 # possible_targets was initially unqualified, keep it unqualified.
293 found.append(gyp.common.ParseQualifiedTarget(target)[1])
294 return found
180 295
181 def CalculateVariables(default_variables, params): 296 def CalculateVariables(default_variables, params):
182 """Calculate additional variables for use in the build (called by gyp).""" 297 """Calculate additional variables for use in the build (called by gyp)."""
183 flavor = gyp.common.GetFlavor(params) 298 flavor = gyp.common.GetFlavor(params)
184 if flavor == 'mac': 299 if flavor == 'mac':
185 default_variables.setdefault('OS', 'mac') 300 default_variables.setdefault('OS', 'mac')
186 elif flavor == 'win': 301 elif flavor == 'win':
187 default_variables.setdefault('OS', 'win') 302 default_variables.setdefault('OS', 'win')
188 # Copy additional generator configuration data from VS, which is shared 303 # Copy additional generator configuration data from VS, which is shared
189 # by the Windows Ninja generator. 304 # by the Windows Ninja generator.
190 import gyp.generator.msvs as msvs_generator 305 import gyp.generator.msvs as msvs_generator
191 generator_additional_non_configuration_keys = getattr(msvs_generator, 306 generator_additional_non_configuration_keys = getattr(msvs_generator,
192 'generator_additional_non_configuration_keys', []) 307 'generator_additional_non_configuration_keys', [])
193 generator_additional_path_sections = getattr(msvs_generator, 308 generator_additional_path_sections = getattr(msvs_generator,
194 'generator_additional_path_sections', []) 309 'generator_additional_path_sections', [])
195 310
196 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params) 311 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
197 else: 312 else:
198 operating_system = flavor 313 operating_system = flavor
199 if flavor == 'android': 314 if flavor == 'android':
200 operating_system = 'linux' # Keep this legacy behavior for now. 315 operating_system = 'linux' # Keep this legacy behavior for now.
201 default_variables.setdefault('OS', operating_system) 316 default_variables.setdefault('OS', operating_system)
202 317
203 def GenerateOutput(target_list, target_dicts, data, params): 318 def GenerateOutput(target_list, target_dicts, data, params):
204 """Called by gyp as the final stage. Outputs results.""" 319 """Called by gyp as the final stage. Outputs results."""
205 files = __GetFiles(params) 320 config = Config()
206 if not files: 321 try:
207 print 'Must specify files to analyze via file_path generator flag' 322 config.Init(params)
208 return 323 if not config.files:
324 if config.look_for_dependency_only:
325 print 'Must specify files to analyze via file_path generator flag'
326 return
327 raise Exception('Must specify files to analyze via config_path generator '
328 'flag')
209 329
210 toplevel_dir = os.path.abspath(params['options'].toplevel_dir) 330 toplevel_dir = os.path.abspath(params['options'].toplevel_dir)
211 if os.sep == '\\' and os.altsep == '/': 331 if os.sep == '\\' and os.altsep == '/':
212 toplevel_dir = toplevel_dir.replace('\\', '/') 332 toplevel_dir = toplevel_dir.replace('\\', '/')
213 if debug: 333 if debug:
214 print 'toplevel_dir', toplevel_dir 334 print 'toplevel_dir', toplevel_dir
215 targets = __GenerateTargets(target_list, target_dicts, toplevel_dir)
216 335
217 files_set = frozenset(files) 336 all_targets, matched = __GenerateTargets(target_list, target_dicts,
218 found_in_all_sources = 0 337 toplevel_dir,
219 for target_name, target in targets.iteritems(): 338 frozenset(config.files))
220 sources = files_set.intersection(target.sources) 339
221 if len(sources): 340 # Set of targets that refer to one of the files.
222 print 'Found dependency' 341 if config.look_for_dependency_only:
223 if debug: 342 print found_dependency_string if matched else no_dependency_string
224 print 'Found dependency in', target_name, target.sources
225 return 343 return
226 344
227 print 'No dependencies' 345 if matched:
346 unqualified_mapping = _GetUnqualifiedToQualifiedMapping(
347 all_targets, config.targets)
348 if len(unqualified_mapping) != len(config.targets):
349 not_found = []
350 for target in config.targets:
351 if not target in unqualified_mapping:
352 not_found.append(target)
353 raise Exception('Unable to find all targets: ' + str(not_found))
354 qualified_targets = [unqualified_mapping[x] for x in config.targets]
355 output_targets = _GetTargetsDependingOn(all_targets, qualified_targets)
356 else:
357 output_targets = []
358
359 print json.dumps(
360 {'targets': output_targets,
361 'status': found_dependency_string if matched else no_dependency_string })
362
363 except Exception as e:
364 print json.dumps({'error': str(e)})
OLDNEW
« no previous file with comments | « no previous file | test/analyzer/gyptest-analyzer.new.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698