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