Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Package Management | |
| 7 | |
| 8 This module is used to bring in external Python dependencies via eggs from the | |
| 9 PyPi repository. It is intended to work with any version of Python from 2.4 | |
| 10 through 2.6 and beyond, as well as on any OS and hardware. | |
| 11 | |
| 12 The approach is to create a site directory in depot_tools root which will | |
| 13 contain those packages that are listed as dependencies in the module variable | |
| 14 PACKAGES. Since we can't guarantee that setuptools is available in all | |
| 15 distributions this module also contains the ability to bootstrap the site | |
| 16 directory by manually downloading and loading setuptools. Once setuptools is | |
| 17 available it uses that to install the other packages in the traditional | |
| 18 manner. | |
| 19 | |
| 20 Use is simple: | |
| 21 | |
| 22 import package_management | |
| 23 | |
| 24 # Before any imports from the site directory, call this. This only needs | |
| 25 # to be called in one place near the beginning of the program. | |
| 26 package_management.SetupSiteDirectory() | |
| 27 | |
| 28 # If 'SetupSiteDirectory' fails it will complain with an error message but | |
| 29 # continue happily. Expect ImportErrors when trying to import any third | |
| 30 # party modules from the site directory. | |
| 31 | |
| 32 import some_third_party_module | |
| 33 | |
| 34 ... etc ... | |
| 35 """ | |
| 36 | |
| 37 import cStringIO | |
| 38 import os | |
| 39 import re | |
| 40 import shutil | |
| 41 import site | |
| 42 import subprocess | |
| 43 import sys | |
| 44 import tempfile | |
| 45 import urllib | |
| 46 | |
| 47 | |
| 48 # This is the version of setuptools that we will download if the local | |
| 49 # python distribution does not include one. | |
| 50 SETUPTOOLS = ('setuptools', '0.6c11') | |
| 51 | |
| 52 # These are the packages that are to be installed in the site directory. | |
| 53 # easy_install makes it so that the most recently installed version of a | |
| 54 # package is the one that takes precedence, even if a newer version exists | |
| 55 # in the site directory. This allows us to blindly install these one on top | |
| 56 # of the other without worrying about whats already installed. | |
| 57 # | |
| 58 # NOTE: If we are often rolling these dependencies then users' site | |
| 59 # directories will grow monotonically. We could be purging any orphaned | |
| 60 # packages using the tools provided by pkg_resources. | |
| 61 PACKAGES = (('logilab-common', '0.57.1'), | |
| 62 ('logilab-astng', '0.23.1'), | |
| 63 ('pylint', '0.25.1')) | |
| 64 | |
| 65 # This is the root directory of the depot_tools installation. | |
| 66 ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) | |
| 67 | |
| 68 # This is the path of the site directory we will use. We make this | |
| 69 # python version specific so that this will continue to work even if the | |
| 70 # python version is rolled. | |
| 71 SITE_DIR = os.path.join(ROOT_DIR, 'site-packages-py%s' % sys.version[0:3]) | |
|
Sigurður Ásgeirsson
2011/12/17 15:26:03
format from sys.version_info, perhaps?
chrisha
2012/01/06 21:21:28
Done.
| |
| 72 | |
| 73 # This status file is created the last time PACKAGES were rolled successfully. | |
| 74 # It is used to determine if packages need to be rolled by comparing against | |
| 75 # the age of __file__. | |
| 76 LAST_ROLLED = os.path.join(SITE_DIR, 'last_rolled.txt') | |
| 77 | |
| 78 | |
| 79 class Error(Exception): | |
| 80 """The base class for all module errors.""" | |
| 81 pass | |
| 82 | |
| 83 | |
| 84 class InstallError(Error): | |
| 85 """Thrown if an installation is unable to complete.""" | |
| 86 pass | |
| 87 | |
| 88 | |
| 89 class Package(object): | |
| 90 """A package represents a release of a project. | |
| 91 | |
| 92 We use this as a lightweight version of pkg_resources, allowing us to | |
| 93 perform an endrun around setuptools for the purpose of bootstrapping. Its | |
|
Sigurður Ásgeirsson
2011/12/17 15:26:03
nit: endrun->end-run?
chrisha
2012/01/06 21:21:28
Done.
| |
| 94 functionality is very limited. | |
| 95 | |
| 96 Attributes: | |
| 97 name: the name of the package. | |
| 98 version: the version of the package. | |
| 99 safe_name: the safe name of the package. | |
| 100 safe_version: the safe version string of the package. | |
| 101 file_name: the filename-safe name of the package. | |
| 102 file_version: the filename-safe version string of the package. | |
| 103 """ | |
| 104 | |
| 105 def __init__(self, name, version): | |
| 106 """Initialize this package. | |
| 107 | |
| 108 Args: | |
| 109 name: the name of the package. | |
| 110 version: the version of the package. | |
| 111 """ | |
| 112 self.name = name | |
| 113 self.version = version | |
| 114 self.safe_name = Package._MakeSafeName(self.name) | |
| 115 self.safe_version = Package._MakeSafeVersion(self.version) | |
| 116 self.file_name = Package._MakeSafeForFilename(self.safe_name) | |
| 117 self.file_version = Package._MakeSafeForFilename(self.safe_version) | |
| 118 | |
| 119 @staticmethod | |
| 120 def _MakeSafeName(name): | |
| 121 """Makes a safe package name. | |
| 122 | |
| 123 Returns: | |
| 124 The package name cleaned as per pkg_resources. | |
| 125 """ | |
| 126 return re.sub('[^A-Za-z0-9]+', '-', name) | |
| 127 | |
| 128 @staticmethod | |
| 129 def _MakeSafeVersion(version): | |
| 130 """Makes a safe package version string. | |
| 131 | |
| 132 Returns: | |
| 133 The version string cleaned as per pkg_resources. | |
| 134 """ | |
| 135 version = re.sub('\s+', '.', version) | |
| 136 return re.sub('[^A-Za-z0-9\.]+', '-', version) | |
| 137 | |
| 138 @staticmethod | |
| 139 def _MakeSafeForFilename(safe_name_or_version): | |
| 140 """Makes a safe name or safe version safe to use in a file name. | |
|
M-A Ruel
2011/12/19 21:04:23
Stylistic note;
I'm not a fan of functions with a
chrisha
2012/01/06 21:21:28
Noted ;)
| |
| 141 | |
| 142 Args: | |
| 143 safe_name_or_version: a safe name or string as returned by | |
| 144 GetSafeName() or GetSafeVersion(). | |
| 145 | |
| 146 Returns: | |
| 147 The safe name or version escaped as per pkg_resources. | |
| 148 """ | |
| 149 return re.sub('-', '_', safe_name_or_version) | |
| 150 | |
| 151 def GetAsRequirementString(self): | |
| 152 """Builds an easy_install requirements string representing this package. | |
| 153 | |
| 154 Returns: | |
| 155 A requirement string that can be used with easy_install. | |
| 156 """ | |
| 157 return '%s==%s' % (self.name, self.version) | |
| 158 | |
| 159 def GetFilename(self, extension=None): | |
| 160 """Builds a filename for this package using the setuptools convention. | |
| 161 | |
| 162 The following url discusses the filename format: | |
| 163 | |
| 164 http://svn.python.org/projects/sandbox/trunk/setuptools/doc/formats.txt | |
| 165 | |
| 166 Args: | |
| 167 extension: If None, returns a basename. Otherwise, uses the provided | |
| 168 extension. | |
| 169 | |
| 170 Returns: | |
| 171 The filename for this package according to the setuptools convention. | |
| 172 """ | |
| 173 filename = '%s-%s-py%s' % (self.file_name, self.file_version, | |
| 174 sys.version[0:3]) | |
| 175 | |
| 176 if extension: | |
| 177 if extension[0] != '.': | |
| 178 filename += '.' | |
| 179 filename += extension | |
| 180 | |
| 181 return filename | |
| 182 | |
| 183 def GetPyPiUrl(self, extension): | |
| 184 """Returns the URL where this package lives on PyPI. | |
| 185 | |
| 186 Returns: | |
| 187 A string representing the HTTP URL where this package is hosted at | |
| 188 pypi.python.org. | |
| 189 """ | |
| 190 return 'http://pypi.python.org/packages/2.6/%c/%s/%s' % ( | |
| 191 self.file_name[0], self.file_name, self.GetFilename(extension)) | |
| 192 | |
| 193 def DownloadEgg(self, dest_dir, overwrite=False): | |
| 194 """Downloads the EGG for this URL. | |
| 195 | |
| 196 Args: | |
| 197 dest_dir: The directory where the EGG should be written. | |
| 198 overwite: If True the destination path will be overwritten even if | |
| 199 it already exists. Defaults to False. | |
| 200 | |
| 201 Returns: | |
| 202 The path to the written EGG. | |
| 203 | |
| 204 Raises: | |
| 205 Error: if dest_dir doesn't exist, the EGG is unable to be written, | |
| 206 the URL doesn't exist, or the server returned an error, or the | |
| 207 transmission was interrupted. | |
| 208 """ | |
| 209 if not os.path.exists(dest_dir): | |
| 210 raise Error('Path does not exist: %s' % dest_dir) | |
| 211 | |
| 212 if not os.path.isdir(dest_dir): | |
| 213 raise Error('Path is not a directory: %s' % dest_dir) | |
| 214 | |
| 215 filename = os.path.abspath(os.path.join(dest_dir, self.GetFilename('egg'))) | |
| 216 if os.path.exists(filename): | |
| 217 if os.path.isdir(filename): | |
| 218 raise Error('Path is a directory: %s' % filename) | |
| 219 if not overwrite: | |
| 220 return filename | |
| 221 | |
| 222 url = self.GetPyPiUrl('egg') | |
| 223 | |
| 224 try: | |
| 225 (path, headers) = urllib.urlretrieve(url, filename) | |
| 226 | |
| 227 # If the path where the object was downloaded to does not match the | |
|
M-A Ruel
2011/12/19 21:04:23
That may happen?
chrisha
2012/01/06 21:21:28
There's some mention of caching; if a particular f
| |
| 228 # location to which we wanted to write it, copy it. | |
| 229 if path != filename: | |
| 230 shutil.copyfile(path, filename) | |
| 231 | |
| 232 except IOError, e: | |
| 233 raise Error, sys.exc_info()[1], sys.exc_info()[2] | |
|
M-A Ruel
2011/12/19 21:04:23
Use the new format;
raise Error(sys.exc_info()[1],
chrisha
2012/01/06 21:21:28
That's not quite equivalent. I want to recast the
| |
| 234 except urllib.ContentTooShortError, e: | |
| 235 raise Error, sys.exc_info()[1], sys.exc_info()[2] | |
| 236 | |
| 237 return filename | |
| 238 | |
| 239 | |
| 240 def AddToPythonPath(path): | |
| 241 """Adds the provided path to the head of PYTHONPATH and sys.path. | |
| 242 | |
| 243 Args: | |
| 244 path: the path to add. | |
|
M-A Ruel
2011/12/19 21:04:23
A path is a path, don't document that.
Silly styl
chrisha
2012/01/06 21:21:28
Done.
| |
| 245 """ | |
| 246 if path not in sys.path: | |
| 247 sys.path.insert(0, path) | |
| 248 | |
| 249 paths = os.environ.get('PYTHONPATH', '').split(os.pathsep) | |
| 250 if path not in paths: | |
| 251 paths.insert(0, path) | |
| 252 os.environ['PYTHONPATH'] = os.pathsep.join(paths) | |
| 253 | |
| 254 | |
| 255 def RemoveFromPythonPath(path): | |
| 256 """Removes the provided path from PYTHONPATH and sys.path. | |
| 257 | |
| 258 Args: | |
| 259 path: the path to remove. | |
| 260 """ | |
| 261 def RemoveFromList(paths): | |
| 262 for i in xrange(len(paths), 0, -1): | |
| 263 if paths[i - 1] == path: | |
| 264 paths.pop(i - 1) | |
| 265 | |
| 266 if path in sys.path: | |
| 267 RemoveFromList(sys.path) | |
| 268 | |
| 269 paths = os.environ.get('PYTHONPATH', '').split(os.pathsep) | |
| 270 if path in paths: | |
| 271 RemoveFromList(paths) | |
| 272 os.environ['PYTHONPATH'] = os.pathsep.join(paths) | |
| 273 | |
| 274 | |
| 275 def AddSiteDirectory(path): | |
| 276 """Adds the provided path to the runtime as a site directory. | |
| 277 | |
| 278 Any modules that are in the site directory will be available for importing | |
| 279 after this returns. If modules are added or deleted this must be called | |
| 280 again for the changes to be reflected in the runtime. | |
| 281 | |
| 282 This calls both AddToPythonPath and site.addsitedir. Both are needed to | |
| 283 convince easy_install to treat |path| as a site directory. | |
| 284 | |
| 285 Args: | |
| 286 path: the path of the site directory to add. | |
| 287 """ | |
| 288 AddToPythonPath(path) | |
| 289 site.addsitedir(path) | |
| 290 | |
| 291 | |
| 292 def CreateOrAddSiteDirectory(path): | |
|
Sigurður Ásgeirsson
2011/12/17 15:26:03
Maybe name EnsureSiteDirectory?
chrisha
2012/01/06 21:21:28
Done.
| |
| 293 """Creates and/or adds the provided path to the runtime as a site directory. | |
| 294 | |
| 295 This works like AddSiteDirectory but it will create the directory if it | |
| 296 does not yet exist. | |
| 297 | |
| 298 Args: | |
| 299 path: the path of the site directory to create and/or add. | |
| 300 | |
| 301 Raise: | |
| 302 Error: if the site directory is unable to be created, or if it exist and | |
| 303 is not a directory. | |
| 304 """ | |
| 305 if os.path.exists(path): | |
| 306 if not os.path.isdir(path): | |
| 307 raise Error('Path is not a directory: %s' % path) | |
| 308 else: | |
| 309 try: | |
| 310 os.mkdir(path) | |
| 311 except IOError: | |
| 312 raise Error('Unable to create directory: %s' % path) | |
| 313 | |
| 314 AddSiteDirectory(path) | |
| 315 | |
| 316 | |
| 317 def ModuleIsFromPackage(module, package_path): | |
| 318 """Determines if a module has been imported from a given package. | |
| 319 | |
| 320 Args: | |
| 321 module: the module to test. | |
| 322 package_path: the path to the package to test. | |
| 323 | |
| 324 Returns: | |
| 325 True if |module| has been imported from |package_path|, False otherwise. | |
| 326 """ | |
| 327 m = os.path.abspath(module.__file__) | |
| 328 p = os.path.abspath(package_path) | |
| 329 if len(m) <= len(p): | |
| 330 return False | |
| 331 if m[0:len(p)] != p: | |
| 332 return False | |
| 333 return m[len(p)] == os.sep | |
| 334 | |
| 335 | |
| 336 def _CaptureStdStreams(function, *args, **kwargs): | |
| 337 """Captures stdout and stderr while running the provided function. | |
| 338 | |
| 339 This only works if |function| only accesses sys.stdout and sys.stderr. If | |
| 340 we need more than this we'll have to use subprocess.Popen. | |
| 341 | |
| 342 Args: | |
| 343 function: the function to be called. | |
| 344 args: the arguments to pass to |function|. | |
| 345 kwargs: the keyword arguments to pass to |function|. | |
| 346 """ | |
| 347 string_stdout = cStringIO.StringIO() | |
| 348 string_stderr = cStringIO.StringIO() | |
| 349 orig_stdout = sys.stdout | |
| 350 orig_stderr = sys.stderr | |
| 351 sys.stdout = string_stdout | |
|
M-A Ruel
2011/12/19 21:04:23
No need to name the variables, just
sys.stdout = c
chrisha
2012/01/06 21:21:28
Done.
| |
| 352 sys.stderr = string_stderr | |
| 353 try: | |
| 354 function(*args, **kwargs) | |
|
M-A Ruel
2011/12/19 21:04:23
return function(*args, **kwargs)
chrisha
2012/01/06 21:21:28
Done.
| |
| 355 finally: | |
| 356 sys.stdout = orig_stdout | |
| 357 sys.stderr = orig_stderr | |
| 358 return | |
|
M-A Ruel
2011/12/19 21:04:23
remove
chrisha
2012/01/06 21:21:28
Done.
| |
| 359 | |
| 360 | |
| 361 def InstallPackage(url_or_req, site_dir): | |
| 362 """Installs a package to a site directory. | |
| 363 | |
| 364 |site_dir| must exist and already be an active site directory. setuptools | |
| 365 must in the path. Uses easy_install which may involve a download from | |
| 366 pypi.python.org, so this also requires network access. | |
| 367 | |
| 368 Args: | |
| 369 url_or_req: the package to install, expressed as an URL (may be local), | |
| 370 or a requirement string. | |
| 371 site_dir: the site directory in which to install it. | |
| 372 | |
| 373 Raises: | |
| 374 InstallError: if installation fails for any reason. | |
| 375 """ | |
| 376 args = ['--quiet', '--install-dir', site_dir, '--exclude-scripts', | |
| 377 '--always-unzip', '--no-deps', url_or_req] | |
| 378 | |
| 379 # The easy_install script only calls SystemExit if something goes wrong. | |
| 380 # Otherwise, it falls through returning None. | |
| 381 try: | |
| 382 import setuptools.command.easy_install | |
| 383 _CaptureStdStreams(setuptools.command.easy_install.main, args) | |
| 384 except (ImportError, SystemExit), e: | |
| 385 # Re-raise the error, preserving the stack trace and message. | |
| 386 raise InstallError, sys.exc_info()[1], sys.exc_info()[2] | |
|
M-A Ruel
2011/12/19 21:04:23
same everywhere
| |
| 387 | |
| 388 | |
| 389 def _RunInSubprocess(pycode): | |
| 390 """Launches a python subprocess with the provided code. | |
| 391 | |
| 392 The subprocess will be launched with the same stdout and stderr. The | |
| 393 subprocess will use the same instance of python as is currently running, | |
| 394 passing |pycode| as arguments to this script. |pycode| will be interpreted | |
| 395 as python code in the context of this module. | |
| 396 | |
| 397 Args: | |
| 398 pycode: the statement to execute. | |
| 399 | |
| 400 Returns: | |
| 401 True if the subprocess returned 0, False if it returned an error. | |
| 402 """ | |
| 403 result = subprocess.call([sys.executable, __file__, pycode]) | |
|
M-A Ruel
2011/12/19 21:04:23
return not subprocess.call(...
chrisha
2012/01/06 21:21:28
Done.
| |
| 404 return result == 0 | |
| 405 | |
| 406 | |
| 407 def _LoadSetupToolsFromEggAndInstall(egg_path): | |
| 408 """Loads setuptools from the provided egg, and installs it to SITE_DIR. | |
| 409 | |
| 410 Args: | |
| 411 egg_path: the path to the downloaded egg. | |
| 412 | |
| 413 Returns: | |
| 414 True on success, False on failure. | |
| 415 """ | |
| 416 AddToPythonPath(egg_path) | |
| 417 | |
| 418 try: | |
| 419 # Import setuptools and ensure it comes from the EGG. | |
| 420 import setuptools | |
| 421 if not ModuleIsFromPackage(setuptools, egg_path): | |
| 422 raise ImportError() | |
| 423 except ImportError: | |
| 424 print ' Unable to import downloaded package!' | |
| 425 return False | |
| 426 | |
| 427 try: | |
| 428 print ' Using setuptools to install itself ...' | |
| 429 InstallPackage(egg_path, SITE_DIR) | |
| 430 except InstallError: | |
| 431 print ' Unable to install setuptools!' | |
| 432 return False | |
| 433 | |
| 434 return True | |
| 435 | |
| 436 | |
| 437 def BootstrapSetupTools(): | |
| 438 """Bootstraps the runtime with setuptools. | |
|
M-A Ruel
2011/12/19 21:04:23
It'd be nice if it was documented that it's meant
chrisha
2012/01/06 21:21:28
Done.
| |
| 439 | |
| 440 Will try to import setuptools directly. If not found it will attempt to | |
| 441 download it and load it from there. If the download is successful it will | |
| 442 then use setuptools to install itself in the site directory. | |
| 443 | |
| 444 Returns: | |
| 445 Returns True if 'import setuptools' will succeed, False otherwise. | |
| 446 """ | |
| 447 AddSiteDirectory(SITE_DIR) | |
| 448 | |
| 449 # Check if setuptools is already available. If so, we're done. | |
| 450 try: | |
| 451 import setuptools | |
| 452 return True | |
| 453 except ImportError: | |
| 454 pass | |
| 455 | |
| 456 print 'Bootstrapping setuptools ...' | |
| 457 | |
| 458 CreateOrAddSiteDirectory(SITE_DIR) | |
| 459 | |
| 460 # Download the egg to a temp directory. | |
| 461 dest_dir = tempfile.mkdtemp('depot_tools') | |
| 462 path = None | |
| 463 try: | |
| 464 package = Package(*SETUPTOOLS) | |
| 465 print ' Downloading %s ...' % package.GetFilename() | |
| 466 path = package.DownloadEgg(dest_dir) | |
| 467 except Error: | |
| 468 print ' Download failed!' | |
| 469 return False | |
| 470 | |
| 471 try: | |
| 472 # Load the downloaded egg, and install it to the site directory. Do this | |
| 473 # in a subprocess so as not to pollute this runtime. | |
| 474 pycode = '_LoadSetupToolsFromEggAndInstall(%s)' % repr(path) | |
| 475 if not _RunInSubprocess(pycode): | |
| 476 raise Error | |
| 477 | |
| 478 # Reload our site directory, which should now contain setuptools. | |
| 479 AddSiteDirectory(SITE_DIR) | |
| 480 | |
| 481 # Try and import setupttols | |
| 482 import setuptools | |
| 483 except ImportError: | |
| 484 print ' Unable to import setuptools!' | |
| 485 return False | |
| 486 except Error: | |
| 487 # This happens if RunInSubProcess fails, and the appropriate error has | |
| 488 # already been written. | |
| 489 return False | |
| 490 finally: | |
| 491 # Delete the temp directory. | |
| 492 shutil.rmtree(dest_dir) | |
| 493 | |
| 494 return True | |
| 495 | |
| 496 | |
| 497 def _GetModTime(path): | |
| 498 """Gets the file modification time associated with the given file. | |
| 499 | |
| 500 If the file does not exist, returns 0. | |
| 501 | |
| 502 Args: | |
| 503 path: the file to stat. | |
| 504 | |
| 505 Returns: | |
| 506 The last modification time of |path| in seconds since epoch, or 0 if | |
| 507 |path| does not exist. | |
| 508 """ | |
| 509 try: | |
| 510 stat = os.stat(path) | |
|
M-A Ruel
2011/12/19 21:04:23
return os.stat(path).st_mtime
| |
| 511 return stat.st_mtime | |
| 512 except: | |
| 513 # This error is different depending on the OS. | |
| 514 return 0 | |
| 515 | |
| 516 | |
| 517 def _SiteDirectoryIsUpToDate(): | |
| 518 return _GetModTime(LAST_ROLLED) > _GetModTime(__file__) | |
| 519 | |
| 520 | |
| 521 def UpdateSiteDirectory(): | |
| 522 """Installs the packages from PACKAGES if they are not already installed. | |
| 523 | |
| 524 At this point we must have setuptools in the site directory. | |
| 525 | |
| 526 Returns: | |
| 527 True on success, False otherwise. | |
| 528 """ | |
| 529 if _SiteDirectoryIsUpToDate(): | |
| 530 return True | |
| 531 | |
| 532 try: | |
| 533 AddSiteDirectory(SITE_DIR) | |
| 534 import pkg_resources | |
| 535 | |
| 536 # Determine if any packages actually need installing. | |
| 537 missing_packages = [] | |
| 538 for package in [SETUPTOOLS] + list(PACKAGES): | |
| 539 pkg = Package(*package) | |
| 540 req = pkg.GetAsRequirementString() | |
| 541 | |
| 542 # It may be that this package is already available in the site | |
| 543 # directory. If so, we can skip past it without trying to install it. | |
| 544 dist = pkg_resources.working_set.find( | |
| 545 pkg_resources.Requirement.parse(req)) | |
| 546 if dist: | |
| 547 continue | |
| 548 | |
| 549 missing_packages.append(pkg) | |
| 550 | |
| 551 # Install the missing packages. | |
| 552 if missing_packages: | |
| 553 print 'Updating python packages ...' | |
| 554 for pkg in missing_packages: | |
| 555 print ' Installing %s ...' % pkg.GetFilename() | |
| 556 InstallPackage(pkg.GetAsRequirementString(), SITE_DIR) | |
| 557 | |
| 558 # Touch the status file so we know that we're up to date next time. | |
| 559 open(LAST_ROLLED, 'wb') | |
| 560 except InstallError, e: | |
| 561 print ' Installation failed: %s' % str(e) | |
| 562 return False | |
| 563 | |
| 564 return True | |
| 565 | |
| 566 | |
| 567 def SetupSiteDirectory(): | |
| 568 """Sets up the site directory, bootstrapping setuptools if necessary. | |
| 569 | |
| 570 If this finishes successfully then SITE_DIR will exist and will contain | |
| 571 the appropriate version of setuptools and all of the packages listed in | |
| 572 PACKAGES. | |
| 573 | |
| 574 This is the main workhorse of this module. Calling this will do everything | |
| 575 necessary to ensure that you have the desired packages installed in the | |
| 576 site directory, and the site directory enabled in this process. | |
| 577 | |
| 578 Returns: | |
| 579 True on success, False on failure. | |
| 580 """ | |
| 581 if _SiteDirectoryIsUpToDate(): | |
| 582 AddSiteDirectory(SITE_DIR) | |
| 583 return True | |
| 584 | |
| 585 if not _RunInSubprocess('BootstrapSetupTools()'): | |
| 586 return False | |
| 587 | |
| 588 if not _RunInSubprocess('UpdateSiteDirectory()'): | |
| 589 return False | |
| 590 | |
| 591 # Process the site directory so that the packages within it are available | |
| 592 # for import. | |
| 593 AddSiteDirectory(SITE_DIR) | |
| 594 | |
| 595 return True | |
| 596 | |
| 597 | |
| 598 def CanImportFromSiteDirectory(package_name): | |
| 599 """Determines if the given package can be imported from the site directory. | |
| 600 | |
| 601 Args: | |
| 602 package_name: the name of the package to import. | |
| 603 | |
| 604 Returns: | |
| 605 True if 'import package_name' will succeed and return a module from the | |
| 606 site directory, False otherwise. | |
| 607 """ | |
| 608 try: | |
| 609 exec('import %s' % package_name) | |
|
M-A Ruel
2011/12/19 21:04:23
__import__(package_name)
chrisha
2012/01/06 21:21:28
Well, you learn something new everyday!
Done.
| |
| 610 except ImportError: | |
| 611 return False | |
| 612 | |
| 613 result = False | |
| 614 exec('result = ModuleIsFromPackage(%s, SITE_DIR)' % package_name) | |
|
M-A Ruel
2011/12/19 21:04:23
??
result = ModuleIsFromPackage(package_name, SITE
chrisha
2012/01/06 21:21:28
That's the intended result. Made more clear by usi
| |
| 615 return result | |
| 616 | |
| 617 | |
| 618 def Main(): | |
| 619 """The main entry for the package management script. | |
| 620 | |
| 621 If no arguments are provided simply runs SetupSiteDirectory. If arguments | |
| 622 have been passed we execute the first argument as python code in the | |
| 623 context of this module. This mechanism is used to during the bootstrap | |
| 624 process so that the main instance of Python does not have its runtime | |
| 625 polluted by various intermediate packages and imports. | |
| 626 | |
| 627 Returns: | |
| 628 0 on success, 1 otherwise. | |
| 629 """ | |
| 630 if len(sys.argv) == 2: | |
| 631 result = False | |
|
M-A Ruel
2011/12/19 21:04:23
result = 0
| |
| 632 exec('result = %s' % sys.argv[1]) | |
| 633 return 0 if result else 1 | |
|
M-A Ruel
2011/12/19 21:04:23
return result and have the function return relevan
chrisha
2012/01/06 21:21:28
For consistency, I made all functions return True/
| |
| 634 else: | |
| 635 return 0 if SetupSiteDirectory() else 1 | |
| 636 | |
| 637 | |
| 638 if __name__ == '__main__': | |
| 639 sys.exit(Main()) | |
| OLD | NEW |