ef9fe43bdd847098bd555fa2bfad235db4a6720d
[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 a somewhat more
114     # official place
115     amalgamation_url = ("http://futeisha.org/sqlcipher/"
116                         "amalgamation-sqlcipher-2.1.0.zip")
117
118     # and download it
119     print 'amalgamation url: %s' % (amalgamation_url,)
120     urllib.urlretrieve(amalgamation_url, "tmp.zip")
121
122     zf = zipfile.ZipFile("tmp.zip")
123     files = ["sqlite3.c", "sqlite3.h"]
124     directory = zf.namelist()[0]
125
126     for fn in files:
127         print "Extracting", fn
128         outf = open(AMALGAMATION_ROOT + os.sep + fn, "wb")
129         outf.write(zf.read(directory + fn))
130         outf.close()
131     zf.close()
132     os.unlink("tmp.zip")
133
134
135 class AmalgamationBuilder(build):
136     description = ("Build a statically built pysqlcipher "
137                    "downloading and using a sqlcipher amalgamation.")
138
139     def __init__(self, *args, **kwargs):
140         MyBuildExt.amalgamation = True
141         MyBuildExt.static = True
142         build.__init__(self, *args, **kwargs)
143
144
145 class MyBuildExt(build_ext):
146     amalgamation = True  # We want amalgamation on the default build for now
147     static = False
148
149     def build_extension(self, ext):
150         if self.amalgamation:
151             get_amalgamation()
152             # build with fulltext search enabled
153             ext.define_macros.append(
154                 ("SQLITE_ENABLE_FTS3", "1"))
155             ext.define_macros.append(
156                 ("SQLITE_ENABLE_RTREE", "1"))
157
158             # SQLCipher options
159             ext.define_macros.append(
160                 ("SQLITE_ENABLE_LOAD_EXTENSION", "1"))
161             ext.define_macros.append(
162                 ("SQLITE_HAS_CODEC", "1"))
163             ext.define_macros.append(
164                 ("SQLITE_TEMP_STORE", "2"))
165
166             ext.sources.append(os.path.join(AMALGAMATION_ROOT, "sqlite3.c"))
167             ext.include_dirs.append(AMALGAMATION_ROOT)
168
169             ext.extra_link_args.append("-lcrypto")
170
171         if self.static:
172             self._build_extension(ext)
173         else:
174             build_ext.build_extension(self, ext)
175
176     def _build_extension(self, ext):
177         sources = ext.sources
178         if sources is None or type(sources) not in (ListType, TupleType):
179             raise DistutilsSetupError, \
180                 ("in 'ext_modules' option (extension '%s'), " +
181                  "'sources' must be present and must be " +
182                  "a list of source filenames") % ext.name
183         sources = list(sources)
184
185         ext_path = self.get_ext_fullpath(ext.name)
186         depends = sources + ext.depends
187         if not (self.force or newer_group(depends, ext_path, 'newer')):
188             log.debug("skipping '%s' extension (up-to-date)", ext.name)
189             return
190         else:
191             log.info("building '%s' extension", ext.name)
192
193         # First, scan the sources for SWIG definition files (.i), run
194         # SWIG on 'em to create .c files, and modify the sources list
195         # accordingly.
196         sources = self.swig_sources(sources, ext)
197
198         # Next, compile the source code to object files.
199
200         # XXX not honouring 'define_macros' or 'undef_macros' -- the
201         # CCompiler API needs to change to accommodate this, and I
202         # want to do one thing at a time!
203
204         # Two possible sources for extra compiler arguments:
205         #   - 'extra_compile_args' in Extension object
206         #   - CFLAGS environment variable (not particularly
207         #     elegant, but people seem to expect it and I
208         #     guess it's useful)
209         # The environment variable should take precedence, and
210         # any sensible compiler will give precedence to later
211         # command line args.  Hence we combine them in order:
212         extra_args = ext.extra_compile_args or []
213
214         macros = ext.define_macros[:]
215         for undef in ext.undef_macros:
216             macros.append((undef,))
217
218         # XXX debug
219         #objects = []
220         objects = self.compiler.compile(sources,
221                                         output_dir=self.build_temp,
222                                         macros=macros,
223                                         include_dirs=ext.include_dirs,
224                                         debug=self.debug,
225                                         extra_postargs=extra_args,
226                                         depends=ext.depends)
227
228         # XXX -- this is a Vile HACK!
229         #
230         # The setup.py script for Python on Unix needs to be able to
231         # get this list so it can perform all the clean up needed to
232         # avoid keeping object files around when cleaning out a failed
233         # build of an extension module.  Since Distutils does not
234         # track dependencies, we have to get rid of intermediates to
235         # ensure all the intermediates will be properly re-built.
236         #
237         self._built_objects = objects[:]
238
239         # Now link the object files together into a "shared object" --
240         # of course, first we have to figure out all the other things
241         # that go into the mix.
242         if ext.extra_objects:
243             objects.extend(ext.extra_objects)
244         extra_args = ext.extra_link_args or []
245
246         # Detect target language, if not provided
247         language = ext.language or self.compiler.detect_language(sources)
248
249         #self.compiler.link_shared_object(
250             #objects, ext_path,
251             #libraries=self.get_libraries(ext),
252             #library_dirs=ext.library_dirs,
253             #runtime_library_dirs=ext.runtime_library_dirs,
254             #extra_postargs=extra_args,
255             #export_symbols=self.get_export_symbols(ext),
256             #debug=self.debug,
257             #build_temp=self.build_temp,
258             #target_lang=language)
259
260         # XXX may I have a static lib please?
261         # hmm but then I cannot load that extension, or can I?
262         output_dir = os.path.sep.join(ext_path.split(os.path.sep)[:-1])
263
264         self.compiler.create_static_lib(
265             objects,
266             #XXX get library name ... splitting ext_path?
267             "sqlite",
268             output_dir=output_dir,
269             target_lang=language)
270
271     def __setattr__(self, k, v):
272         # Make sure we don't link against the SQLite
273         # library, no matter what setup.cfg says
274         if self.amalgamation and k == "libraries":
275             v = None
276         self.__dict__[k] = v
277
278
279 def get_setup_args():
280
281     PYSQLITE_VERSION = None
282
283     version_re = re.compile('#define PYSQLITE_VERSION "(.*)"')
284     f = open(os.path.join("src", "module.h"))
285     for line in f:
286         match = version_re.match(line)
287         if match:
288             PYSQLITE_VERSION = match.groups()[0]
289             PYSQLITE_MINOR_VERSION = ".".join(PYSQLITE_VERSION.split('.')[:2])
290             break
291     f.close()
292
293     if DEV_VERSION:
294         PYSQLITE_VERSION += ".dev%s" % DEV_VERSION
295
296     if not PYSQLITE_VERSION:
297         print "Fatal error: PYSQLITE_VERSION could not be detected!"
298         sys.exit(1)
299
300     data_files = [("pysqlcipher-doc",
301                   glob.glob("doc/*.html")
302                   + glob.glob("doc/*.txt")
303                   + glob.glob("doc/*.css")),
304                   ("pysqlcipher-doc/code",
305                   glob.glob("doc/code/*.py"))]
306
307     py_modules = ["sqlcipher"],
308
309     setup_args = dict(
310         name="pysqlcipher",
311         version=PYSQLITE_VERSION,
312         #version="0.0.1",
313         description="DB-API 2.0 interface for SQLCIPHER 3.x",
314         long_description=long_description,
315         author="Kali Kaneko",
316         author_email="kali@futeisha.org",
317         license="zlib/libpng",  # is THIS a license?
318         # It says MIT in the google project
319         platforms="ALL",
320         #XXX missing url
321         url="http://github.com/leapcode/pysqlcipher/",
322         # Description of the modules and packages in the distribution
323         package_dir={"pysqlcipher": "lib"},
324         packages=["pysqlcipher", "pysqlcipher.test"] +
325             (["pysqlcipher.test.py25"], [])[sys.version_info < (2, 5)],
326         scripts=[],
327         data_files=data_files,
328
329         ext_modules=[
330             Extension(
331                 name="pysqlcipher._sqlite",
332                 sources=sources,
333                 include_dirs=include_dirs,
334                 library_dirs=library_dirs,
335                 runtime_library_dirs=runtime_library_dirs,
336                 libraries=libraries,
337                 extra_objects=extra_objects,
338                 define_macros=define_macros)
339         ],
340         classifiers=[
341             "Development Status :: 5 - Production/Stable",
342             "Intended Audience :: Developers",
343             "License :: OSI Approved :: zlib/libpng License",
344             "Operating System :: MacOS :: MacOS X",
345             "Operating System :: Microsoft :: Windows",
346             "Operating System :: POSIX",
347             "Programming Language :: C",
348             "Programming Language :: Python",
349             "Topic :: Database :: Database Engines/Servers",
350             "Topic :: Software Development :: Libraries :: Python Modules"],
351         cmdclass={"build_docs": DocBuilder}
352     )
353
354     setup_args["cmdclass"].update(
355         {"build_docs": DocBuilder,
356          "build_ext": MyBuildExt,
357          "build_static": AmalgamationBuilder,
358          "cross_bdist_wininst": cross_bdist_wininst.bdist_wininst})
359     return setup_args
360
361
362 def main():
363     setup(**get_setup_args())
364
365 if __name__ == "__main__":
366     main()