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