Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / isolate.py
index c3b6698..5ae4d01 100755 (executable)
@@ -28,9 +28,6 @@ import isolateserver
 import run_isolated
 import trace_inputs
 
-# Import here directly so isolate is easier to use as a library.
-from run_isolated import get_flavor
-
 from third_party import colorama
 from third_party.depot_tools import fix_encoding
 from third_party.depot_tools import subcommand
@@ -181,12 +178,6 @@ def classify_files(root_dir, tracked, untracked):
              probably be tracked.
   - untracked: list of files names that must not be tracked.
   """
-  # These directories are not guaranteed to be always present on every builder.
-  CHROMIUM_OPTIONAL_DIRECTORIES = (
-    'test/data/plugin',
-    'third_party/WebKit/LayoutTests',
-  )
-
   new_tracked = []
   new_untracked = list(untracked)
 
@@ -198,8 +189,6 @@ def classify_files(root_dir, tracked, untracked):
       return False
     if ' ' in filepath:
       return False
-    if any(i in filepath for i in CHROMIUM_OPTIONAL_DIRECTORIES):
-      return False
     # Look if any element in the path is a symlink.
     split = filepath.split('/')
     for i in range(len(split)):
@@ -224,19 +213,12 @@ def classify_files(root_dir, tracked, untracked):
 
 def chromium_fix(f, variables):
   """Fixes an isolate dependency with Chromium-specific fixes."""
-  # Skip log in PRODUCT_DIR. Note that these are applied on '/' style path
-  # separator.
-  LOG_FILE = re.compile(r'^\<\(PRODUCT_DIR\)\/[^\/]+\.log$')
-  # Ignored items.
-  IGNORED_ITEMS = (
-      # http://crbug.com/160539, on Windows, it's in chrome/.
-      'Media Cache/',
-      'chrome/Media Cache/',
-      # 'First Run' is not created by the compile, but by the test itself.
-      '<(PRODUCT_DIR)/First Run')
-
   # Blacklist logs and other unimportant files.
-  if LOG_FILE.match(f) or f in IGNORED_ITEMS:
+  # - 'First Run' is not created by the compile but by the test itself.
+  # - Skip log in PRODUCT_DIR. Note that these are applied on '/' style path
+  #   separator at this point.
+  if (re.match(r'^\<\(PRODUCT_DIR\)\/[^\/]+\.log$', f) or
+      f == '<(PRODUCT_DIR)/First Run'):
     logging.debug('Ignoring %s', f)
     return None
 
@@ -360,9 +342,10 @@ def generate_isolate(
   config_variable_names, config_values = zip(
       *sorted(config_variables.iteritems()))
   out = isolate_format.Configs(None, config_variable_names)
-  # TODO(maruel): Create a public interface in Configs to add a ConfigSettings.
-  # pylint: disable=W0212
-  out._by_config[config_values] = isolate_format.ConfigSettings(dependencies)
+  out.set_config(
+      config_values,
+      isolate_format.ConfigSettings(
+          dependencies, os.path.abspath(relative_cwd)))
   return out.make_isolate_file()
 
 
@@ -379,7 +362,6 @@ def chromium_save_isolated(isolated, data, path_variables, algo):
     new_slave = {
       'algo': data['algo'],
       'files': {},
-      'os': data['os'],
       'version': data['version'],
     }
     for f in data['files'].keys():
@@ -463,9 +445,16 @@ class SavedState(Flattenable):
   separator instead of '/' used in .isolate file.
   """
   MEMBERS = (
+    # Value of sys.platform so that the file is rejected if loaded from a
+    # different OS. While this should never happen in practice, users are ...
+    # "creative".
+    'OS',
     # Algorithm used to generate the hash. The only supported value is at the
     # time of writting 'sha-1'.
     'algo',
+    # List of included .isolated files. Used to support/remember 'slave'
+    # .isolated files. Relative path to isolated_basedir.
+    'child_isolated_files',
     # Cache of the processed command. This value is saved because .isolated
     # files are never loaded by isolate.py so it's the only way to load the
     # command safely.
@@ -480,26 +469,31 @@ class SavedState(Flattenable):
     'files',
     # Path of the original .isolate file. Relative path to isolated_basedir.
     'isolate_file',
-    # List of included .isolated files. Used to support/remember 'slave'
-    # .isolated files. Relative path to isolated_basedir.
-    'child_isolated_files',
+    # GYP variables used to generate the .isolated files paths based on path
+    # variables. Frequent examples are DEPTH and PRODUCT_DIR.
+    'path_variables',
     # If the generated directory tree should be read-only.
     'read_only',
     # Relative cwd to use to start the command.
     'relative_cwd',
-    # GYP variables used to generate the .isolated files paths based on path
-    # variables. Frequent examples are DEPTH and PRODUCT_DIR.
-    'path_variables',
-    # Version of the file format in format 'major.minor'. Any non-breaking
-    # change must update minor. Any breaking change must update major.
+    # Root directory the files are mapped from.
+    'root_dir',
+    # Version of the saved state file format. Any breaking change must update
+    # the value.
     'version',
   )
 
+  # Bump this version whenever the saved state changes. It is also keyed on the
+  # .isolated file version so any change in the generator will invalidate .state
+  # files.
+  EXPECTED_VERSION = isolateserver.ISOLATED_FILE_VERSION + '.2'
+
   def __init__(self, isolated_basedir):
     """Creates an empty SavedState.
 
-    |isolated_basedir| is the directory where the .isolated and .isolated.state
-    files are saved.
+    Arguments:
+      isolated_basedir: the directory where the .isolated and .isolated.state
+          files are saved.
     """
     super(SavedState, self).__init__()
     assert os.path.isabs(isolated_basedir), isolated_basedir
@@ -507,6 +501,7 @@ class SavedState(Flattenable):
     self.isolated_basedir = isolated_basedir
 
     # The default algorithm used.
+    self.OS = sys.platform
     self.algo = isolateserver.SUPPORTED_ALGOS['sha-1']
     self.child_isolated_files = []
     self.command = []
@@ -517,10 +512,14 @@ class SavedState(Flattenable):
     self.path_variables = {}
     self.read_only = None
     self.relative_cwd = None
-    self.version = isolateserver.ISOLATED_FILE_VERSION
+    self.root_dir = None
+    self.version = self.EXPECTED_VERSION
+
+  def update_config(self, config_variables):
+    """Updates the saved state with only config variables."""
+    self.config_variables.update(config_variables)
 
-  def update(
-      self, isolate_file, path_variables, config_variables, extra_variables):
+  def update(self, isolate_file, path_variables, extra_variables):
     """Updates the saved state with new data to keep GYP variables and internal
     reference to the original .isolate file.
     """
@@ -534,7 +533,6 @@ class SavedState(Flattenable):
     # .isolated.state.
     assert isolate_file == self.isolate_file or not self.isolate_file, (
         isolate_file, self.isolate_file)
-    self.config_variables.update(config_variables)
     self.extra_variables.update(extra_variables)
     self.isolate_file = isolate_file
     self.path_variables.update(path_variables)
@@ -571,10 +569,10 @@ class SavedState(Flattenable):
       'algo': isolateserver.SUPPORTED_ALGOS_REVERSE[self.algo],
       'files': dict(
           (filepath, strip(data)) for filepath, data in self.files.iteritems()),
-      'version': self.version,
+      # The version of the .state file is different than the one of the
+      # .isolated file.
+      'version': isolateserver.ISOLATED_FILE_VERSION,
     }
-    if self.config_variables.get('OS'):
-      out['os'] = self.config_variables['OS']
     if self.command:
       out['command'] = self.command
     if self.read_only is not None:
@@ -598,11 +596,11 @@ class SavedState(Flattenable):
     file is saved in OS-specific format.
     """
     out = super(SavedState, cls).load(data, isolated_basedir)
-    if data.get('os'):
-      out.config_variables['OS'] = data['os']
+    if data.get('OS') != sys.platform:
+      raise isolateserver.ConfigError('Unexpected OS %s', data.get('OS'))
 
     # Converts human readable form back into the proper class type.
-    algo = data.get('algo', 'sha-1')
+    algo = data.get('algo')
     if not algo in isolateserver.SUPPORTED_ALGOS:
       raise isolateserver.ConfigError('Unknown algo \'%s\'' % out.algo)
     out.algo = isolateserver.SUPPORTED_ALGOS[algo]
@@ -610,9 +608,7 @@ class SavedState(Flattenable):
     # Refuse the load non-exact version, even minor difference. This is unlike
     # isolateserver.load_isolated(). This is because .isolated.state could have
     # changed significantly even in minor version difference.
-    if not re.match(r'^(\d+)\.(\d+)$', out.version):
-      raise isolateserver.ConfigError('Unknown version \'%s\'' % out.version)
-    if out.version != isolateserver.ISOLATED_FILE_VERSION:
+    if out.version != cls.EXPECTED_VERSION:
       raise isolateserver.ConfigError(
           'Unsupported version \'%s\'' % out.version)
 
@@ -687,24 +683,26 @@ class CompleteState(object):
         'CompleteState.load_isolate(%s, %s, %s, %s, %s, %s)',
         cwd, isolate_file, path_variables, config_variables, extra_variables,
         ignore_broken_items)
-    relative_base_dir = os.path.dirname(isolate_file)
 
-    # Processes the variables.
-    path_variables = normalize_path_variables(
-        cwd, path_variables, relative_base_dir)
-    # Update the saved state.
-    self.saved_state.update(
-        isolate_file, path_variables, config_variables, extra_variables)
-    path_variables = self.saved_state.path_variables
+    # Config variables are not affected by the paths and must be used to
+    # retrieve the paths, so update them first.
+    self.saved_state.update_config(config_variables)
 
     with open(isolate_file, 'r') as f:
       # At that point, variables are not replaced yet in command and infiles.
       # infiles may contain directory entries and is in posix style.
-      command, infiles, touched, read_only = (
+      command, infiles, touched, read_only, isolate_cmd_dir = (
           isolate_format.load_isolate_for_config(
               os.path.dirname(isolate_file), f.read(),
               self.saved_state.config_variables))
 
+    # Processes the variables with the new found relative root. Note that 'cwd'
+    # is used when path variables are used.
+    path_variables = normalize_path_variables(
+        cwd, path_variables, isolate_cmd_dir)
+    # Update the rest of the saved state.
+    self.saved_state.update(isolate_file, path_variables, extra_variables)
+
     total_variables = self.saved_state.path_variables.copy()
     total_variables.update(self.saved_state.config_variables)
     total_variables.update(self.saved_state.extra_variables)
@@ -723,38 +721,41 @@ class CompleteState(object):
     # root_dir is automatically determined by the deepest root accessed with the
     # form '../../foo/bar'. Note that path variables must be taken in account
     # too, add them as if they were input files.
-    root_dir = isolate_format.determine_root_dir(
-        relative_base_dir, infiles + touched +
+    self.saved_state.root_dir = isolate_format.determine_root_dir(
+        isolate_cmd_dir, infiles + touched +
         self.saved_state.path_variables.values())
     # The relative directory is automatically determined by the relative path
     # between root_dir and the directory containing the .isolate file,
     # isolate_base_dir.
-    relative_cwd = os.path.relpath(relative_base_dir, root_dir)
+    relative_cwd = os.path.relpath(isolate_cmd_dir, self.saved_state.root_dir)
     # Now that we know where the root is, check that the path_variables point
     # inside it.
     for k, v in self.saved_state.path_variables.iteritems():
-      if not file_path.path_starts_with(
-          root_dir, os.path.join(relative_base_dir, v)):
+      dest = os.path.join(isolate_cmd_dir, relative_cwd, v)
+      if not file_path.path_starts_with(self.saved_state.root_dir, dest):
         raise isolateserver.MappingError(
-            'Path variable %s=%r points outside the inferred root directory %s'
-            % (k, v, root_dir))
-    # Normalize the files based to root_dir. It is important to keep the
-    # trailing os.path.sep at that step.
+            'Path variable %s=%r points outside the inferred root directory '
+            '%s; %s'
+            % (k, v, self.saved_state.root_dir, dest))
+    # Normalize the files based to self.saved_state.root_dir. It is important to
+    # keep the trailing os.path.sep at that step.
     infiles = [
       file_path.relpath(
-          file_path.normpath(os.path.join(relative_base_dir, f)), root_dir)
+          file_path.normpath(os.path.join(isolate_cmd_dir, f)),
+          self.saved_state.root_dir)
       for f in infiles
     ]
     touched = [
       file_path.relpath(
-          file_path.normpath(os.path.join(relative_base_dir, f)), root_dir)
+          file_path.normpath(os.path.join(isolate_cmd_dir, f)),
+          self.saved_state.root_dir)
       for f in touched
     ]
-    follow_symlinks = config_variables['OS'] != 'win'
+    follow_symlinks = sys.platform != 'win32'
     # Expand the directories by listing each file inside. Up to now, trailing
     # os.path.sep must be kept. Do not expand 'touched'.
     infiles = expand_directories_and_symlinks(
-        root_dir,
+        self.saved_state.root_dir,
         infiles,
         lambda x: re.match(r'.*\.(git|svn|pyc)$', x),
         follow_symlinks,
@@ -792,7 +793,6 @@ class CompleteState(object):
             filepath,
             self.saved_state.files[infile],
             self.saved_state.read_only,
-            self.saved_state.config_variables['OS'],
             self.saved_state.algo)
 
   def save_files(self):
@@ -814,36 +814,7 @@ class CompleteState(object):
 
   @property
   def root_dir(self):
-    """Returns the absolute path of the root_dir to reference the .isolate file
-    via relative_cwd.
-
-    So that join(root_dir, relative_cwd, basename(isolate_file)) is equivalent
-    to isolate_filepath.
-    """
-    if not self.saved_state.isolate_file:
-      raise ExecutionError('Please specify --isolate')
-    isolate_dir = os.path.dirname(self.saved_state.isolate_filepath)
-    # Special case '.'.
-    if self.saved_state.relative_cwd == '.':
-      root_dir = isolate_dir
-    else:
-      if not isolate_dir.endswith(self.saved_state.relative_cwd):
-        raise ExecutionError(
-            ('Make sure the .isolate file is in the directory that will be '
-             'used as the relative directory. It is currently in %s and should '
-             'be in %s') % (isolate_dir, self.saved_state.relative_cwd))
-      # Walk back back to the root directory.
-      root_dir = isolate_dir[:-(len(self.saved_state.relative_cwd) + 1)]
-    return file_path.get_native_path_case(root_dir)
-
-  @property
-  def resultdir(self):
-    """Returns the absolute path containing the .isolated file.
-
-    It is usually equivalent to the variable PRODUCT_DIR. Uses the .isolated
-    path as the value.
-    """
-    return os.path.dirname(self.isolated_filepath)
+    return self.saved_state.root_dir
 
   def __str__(self):
     def indent(data, indent_length):
@@ -882,8 +853,9 @@ def load_complete_state(options, cwd, subdir, skip_update):
     complete_state = CompleteState.load_files(options.isolated)
   else:
     # Constructs a dummy object that cannot be saved. Useful for temporary
-    # commands like 'run'.
-    complete_state = CompleteState(None, SavedState())
+    # commands like 'run'. There is no directory containing a .isolated file so
+    # specify the current working directory as a valid directory.
+    complete_state = CompleteState(None, SavedState(os.getcwd()))
 
   if not options.isolate:
     if not complete_state.saved_state.isolate_file:
@@ -898,9 +870,16 @@ def load_complete_state(options, cwd, subdir, skip_update):
       rel_isolate = file_path.safe_relpath(
           options.isolate, complete_state.saved_state.isolated_basedir)
       if rel_isolate != complete_state.saved_state.isolate_file:
-        raise ExecutionError(
-            '%s and %s do not match.' % (
-              options.isolate, complete_state.saved_state.isolate_file))
+        # This happens if the .isolate file was moved for example. In this case,
+        # discard the saved state.
+        logging.warning(
+            '--isolated %s != %s as saved in %s. Discarding saved state',
+            rel_isolate,
+            complete_state.saved_state.isolate_file,
+            isolatedfile_to_state(options.isolated))
+        complete_state = CompleteState(
+            options.isolated,
+            SavedState(complete_state.saved_state.isolated_basedir))
 
   if not skip_update:
     # Then load the .isolate and expands directories.
@@ -975,7 +954,7 @@ def merge(complete_state, trace_blacklist):
       isolate_format.eval_content(prev_content),
       isolate_format.extract_comment(prev_content))
   new_config = isolate_format.load_isolate_as_config(isolate_dir, value, '')
-  config = isolate_format.union(prev_config, new_config)
+  config = prev_config.union(new_config)
   data = config.make_isolate_file()
   print('Updating %s' % complete_state.saved_state.isolate_file)
   with open(complete_state.saved_state.isolate_filepath, 'wb') as f:
@@ -1401,7 +1380,7 @@ def add_variable_option(parser):
       '--config-variable',
       action='callback',
       callback=_process_variable_arg,
-      default=[('OS', get_flavor())],
+      default=[],
       dest='config_variables',
       metavar='FOO BAR',
       help='Config variables are used to determine which conditions should be '