bootstrap.py
changeset 16 0aff8587343b
parent 0 8a19e25e39e4
child 217 2775c336f60f
equal deleted inserted replaced
15:5598ac5425de 16:0aff8587343b
    16 Simply run this script in a directory containing a buildout.cfg.
    16 Simply run this script in a directory containing a buildout.cfg.
    17 The script accepts buildout command-line options, so you can
    17 The script accepts buildout command-line options, so you can
    18 use the -c option to specify an alternate configuration file.
    18 use the -c option to specify an alternate configuration file.
    19 """
    19 """
    20 
    20 
    21 import os, shutil, sys, tempfile, urllib, urllib2, subprocess
    21 import os
       
    22 import shutil
       
    23 import sys
       
    24 import tempfile
       
    25 
    22 from optparse import OptionParser
    26 from optparse import OptionParser
    23 
    27 
    24 if sys.platform == 'win32':
    28 tmpeggs = tempfile.mkdtemp()
    25     def quote(c):
       
    26         if ' ' in c:
       
    27             return '"%s"' % c  # work around spawn lamosity on windows
       
    28         else:
       
    29             return c
       
    30 else:
       
    31     quote = str
       
    32 
       
    33 # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
       
    34 stdout, stderr = subprocess.Popen(
       
    35     [sys.executable, '-Sc',
       
    36      'try:\n'
       
    37      '    import ConfigParser\n'
       
    38      'except ImportError:\n'
       
    39      '    print 1\n'
       
    40      'else:\n'
       
    41      '    print 0\n'],
       
    42     stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
       
    43 has_broken_dash_S = bool(int(stdout.strip()))
       
    44 
       
    45 # In order to be more robust in the face of system Pythons, we want to
       
    46 # run without site-packages loaded.  This is somewhat tricky, in
       
    47 # particular because Python 2.6's distutils imports site, so starting
       
    48 # with the -S flag is not sufficient.  However, we'll start with that:
       
    49 if not has_broken_dash_S and 'site' in sys.modules:
       
    50     # We will restart with python -S.
       
    51     args = sys.argv[:]
       
    52     args[0:0] = [sys.executable, '-S']
       
    53     args = map(quote, args)
       
    54     os.execv(sys.executable, args)
       
    55 # Now we are running with -S.  We'll get the clean sys.path, import site
       
    56 # because distutils will do it later, and then reset the path and clean
       
    57 # out any namespace packages from site-packages that might have been
       
    58 # loaded by .pth files.
       
    59 clean_path = sys.path[:]
       
    60 import site  # imported because of its side effects
       
    61 sys.path[:] = clean_path
       
    62 for k, v in sys.modules.items():
       
    63     if k in ('setuptools', 'pkg_resources') or (
       
    64         hasattr(v, '__path__') and
       
    65         len(v.__path__) == 1 and
       
    66         not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
       
    67         # This is a namespace package.  Remove it.
       
    68         sys.modules.pop(k)
       
    69 
       
    70 is_jython = sys.platform.startswith('java')
       
    71 
       
    72 setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
       
    73 distribute_source = 'http://python-distribute.org/distribute_setup.py'
       
    74 
       
    75 
       
    76 # parsing arguments
       
    77 def normalize_to_url(option, opt_str, value, parser):
       
    78     if value:
       
    79         if '://' not in value:  # It doesn't smell like a URL.
       
    80             value = 'file://%s' % (
       
    81                 urllib.pathname2url(
       
    82                     os.path.abspath(os.path.expanduser(value))),)
       
    83         if opt_str == '--download-base' and not value.endswith('/'):
       
    84             # Download base needs a trailing slash to make the world happy.
       
    85             value += '/'
       
    86     else:
       
    87         value = None
       
    88     name = opt_str[2:].replace('-', '_')
       
    89     setattr(parser.values, name, value)
       
    90 
    29 
    91 usage = '''\
    30 usage = '''\
    92 [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
    31 [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
    93 
    32 
    94 Bootstraps a buildout-based project.
    33 Bootstraps a buildout-based project.
    95 
    34 
    96 Simply run this script in a directory containing a buildout.cfg, using the
    35 Simply run this script in a directory containing a buildout.cfg, using the
    97 Python that you want bin/buildout to use.
    36 Python that you want bin/buildout to use.
    98 
    37 
    99 Note that by using --setup-source and --download-base to point to
    38 Note that by using --find-links to point to local resources, you can keep 
   100 local resources, you can keep this script from going over the network.
    39 this script from going over the network.
   101 '''
    40 '''
   102 
    41 
   103 parser = OptionParser(usage=usage)
    42 parser = OptionParser(usage=usage)
   104 parser.add_option("-v", "--version", dest="version",
    43 parser.add_option("-v", "--version", help="use a specific zc.buildout version")
   105                           help="use a specific zc.buildout version")
    44 
   106 parser.add_option("-d", "--distribute",
       
   107                    action="store_true", dest="use_distribute", default=False,
       
   108                    help="Use Distribute rather than Setuptools.")
       
   109 parser.add_option("--setup-source", action="callback", dest="setup_source",
       
   110                   callback=normalize_to_url, nargs=1, type="string",
       
   111                   help=("Specify a URL or file location for the setup file. "
       
   112                         "If you use Setuptools, this will default to " +
       
   113                         setuptools_source + "; if you use Distribute, this "
       
   114                         "will default to " + distribute_source + "."))
       
   115 parser.add_option("--download-base", action="callback", dest="download_base",
       
   116                   callback=normalize_to_url, nargs=1, type="string",
       
   117                   help=("Specify a URL or directory for downloading "
       
   118                         "zc.buildout and either Setuptools or Distribute. "
       
   119                         "Defaults to PyPI."))
       
   120 parser.add_option("--eggs",
       
   121                   help=("Specify a directory for storing eggs.  Defaults to "
       
   122                         "a temporary directory that is deleted when the "
       
   123                         "bootstrap script completes."))
       
   124 parser.add_option("-t", "--accept-buildout-test-releases",
    45 parser.add_option("-t", "--accept-buildout-test-releases",
   125                   dest='accept_buildout_test_releases',
    46                   dest='accept_buildout_test_releases',
   126                   action="store_true", default=False,
    47                   action="store_true", default=False,
   127                   help=("Normally, if you do not specify a --version, the "
    48                   help=("Normally, if you do not specify a --version, the "
   128                         "bootstrap script and buildout gets the newest "
    49                         "bootstrap script and buildout gets the newest "
   129                         "*final* versions of zc.buildout and its recipes and "
    50                         "*final* versions of zc.buildout and its recipes and "
   130                         "extensions for you.  If you use this flag, "
    51                         "extensions for you.  If you use this flag, "
   131                         "bootstrap and buildout will get the newest releases "
    52                         "bootstrap and buildout will get the newest releases "
   132                         "even if they are alphas or betas."))
    53                         "even if they are alphas or betas."))
   133 parser.add_option("-c", None, action="store", dest="config_file",
    54 parser.add_option("-c", "--config-file",
   134                    help=("Specify the path to the buildout configuration "
    55                   help=("Specify the path to the buildout configuration "
   135                          "file to be used."))
    56                         "file to be used."))
       
    57 parser.add_option("-f", "--find-links",
       
    58                   help=("Specify a URL to search for buildout releases"))
       
    59 parser.add_option("--allow-site-packages",
       
    60                   action="store_true", default=False,
       
    61                   help=("Let bootstrap.py use existing site packages"))
       
    62 
   136 
    63 
   137 options, args = parser.parse_args()
    64 options, args = parser.parse_args()
   138 
    65 
   139 if options.eggs:
    66 ######################################################################
   140     eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
    67 # load/install setuptools
   141 else:
       
   142     eggs_dir = tempfile.mkdtemp()
       
   143 
       
   144 if options.setup_source is None:
       
   145     if options.use_distribute:
       
   146         options.setup_source = distribute_source
       
   147     else:
       
   148         options.setup_source = setuptools_source
       
   149 
       
   150 if options.accept_buildout_test_releases:
       
   151     args.insert(0, 'buildout:accept-buildout-test-releases=true')
       
   152 
    68 
   153 try:
    69 try:
   154     import pkg_resources
    70     if options.allow_site_packages:
   155     import setuptools  # A flag.  Sometimes pkg_resources is installed alone.
    71         import setuptools
   156     if not hasattr(pkg_resources, '_distribute'):
    72         import pkg_resources
   157         raise ImportError
    73     from urllib.request import urlopen
   158 except ImportError:
    74 except ImportError:
   159     ez_code = urllib2.urlopen(
    75     from urllib2 import urlopen
   160         options.setup_source).read().replace('\r\n', '\n')
       
   161     ez = {}
       
   162     exec ez_code in ez
       
   163     setup_args = dict(to_dir=eggs_dir, download_delay=0)
       
   164     if options.download_base:
       
   165         setup_args['download_base'] = options.download_base
       
   166     if options.use_distribute:
       
   167         setup_args['no_fake'] = True
       
   168         if sys.version_info[:2] == (2, 4):
       
   169             setup_args['version'] = '0.6.32'
       
   170     ez['use_setuptools'](**setup_args)
       
   171     if 'pkg_resources' in sys.modules:
       
   172         reload(sys.modules['pkg_resources'])
       
   173     import pkg_resources
       
   174     # This does not (always?) update the default working set.  We will
       
   175     # do it.
       
   176     for path in sys.path:
       
   177         if path not in pkg_resources.working_set.entries:
       
   178             pkg_resources.working_set.add_entry(path)
       
   179 
    76 
   180 cmd = [quote(sys.executable),
    77 ez = {}
   181        '-c',
    78 exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
   182        quote('from setuptools.command.easy_install import main; main()'),
       
   183        '-mqNxd',
       
   184        quote(eggs_dir)]
       
   185 
    79 
   186 if not has_broken_dash_S:
    80 if not options.allow_site_packages:
   187     cmd.insert(1, '-S')
    81     # ez_setup imports site, which adds site packages
       
    82     # this will remove them from the path to ensure that incompatible versions 
       
    83     # of setuptools are not in the path
       
    84     import site
       
    85     # inside a virtualenv, there is no 'getsitepackages'. 
       
    86     # We can't remove these reliably
       
    87     if hasattr(site, 'getsitepackages'):
       
    88         for sitepackage_path in site.getsitepackages():
       
    89             sys.path[:] = [x for x in sys.path if sitepackage_path not in x]
   188 
    90 
   189 find_links = options.download_base
    91 setup_args = dict(to_dir=tmpeggs, download_delay=0)
   190 if not find_links:
    92 ez['use_setuptools'](**setup_args)
   191     find_links = os.environ.get('bootstrap-testing-find-links')
    93 import setuptools
   192 if not find_links and options.accept_buildout_test_releases:
    94 import pkg_resources
   193     find_links = 'http://downloads.buildout.org/'
    95 
       
    96 # This does not (always?) update the default working set.  We will
       
    97 # do it.
       
    98 for path in sys.path:
       
    99     if path not in pkg_resources.working_set.entries:
       
   100         pkg_resources.working_set.add_entry(path)
       
   101 
       
   102 ######################################################################
       
   103 # Install buildout
       
   104 
       
   105 ws = pkg_resources.working_set
       
   106 
       
   107 cmd = [sys.executable, '-c',
       
   108        'from setuptools.command.easy_install import main; main()',
       
   109        '-mZqNxd', tmpeggs]
       
   110 
       
   111 find_links = os.environ.get(
       
   112     'bootstrap-testing-find-links',
       
   113     options.find_links or
       
   114     ('http://downloads.buildout.org/'
       
   115      if options.accept_buildout_test_releases else None)
       
   116     )
   194 if find_links:
   117 if find_links:
   195     cmd.extend(['-f', quote(find_links)])
   118     cmd.extend(['-f', find_links])
   196 
   119 
   197 if options.use_distribute:
   120 setuptools_path = ws.find(
   198     setup_requirement = 'distribute'
   121     pkg_resources.Requirement.parse('setuptools')).location
   199 else:
       
   200     setup_requirement = 'setuptools'
       
   201 ws = pkg_resources.working_set
       
   202 setup_requirement_path = ws.find(
       
   203     pkg_resources.Requirement.parse(setup_requirement)).location
       
   204 env = dict(
       
   205     os.environ,
       
   206     PYTHONPATH=setup_requirement_path)
       
   207 
   122 
   208 requirement = 'zc.buildout'
   123 requirement = 'zc.buildout'
   209 version = options.version
   124 version = options.version
   210 if version is None and not options.accept_buildout_test_releases:
   125 if version is None and not options.accept_buildout_test_releases:
   211     # Figure out the most recent final version of zc.buildout.
   126     # Figure out the most recent final version of zc.buildout.
   216         for part in parsed_version:
   131         for part in parsed_version:
   217             if (part[:1] == '*') and (part not in _final_parts):
   132             if (part[:1] == '*') and (part not in _final_parts):
   218                 return False
   133                 return False
   219         return True
   134         return True
   220     index = setuptools.package_index.PackageIndex(
   135     index = setuptools.package_index.PackageIndex(
   221         search_path=[setup_requirement_path])
   136         search_path=[setuptools_path])
   222     if find_links:
   137     if find_links:
   223         index.add_find_links((find_links,))
   138         index.add_find_links((find_links,))
   224     req = pkg_resources.Requirement.parse(requirement)
   139     req = pkg_resources.Requirement.parse(requirement)
   225     if index.obtain(req) is not None:
   140     if index.obtain(req) is not None:
   226         best = []
   141         best = []
   227         bestv = None
   142         bestv = None
   228         for dist in index[req.project_name]:
   143         for dist in index[req.project_name]:
   229             distv = dist.parsed_version
   144             distv = dist.parsed_version
   230             if distv >= pkg_resources.parse_version('2dev'):
       
   231                 continue
       
   232             if _final_version(distv):
   145             if _final_version(distv):
   233                 if bestv is None or distv > bestv:
   146                 if bestv is None or distv > bestv:
   234                     best = [dist]
   147                     best = [dist]
   235                     bestv = distv
   148                     bestv = distv
   236                 elif distv == bestv:
   149                 elif distv == bestv:
   237                     best.append(dist)
   150                     best.append(dist)
   238         if best:
   151         if best:
   239             best.sort()
   152             best.sort()
   240             version = best[-1].version
   153             version = best[-1].version
   241 
       
   242 if version:
   154 if version:
   243     requirement += '=='+version
   155     requirement = '=='.join((requirement, version))
   244 else:
       
   245     requirement += '<2dev'
       
   246 
       
   247 cmd.append(requirement)
   156 cmd.append(requirement)
   248 
   157 
   249 if is_jython:
   158 import subprocess
   250     import subprocess
   159 if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0:
   251     exitcode = subprocess.Popen(cmd, env=env).wait()
   160     raise Exception(
   252 else:  # Windows prefers this, apparently; otherwise we would prefer subprocess
   161         "Failed to execute command:\n%s" % repr(cmd)[1:-1])
   253     exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
       
   254 if exitcode != 0:
       
   255     sys.stdout.flush()
       
   256     sys.stderr.flush()
       
   257     print ("An error occurred when trying to install zc.buildout. "
       
   258            "Look above this message for any errors that "
       
   259            "were output by easy_install.")
       
   260     sys.exit(exitcode)
       
   261 
   162 
   262 ws.add_entry(eggs_dir)
   163 ######################################################################
       
   164 # Import and run buildout
       
   165 
       
   166 ws.add_entry(tmpeggs)
   263 ws.require(requirement)
   167 ws.require(requirement)
   264 import zc.buildout.buildout
   168 import zc.buildout.buildout
   265 
   169 
   266 # If there isn't already a command in the args, add bootstrap
       
   267 if not [a for a in args if '=' not in a]:
   170 if not [a for a in args if '=' not in a]:
   268     args.append('bootstrap')
   171     args.append('bootstrap')
   269 
   172 
   270 
   173 # if -c was provided, we push it back into args for buildout' main function
   271 # if -c was provided, we push it back into args for buildout's main function
       
   272 if options.config_file is not None:
   174 if options.config_file is not None:
   273     args[0:0] = ['-c', options.config_file]
   175     args[0:0] = ['-c', options.config_file]
   274 
   176 
   275 zc.buildout.buildout.main(args)
   177 zc.buildout.buildout.main(args)
   276 if not options.eggs:  # clean up temporary egg directory
   178 shutil.rmtree(tmpeggs)
   277     shutil.rmtree(eggs_dir)