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

Unified Diff: infra/scripts/legacy/scripts/common/env.py

Issue 1213433006: Fork runtest.py and everything it needs src-side for easier hacking (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: runisolatedtest.py Created 5 years, 6 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 side-by-side diff with in-line comments
Download patch
Index: infra/scripts/legacy/scripts/common/env.py
diff --git a/infra/scripts/legacy/scripts/common/env.py b/infra/scripts/legacy/scripts/common/env.py
new file mode 100755
index 0000000000000000000000000000000000000000..3e497ff48bf188938178b9eaa5334e59b3e48273
--- /dev/null
+++ b/infra/scripts/legacy/scripts/common/env.py
@@ -0,0 +1,439 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Implements a standard mechanism for Chrome Infra Python environment setup.
+
+This library provides a central location to define Chrome Infra environment
+setup. It also provides several faculties to install this environment.
+
+Within a cooperating script, the environment can be setup by importing this
+module and running its 'Install' method:
+
+ # Install Chrome-Infra environment (replaces 'sys.path').
+ sys.path.insert(0,
+ os.path.join(os.path.dirname(__file__), os.pardir, ...))
+ # (/path/to/build/scripts)
+ import common.env
+ common.env.Install()
+
+When attempting to export the Chrome Infra path to external scripts, this
+script can be invoked as an executable with various subcommands to emit a valid
+PYTHONPATH clause.
+
+In addition, this module has several functions to construct the path.
+
+The goal is to deploy this module universally among Chrome-Infra scripts,
+BuildBot configurations, tool invocations, and tests to ensure that they all
+execute with the same centrally-defined environment.
+"""
+
+import argparse
+import collections
+import contextlib
+import imp
+import itertools
+import os
+import sys
+import traceback
+
+
+# Export for bootstrapping.
+__all__ = [
+ 'Install',
+ 'PythonPath',
+ ]
+
+
+# Name of enviornment extension file to seek.
+ENV_EXTENSION_NAME = 'environment.cfg.py'
+
+# Standard directories (based on this file's location in the <build> tree).
+def path_if(*args):
+ if not all(args):
+ return None
+ path = os.path.abspath(os.path.join(*args))
+ return (path) if os.path.exists(path) else (None)
+
+# The path to the <build> directory in which this script resides.
+Build = path_if(os.path.dirname(__file__), os.pardir, os.pardir)
+# The path to the <build_internal> directory.
+BuildInternal = path_if(Build, os.pardir, 'build_internal')
+
+
+def SetPythonPathEnv(value):
+ """Sets the system's PYTHONPATH environemnt variable.
+
+ Args:
+ value (str): The value to use. If this is empty/None, the system's
+ PYTHONPATH will be cleared.
+ """
+ # Since we can't assign None to the environment "dictionary", we have to
+ # either set or delete the key depending on the original value.
+ if value is not None:
+ os.environ['PYTHONPATH'] = str(value)
+ else:
+ os.environ.pop('PYTHONPATH', None)
+
+
+def Install(**kwargs):
+ """Replaces the current 'sys.path' with a hermetic Chrome-Infra path.
+
+ Args:
+ kwargs (dict): See GetInfraPythonPath arguments.
+
+ Returns (PythonPath): The PythonPath object that was installed.
+ """
+ infra_python_path = GetInfraPythonPath(**kwargs)
+ infra_python_path.Install()
+ return infra_python_path
+
+
+def SplitPath(path):
+ """Returns (list): A list of path elements.
+
+ Splits a path into path elements. For example (assuming '/' is the local
+ system path separator):
+ >>> print SplitPath('/a/b/c/d')
+ ['/', 'a', 'b', 'c', 'd']
+ >>> print SplitPath('a/b/c')
+ ['a', 'b,' 'c']
+ """
+ parts = []
+ while True:
+ path, component = os.path.split(path)
+ if not component:
+ if path:
+ parts.append(path)
+ break
+ parts.append(component)
+ parts.reverse()
+ return parts
+
+
+def ExtendPath(base, root_dir):
+ """Returns (PythonPath): The extended python path.
+
+ This method looks for the ENV_EXTENSION_NAME file within "root_dir". If
+ present, it will be loaded as a Python module and have its "Extend" method
+ called.
+
+ If no extension is found, the base PythonPath will be returned.
+
+ Args:
+ base (PythonPath): The base python path.
+ root_dir (str): The path to check for an extension.
+ """
+ extension_path = os.path.join(root_dir, ENV_EXTENSION_NAME)
+ if not os.path.isfile(extension_path):
+ return base
+ with open(extension_path, 'r') as fd:
+ extension = fd.read()
+ extension_module = imp.new_module('env-extension')
+
+ # Execute the enviornment extension.
+ try:
+ exec extension in extension_module.__dict__
+
+ extend_func = getattr(extension_module, 'Extend', None)
+ assert extend_func, (
+ "The environment extension module is missing the 'Extend()' method.")
+ base = extend_func(base, root_dir)
+ if not isinstance(base, PythonPath):
+ raise TypeError("Extension module returned non-PythonPath object (%s)" % (
+ type(base).__name__,))
+ except Exception:
+ # Re-raise the exception, but include the configuration file name.
+ tb = traceback.format_exc()
+ raise RuntimeError("Environment extension [%s] raised exception: %s" % (
+ extension_path, tb))
+ return base
+
+
+def IsSystemPythonPath(path):
+ """Returns (bool): If a python path is user-installed.
+
+ Paths that are known to be user-installed paths can be ignored when setting
+ up a hermetic Python path environment to avoid user libraries that would not
+ be present in other environments falsely affecting code.
+
+ This function can be updated as-needed to exclude other non-system paths
+ encountered on bots and in the wild.
+ """
+ components = SplitPath(path)
+ for component in components:
+ if component in ('dist-packages', 'site-packages'):
+ return False
+ return True
+
+
+class PythonPath(collections.Sequence):
+ """An immutable set of Python path elements.
+
+ All paths represented in this structure are absolute. If a relative path
+ is passed into this structure, it will be converted to absolute based on
+ the current working directory (via os.path.abspath).
+ """
+
+ def __init__(self, components=None):
+ """Initializes a new PythonPath instance.
+
+ Args:
+ components (list): A list of path component strings.
+ """
+ seen = set()
+ self._components = []
+ for component in (components or ()):
+ component = os.path.abspath(component)
+ assert isinstance(component, basestring), (
+ "Path component '%s' is not a string (%s)" % (
+ component, type(component).__name__))
+ if component in seen:
+ continue
+ seen.add(component)
+ self._components.append(component)
+
+ def __getitem__(self, value):
+ return self._components[value]
+
+ def __len__(self):
+ return len(self._components)
+
+ def __iadd__(self, other):
+ return self.Append(other)
+
+ def __repr__(self):
+ return self.pathstr
+
+ def __eq__(self, other):
+ assert isinstance(other, type(self))
+ return self._components == other._components
+
+ @classmethod
+ def Flatten(cls, *paths):
+ """Returns (list): A single-level list containing flattened path elements.
+
+ >>> print PythonPath.Flatten('a', ['b', ['c', 'd']])
+ ['a', 'b', 'c', 'd']
+ """
+ result = []
+ for path in paths:
+ if not isinstance(path, basestring):
+ # Assume it's an iterable of paths.
+ result += cls.Flatten(*path)
+ else:
+ result.append(path)
+ return result
+
+ @classmethod
+ def FromPaths(cls, *paths):
+ """Returns (PythonPath): A PythonPath instantiated from path elements.
+
+ Args:
+ paths (tuple): A tuple of path elements or iterables containing path
+ elements (e.g., PythonPath instances).
+ """
+ return cls(cls.Flatten(*paths))
+
+ @classmethod
+ def FromPathStr(cls, pathstr):
+ """Returns (PythonPath): A PythonPath instantiated from the path string.
+
+ Args:
+ pathstr (str): An os.pathsep()-delimited path string.
+ """
+ return cls(pathstr.split(os.pathsep))
+
+ @property
+ def pathstr(self):
+ """Returns (str): A path string for the instance's path elements."""
+ return os.pathsep.join(self)
+
+ def IsHermetic(self):
+ """Returns (bool): True if this instance contains only system paths."""
+ return all(IsSystemPythonPath(p) for p in self)
+
+ def GetHermetic(self):
+ """Returns (PythonPath): derivative PythonPath containing only system paths.
+ """
+ return type(self).FromPaths(*(p for p in self if IsSystemPythonPath(p)))
+
+ def Append(self, *paths):
+ """Returns (PythonPath): derivative PythonPath with paths added to the end.
+
+ Args:
+ paths (tuple): A tuple of path elements to append to the current instance.
+ """
+ return type(self)(itertools.chain(self, self.FromPaths(*paths)))
+
+ def Override(self, *paths):
+ """Returns (PythonPath): derivative PythonPath with paths prepended.
+
+ Args:
+ paths (tuple): A tuple of path elements to prepend to the current
+ instance.
+ """
+ return self.FromPaths(*paths).Append(self)
+
+ def Install(self):
+ """Overwrites Python runtime variables based on the current instance.
+
+ Performs the following operations:
+ - Replaces sys.path with the current instance's path.
+ - Replaces os.environ['PYTHONPATH'] with the current instance's path
+ string.
+ """
+ sys.path = list(self)
+ SetPythonPathEnv(self.pathstr)
+
+ @contextlib.contextmanager
+ def Enter(self):
+ """Context manager wrapper for Install.
+
+ On exit, the context manager will restore the original environment.
+ """
+ orig_sys_path = sys.path[:]
+ orig_pythonpath = os.environ.get('PYTHONPATH')
+
+ try:
+ self.Install()
+ yield
+ finally:
+ sys.path = orig_sys_path
+ SetPythonPathEnv(orig_pythonpath)
+
+
+def GetSysPythonPath(hermetic=True):
+ """Returns (PythonPath): A path based on 'sys.path'.
+
+ Args:
+ hermetic (bool): If True, prune any non-system path.
+ """
+ path = PythonPath.FromPaths(*sys.path)
+ if hermetic:
+ path = path.GetHermetic()
+ return path
+
+
+def GetEnvPythonPath():
+ """Returns (PythonPath): A path based on the PYTHONPATH environment variable.
+ """
+ pythonpath = os.environ.get('PYTHONPATH')
+ if not pythonpath:
+ return PythonPath.FromPaths()
+ return PythonPath.FromPathStr(pythonpath)
+
+
+def GetMasterPythonPath(master_dir):
+ """Returns (PythonPath): A path including a BuildBot master's directory.
+
+ Args:
+ master_dir (str): The BuildBot master root directory.
+ """
+ return PythonPath.FromPaths(master_dir)
+
+
+def GetBuildPythonPath():
+ """Returns (PythonPath): The Chrome Infra build path."""
+ build_path = PythonPath.FromPaths()
+ for extension_dir in (
+ Build,
+ BuildInternal,
+ ):
+ if extension_dir:
+ build_path = ExtendPath(build_path, extension_dir)
+ return build_path
+
+
+def GetInfraPythonPath(hermetic=True, master_dir=None):
+ """Returns (PythonPath): The full working Chrome Infra utility path.
+
+ This path is consistent for master, slave, and tool usage. It includes (in
+ this order):
+ - Any environment PYTHONPATH overrides.
+ - If 'master_dir' is supplied, the master's python path component.
+ - The Chrome Infra build path.
+ - The system python path.
+
+ Args:
+ hermetic (bool): True, prune any non-system path from the system path.
+ master_dir (str): If not None, include a master path component.
+ """
+ path = GetEnvPythonPath()
+ if master_dir:
+ path += GetMasterPythonPath(master_dir)
+ path += GetBuildPythonPath()
+ path += GetSysPythonPath(hermetic=hermetic)
+ return path
+
+
+def _InfraPathFromArgs(args):
+ """Returns (PythonPath): A PythonPath populated from command-line arguments.
+
+ Args:
+ args (argparse.Namespace): The command-line arguments constructed by 'main'.
+ """
+ return GetInfraPythonPath(
+ master_dir=args.master_dir,
+ )
+
+
+def _Command_Echo(args, path):
+ """Returns (int): Return code.
+
+ Command function for the 'echo' subcommand. Outputs the path string for
+ 'path'.
+
+ Args:
+ args (argparse.Namespace): The command-line arguments constructed by 'main'.
+ path (PythonPath): The python path to use.
+ """
+ args.output.write(path.pathstr)
+ return 0
+
+
+def _Command_Print(args, path):
+ """Returns (int): Return code.
+
+ Command function for the 'print' subcommand. Outputs each path component in
+ path on a separate line.
+
+ Args:
+ args (argparse.Namespace): The command-line arguments constructed by 'main'.
+ path (PythonPath): The python path to use.
+ """
+ for component in path:
+ print >>args.output, component
+ return 0
+
+
+def main():
+ """Main execution function."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-M', '--master_dir',
+ help="Augment the path with the master's directory.")
+ parser.add_argument('-o', '--output', metavar='PATH',
+ type=argparse.FileType('w'), default='-',
+ help="File to output to (use '-' for STDOUT).")
+
+ subparsers = parser.add_subparsers()
+
+ # 'echo'
+ subparser = subparsers.add_parser('echo')
+ subparser.set_defaults(func=_Command_Echo)
+
+ # 'print'
+ subparser = subparsers.add_parser('print')
+ subparser.set_defaults(func=_Command_Print)
+
+ # Parse
+ args = parser.parse_args()
+
+ # Execute our subcommand function, which will return the exit code.
+ path = _InfraPathFromArgs(args)
+ return args.func(args, path)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
« no previous file with comments | « infra/scripts/legacy/scripts/common/chromium_utils.py ('k') | infra/scripts/legacy/scripts/common/gtest_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698