]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - setup.py
zetuptoolz: don't add ';' to an empty path when appending a new item.
[tahoe-lafs/tahoe-lafs.git] / setup.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import sys; assert sys.version_info < (3,), ur"Tahoe-LAFS does not run under Python 3. Please use a version of Python between 2.6 and 2.7.x inclusive."
4
5 # Tahoe-LAFS -- secure, distributed storage grid
6 #
7 # Copyright © 2006-2012 The Tahoe-LAFS Software Foundation
8 #
9 # This file is part of Tahoe-LAFS.
10 #
11 # See the docs/about.rst file for licensing information.
12
13 import os, stat, subprocess, re
14
15 ##### sys.path management
16
17 def pylibdir(prefixdir):
18     pyver = "python%d.%d" % (sys.version_info[:2])
19     if sys.platform == "win32":
20         return os.path.join(prefixdir, "Lib", "site-packages")
21     else:
22         return os.path.join(prefixdir, "lib", pyver, "site-packages")
23
24 basedir = os.path.dirname(os.path.abspath(__file__))
25 supportlib = pylibdir(os.path.join(basedir, "support"))
26
27 # locate our version number
28
29 def read_version_py(infname):
30     try:
31         verstrline = open(infname, "rt").read()
32     except EnvironmentError:
33         return None
34     else:
35         VSRE = r"^verstr = ['\"]([^'\"]*)['\"]"
36         mo = re.search(VSRE, verstrline, re.M)
37         if mo:
38             return mo.group(1)
39
40 VERSION_PY_FILENAME = 'src/allmydata/_version.py'
41 VERSION_H_FILENAME = 'misc/build_helpers/windows/installer/installer/_version.h'
42
43 version = read_version_py(VERSION_PY_FILENAME)
44
45 APPNAME='allmydata-tahoe'
46 APPNAMEFILE = os.path.join('src', 'allmydata', '_appname.py')
47 APPNAMEFILESTR = "__appname__ = '%s'" % (APPNAME,)
48 try:
49     curappnamefilestr = open(APPNAMEFILE, 'rU').read()
50 except EnvironmentError:
51     # No file, or unreadable or something, okay then let's try to write one.
52     open(APPNAMEFILE, "w").write(APPNAMEFILESTR)
53 else:
54     if curappnamefilestr.strip() != APPNAMEFILESTR:
55         print("Error -- this setup.py file is configured with the 'application name' to be '%s', but there is already a file in place in '%s' which contains the contents '%s'.  If the file is wrong, please remove it and setup.py will regenerate it and write '%s' into it." % (APPNAME, APPNAMEFILE, curappnamefilestr, APPNAMEFILESTR))
56         sys.exit(-1)
57
58 # setuptools/zetuptoolz looks in __main__.__requires__ for a list of
59 # requirements. When running "python setup.py test", __main__ is
60 # setup.py, so we put the list here so that the requirements will be
61 # available for tests:
62
63 # Tahoe's dependencies are managed by the find_links= entry in setup.cfg and
64 # the _auto_deps.install_requires list, which is used in the call to setup()
65 # below.
66 adglobals = {}
67 execfile('src/allmydata/_auto_deps.py', adglobals)
68 install_requires = adglobals['install_requires']
69
70 if len(sys.argv) > 1 and sys.argv[1] == '--fakedependency':
71     del sys.argv[1]
72     install_requires += ["fakedependency >= 1.0.0"]
73
74 __requires__ = install_requires[:]
75
76 egg = os.path.realpath('setuptools-0.6c16dev5.egg')
77 sys.path.insert(0, egg)
78 import setuptools; setuptools.bootstrap_install_from = egg
79
80 from setuptools import setup
81 from setuptools.command import sdist
82 from setuptools import Command
83
84 trove_classifiers=[
85     "Development Status :: 5 - Production/Stable",
86     "Environment :: Console",
87     "Environment :: Web Environment",
88     "License :: OSI Approved :: GNU General Public License (GPL)",
89     "License :: DFSG approved",
90     "License :: Other/Proprietary License",
91     "Intended Audience :: Developers",
92     "Intended Audience :: End Users/Desktop",
93     "Intended Audience :: System Administrators",
94     "Operating System :: Microsoft",
95     "Operating System :: Microsoft :: Windows",
96     "Operating System :: Unix",
97     "Operating System :: POSIX :: Linux",
98     "Operating System :: POSIX",
99     "Operating System :: MacOS :: MacOS X",
100     "Operating System :: OS Independent",
101     "Natural Language :: English",
102     "Programming Language :: C",
103     "Programming Language :: Python",
104     "Programming Language :: Python :: 2",
105     "Programming Language :: Python :: 2.6",
106     "Programming Language :: Python :: 2.7",
107     "Topic :: Utilities",
108     "Topic :: System :: Systems Administration",
109     "Topic :: System :: Filesystems",
110     "Topic :: System :: Distributed Computing",
111     "Topic :: Software Development :: Libraries",
112     "Topic :: System :: Archiving :: Backup",
113     "Topic :: System :: Archiving :: Mirroring",
114     "Topic :: System :: Archiving",
115     ]
116
117
118 setup_requires = []
119
120 # Nevow imports itself when building, which causes Twisted and zope.interface
121 # to be imported. We need to make sure that the versions of Twisted and
122 # zope.interface used at build time satisfy Nevow's requirements. If not
123 # then there are two problems:
124 #  - prior to Nevow v0.9.33, Nevow didn't declare its dependency on Twisted
125 #    in a way that enabled setuptools to satisfy that requirement at
126 #    build time.
127 #  - some versions of zope.interface, e.g. v3.6.4, are incompatible with
128 #    Nevow, and we need to avoid those both at build and run-time.
129 #
130 # This only matters when compatible versions of Twisted and zope.interface
131 # are not already installed. Retire this hack when
132 # https://bugs.launchpad.net/nevow/+bug/812537 has been fixed.
133 setup_requires += [req for req in install_requires if req.startswith('Twisted') or req.startswith('zope.interface')]
134
135 # We no longer have any requirements specific to tests.
136 tests_require=[]
137
138
139 class Trial(Command):
140     description = "run trial (use 'bin%stahoe debug trial' for the full set of trial options)" % (os.sep,)
141     # This is just a subset of the most useful options, for compatibility.
142     user_options = [ ("no-rterrors", None, "Don't print out tracebacks as they occur."),
143                      ("rterrors", "e", "Print out tracebacks as they occur (default, so ignored)."),
144                      ("until-failure", "u", "Repeat a test (specified by -s) until it fails."),
145                      ("reporter=", None, "The reporter to use for this test run."),
146                      ("suite=", "s", "Specify the test suite."),
147                      ("quiet", None, "Don't display version numbers and paths of Tahoe dependencies."),
148                      ("coverage", "c", "Collect branch coverage information."),
149                    ]
150
151     def initialize_options(self):
152         self.rterrors = False
153         self.no_rterrors = False
154         self.until_failure = False
155         self.reporter = None
156         self.suite = "allmydata"
157         self.quiet = False
158         self.coverage = False
159
160     def finalize_options(self):
161         pass
162
163     def run(self):
164         args = [sys.executable, os.path.join('bin', 'tahoe')]
165
166         if self.coverage:
167             from errno import ENOENT
168             coverage_cmd = 'coverage'
169             try:
170                 subprocess.call([coverage_cmd, 'help'])
171             except OSError as e:
172                 if e.errno != ENOENT:
173                     raise
174                 coverage_cmd = 'python-coverage'
175                 try:
176                     rc = subprocess.call([coverage_cmd, 'help'])
177                 except OSError as e:
178                     if e.errno != ENOENT:
179                         raise
180                     print >>sys.stderr
181                     print >>sys.stderr, "Couldn't find the command 'coverage' nor 'python-coverage'."
182                     print >>sys.stderr, "coverage can be installed using 'pip install coverage', or on Debian-based systems, 'apt-get install python-coverage'."
183                     sys.exit(1)
184
185             args += ['@' + coverage_cmd, 'run', '--branch', '--source=src/allmydata', '@tahoe']
186
187         if not self.quiet:
188             args.append('--version-and-path')
189         args += ['debug', 'trial']
190         if self.rterrors and self.no_rterrors:
191             raise AssertionError("--rterrors and --no-rterrors conflict.")
192         if not self.no_rterrors:
193             args.append('--rterrors')
194         if self.until_failure:
195             args.append('--until-failure')
196         if self.reporter:
197             args.append('--reporter=' + self.reporter)
198         if self.suite:
199             args.append(self.suite)
200         rc = subprocess.call(args)
201         sys.exit(rc)
202
203
204 class MakeExecutable(Command):
205     description = "make the 'bin%stahoe' scripts" % (os.sep,)
206     user_options = []
207
208     def initialize_options(self):
209         pass
210     def finalize_options(self):
211         pass
212     def run(self):
213         bin_tahoe_template = os.path.join("bin", "tahoe-script.template")
214
215         # tahoe.pyscript is really only necessary for Windows, but we also
216         # create it on Unix for consistency.
217         script_names = ["tahoe.pyscript", "tahoe"]
218
219         # Create the tahoe script file under the 'bin' directory. This
220         # file is exactly the same as the 'tahoe-script.template' script
221         # except that the shebang line is rewritten to use our sys.executable
222         # for the interpreter.
223         f = open(bin_tahoe_template, "rU")
224         script_lines = f.readlines()
225         f.close()
226         script_lines[0] = '#!%s\n' % (sys.executable,)
227         for script_name in script_names:
228             tahoe_script = os.path.join("bin", script_name)
229             try:
230                 os.remove(tahoe_script)
231             except Exception:
232                 if os.path.exists(tahoe_script):
233                    raise
234             f = open(tahoe_script, "wb")
235             for line in script_lines:
236                 f.write(line)
237             f.close()
238
239         # chmod +x
240         unix_script = os.path.join("bin", "tahoe")
241         old_mode = stat.S_IMODE(os.stat(unix_script)[stat.ST_MODE])
242         new_mode = old_mode | (stat.S_IXUSR | stat.S_IRUSR |
243                                stat.S_IXGRP | stat.S_IRGRP |
244                                stat.S_IXOTH | stat.S_IROTH )
245         os.chmod(unix_script, new_mode)
246
247         old_tahoe_exe = os.path.join("bin", "tahoe.exe")
248         try:
249             os.remove(old_tahoe_exe)
250         except Exception:
251             if os.path.exists(old_tahoe_exe):
252                 raise
253
254
255 GIT_VERSION_PY = '''
256 # This _version.py is generated from git metadata by the tahoe setup.py.
257
258 __pkgname__ = %(pkgname)r
259 real_version = %(version)r
260 full_version = %(full)r
261 branch = %(branch)r
262 verstr = %(normalized)r
263 __version__ = verstr
264 '''
265
266 GIT_VERSION_H = '''
267 // This _version.h is generated from git metadata by the tahoe setup.py.
268
269 #define PKGNAME_AND_VERSION L"%(pkgname)s-%(normalized)s"
270 '''
271
272 def run_command(args, cwd=None):
273     try:
274         # remember shell=False, so use git.cmd on windows, not just git
275         p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd)
276     except EnvironmentError as e:  # if this gives a SyntaxError, note that Tahoe-LAFS requires Python 2.6+
277         print("Warning: unable to run %r." % (" ".join(args),))
278         print(e)
279         return None
280     stdout = p.communicate()[0].strip()
281     if p.returncode != 0:
282         print("Warning: %r returned error code %r." % (" ".join(args), p.returncode))
283         return None
284     return stdout
285
286
287 def versions_from_git(tag_prefix):
288     # This runs 'git' from the directory that contains this file. That either
289     # means someone ran a setup.py command (and this code is in
290     # versioneer.py, thus the containing directory is the root of the source
291     # tree), or someone ran a project-specific entry point (and this code is
292     # in _version.py, thus the containing directory is somewhere deeper in
293     # the source tree). This only gets called if the git-archive 'subst'
294     # variables were *not* expanded, and _version.py hasn't already been
295     # rewritten with a short version string, meaning we're inside a checked
296     # out source tree.
297
298     # versions_from_git (as copied from python-versioneer) returns strings
299     # like "1.9.0-25-gb73aba9-dirty", which means we're in a tree with
300     # uncommited changes (-dirty), the latest checkin is revision b73aba9,
301     # the most recent tag was 1.9.0, and b73aba9 has 25 commits that weren't
302     # in 1.9.0 . The narrow-minded NormalizedVersion parser that takes our
303     # output (meant to enable sorting of version strings) refuses most of
304     # that. Tahoe uses a function named suggest_normalized_version() that can
305     # handle "1.9.0.post25", so dumb down our output to match.
306
307     try:
308         source_dir = os.path.dirname(os.path.abspath(__file__))
309     except NameError as e:
310         # some py2exe/bbfreeze/non-CPython implementations don't do __file__
311         print("Warning: unable to find version because we could not obtain the source directory.")
312         print(e)
313         return {}
314     GIT = "git"
315     if sys.platform == "win32":
316         GIT = "git.cmd"
317     stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"],
318                          cwd=source_dir)
319     if stdout is None:
320         # run_command already complained.
321         return {}
322     if not stdout.startswith(tag_prefix):
323         print("Warning: tag %r doesn't start with prefix %r." % (stdout, tag_prefix))
324         return {}
325     version = stdout[len(tag_prefix):]
326     pieces = version.split("-")
327     if len(pieces) == 1:
328         normalized_version = pieces[0]
329     else:
330         normalized_version = "%s.post%s" % (pieces[0], pieces[1])
331
332     stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=source_dir)
333     if stdout is None:
334         # run_command already complained.
335         return {}
336     full = stdout.strip()
337     if version.endswith("-dirty"):
338         full += "-dirty"
339         normalized_version += ".dev0"
340
341     # Thanks to Jistanidiot at <http://stackoverflow.com/questions/6245570/get-current-branch-name>.
342     stdout = run_command([GIT, "rev-parse", "--abbrev-ref", "HEAD"], cwd=source_dir)
343     branch = (stdout or "unknown").strip()
344
345     return {"version": version, "normalized": normalized_version, "full": full, "branch": branch}
346
347 # setup.cfg has an [aliases] section which runs "update_version" before many
348 # commands (like "build" and "sdist") that need to know our package version
349 # ahead of time. If you add different commands (or if we forgot some), you
350 # may need to add it to setup.cfg and configure it to run update_version
351 # before your command.
352
353 class UpdateVersion(Command):
354     description = "update _version.py from revision-control metadata"
355     user_options = []
356
357     def initialize_options(self):
358         pass
359     def finalize_options(self):
360         pass
361     def run(self):
362         global version
363         verstr = version
364         if os.path.isdir(os.path.join(basedir, ".git")):
365             verstr = self.try_from_git()
366
367         if verstr:
368             self.distribution.metadata.version = verstr
369         else:
370             print("""\
371 ********************************************************************
372 Warning: no version information found. This may cause tests to fail.
373 ********************************************************************
374 """)
375
376     def try_from_git(self):
377         # If we change APPNAME, the release tag names should also change from then on.
378         versions = versions_from_git(APPNAME + '-')
379         if versions:
380             version_info = {
381                 "pkgname": self.distribution.get_name(),
382                 "version": versions["version"],
383                 "normalized": versions["normalized"],
384                 "full": versions["full"],
385                 "branch": versions["branch"],
386             }
387
388             f = open(VERSION_PY_FILENAME, "wb")
389             f.write(GIT_VERSION_PY % version_info)
390             f.close()
391             print("Wrote normalized version %r into '%s'" % (versions["normalized"], VERSION_PY_FILENAME))
392
393             f = open(VERSION_H_FILENAME, "wb")
394             f.write(GIT_VERSION_H % version_info)
395             f.close()
396
397         return versions.get("normalized", None)
398
399
400 class MySdist(sdist.sdist):
401     """ A hook in the sdist command so that we can determine whether this the
402     tarball should be 'SUMO' or not, i.e. whether or not to include the
403     external dependency tarballs. Note that we always include
404     misc/dependencies/* in the tarball; --sumo controls whether tahoe-deps/*
405     is included as well.
406     """
407
408     user_options = sdist.sdist.user_options + \
409         [('sumo', 's',
410           "create a 'sumo' sdist which includes the contents of tahoe-deps/*"),
411          ]
412     boolean_options = ['sumo']
413
414     def initialize_options(self):
415         sdist.sdist.initialize_options(self)
416         self.sumo = False
417
418     def make_distribution(self):
419         # add our extra files to the list just before building the
420         # tarball/zipfile. We override make_distribution() instead of run()
421         # because setuptools.command.sdist.run() does not lend itself to
422         # easy/robust subclassing (the code we need to add goes right smack
423         # in the middle of a 12-line method). If this were the distutils
424         # version, we'd override get_file_list().
425
426         if self.sumo:
427             # If '--sumo' was specified, include tahoe-deps/* in the sdist.
428             # We assume that the user has fetched the tahoe-deps.tar.gz
429             # tarball and unpacked it already.
430             self.filelist.extend([os.path.join("tahoe-deps", fn)
431                                   for fn in os.listdir("tahoe-deps")])
432             # In addition, we want the tarball/zipfile to have -SUMO in the
433             # name, and the unpacked directory to have -SUMO too. The easiest
434             # way to do this is to patch self.distribution and override the
435             # get_fullname() method. (an alternative is to modify
436             # self.distribution.metadata.version, but that also affects the
437             # contents of PKG-INFO).
438             fullname = self.distribution.get_fullname()
439             def get_fullname():
440                 return fullname + "-SUMO"
441             self.distribution.get_fullname = get_fullname
442
443         try:
444             old_mask = os.umask(int("022", 8))
445             return sdist.sdist.make_distribution(self)
446         finally:
447             os.umask(old_mask)
448
449
450 setup_args = {}
451 if version:
452     setup_args["version"] = version
453
454 setup(name=APPNAME,
455       description='secure, decentralized, fault-tolerant filesystem',
456       long_description=open('README.rst', 'rU').read(),
457       author='the Tahoe-LAFS project',
458       author_email='tahoe-dev@tahoe-lafs.org',
459       url='https://tahoe-lafs.org/',
460       license='GNU GPL', # see README.rst -- there is an alternative licence
461       cmdclass={"trial": Trial,
462                 "make_executable": MakeExecutable,
463                 "update_version": UpdateVersion,
464                 "sdist": MySdist,
465                 },
466       package_dir = {'':'src'},
467       packages=['allmydata',
468                 'allmydata.frontends',
469                 'allmydata.immutable',
470                 'allmydata.immutable.downloader',
471                 'allmydata.introducer',
472                 'allmydata.mutable',
473                 'allmydata.scripts',
474                 'allmydata.storage',
475                 'allmydata.test',
476                 'allmydata.util',
477                 'allmydata.web',
478                 'allmydata.windows',
479                 'buildtest'],
480       classifiers=trove_classifiers,
481       test_suite="allmydata.test",
482       install_requires=install_requires,
483       tests_require=tests_require,
484       package_data={"allmydata.web": ["*.xhtml",
485                                       "static/*.js", "static/*.png", "static/*.css",
486                                       "static/img/*.png",
487                                       "static/css/*.css",
488                                       ]
489                     },
490       setup_requires=setup_requires,
491       entry_points = { 'console_scripts': [ 'tahoe = allmydata.scripts.runner:run' ] },
492       zip_safe=False, # We prefer unzipped for easier access.
493       **setup_args
494       )