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