- Added a built-time check for availability of the Keychain authentication provider...
[jelmer/subvertpy.git] / setup.py
1 #!/usr/bin/env python
2 # Setup file for bzr-svn
3 # Copyright (C) 2005-2008 Jelmer Vernooij <jelmer@samba.org>
4
5 from distutils.core import setup
6 from distutils.extension import Extension
7 from distutils.command.install_lib import install_lib
8 from distutils import log
9 import sys
10 import os
11 import re
12
13 # Build instructions for Windows:
14 # * Install the SVN dev kit ZIP for Windows from
15 #   http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91
16 #   At time of writing, this was svn-win32-1.4.6_dev.zip
17 # * Find the SVN binary ZIP file with the binaries for your dev kit.
18 #   At time of writing, this was svn-win32-1.4.6.zip
19 #   Unzip this in the *same directory* as the dev kit - README.txt will be
20 #   overwritten, but that is all. This is the default location the .ZIP file
21 #   will suggest (ie, the directory embedded in both .zip files are the same)
22 # * Set SVN_DEV to point at this directory.
23 # * Install the APR BDB and INTL packages - see README.txt from the devkit
24 # * Set SVN_BDB and SVN_LIBINTL to point at these dirs.
25 #
26 #  To install into a particular bzr location, use:
27 #  % python setup.py install --install-lib=c:\root\of\bazaar
28
29 class CommandException(Exception):
30     """Encapsulate exit status of apr-config execution"""
31     def __init__(self, msg, cmd, arg, status, val):
32         self.message = msg % (cmd, val)
33         Exception.__init__(self, self.message)
34         self.cmd = cmd
35         self.arg = arg
36         self.status = status
37     def not_found(self):
38         return os.WIFEXITED(self.status) and os.WEXITSTATUS(self.status) == 127
39
40
41 def run_cmd(cmd, arg):
42     """Run specified command with given arguments, handling status"""
43     f = os.popen("'%s' %s" % (cmd, arg))
44     dir = f.read().rstrip("\n")
45     status = f.close()
46     if status is None:
47         return dir
48     if os.WIFEXITED(status):
49         code = os.WEXITSTATUS(status)
50         if code == 0:
51             return dir
52         raise CommandException("%s exited with status %d",
53                                cmd, arg, status, code)
54     if os.WIFSIGNALED(status):
55         signal = os.WTERMSIG(status)
56         raise CommandException("%s killed by signal %d",
57                                cmd, arg, status, signal)
58     raise CommandException("%s terminated abnormally (%d)",
59                            cmd, arg, status, status)
60
61
62 def apr_config(arg):
63     apr_config_cmd = os.getenv("APR_CONFIG")
64     if apr_config_cmd is None:
65         cmds = ["apr-1-config", "/usr/local/apr/bin/apr-1-config", 
66                 "/opt/local/bin/apr-1-config", ]
67         for cmd in cmds:
68             try:
69                 res = run_cmd(cmd, arg)
70                 apr_config_cmd = cmd
71                 break
72             except CommandException, e:
73                 if not e.not_found():
74                     raise
75         else:
76             raise Exception("apr-config not found."
77                             " Please set APR_CONFIG environment variable")
78     else:
79         res = run_cmd(apr_config_cmd, arg)
80     return res
81
82
83 def apr_build_data():
84     """Determine the APR header file location."""
85     includedir = apr_config("--includedir")
86     if not os.path.isdir(includedir):
87         raise Exception("APR development headers not found")
88     return (includedir,)
89
90
91 def svn_build_data():
92     """Determine the Subversion header file location."""
93     if "SVN_HEADER_PATH" in os.environ and "SVN_LIBRARY_PATH" in os.environ:
94         return ([os.getenv("SVN_HEADER_PATH")], [os.getenv("SVN_LIBRARY_PATH")], [])
95     svn_prefix = os.getenv("SVN_PREFIX")
96     if svn_prefix is None:
97         basedirs = ["/usr/local", "/usr"]
98         for basedir in basedirs:
99             includedir = os.path.join(basedir, "include/subversion-1")
100             if os.path.isdir(includedir):
101                 svn_prefix = basedir
102                 break
103     if svn_prefix is not None:
104         return ([os.path.join(svn_prefix, "include/subversion-1")], 
105                 [os.path.join(svn_prefix, "lib")], [])
106     raise Exception("Subversion development files not found. "
107                     "Please set SVN_PREFIX or (SVN_LIBRARY_PATH and SVN_HEADER_PATH) environment variable. ")
108
109 def is_keychain_provider_available():
110     """
111     Checks for the availability of the Keychain simple authentication provider in Subversion by compiler a simple test program.
112     """
113     abd = apr_build_data()
114     sbd = svn_build_data()
115     gcc_command_args = ['-I' + inc for inc in sbd[0]] + ['-L' + lib for lib in sbd[1]] + ['-I' + abd[0], '-lsvn_subr-1', '-o', 'keychain_provider_check', 'keychain_provider_check.c']
116     try:
117         run_cmd('gcc', ' '.join(gcc_command_args))
118     except:
119         return False
120     try:
121         os.unlink('keychain_provider_check')
122     except:
123         pass
124     return True
125
126 class VersionQuery(object):
127     def __init__(self, filename):
128         self.filename = filename
129         f = file(filename, "rU")
130         try:
131             self.text = f.read()
132         finally:
133             f.close()
134
135     def grep(self, what):
136         m = re.search(r"^#define\s+%s\s+(\d+)\s*$" % (what,), self.text, re.MULTILINE)
137         if not m:
138             raise Exception, "Definition for %s was not found in file %s." % (what, self.filename)
139         return int(m.group(1))
140
141 # Windows versions - we use environment variables to locate the directories
142 # and hard-code a list of libraries.
143 if os.name == "nt":
144     def get_apr_version():
145         apr_version_file = os.path.join(os.environ["SVN_DEV"], r"include\apr\apr_version.h")
146         if not os.path.isfile(apr_version_file):
147             raise Exception(
148                 "Please check that your SVN_DEV location is correct.\n"
149                 "Unable to find required apr\\apr_version.h file.")
150         query = VersionQuery(apr_version_file)
151         return query.grep("APR_MAJOR_VERSION"), query.grep("APR_MINOR_VERSION"), query.grep("APR_PATCH_VERSION")
152
153     def get_svn_version():
154         svn_version_file = os.path.join(os.environ["SVN_DEV"], r"include\svn_version.h")
155         if not os.path.isfile(svn_version_file):
156             raise Exception(
157                 "Please check that your SVN_DEV location is correct.\n"
158                 "Unable to find required svn_version.h file.")
159         query = VersionQuery(svn_version_file)
160         return query.grep("SVN_VER_MAJOR"), query.grep("SVN_VER_MINOR"), query.grep("SVN_VER_PATCH")
161
162     # just clobber the functions above we can't use
163     # for simplicitly, everything is done in the 'svn' one
164     def apr_build_data():
165         return '.', 
166
167     def svn_build_data():
168         # environment vars for the directories we need.
169         svn_dev_dir = os.environ.get("SVN_DEV")
170         if not svn_dev_dir or not os.path.isdir(svn_dev_dir):
171             raise Exception(
172                 "Please set SVN_DEV to the location of the svn development "
173                 "packages.\nThese can be downloaded from:\n"
174                 "http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91")
175         svn_bdb_dir = os.environ.get("SVN_BDB")
176         if not svn_bdb_dir or not os.path.isdir(svn_bdb_dir):
177             raise Exception(
178                 "Please set SVN_BDB to the location of the svn BDB packages "
179                 "- see README.txt in the SV_DEV dir")
180         svn_libintl_dir = os.environ.get("SVN_LIBINTL")
181         if not svn_libintl_dir or not os.path.isdir(svn_libintl_dir):
182             raise Exception(
183                 "Please set SVN_LIBINTL to the location of the svn libintl "
184                 "packages - see README.txt in the SV_DEV dir")
185
186         svn_version = get_svn_version()
187         apr_version = get_apr_version()
188
189         includes = [
190             # apr dirs.
191             os.path.join(svn_dev_dir, r"include\apr"),
192             os.path.join(svn_dev_dir, r"include\apr-utils"),
193             os.path.join(svn_dev_dir, r"include\apr-iconv"),
194             # svn dirs.
195             os.path.join(svn_dev_dir, "include"), 
196         ]
197         lib_dirs = [
198             os.path.join(svn_dev_dir, "lib"),
199             os.path.join(svn_dev_dir, "lib", "apr"),
200             os.path.join(svn_dev_dir, "lib", "apr-iconv"),
201             os.path.join(svn_dev_dir, "lib", "apr-util"),
202             os.path.join(svn_dev_dir, "lib", "neon"),
203             os.path.join(svn_bdb_dir, "lib"),
204             os.path.join(svn_libintl_dir, "lib"),
205         ]
206         aprlibs = """libapr libapriconv libaprutil""".split()
207         if apr_version[0] == 1:
208             aprlibs = [aprlib + "-1" for aprlib in aprlibs]
209         elif apr_version[0] > 1:
210             raise Exception(
211                 "You have apr version %d.%d.%d.\n"
212                 "This setup only knows how to build with 0.*.* or 1.*.*." % apr_version)
213         libs = """libneon libsvn_subr-1 libsvn_client-1 libsvn_ra-1
214                   libsvn_ra_dav-1 libsvn_ra_local-1 libsvn_ra_svn-1
215                   libsvn_repos-1 libsvn_wc-1 libsvn_delta-1 libsvn_diff-1
216                   libsvn_fs-1 libsvn_repos-1 libsvn_fs_fs-1 libsvn_fs_base-1
217                   intl3_svn
218                   libdb44 xml
219                   advapi32 shell32 ws2_32 zlibstat
220                """.split()
221         if svn_version >= (1,5,0):
222             # Since 1.5.0 libsvn_ra_dav-1 was removed
223             libs.remove("libsvn_ra_dav-1")
224
225         return includes, lib_dirs, aprlibs+libs,
226
227 (apr_includedir, ) = apr_build_data()
228 (svn_includedirs, svn_libdirs, extra_libs) = svn_build_data()
229
230 def SvnExtension(name, *args, **kwargs):
231     kwargs["include_dirs"] = [apr_includedir] + svn_includedirs
232     kwargs["library_dirs"] = svn_libdirs
233     if os.name == 'nt':
234         # on windows, just ignore and overwrite the libraries!
235         kwargs["libraries"] = extra_libs
236         # APR needs WIN32 defined.
237         kwargs["define_macros"] = [("WIN32", None)]
238     if sys.platform == 'darwin':
239         # on Mac OS X, we need to check for Keychain availability
240         if is_keychain_provider_available():
241             if "define_macros" not in kwargs:
242                 kwargs["define_macros"] = []
243             kwargs["define_macros"].extend((('DARWIN', None), ('BZR_SVN_KEYCHAIN_PROVIDER_AVAILABLE', '1')))
244     return Extension("bzrlib.plugins.svn.%s" % name, *args, **kwargs)
245
246
247 # On Windows, we install the apr binaries too.
248 class install_lib_with_dlls(install_lib):
249     def _get_dlls(self):
250         # return a list of of (FQ-in-name, relative-out-name) tuples.
251         ret = []
252         apr_bins = [libname + ".dll" for libname in extra_libs if libname.startswith("libapr")]
253         if get_svn_version() >= (1,5,0):
254             # Since 1.5.0 these libraries became shared
255             apr_bins += """libsvn_client-1.dll libsvn_delta-1.dll libsvn_diff-1.dll
256                            libsvn_fs-1.dll libsvn_ra-1.dll libsvn_repos-1.dll
257                            libsvn_subr-1.dll libsvn_wc-1.dll libsasl.dll""".split()
258         apr_bins += """intl3_svn.dll libdb44.dll libeay32.dll ssleay32.dll""".split()
259         look_dirs = os.environ.get("PATH","").split(os.pathsep)
260         look_dirs.insert(0, os.path.join(os.environ["SVN_DEV"], "bin"))
261     
262         for bin in apr_bins:
263             for look in look_dirs:
264                 f = os.path.join(look, bin)
265                 if os.path.isfile(f):
266                     target = os.path.join(self.install_dir, "bzrlib",
267                                           "plugins", "svn", bin)
268                     ret.append((f, target))
269                     break
270             else:
271                 log.warn("Could not find required DLL %r to include", bin)
272                 log.debug("(looked in %s)", look_dirs)
273         return ret
274
275     def run(self):
276         install_lib.run(self)
277         # the apr binaries.
278         if os.name == 'nt':
279             # On Windows we package up the apr dlls with the plugin.
280             for s, d in self._get_dlls():
281                 self.copy_file(s, d)
282
283     def get_outputs(self):
284         ret = install_lib.get_outputs()
285         if os.name == 'nt':
286             ret.extend([info[1] for info in self._get_dlls()])
287         return ret
288
289 setup(name='bzr-svn',
290       description='Support for Subversion branches in Bazaar',
291       keywords='plugin bzr svn',
292       version='0.4.13',
293       url='http://bazaar-vcs.org/BzrForeignBranches/Subversion',
294       download_url='http://bazaar-vcs.org/BzrSvn',
295       license='GPL',
296       author='Jelmer Vernooij',
297       author_email='jelmer@samba.org',
298       long_description="""
299       This plugin adds support for branching off and 
300       committing to Subversion repositories from 
301       Bazaar.
302       """,
303       package_dir={'bzrlib.plugins.svn':'.', 
304                    'bzrlib.plugins.svn.tests':'tests'},
305       packages=['bzrlib.plugins.svn', 
306                 'bzrlib.plugins.svn.mapping3', 
307                 'bzrlib.plugins.svn.tests'],
308       ext_modules=[
309           SvnExtension("client", ["client.c", "editor.c", "util.c", "ra.c", "wc.c"], libraries=["svn_client-1", "svn_subr-1"]), 
310           SvnExtension("ra", ["ra.c", "util.c", "editor.c"], libraries=["svn_ra-1", "svn_delta-1", "svn_subr-1"]),
311           SvnExtension("repos", ["repos.c", "util.c"], libraries=["svn_repos-1", "svn_subr-1"]),
312           SvnExtension("wc", ["wc.c", "util.c", "editor.c"], libraries=["svn_wc-1", "svn_subr-1"]),
313           ],
314       cmdclass = { 'install_lib': install_lib_with_dlls },
315       )