| OLD | NEW | 
 | (Empty) | 
|    1 # Copyright 2014 The Chromium Authors. 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 import fnmatch |  | 
|    6 import imp |  | 
|    7 import logging |  | 
|    8 import optparse |  | 
|    9 import os |  | 
|   10 import sys |  | 
|   11 import zipfile |  | 
|   12  |  | 
|   13 from telemetry import benchmark |  | 
|   14 from telemetry.core import discover |  | 
|   15 from telemetry.internal.util import command_line |  | 
|   16 from telemetry.internal.util import path |  | 
|   17 from telemetry.internal.util import path_set |  | 
|   18  |  | 
|   19 try: |  | 
|   20   from modulegraph import modulegraph  # pylint: disable=import-error |  | 
|   21 except ImportError as err: |  | 
|   22   modulegraph = None |  | 
|   23   import_error = err |  | 
|   24  |  | 
|   25 from core import bootstrap |  | 
|   26 from core import path_util |  | 
|   27  |  | 
|   28 DEPS_FILE = 'bootstrap_deps' |  | 
|   29  |  | 
|   30  |  | 
|   31 def FindBootstrapDependencies(base_dir): |  | 
|   32   deps_file = os.path.join(base_dir, DEPS_FILE) |  | 
|   33   if not os.path.exists(deps_file): |  | 
|   34     return [] |  | 
|   35   deps_paths = bootstrap.ListAllDepsPaths(deps_file) |  | 
|   36   return set(os.path.realpath(os.path.join( |  | 
|   37       path_util.GetChromiumSrcDir(), '..', deps_path)) |  | 
|   38              for deps_path in deps_paths) |  | 
|   39  |  | 
|   40  |  | 
|   41 def FindPythonDependencies(module_path): |  | 
|   42   logging.info('Finding Python dependencies of %s', module_path) |  | 
|   43   if modulegraph is None: |  | 
|   44     raise import_error |  | 
|   45  |  | 
|   46   sys_path = sys.path |  | 
|   47   sys.path = list(sys_path) |  | 
|   48   try: |  | 
|   49     # Load the module to inherit its sys.path modifications. |  | 
|   50     sys.path.insert(0, os.path.abspath(os.path.dirname(module_path))) |  | 
|   51     imp.load_source( |  | 
|   52         os.path.splitext(os.path.basename(module_path))[0], module_path) |  | 
|   53  |  | 
|   54     # Analyze the module for its imports. |  | 
|   55     graph = modulegraph.ModuleGraph() |  | 
|   56     graph.run_script(module_path) |  | 
|   57  |  | 
|   58     # Filter for only imports in Chromium. |  | 
|   59     for node in graph.nodes(): |  | 
|   60       if not node.filename: |  | 
|   61         continue |  | 
|   62       module_path = os.path.realpath(node.filename) |  | 
|   63  |  | 
|   64       _, incoming_edges = graph.get_edges(node) |  | 
|   65       message = 'Discovered %s (Imported by: %s)' % ( |  | 
|   66           node.filename, ', '.join( |  | 
|   67               d.filename for d in incoming_edges |  | 
|   68               if d is not None and d.filename is not None)) |  | 
|   69       logging.info(message) |  | 
|   70  |  | 
|   71       # This check is done after the logging/printing above to make sure that |  | 
|   72       # we also print out the dependency edges that include python packages |  | 
|   73       # that are not in chromium. |  | 
|   74       if not path.IsSubpath(module_path, path_util.GetChromiumSrcDir()): |  | 
|   75         continue |  | 
|   76  |  | 
|   77       yield module_path |  | 
|   78       if node.packagepath is not None: |  | 
|   79         for p in node.packagepath: |  | 
|   80           yield p |  | 
|   81  |  | 
|   82   finally: |  | 
|   83     sys.path = sys_path |  | 
|   84  |  | 
|   85  |  | 
|   86 def FindPageSetDependencies(base_dir): |  | 
|   87   logging.info('Finding page sets in %s', base_dir) |  | 
|   88  |  | 
|   89   # Add base_dir to path so our imports relative to base_dir will work. |  | 
|   90   sys.path.append(base_dir) |  | 
|   91   tests = discover.DiscoverClasses(base_dir, base_dir, benchmark.Benchmark, |  | 
|   92                                    index_by_class_name=True) |  | 
|   93  |  | 
|   94   for test_class in tests.itervalues(): |  | 
|   95     test_obj = test_class() |  | 
|   96  |  | 
|   97     # Ensure the test's default options are set if needed. |  | 
|   98     parser = optparse.OptionParser() |  | 
|   99     test_obj.AddCommandLineArgs(parser, None) |  | 
|  100     options = optparse.Values() |  | 
|  101     for k, v in parser.get_default_values().__dict__.iteritems(): |  | 
|  102       options.ensure_value(k, v) |  | 
|  103  |  | 
|  104     # Page set paths are relative to their runner script, not relative to us. |  | 
|  105     path.GetBaseDir = lambda: base_dir |  | 
|  106     # TODO: Loading the page set will automatically download its Cloud Storage |  | 
|  107     # deps. This is really expensive, and we don't want to do this by default. |  | 
|  108     story_set = test_obj.CreateStorySet(options) |  | 
|  109  |  | 
|  110     # Add all of its serving_dirs as dependencies. |  | 
|  111     for serving_dir in story_set.serving_dirs: |  | 
|  112       yield serving_dir |  | 
|  113  |  | 
|  114  |  | 
|  115 def FindExcludedFiles(files, options): |  | 
|  116   # Define some filters for files. |  | 
|  117   def IsHidden(path_string): |  | 
|  118     for pathname_component in path_string.split(os.sep): |  | 
|  119       if pathname_component.startswith('.'): |  | 
|  120         return True |  | 
|  121     return False |  | 
|  122  |  | 
|  123   def IsPyc(path_string): |  | 
|  124     return os.path.splitext(path_string)[1] == '.pyc' |  | 
|  125  |  | 
|  126   def IsInCloudStorage(path_string): |  | 
|  127     return os.path.exists(path_string + '.sha1') |  | 
|  128  |  | 
|  129   def MatchesExcludeOptions(path_string): |  | 
|  130     for pattern in options.exclude: |  | 
|  131       if (fnmatch.fnmatch(path_string, pattern) or |  | 
|  132           fnmatch.fnmatch(os.path.basename(path_string), pattern)): |  | 
|  133         return True |  | 
|  134     return False |  | 
|  135  |  | 
|  136   # Collect filters we're going to use to exclude files. |  | 
|  137   exclude_conditions = [ |  | 
|  138       IsHidden, |  | 
|  139       IsPyc, |  | 
|  140       IsInCloudStorage, |  | 
|  141       MatchesExcludeOptions, |  | 
|  142   ] |  | 
|  143  |  | 
|  144   # Check all the files against the filters. |  | 
|  145   for file_path in files: |  | 
|  146     if any(condition(file_path) for condition in exclude_conditions): |  | 
|  147       yield file_path |  | 
|  148  |  | 
|  149  |  | 
|  150 def FindDependencies(target_paths, options): |  | 
|  151   # Verify arguments. |  | 
|  152   for target_path in target_paths: |  | 
|  153     if not os.path.exists(target_path): |  | 
|  154       raise ValueError('Path does not exist: %s' % target_path) |  | 
|  155  |  | 
|  156   dependencies = path_set.PathSet() |  | 
|  157  |  | 
|  158   # Including Telemetry's major entry points will (hopefully) include Telemetry |  | 
|  159   # and all its dependencies. If the user doesn't pass any arguments, we just |  | 
|  160   # have Telemetry. |  | 
|  161   dependencies |= FindPythonDependencies(os.path.realpath( |  | 
|  162       os.path.join(path_util.GetTelemetryDir(), |  | 
|  163                    'telemetry', 'benchmark_runner.py'))) |  | 
|  164   dependencies |= FindPythonDependencies(os.path.realpath( |  | 
|  165       os.path.join(path_util.GetTelemetryDir(), |  | 
|  166                    'telemetry', 'testing', 'run_tests.py'))) |  | 
|  167  |  | 
|  168   # Add dependencies. |  | 
|  169   for target_path in target_paths: |  | 
|  170     base_dir = os.path.dirname(os.path.realpath(target_path)) |  | 
|  171  |  | 
|  172     dependencies.add(base_dir) |  | 
|  173     dependencies |= FindBootstrapDependencies(base_dir) |  | 
|  174     dependencies |= FindPythonDependencies(target_path) |  | 
|  175     if options.include_page_set_data: |  | 
|  176       dependencies |= FindPageSetDependencies(base_dir) |  | 
|  177  |  | 
|  178   # Remove excluded files. |  | 
|  179   dependencies -= FindExcludedFiles(set(dependencies), options) |  | 
|  180  |  | 
|  181   return dependencies |  | 
|  182  |  | 
|  183  |  | 
|  184 def ZipDependencies(target_paths, dependencies, options): |  | 
|  185   base_dir = os.path.dirname(os.path.realpath(path_util.GetChromiumSrcDir())) |  | 
|  186  |  | 
|  187   with zipfile.ZipFile(options.zip, 'w', zipfile.ZIP_DEFLATED) as zip_file: |  | 
|  188     # Add dependencies to archive. |  | 
|  189     for dependency_path in dependencies: |  | 
|  190       path_in_archive = os.path.join( |  | 
|  191           'telemetry', os.path.relpath(dependency_path, base_dir)) |  | 
|  192       zip_file.write(dependency_path, path_in_archive) |  | 
|  193  |  | 
|  194     # Add symlinks to executable paths, for ease of use. |  | 
|  195     for target_path in target_paths: |  | 
|  196       link_info = zipfile.ZipInfo( |  | 
|  197           os.path.join('telemetry', os.path.basename(target_path))) |  | 
|  198       link_info.create_system = 3  # Unix attributes. |  | 
|  199       # 010 is regular file, 0111 is the permission bits rwxrwxrwx. |  | 
|  200       link_info.external_attr = 0100777 << 16  # Octal. |  | 
|  201  |  | 
|  202       relative_path = os.path.relpath(target_path, base_dir) |  | 
|  203       link_script = ( |  | 
|  204           '#!/usr/bin/env python\n\n' |  | 
|  205           'import os\n' |  | 
|  206           'import sys\n\n\n' |  | 
|  207           'script = os.path.join(os.path.dirname(__file__), \'%s\')\n' |  | 
|  208           'os.execv(sys.executable, [sys.executable, script] + sys.argv[1:])' |  | 
|  209           % relative_path) |  | 
|  210  |  | 
|  211       zip_file.writestr(link_info, link_script) |  | 
|  212  |  | 
|  213  |  | 
|  214 class FindDependenciesCommand(command_line.OptparseCommand): |  | 
|  215   """Prints all dependencies""" |  | 
|  216  |  | 
|  217   @classmethod |  | 
|  218   def AddCommandLineArgs(cls, parser, _): |  | 
|  219     parser.add_option( |  | 
|  220         '-v', '--verbose', action='count', dest='verbosity', |  | 
|  221         help='Increase verbosity level (repeat as needed).') |  | 
|  222  |  | 
|  223     parser.add_option( |  | 
|  224         '-p', '--include-page-set-data', action='store_true', default=False, |  | 
|  225         help='Scan tests for page set data and include them.') |  | 
|  226  |  | 
|  227     parser.add_option( |  | 
|  228         '-e', '--exclude', action='append', default=[], |  | 
|  229         help='Exclude paths matching EXCLUDE. Can be used multiple times.') |  | 
|  230  |  | 
|  231     parser.add_option( |  | 
|  232         '-z', '--zip', |  | 
|  233         help='Store files in a zip archive at ZIP.') |  | 
|  234  |  | 
|  235   @classmethod |  | 
|  236   def ProcessCommandLineArgs(cls, parser, args, _): |  | 
|  237     if args.verbosity >= 2: |  | 
|  238       logging.getLogger().setLevel(logging.DEBUG) |  | 
|  239     elif args.verbosity: |  | 
|  240       logging.getLogger().setLevel(logging.INFO) |  | 
|  241     else: |  | 
|  242       logging.getLogger().setLevel(logging.WARNING) |  | 
|  243  |  | 
|  244   def Run(self, args): |  | 
|  245     target_paths = args.positional_args |  | 
|  246     dependencies = FindDependencies(target_paths, args) |  | 
|  247     if args.zip: |  | 
|  248       ZipDependencies(target_paths, dependencies, args) |  | 
|  249       print 'Zip archive written to %s.' % args.zip |  | 
|  250     else: |  | 
|  251       print '\n'.join(sorted(dependencies)) |  | 
|  252     return 0 |  | 
| OLD | NEW |