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