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

Unified Diff: package_management.py

Issue 10447014: Add pylint to depot_tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fix unittests. Created 8 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
« no previous file with comments | « bootstrap/win/win_tools.bat ('k') | presubmit_canned_checks.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: package_management.py
diff --git a/package_management.py b/package_management.py
deleted file mode 100644
index 4180a3ffb7298de8a243394a06181fb04aad86df..0000000000000000000000000000000000000000
--- a/package_management.py
+++ /dev/null
@@ -1,602 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 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.
-
-"""Package Management
-
-This module is used to bring in external Python dependencies via eggs from the
-PyPi repository.
-
-The approach is to create a site directory in depot_tools root which will
-contain those packages that are listed as dependencies in the module variable
-PACKAGES. Since we can't guarantee that setuptools is available in all
-distributions this module also contains the ability to bootstrap the site
-directory by manually downloading and installing setuptools. Once setuptools is
-available it uses that to install the other packages in the traditional
-manner.
-
-Use is simple:
-
- import package_management
-
- # Before any imports from the site directory, call this. This only needs
- # to be called in one place near the beginning of the program.
- package_management.SetupSiteDirectory()
-
- # If 'SetupSiteDirectory' fails it will complain with an error message but
- # continue happily. Expect ImportErrors when trying to import any third
- # party modules from the site directory.
-
- import some_third_party_module
-
- ... etc ...
-"""
-
-import cStringIO
-import os
-import re
-import shutil
-import site
-import subprocess
-import sys
-import tempfile
-import urllib2
-
-
-# This is the version of setuptools that we will download if the local
-# python distribution does not include one.
-SETUPTOOLS = ('setuptools', '0.6c11')
-
-# These are the packages that are to be installed in the site directory.
-# easy_install makes it so that the most recently installed version of a
-# package is the one that takes precedence, even if a newer version exists
-# in the site directory. This allows us to blindly install these one on top
-# of the other without worrying about whats already installed.
-#
-# NOTE: If we are often rolling these dependencies then users' site
-# directories will grow monotonically. We could be purging any orphaned
-# packages using the tools provided by pkg_resources.
-PACKAGES = (('logilab-common', '0.57.1'),
- ('logilab-astng', '0.23.1'),
- ('pylint', '0.25.1'))
-
-
-# The Python version suffix used in generating the site directory and in
-# requesting packages from PyPi.
-VERSION_SUFFIX = "%d.%d" % sys.version_info[0:2]
-
-# This is the root directory of the depot_tools installation.
-ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
-
-# This is the path of the site directory we will use. We make this
-# python version specific so that this will continue to work even if the
-# python version is rolled.
-SITE_DIR = os.path.join(ROOT_DIR, 'site-packages-py%s' % VERSION_SUFFIX)
-
-# This status file is created the last time PACKAGES were rolled successfully.
-# It is used to determine if packages need to be rolled by comparing against
-# the age of __file__.
-LAST_ROLLED = os.path.join(SITE_DIR, 'last_rolled.txt')
-
-
-class Error(Exception):
- """The base class for all module errors."""
- pass
-
-
-class InstallError(Error):
- """Thrown if an installation is unable to complete."""
- pass
-
-
-class Package(object):
- """A package represents a release of a project.
-
- We use this as a lightweight version of pkg_resources, allowing us to
- perform an end-run around setuptools for the purpose of bootstrapping. Its
- functionality is very limited.
-
- Attributes:
- name: the name of the package.
- version: the version of the package.
- safe_name: the safe name of the package.
- safe_version: the safe version string of the package.
- file_name: the filename-safe name of the package.
- file_version: the filename-safe version string of the package.
- """
-
- def __init__(self, name, version):
- """Initialize this package.
-
- Args:
- name: the name of the package.
- version: the version of the package.
- """
- self.name = name
- self.version = version
- self.safe_name = Package._MakeSafeName(self.name)
- self.safe_version = Package._MakeSafeVersion(self.version)
- self.file_name = Package._MakeSafeForFilename(self.safe_name)
- self.file_version = Package._MakeSafeForFilename(self.safe_version)
-
- @staticmethod
- def _MakeSafeName(name):
- """Makes a safe package name, as per pkg_resources."""
- return re.sub('[^A-Za-z0-9]+', '-', name)
-
- @staticmethod
- def _MakeSafeVersion(version):
- """Makes a safe package version string, as per pkg_resources."""
- version = re.sub('\s+', '.', version)
- return re.sub('[^A-Za-z0-9\.]+', '-', version)
-
- @staticmethod
- def _MakeSafeForFilename(safe_name_or_version):
- """Makes a safe name or safe version safe to use in a file name.
- |safe_name_or_version| must be a safe name or version string as returned
- by GetSafeName or GetSafeVersion.
- """
- return re.sub('-', '_', safe_name_or_version)
-
- def GetAsRequirementString(self):
- """Builds an easy_install requirements string representing this package."""
- return '%s==%s' % (self.name, self.version)
-
- def GetFilename(self, extension=None):
- """Builds a filename for this package using the setuptools convention.
- If |extensions| is provided it will be appended to the generated filename,
- otherwise only the basename is returned.
-
- The following url discusses the filename format:
-
- http://svn.python.org/projects/sandbox/trunk/setuptools/doc/formats.txt
- """
- filename = '%s-%s-py%s' % (self.file_name, self.file_version,
- VERSION_SUFFIX)
-
- if extension:
- if not extension.startswith('.'):
- filename += '.'
- filename += extension
-
- return filename
-
- def GetPyPiUrl(self, extension):
- """Returns the URL where this package is hosted on PyPi."""
- return 'http://pypi.python.org/packages/%s/%c/%s/%s' % (VERSION_SUFFIX,
- self.file_name[0], self.file_name, self.GetFilename(extension))
-
- def DownloadEgg(self, dest_dir, overwrite=False):
- """Downloads the EGG for this URL.
-
- Args:
- dest_dir: The directory where the EGG should be written. If the EGG
- has already been downloaded and cached the returned path may not
- be in this directory.
- overwite: If True the destination path will be overwritten even if
- it already exists. Defaults to False.
-
- Returns:
- The path to the written EGG.
-
- Raises:
- Error: if dest_dir doesn't exist, the EGG is unable to be written,
- the URL doesn't exist, or the server returned an error, or the
- transmission was interrupted.
- """
- if not os.path.exists(dest_dir):
- raise Error('Path does not exist: %s' % dest_dir)
-
- if not os.path.isdir(dest_dir):
- raise Error('Path is not a directory: %s' % dest_dir)
-
- filename = os.path.abspath(os.path.join(dest_dir, self.GetFilename('egg')))
- if os.path.exists(filename):
- if os.path.isdir(filename):
- raise Error('Path is a directory: %s' % filename)
- if not overwrite:
- return filename
-
- url = self.GetPyPiUrl('egg')
-
- try:
- url_stream = urllib2.urlopen(url)
- local_file = open(filename, 'wb')
- local_file.write(url_stream.read())
- local_file.close()
- return filename
- except (IOError, urllib2.HTTPError, urllib2.URLError):
- # Reraise with a new error type, keeping the original message and
- # traceback.
- raise Error, sys.exc_info()[1], sys.exc_info()[2]
-
-
-def AddToPythonPath(path):
- """Adds the provided path to the head of PYTHONPATH and sys.path."""
- path = os.path.abspath(path)
- if path not in sys.path:
- sys.path.insert(0, path)
-
- paths = os.environ.get('PYTHONPATH', '').split(os.pathsep)
- if path not in paths:
- paths.insert(0, path)
- os.environ['PYTHONPATH'] = os.pathsep.join(paths)
-
-
-def AddSiteDirectory(path):
- """Adds the provided path to the runtime as a site directory.
-
- Any modules that are in the site directory will be available for importing
- after this returns. If modules are added or deleted this must be called
- again for the changes to be reflected in the runtime.
-
- This calls both AddToPythonPath and site.addsitedir. Both are needed to
- convince easy_install to treat |path| as a site directory.
- """
- AddToPythonPath(path)
- site.addsitedir(path) # pylint: disable=E1101
-
-def EnsureSiteDirectory(path):
- """Creates and/or adds the provided path to the runtime as a site directory.
-
- This works like AddSiteDirectory but it will create the directory if it
- does not yet exist.
-
- Raise:
- Error: if the site directory is unable to be created, or if it exists and
- is not a directory.
- """
- if os.path.exists(path):
- if not os.path.isdir(path):
- raise Error('Path is not a directory: %s' % path)
- else:
- try:
- os.mkdir(path)
- except IOError:
- raise Error('Unable to create directory: %s' % path)
-
- AddSiteDirectory(path)
-
-
-def ModuleIsFromPackage(module, package_path):
- """Determines if a module has been imported from a given package.
-
- Args:
- module: the module to test.
- package_path: the path to the package to test.
-
- Returns:
- True if |module| has been imported from |package_path|, False otherwise.
- """
- try:
- m = os.path.abspath(module.__file__)
- p = os.path.abspath(package_path)
- if len(m) <= len(p):
- return False
- if m[0:len(p)] != p:
- return False
- return m[len(p)] == os.sep
- except AttributeError:
- return False
-
-
-def _CaptureStdStreams(function, *args, **kwargs):
- """Captures stdout and stderr while running the provided function.
-
- This only works if |function| only accesses sys.stdout and sys.stderr. If
- we need more than this we'll have to use subprocess.Popen.
-
- Args:
- function: the function to be called.
- args: the arguments to pass to |function|.
- kwargs: the keyword arguments to pass to |function|.
- """
- orig_stdout = sys.stdout
- orig_stderr = sys.stderr
- sys.stdout = cStringIO.StringIO()
- sys.stderr = cStringIO.StringIO()
- try:
- return function(*args, **kwargs)
- finally:
- sys.stdout = orig_stdout
- sys.stderr = orig_stderr
-
-
-def InstallPackage(url_or_req, site_dir):
- """Installs a package to a site directory.
-
- |site_dir| must exist and already be an active site directory. setuptools
- must in the path. Uses easy_install which may involve a download from
- pypi.python.org, so this also requires network access.
-
- Args:
- url_or_req: the package to install, expressed as an URL (may be local),
- or a requirement string.
- site_dir: the site directory in which to install it.
-
- Raises:
- InstallError: if installation fails for any reason.
- """
- args = ['--quiet', '--install-dir', site_dir, '--exclude-scripts',
- '--always-unzip', '--no-deps', url_or_req]
-
- # The easy_install script only calls SystemExit if something goes wrong.
- # Otherwise, it falls through returning None.
- try:
- import setuptools.command.easy_install
- _CaptureStdStreams(setuptools.command.easy_install.main, args)
- except (ImportError, SystemExit):
- # Re-raise the error, preserving the stack trace and message.
- raise InstallError, sys.exc_info()[1], sys.exc_info()[2]
-
-
-def _RunInSubprocess(pycode):
- """Launches a python subprocess with the provided code.
-
- The subprocess will be launched with the same stdout and stderr. The
- subprocess will use the same instance of python as is currently running,
- passing |pycode| as arguments to this script. |pycode| will be interpreted
- as python code in the context of this module.
-
- Returns:
- True if the subprocess returned 0, False if it returned an error.
- """
- return not subprocess.call([sys.executable, __file__, pycode])
-
-
-def _LoadSetupToolsFromEggAndInstall(egg_path):
- """Loads setuptools from the provided egg |egg_path|, and installs it to
- SITE_DIR.
-
- This is intended to be run from a subprocess as it pollutes the running
- instance of Python by importing a module and then forcibly deleting its
- source.
-
- Returns:
- True on success, False on failure.
- """
- AddToPythonPath(egg_path)
-
- try:
- # Import setuptools and ensure it comes from the EGG.
- import setuptools
- if not ModuleIsFromPackage(setuptools, egg_path):
- raise ImportError()
- except ImportError:
- print ' Unable to import downloaded package!'
- return False
-
- try:
- print ' Using setuptools to install itself ...'
- InstallPackage(egg_path, SITE_DIR)
- except InstallError:
- print ' Unable to install setuptools!'
- return False
-
- return True
-
-
-def BootstrapSetupTools():
- """Bootstraps the runtime with setuptools.
-
- Will try to import setuptools directly. If not found it will attempt to
- download it and load it from there. If the download is successful it will
- then use setuptools to install itself in the site directory.
-
- This is meant to be run from a child process as it modifies the running
- instance of Python by importing modules and then physically deleting them
- from disk.
-
- Returns:
- Returns True if 'import setuptools' will succeed, False otherwise.
- """
- AddSiteDirectory(SITE_DIR)
-
- # Check if setuptools is already available. If so, we're done.
- try:
- import setuptools # pylint: disable=W0612
- return True
- except ImportError:
- pass
-
- print 'Bootstrapping setuptools ...'
-
- EnsureSiteDirectory(SITE_DIR)
-
- # Download the egg to a temp directory.
- dest_dir = tempfile.mkdtemp('depot_tools')
- path = None
- try:
- package = Package(*SETUPTOOLS)
- print ' Downloading %s ...' % package.GetFilename()
- path = package.DownloadEgg(dest_dir)
- except Error:
- print ' Download failed!'
- shutil.rmtree(dest_dir)
- return False
-
- try:
- # Load the downloaded egg, and install it to the site directory. Do this
- # in a subprocess so as not to pollute this runtime.
- pycode = '_LoadSetupToolsFromEggAndInstall(%s)' % repr(path)
- if not _RunInSubprocess(pycode):
- raise Error()
-
- # Reload our site directory, which should now contain setuptools.
- AddSiteDirectory(SITE_DIR)
-
- # Try to import setuptools
- import setuptools
- except ImportError:
- print ' Unable to import setuptools!'
- return False
- except Error:
- # This happens if RunInSubProcess fails, and the appropriate error has
- # already been written to stdout.
- return False
- finally:
- # Delete the temp directory.
- shutil.rmtree(dest_dir)
-
- return True
-
-
-def _GetModTime(path):
- """Gets the last modification time associated with |path| in seconds since
- epoch, returning 0 if |path| does not exist.
- """
- try:
- return os.stat(path).st_mtime
- except: # pylint: disable=W0702
- # This error is different depending on the OS, hence no specified type.
- return 0
-
-
-def _SiteDirectoryIsUpToDate():
- return _GetModTime(LAST_ROLLED) > _GetModTime(__file__)
-
-
-def UpdateSiteDirectory():
- """Installs the packages from PACKAGES if they are not already installed.
- At this point we must have setuptools in the site directory.
-
- This is intended to be run in a subprocess *prior* to the site directory
- having been added to the parent process as it may cause packages to be
- added and/or removed.
-
- Returns:
- True on success, False otherwise.
- """
- if _SiteDirectoryIsUpToDate():
- return True
-
- try:
- EnsureSiteDirectory(SITE_DIR)
- import pkg_resources
-
- # Determine if any packages actually need installing.
- missing_packages = []
- for package in [SETUPTOOLS] + list(PACKAGES):
- pkg = Package(*package)
- req = pkg.GetAsRequirementString()
-
- # It may be that this package is already available in the site
- # directory. If so, we can skip past it without trying to install it.
- pkg_req = pkg_resources.Requirement.parse(req)
- try:
- dist = pkg_resources.working_set.find(pkg_req)
- if dist:
- continue
- except pkg_resources.VersionConflict:
- # This happens if another version of the package is already
- # installed in another site directory (ie: the system site directory).
- pass
-
- missing_packages.append(pkg)
-
- # Install the missing packages.
- if missing_packages:
- print 'Updating python packages ...'
- for pkg in missing_packages:
- print ' Installing %s ...' % pkg.GetFilename()
- InstallPackage(pkg.GetAsRequirementString(), SITE_DIR)
-
- # Touch the status file so we know that we're up to date next time.
- open(LAST_ROLLED, 'wb')
- except InstallError, e:
- print ' Installation failed: %s' % str(e)
- return False
-
- return True
-
-
-def SetupSiteDirectory():
- """Sets up the site directory, bootstrapping setuptools if necessary.
-
- If this finishes successfully then SITE_DIR will exist and will contain
- the appropriate version of setuptools and all of the packages listed in
- PACKAGES.
-
- This is the main workhorse of this module. Calling this will do everything
- necessary to ensure that you have the desired packages installed in the
- site directory, and the site directory enabled in this process.
-
- Returns:
- True on success, False on failure.
- """
- if _SiteDirectoryIsUpToDate():
- AddSiteDirectory(SITE_DIR)
- return True
-
- if not _RunInSubprocess('BootstrapSetupTools()'):
- return False
-
- if not _RunInSubprocess('UpdateSiteDirectory()'):
- return False
-
- # Process the site directory so that the packages within it are available
- # for import.
- AddSiteDirectory(SITE_DIR)
-
- return True
-
-
-def CanImportFromSiteDirectory(package_name):
- """Determines if the given package can be imported from the site directory.
-
- Args:
- package_name: the name of the package to import.
-
- Returns:
- True if 'import package_name' will succeed and return a module from the
- site directory, False otherwise.
- """
- try:
- return ModuleIsFromPackage(__import__(package_name), SITE_DIR)
- except ImportError:
- return False
-
-
-def Test():
- """Runs SetupSiteDirectory and then tries to load pylint, ensuring that it
- comes from the site directory just created. This is an end-to-end unittest
- and allows for simple testing from the command-line by running
-
- ./package_management.py 'Test()'
- """
- print 'Testing package_management.'
- if not SetupSiteDirectory():
- print 'SetupSiteDirectory failed.'
- return False
- if not CanImportFromSiteDirectory('pylint'):
- print 'CanImportFromSiteDirectory failed.'
- return False
- print 'Success!'
- return True
-
-
-def Main():
- """The main entry for the package management script.
-
- If no arguments are provided simply runs SetupSiteDirectory. If arguments
- have been passed we execute the first argument as python code in the
- context of this module. This mechanism is used during the bootstrap
- process so that the main instance of Python does not have its runtime
- polluted by various intermediate packages and imports.
-
- Returns:
- 0 on success, 1 otherwise.
- """
- if len(sys.argv) == 2:
- result = False
- exec('result = %s' % sys.argv[1])
-
- # Translate the success state to a return code.
- return not result
- else:
- return not SetupSiteDirectory()
-
-
-if __name__ == '__main__':
- sys.exit(Main())
« no previous file with comments | « bootstrap/win/win_tools.bat ('k') | presubmit_canned_checks.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698