add build_sqlcipher option
[pysqlcipher.git] / setup.py
1 #-*- coding: ISO-8859-1 -*-
2 # setup.py: the distutils script
3 #
4 # Copyright (C) 2013 Kali Kaneko <kali@futeisha.org> (sqlcipher support)
5 # Copyright (C) 2005-2010 Gerhard Häring <gh@ghaering.de>
6 #
7 # This file is part of pysqlcipher.
8 #
9 # This software is provided 'as-is', without any express or implied
10 # warranty.  In no event will the authors be held liable for any damages
11 # arising from the use of this software.
12 #
13 # Permission is granted to anyone to use this software for any purpose,
14 # including commercial applications, and to alter it and redistribute it
15 # freely, subject to the following restrictions:
16 #
17 # 1. The origin of this software must not be misrepresented; you must not
18 #    claim that you wrote the original software. If you use this software
19 #    in a product, an acknowledgment in the product documentation would be
20 #    appreciated but is not required.
21 # 2. Altered source versions must be plainly marked as such, and must not be
22 #    misrepresented as being the original software.
23 # 3. This notice may not be removed or altered from any source distribution.
24
25 import glob
26 import os
27 import re
28 import sys
29 import urllib
30 import zipfile
31
32 from types import ListType, TupleType
33
34 from distutils.core import setup, Extension, Command
35 #from setuptools import setup, Extension, Command
36 from distutils.command.build import build
37 from distutils.command.build_ext import build_ext
38 from distutils.dep_util import newer_group
39 from distutils.errors import DistutilsSetupError
40 from distutils import log
41
42 import cross_bdist_wininst
43
44 # If you need to change anything, it should be enough to change setup.cfg.
45
46 sqlite = "sqlite"
47
48 PYSQLITE_EXPERIMENTAL = False
49
50 #DEV_VERSION = None
51 DEV_VERSION = "1"
52
53 sources = ["src/module.c", "src/connection.c", "src/cursor.c", "src/cache.c",
54            "src/microprotocols.c", "src/prepare_protocol.c", "src/statement.c",
55            "src/util.c", "src/row.c"]
56
57 if PYSQLITE_EXPERIMENTAL:
58     sources.append("src/backup.c")
59
60 include_dirs = []
61 library_dirs = []
62 libraries = []
63 runtime_library_dirs = []
64 extra_objects = []
65 define_macros = []
66
67 long_description = \
68 """Python interface to SQLCipher
69
70 pysqlcipher is an interface to the SQLite 3.x embedded relational
71 database engine. It is almost fully compliant with the Python database API
72 version 2.0. At the same time, it also exposes the unique features of
73 SQLCipher."""
74
75 if sys.platform != "win32":
76     define_macros.append(('MODULE_NAME', '"pysqlcipher.dbapi2"'))
77 else:
78     define_macros.append(('MODULE_NAME', '\\"pysqlcipher.dbapi2\\"'))
79
80
81 class DocBuilder(Command):
82     description = "Builds the documentation"
83     user_options = []
84
85     def initialize_options(self):
86         pass
87
88     def finalize_options(self):
89         pass
90
91     def run(self):
92         import shutil
93         try:
94             shutil.rmtree("build/doc")
95         except OSError:
96             pass
97         os.makedirs("build/doc")
98         rc = os.system("sphinx-build doc/sphinx build/doc")
99         if rc != 0:
100             print ("Is sphinx installed? If not, "
101                    "try 'sudo easy_install sphinx'.")
102
103 AMALGAMATION_ROOT = "amalgamation"
104
105
106 def get_amalgamation():
107     """Download the SQLite amalgamation if it isn't there, already."""
108     if os.path.exists(AMALGAMATION_ROOT):
109         return
110     os.mkdir(AMALGAMATION_ROOT)
111     print "Downloading amalgation."
112
113     # XXX upload the amalgamation file to downloads.leap.se
114     amalgamation_url = ("http://futeisha.org/sqlcipher/"
115                         "amalgamation-sqlcipher-2.1.0.zip")
116
117     # and download it
118     print 'amalgamation url: %s' % (amalgamation_url,)
119     urllib.urlretrieve(amalgamation_url, "tmp.zip")
120
121     zf = zipfile.ZipFile("tmp.zip")
122     files = ["sqlite3.c", "sqlite3.h"]
123     directory = zf.namelist()[0]
124
125     for fn in files:
126         print "Extracting", fn
127         outf = open(AMALGAMATION_ROOT + os.sep + fn, "wb")
128         outf.write(zf.read(directory + fn))
129         outf.close()
130     zf.close()
131     os.unlink("tmp.zip")
132
133
134 class AmalgamationBuilder(build):
135     description = ("Build a statically built pysqlcipher "
136                    "downloading and using a sqlcipher amalgamation.")
137
138     def __init__(self, *args, **kwargs):
139         MyBuildExt.amalgamation = True
140         MyBuildExt.static = True
141         build.__init__(self, *args, **kwargs)
142
143
144 class LibSQLCipherBuilder(build_ext):
145
146     description = ("Build C extension linking against libsqlcipher library.")
147
148     def build_extension(self, ext):
149         ext.extra_link_args.append("-lsqlcipher")
150         build_ext.build_extension(self, ext)
151
152
153 class MyBuildExt(build_ext):
154     amalgamation = True  # We want amalgamation on the default build for now
155     static = False
156
157     def build_extension(self, ext):
158         if self.amalgamation:
159             get_amalgamation()
160             # build with fulltext search enabled
161             ext.define_macros.append(
162                 ("SQLITE_ENABLE_FTS3", "1"))
163             ext.define_macros.append(
164                 ("SQLITE_ENABLE_RTREE", "1"))
165
166             # SQLCipher options
167             ext.define_macros.append(
168                 ("SQLITE_ENABLE_LOAD_EXTENSION", "1"))
169             ext.define_macros.append(
170                 ("SQLITE_HAS_CODEC", "1"))
171             ext.define_macros.append(
172                 ("SQLITE_TEMP_STORE", "2"))
173
174             ext.sources.append(os.path.join(AMALGAMATION_ROOT, "sqlite3.c"))
175             ext.include_dirs.append(AMALGAMATION_ROOT)
176
177             ext.extra_link_args.append("-lcrypto")
178
179         if self.static:
180             self._build_extension(ext)
181         else:
182             build_ext.build_extension(self, ext)
183
184     def _build_extension(self, ext):
185         sources = ext.sources
186         if sources is None or type(sources) not in (ListType, TupleType):
187             raise DistutilsSetupError, \
188                 ("in 'ext_modules' option (extension '%s'), " +
189                  "'sources' must be present and must be " +
190                  "a list of source filenames") % ext.name
191         sources = list(sources)
192
193         ext_path = self.get_ext_fullpath(ext.name)
194         depends = sources + ext.depends
195         if not (self.force or newer_group(depends, ext_path, 'newer')):
196             log.debug("skipping '%s' extension (up-to-date)", ext.name)
197             return
198         else:
199             log.info("building '%s' extension", ext.name)
200
201         # First, scan the sources for SWIG definition files (.i), run
202         # SWIG on 'em to create .c files, and modify the sources list
203         # accordingly.
204         sources = self.swig_sources(sources, ext)
205
206         # Next, compile the source code to object files.
207
208         # XXX not honouring 'define_macros' or 'undef_macros' -- the
209         # CCompiler API needs to change to accommodate this, and I
210         # want to do one thing at a time!
211
212         # Two possible sources for extra compiler arguments:
213         #   - 'extra_compile_args' in Extension object
214         #   - CFLAGS environment variable (not particularly
215         #     elegant, but people seem to expect it and I
216         #     guess it's useful)
217         # The environment variable should take precedence, and
218         # any sensible compiler will give precedence to later
219         # command line args.  Hence we combine them in order:
220         extra_args = ext.extra_compile_args or []
221
222         macros = ext.define_macros[:]
223         for undef in ext.undef_macros:
224             macros.append((undef,))
225
226         # XXX debug
227         #objects = []
228         objects = self.compiler.compile(sources,
229                                         output_dir=self.build_temp,
230                                         macros=macros,
231                                         include_dirs=ext.include_dirs,
232                                         debug=self.debug,
233                                         extra_postargs=extra_args,
234                                         depends=ext.depends)
235
236         # XXX -- this is a Vile HACK!
237         #
238         # The setup.py script for Python on Unix needs to be able to
239         # get this list so it can perform all the clean up needed to
240         # avoid keeping object files around when cleaning out a failed
241         # build of an extension module.  Since Distutils does not
242         # track dependencies, we have to get rid of intermediates to
243         # ensure all the intermediates will be properly re-built.
244         #
245         self._built_objects = objects[:]
246
247         # Now link the object files together into a "shared object" --
248         # of course, first we have to figure out all the other things
249         # that go into the mix.
250         if ext.extra_objects:
251             objects.extend(ext.extra_objects)
252         extra_args = ext.extra_link_args or []
253
254         # Detect target language, if not provided
255         language = ext.language or self.compiler.detect_language(sources)
256
257         #self.compiler.link_shared_object(
258             #objects, ext_path,
259             #libraries=self.get_libraries(ext),
260             #library_dirs=ext.library_dirs,
261             #runtime_library_dirs=ext.runtime_library_dirs,
262             #extra_postargs=extra_args,
263             #export_symbols=self.get_export_symbols(ext),
264             #debug=self.debug,
265             #build_temp=self.build_temp,
266             #target_lang=language)
267
268         # XXX may I have a static lib please?
269         # hmm but then I cannot load that extension, or can I?
270         output_dir = os.path.sep.join(ext_path.split(os.path.sep)[:-1])
271
272         self.compiler.create_static_lib(
273             objects,
274             #XXX get library name ... splitting ext_path?
275             "sqlite",
276             output_dir=output_dir,
277             target_lang=language)
278
279     def __setattr__(self, k, v):
280         # Make sure we don't link against the SQLite
281         # library, no matter what setup.cfg says
282         if self.amalgamation and k == "libraries":
283             v = None
284         self.__dict__[k] = v
285
286
287 def get_setup_args():
288
289     PYSQLITE_VERSION = None
290
291     version_re = re.compile('#define PYSQLITE_VERSION "(.*)"')
292     f = open(os.path.join("src", "module.h"))
293     for line in f:
294         match = version_re.match(line)
295         if match:
296             PYSQLITE_VERSION = match.groups()[0]
297             PYSQLITE_MINOR_VERSION = ".".join(PYSQLITE_VERSION.split('.')[:2])
298             break
299     f.close()
300
301     if DEV_VERSION:
302         PYSQLITE_VERSION += ".dev%s" % DEV_VERSION
303
304     if not PYSQLITE_VERSION:
305         print "Fatal error: PYSQLITE_VERSION could not be detected!"
306         sys.exit(1)
307
308     data_files = [("pysqlcipher-doc",
309                   glob.glob("doc/*.html")
310                   + glob.glob("doc/*.txt")
311                   + glob.glob("doc/*.css")),
312                   ("pysqlcipher-doc/code",
313                   glob.glob("doc/code/*.py"))]
314
315     #XXX ?
316     #py_modules = ["sqlcipher"],
317
318     setup_args = dict(
319         name="pysqlcipher",
320         version=PYSQLITE_VERSION,
321         #version="0.0.1",
322         description="DB-API 2.0 interface for SQLCIPHER 3.x",
323         long_description=long_description,
324         author="Kali Kaneko",
325         author_email="kali@futeisha.org",
326         license="zlib/libpng",  # is THIS a license?
327         # It says MIT in the google project
328         platforms="ALL",
329         url="http://github.com/leapcode/pysqlcipher/",
330         # Description of the modules and packages in the distribution
331         package_dir={"pysqlcipher": "lib"},
332         packages=["pysqlcipher", "pysqlcipher.test"] +
333             (["pysqlcipher.test.py25"], [])[sys.version_info < (2, 5)],
334         scripts=[],
335         data_files=data_files,
336
337         ext_modules=[
338             Extension(
339                 name="pysqlcipher._sqlite",
340                 sources=sources,
341                 include_dirs=include_dirs,
342                 library_dirs=library_dirs,
343                 runtime_library_dirs=runtime_library_dirs,
344                 libraries=libraries,
345                 extra_objects=extra_objects,
346                 define_macros=define_macros)
347         ],
348         classifiers=[
349             "Development Status :: 4 - Beta",
350             "Intended Audience :: Developers",
351             "License :: OSI Approved :: zlib/libpng License",
352             "Operating System :: MacOS :: MacOS X",
353             "Operating System :: Microsoft :: Windows",
354             "Operating System :: POSIX",
355             "Programming Language :: C",
356             "Programming Language :: Python",
357             "Topic :: Database :: Database Engines/Servers",
358             "Topic :: Software Development :: Libraries :: Python Modules"],
359         cmdclass={"build_docs": DocBuilder}
360     )
361
362     setup_args["cmdclass"].update(
363         {"build_docs": DocBuilder,
364          "build_ext": MyBuildExt,
365          "build_static": AmalgamationBuilder,
366          "build_sqlcipher": LibSQLCipherBuilder,
367          "cross_bdist_wininst": cross_bdist_wininst.bdist_wininst})
368     return setup_args
369
370
371 def main():
372     setup(**get_setup_args())
373
374 if __name__ == "__main__":
375     main()