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 |