Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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) | |
|
sky
2014/07/28 21:02:05
I'm not the biggest fan of exceptions in this case
| |
| 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. | |
| 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)}) | |
| OLD | NEW |