Skip to content

Commit 15ba96b

Browse files
committedOct 3, 2013
Add auto installation of setuptools if not already present or old version is present.
1 parent c653fe3 commit 15ba96b

File tree

2 files changed

+374
-0
lines changed

2 files changed

+374
-0
lines changed
 

‎ez_setup.py

+370
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
#!python
2+
"""Bootstrap setuptools installation
3+
4+
If you want to use setuptools in your package's setup.py, just include this
5+
file in the same directory with it, and add this to the top of your setup.py::
6+
7+
from ez_setup import use_setuptools
8+
use_setuptools()
9+
10+
If you want to require a specific version of setuptools, set a download
11+
mirror, or use an alternate download directory, you can do so by supplying
12+
the appropriate options to ``use_setuptools()``.
13+
14+
This file can also be run as a script to install or upgrade setuptools.
15+
"""
16+
import os
17+
import shutil
18+
import sys
19+
import tempfile
20+
import tarfile
21+
import optparse
22+
import subprocess
23+
import platform
24+
25+
from distutils import log
26+
27+
try:
28+
from site import USER_SITE
29+
except ImportError:
30+
USER_SITE = None
31+
32+
DEFAULT_VERSION = "1.1.6"
33+
DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
34+
35+
def _python_cmd(*args):
36+
args = (sys.executable,) + args
37+
return subprocess.call(args) == 0
38+
39+
def _check_call_py24(cmd, *args, **kwargs):
40+
res = subprocess.call(cmd, *args, **kwargs)
41+
class CalledProcessError(Exception):
42+
pass
43+
if not res == 0:
44+
msg = "Command '%s' return non-zero exit status %d" % (cmd, res)
45+
raise CalledProcessError(msg)
46+
vars(subprocess).setdefault('check_call', _check_call_py24)
47+
48+
def _install(tarball, install_args=()):
49+
# extracting the tarball
50+
tmpdir = tempfile.mkdtemp()
51+
log.warn('Extracting in %s', tmpdir)
52+
old_wd = os.getcwd()
53+
try:
54+
os.chdir(tmpdir)
55+
tar = tarfile.open(tarball)
56+
_extractall(tar)
57+
tar.close()
58+
59+
# going in the directory
60+
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
61+
os.chdir(subdir)
62+
log.warn('Now working in %s', subdir)
63+
64+
# installing
65+
log.warn('Installing Setuptools')
66+
if not _python_cmd('setup.py', 'install', *install_args):
67+
log.warn('Something went wrong during the installation.')
68+
log.warn('See the error message above.')
69+
# exitcode will be 2
70+
return 2
71+
finally:
72+
os.chdir(old_wd)
73+
shutil.rmtree(tmpdir)
74+
75+
76+
def _build_egg(egg, tarball, to_dir):
77+
# extracting the tarball
78+
tmpdir = tempfile.mkdtemp()
79+
log.warn('Extracting in %s', tmpdir)
80+
old_wd = os.getcwd()
81+
try:
82+
os.chdir(tmpdir)
83+
tar = tarfile.open(tarball)
84+
_extractall(tar)
85+
tar.close()
86+
87+
# going in the directory
88+
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
89+
os.chdir(subdir)
90+
log.warn('Now working in %s', subdir)
91+
92+
# building an egg
93+
log.warn('Building a Setuptools egg in %s', to_dir)
94+
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
95+
96+
finally:
97+
os.chdir(old_wd)
98+
shutil.rmtree(tmpdir)
99+
# returning the result
100+
log.warn(egg)
101+
if not os.path.exists(egg):
102+
raise IOError('Could not build the egg.')
103+
104+
105+
def _do_download(version, download_base, to_dir, download_delay):
106+
egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
107+
% (version, sys.version_info[0], sys.version_info[1]))
108+
if not os.path.exists(egg):
109+
tarball = download_setuptools(version, download_base,
110+
to_dir, download_delay)
111+
_build_egg(egg, tarball, to_dir)
112+
sys.path.insert(0, egg)
113+
114+
# Remove previously-imported pkg_resources if present (see
115+
# https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
116+
if 'pkg_resources' in sys.modules:
117+
del sys.modules['pkg_resources']
118+
119+
import setuptools
120+
setuptools.bootstrap_install_from = egg
121+
122+
123+
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
124+
to_dir=os.curdir, download_delay=15):
125+
# making sure we use the absolute path
126+
to_dir = os.path.abspath(to_dir)
127+
was_imported = 'pkg_resources' in sys.modules or \
128+
'setuptools' in sys.modules
129+
try:
130+
import pkg_resources
131+
except ImportError:
132+
return _do_download(version, download_base, to_dir, download_delay)
133+
try:
134+
pkg_resources.require("setuptools>=" + version)
135+
return
136+
except pkg_resources.VersionConflict:
137+
e = sys.exc_info()[1]
138+
if was_imported:
139+
sys.stderr.write(
140+
"The required version of setuptools (>=%s) is not available,\n"
141+
"and can't be installed while this script is running. Please\n"
142+
"install a more recent version first, using\n"
143+
"'easy_install -U setuptools'."
144+
"\n\n(Currently using %r)\n" % (version, e.args[0]))
145+
sys.exit(2)
146+
else:
147+
del pkg_resources, sys.modules['pkg_resources'] # reload ok
148+
return _do_download(version, download_base, to_dir,
149+
download_delay)
150+
except pkg_resources.DistributionNotFound:
151+
return _do_download(version, download_base, to_dir,
152+
download_delay)
153+
154+
def download_file_powershell(url, target):
155+
"""
156+
Download the file at url to target using Powershell (which will validate
157+
trust). Raise an exception if the command cannot complete.
158+
"""
159+
target = os.path.abspath(target)
160+
cmd = [
161+
'powershell',
162+
'-Command',
163+
"(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(),
164+
]
165+
subprocess.check_call(cmd)
166+
167+
def has_powershell():
168+
if platform.system() != 'Windows':
169+
return False
170+
cmd = ['powershell', '-Command', 'echo test']
171+
devnull = open(os.path.devnull, 'wb')
172+
try:
173+
try:
174+
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
175+
except:
176+
return False
177+
finally:
178+
devnull.close()
179+
return True
180+
181+
download_file_powershell.viable = has_powershell
182+
183+
def download_file_curl(url, target):
184+
cmd = ['curl', url, '--silent', '--output', target]
185+
subprocess.check_call(cmd)
186+
187+
def has_curl():
188+
cmd = ['curl', '--version']
189+
devnull = open(os.path.devnull, 'wb')
190+
try:
191+
try:
192+
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
193+
except:
194+
return False
195+
finally:
196+
devnull.close()
197+
return True
198+
199+
download_file_curl.viable = has_curl
200+
201+
def download_file_wget(url, target):
202+
cmd = ['wget', url, '--quiet', '--output-document', target]
203+
subprocess.check_call(cmd)
204+
205+
def has_wget():
206+
cmd = ['wget', '--version']
207+
devnull = open(os.path.devnull, 'wb')
208+
try:
209+
try:
210+
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
211+
except:
212+
return False
213+
finally:
214+
devnull.close()
215+
return True
216+
217+
download_file_wget.viable = has_wget
218+
219+
def download_file_insecure(url, target):
220+
"""
221+
Use Python to download the file, even though it cannot authenticate the
222+
connection.
223+
"""
224+
try:
225+
from urllib.request import urlopen
226+
except ImportError:
227+
from urllib2 import urlopen
228+
src = dst = None
229+
try:
230+
src = urlopen(url)
231+
# Read/write all in one block, so we don't create a corrupt file
232+
# if the download is interrupted.
233+
data = src.read()
234+
dst = open(target, "wb")
235+
dst.write(data)
236+
finally:
237+
if src:
238+
src.close()
239+
if dst:
240+
dst.close()
241+
242+
download_file_insecure.viable = lambda: True
243+
244+
def get_best_downloader():
245+
downloaders = [
246+
download_file_powershell,
247+
download_file_curl,
248+
download_file_wget,
249+
download_file_insecure,
250+
]
251+
252+
for dl in downloaders:
253+
if dl.viable():
254+
return dl
255+
256+
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
257+
to_dir=os.curdir, delay=15,
258+
downloader_factory=get_best_downloader):
259+
"""Download setuptools from a specified location and return its filename
260+
261+
`version` should be a valid setuptools version number that is available
262+
as an egg for download under the `download_base` URL (which should end
263+
with a '/'). `to_dir` is the directory where the egg will be downloaded.
264+
`delay` is the number of seconds to pause before an actual download
265+
attempt.
266+
267+
``downloader_factory`` should be a function taking no arguments and
268+
returning a function for downloading a URL to a target.
269+
"""
270+
# making sure we use the absolute path
271+
to_dir = os.path.abspath(to_dir)
272+
tgz_name = "setuptools-%s.tar.gz" % version
273+
url = download_base + tgz_name
274+
saveto = os.path.join(to_dir, tgz_name)
275+
if not os.path.exists(saveto): # Avoid repeated downloads
276+
log.warn("Downloading %s", url)
277+
downloader = downloader_factory()
278+
downloader(url, saveto)
279+
return os.path.realpath(saveto)
280+
281+
282+
def _extractall(self, path=".", members=None):
283+
"""Extract all members from the archive to the current working
284+
directory and set owner, modification time and permissions on
285+
directories afterwards. `path' specifies a different directory
286+
to extract to. `members' is optional and must be a subset of the
287+
list returned by getmembers().
288+
"""
289+
import copy
290+
import operator
291+
from tarfile import ExtractError
292+
directories = []
293+
294+
if members is None:
295+
members = self
296+
297+
for tarinfo in members:
298+
if tarinfo.isdir():
299+
# Extract directories with a safe mode.
300+
directories.append(tarinfo)
301+
tarinfo = copy.copy(tarinfo)
302+
tarinfo.mode = 448 # decimal for oct 0700
303+
self.extract(tarinfo, path)
304+
305+
# Reverse sort directories.
306+
if sys.version_info < (2, 4):
307+
def sorter(dir1, dir2):
308+
return cmp(dir1.name, dir2.name)
309+
directories.sort(sorter)
310+
directories.reverse()
311+
else:
312+
directories.sort(key=operator.attrgetter('name'), reverse=True)
313+
314+
# Set correct owner, mtime and filemode on directories.
315+
for tarinfo in directories:
316+
dirpath = os.path.join(path, tarinfo.name)
317+
try:
318+
self.chown(tarinfo, dirpath)
319+
self.utime(tarinfo, dirpath)
320+
self.chmod(tarinfo, dirpath)
321+
except ExtractError:
322+
e = sys.exc_info()[1]
323+
if self.errorlevel > 1:
324+
raise
325+
else:
326+
self._dbg(1, "tarfile: %s" % e)
327+
328+
329+
def _build_install_args(options):
330+
"""
331+
Build the arguments to 'python setup.py install' on the setuptools package
332+
"""
333+
install_args = []
334+
if options.user_install:
335+
if sys.version_info < (2, 6):
336+
log.warn("--user requires Python 2.6 or later")
337+
raise SystemExit(1)
338+
install_args.append('--user')
339+
return install_args
340+
341+
def _parse_args():
342+
"""
343+
Parse the command line for options
344+
"""
345+
parser = optparse.OptionParser()
346+
parser.add_option(
347+
'--user', dest='user_install', action='store_true', default=False,
348+
help='install in user site package (requires Python 2.6 or later)')
349+
parser.add_option(
350+
'--download-base', dest='download_base', metavar="URL",
351+
default=DEFAULT_URL,
352+
help='alternative URL from where to download the setuptools package')
353+
parser.add_option(
354+
'--insecure', dest='downloader_factory', action='store_const',
355+
const=lambda: download_file_insecure, default=get_best_downloader,
356+
help='Use internal, non-validating downloader'
357+
)
358+
options, args = parser.parse_args()
359+
# positional arguments are ignored
360+
return options
361+
362+
def main(version=DEFAULT_VERSION):
363+
"""Install or upgrade setuptools and EasyInstall"""
364+
options = _parse_args()
365+
tarball = download_setuptools(download_base=options.download_base,
366+
downloader_factory=options.downloader_factory)
367+
return _install(tarball, _build_install_args(options))
368+
369+
if __name__ == '__main__':
370+
sys.exit(main())

‎setup.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# coding=utf-8
22

3+
# Make sure setup tools is installed, if not install it.
4+
from ez_setup import use_setuptools
5+
use_setuptools()
6+
37
from setuptools import setup
48

59
import sys, os

0 commit comments

Comments
 (0)
Please sign in to comment.