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