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