summaryrefslogtreecommitdiff
path: root/setup.py
diff options
context:
space:
mode:
Diffstat (limited to 'setup.py')
-rwxr-xr-xsetup.py472
1 files changed, 472 insertions, 0 deletions
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..a5fbc0d
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,472 @@
+#!/usr/bin/python
+#
+# Copyright 2010, Michael Cohen <scudette@gmail.com>.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Install the pytsk python module.
+
+You can control the installation process using the following environment
+variables:
+
+SLEUTHKIT_SOURCE: The path to the locally downloaded tarball of the
+ sleuthkit. If not specified we download from the internet.
+
+SLEUTHKIT_PATH: A path to the locally build sleuthkit source tree. If not
+ specified we use SLEUTHKIT_SOURCE environment variable (above).
+
+"""
+
+from __future__ import print_function
+
+import copy
+import glob
+import re
+import os
+import subprocess
+import sys
+import time
+
+import distutils.ccompiler
+
+from distutils.ccompiler import new_compiler
+from setuptools import setup, Command, Extension
+from setuptools.command.build_ext import build_ext
+from setuptools.command.sdist import sdist
+
+try:
+ from distutils.command.bdist_msi import bdist_msi
+except ImportError:
+ bdist_msi = None
+
+try:
+ from distutils.command.bdist_rpm import bdist_rpm
+except ImportError:
+ bdist_rpm = None
+
+import generate_bindings
+import run_tests
+
+
+version_tuple = (sys.version_info[0], sys.version_info[1])
+if version_tuple < (3, 5):
+ print((
+ 'Unsupported Python version: {0:s}, version 3.5 or higher '
+ 'required.').format(sys.version))
+ sys.exit(1)
+
+
+if not bdist_msi:
+ BdistMSICommand = None
+else:
+ class BdistMSICommand(bdist_msi):
+ """Custom handler for the bdist_msi command."""
+
+ def run(self):
+ """Builds an MSI."""
+ # Make a deepcopy of distribution so the following version changes
+ # only apply to bdist_msi.
+ self.distribution = copy.deepcopy(self.distribution)
+
+ # bdist_msi does not support the library version so we add ".1"
+ # as a work around.
+ self.distribution.metadata.version += ".1"
+
+ bdist_msi.run(self)
+
+
+if not bdist_rpm:
+ BdistRPMCommand = None
+else:
+ class BdistRPMCommand(bdist_rpm):
+ """Custom handler for the bdist_rpm command."""
+
+ def make_spec_file(self, spec_file):
+ """Make an RPM Spec file."""
+ # Note that bdist_rpm can be an old style class.
+ if issubclass(BdistRPMCommand, object):
+ spec_file = super(BdistRPMCommand, self)._make_spec_file()
+ else:
+ spec_file = bdist_rpm._make_spec_file(self)
+
+ if sys.version_info[0] < 3:
+ python_package = 'python2'
+ else:
+ python_package = 'python3'
+
+ description = []
+ requires = ''
+ summary = ''
+ in_description = False
+
+ python_spec_file = []
+ for line in iter(spec_file):
+ if line.startswith('Summary: '):
+ summary = line
+
+ elif line.startswith('BuildRequires: '):
+ line = 'BuildRequires: {0:s}-setuptools, {0:s}-devel'.format(
+ python_package)
+
+ elif line.startswith('Requires: '):
+ requires = line[10:]
+ if python_package == 'python3':
+ requires = requires.replace('python-', 'python3-')
+ requires = requires.replace('python2-', 'python3-')
+
+ elif line.startswith('%description'):
+ in_description = True
+
+ elif line.startswith('python setup.py build'):
+ if python_package == 'python3':
+ line = '%py3_build'
+ else:
+ line = '%py2_build'
+
+ elif line.startswith('python setup.py install'):
+ if python_package == 'python3':
+ line = '%py3_install'
+ else:
+ line = '%py2_install'
+
+ elif line.startswith('%files'):
+ lines = [
+ '%files -n {0:s}-%{{name}}'.format(python_package),
+ '%defattr(644,root,root,755)',
+ '%license LICENSE',
+ '%doc README']
+
+ if python_package == 'python3':
+ lines.extend([
+ '%{_libdir}/python3*/site-packages/*.so',
+ '%{_libdir}/python3*/site-packages/pytsk3*.egg-info/*',
+ '',
+ '%exclude %{_prefix}/share/doc/*'])
+
+ else:
+ lines.extend([
+ '%{_libdir}/python2*/site-packages/*.so',
+ '%{_libdir}/python2*/site-packages/pytsk3*.egg-info/*',
+ '',
+ '%exclude %{_prefix}/share/doc/*'])
+
+ python_spec_file.extend(lines)
+ break
+
+ elif line.startswith('%prep'):
+ in_description = False
+
+ python_spec_file.append(
+ '%package -n {0:s}-%{{name}}'.format(python_package))
+ if python_package == 'python2':
+ python_spec_file.extend([
+ 'Obsoletes: python-pytsk3 < %{version}',
+ 'Provides: python-pytsk3 = %{version}'])
+
+ if requires:
+ python_spec_file.append('Requires: {0:s}'.format(requires))
+
+ python_spec_file.extend([
+ '{0:s}'.format(summary),
+ '',
+ '%description -n {0:s}-%{{name}}'.format(python_package)])
+
+ python_spec_file.extend(description)
+
+ elif in_description:
+ # Ignore leading white lines in the description.
+ if not description and not line:
+ continue
+
+ description.append(line)
+
+ python_spec_file.append(line)
+
+ return python_spec_file
+
+ def _make_spec_file(self):
+ """Generates the text of an RPM spec file.
+
+ Returns:
+ list[str]: lines of text.
+ """
+ return self.make_spec_file(
+ bdist_rpm._make_spec_file(self))
+
+
+class BuildExtCommand(build_ext):
+ """Custom handler for the build_ext command."""
+
+ def configure_source_tree(self, compiler):
+ """Configures the source and returns a dict of defines."""
+ define_macros = []
+ define_macros.append(("HAVE_TSK_LIBTSK_H", ""))
+
+ if compiler.compiler_type == "msvc":
+ return define_macros + [
+ ("WIN32", "1"),
+ ("UNICODE", "1"),
+ ("_CRT_SECURE_NO_WARNINGS", "1"),
+ ]
+
+ # We want to build as much as possible self contained Python
+ # binding.
+ command = [
+ "sh", "configure", "--disable-java", "--without-afflib",
+ "--without-libewf", "--without-libpq", "--without-libvhdi",
+ "--without-libvmdk", "--without-zlib"]
+
+ output = subprocess.check_output(command, cwd="sleuthkit")
+ print_line = False
+ for line in output.split(b"\n"):
+ line = line.rstrip()
+ if line == b"configure:":
+ print_line = True
+
+ if print_line:
+ if sys.version_info[0] >= 3:
+ line = line.decode("ascii")
+ print(line)
+
+ return define_macros + [
+ ("HAVE_CONFIG_H", "1"),
+ ("LOCALEDIR", "\"/usr/share/locale\""),
+ ]
+
+ def run(self):
+ compiler = new_compiler(compiler=self.compiler)
+ # pylint: disable=attribute-defined-outside-init
+ self.define = self.configure_source_tree(compiler)
+
+ libtsk_path = os.path.join("sleuthkit", "tsk")
+
+ if not os.access("pytsk3.c", os.R_OK):
+ # Generate the Python binding code (pytsk3.c).
+ libtsk_header_files = [
+ os.path.join(libtsk_path, "libtsk.h"),
+ os.path.join(libtsk_path, "base", "tsk_base.h"),
+ os.path.join(libtsk_path, "fs", "tsk_fs.h"),
+ os.path.join(libtsk_path, "img", "tsk_img.h"),
+ os.path.join(libtsk_path, "vs", "tsk_vs.h"),
+ "tsk3.h"]
+
+ print("Generating bindings...")
+ generate_bindings.generate_bindings(
+ "pytsk3.c", libtsk_header_files, initialization="tsk_init();")
+
+ build_ext.run(self)
+
+
+class SDistCommand(sdist):
+ """Custom handler for generating source dist."""
+ def run(self):
+ libtsk_path = os.path.join("sleuthkit", "tsk")
+
+ # sleuthkit submodule is not there, probably because this has been
+ # freshly checked out.
+ if not os.access(libtsk_path, os.R_OK):
+ subprocess.check_call(["git", "submodule", "init"])
+ subprocess.check_call(["git", "submodule", "update"])
+
+ if not os.path.exists(os.path.join("sleuthkit", "configure")):
+ raise RuntimeError(
+ "Missing: sleuthkit/configure run 'setup.py build' first.")
+
+ sdist.run(self)
+
+
+class UpdateCommand(Command):
+ """Update sleuthkit source.
+
+ This is normally only run by packagers to make a new release.
+ """
+ _SLEUTHKIT_GIT_TAG = "4.7.0"
+
+ version = time.strftime("%Y%m%d")
+
+ timezone_minutes, _ = divmod(time.timezone, 60)
+ timezone_hours, timezone_minutes = divmod(timezone_minutes, 60)
+
+ # If timezone_hours is -1 %02d will format as -1 instead of -01
+ # hence we detect the sign and force a leading zero.
+ if timezone_hours < 0:
+ timezone_string = "-%02d%02d" % (-timezone_hours, timezone_minutes)
+ else:
+ timezone_string = "+%02d%02d" % (timezone_hours, timezone_minutes)
+
+ version_pkg = "%s %s" % (
+ time.strftime("%a, %d %b %Y %H:%M:%S"), timezone_string)
+
+ user_options = [("use-head", None, (
+ "Use the latest version of Sleuthkit checked into git (HEAD) instead of "
+ "tag: {0:s}".format(_SLEUTHKIT_GIT_TAG)))]
+
+ def initialize_options(self):
+ self.use_head = False
+
+ def finalize_options(self):
+ self.use_head = bool(self.use_head)
+
+ files = {
+ "sleuthkit/Makefile.am": [
+ ("SUBDIRS = .+", "SUBDIRS = tsk"),
+ ],
+ "class_parser.py": [
+ ('VERSION = "[^"]+"', 'VERSION = "%s"' % version),
+ ],
+ "dpkg/changelog": [
+ (r"pytsk3 \([^\)]+\)", "pytsk3 (%s-1)" % version),
+ ("(<[^>]+>).+", r"\1 %s" % version_pkg),
+ ],
+ }
+
+ def patch_sleuthkit(self):
+ """Applies patches to the SleuthKit source code."""
+ for filename, rules in iter(self.files.items()):
+ filename = os.path.join(*filename.split("/"))
+
+ with open(filename, "r") as file_object:
+ data = file_object.read()
+
+ for search, replace in rules:
+ data = re.sub(search, replace, data)
+
+ with open(filename, "w") as fd:
+ fd.write(data)
+
+ patch_files = [
+ "sleuthkit-{0:s}-configure.ac".format(self._SLEUTHKIT_GIT_TAG)]
+
+ for patch_file in patch_files:
+ patch_file = os.path.join("patches", patch_file)
+ if not os.path.exists(patch_file):
+ print("No such patch file: {0:s}".format(patch_file))
+ continue
+
+ patch_file = os.path.join("..", patch_file)
+ subprocess.check_call(["git", "apply", patch_file], cwd="sleuthkit")
+
+ def run(self):
+ subprocess.check_call(["git", "stash"], cwd="sleuthkit")
+
+ subprocess.check_call(["git", "submodule", "init"])
+ subprocess.check_call(["git", "submodule", "update"])
+
+ print("Updating sleuthkit")
+ subprocess.check_call(["git", "reset", "--hard"], cwd="sleuthkit")
+ subprocess.check_call(["git", "clean", "-x", "-f", "-d"], cwd="sleuthkit")
+ subprocess.check_call(["git", "checkout", "master"], cwd="sleuthkit")
+ subprocess.check_call(["git", "pull"], cwd="sleuthkit")
+ if self.use_head:
+ print("Pulling from HEAD")
+ else:
+ print("Pulling from tag: {0:s}".format(self._SLEUTHKIT_GIT_TAG))
+ subprocess.check_call(["git", "fetch", "--tags"], cwd="sleuthkit")
+ git_tag_path = "tags/sleuthkit-{0:s}".format(self._SLEUTHKIT_GIT_TAG)
+ subprocess.check_call(["git", "checkout", git_tag_path], cwd="sleuthkit")
+
+ self.patch_sleuthkit()
+
+ compiler_type = distutils.ccompiler.get_default_compiler()
+ if compiler_type != "msvc":
+ subprocess.check_call(["./bootstrap"], cwd="sleuthkit")
+
+ # Now derive the version based on the date.
+ with open("version.txt", "w") as fd:
+ fd.write(self.version)
+
+ libtsk_path = os.path.join("sleuthkit", "tsk")
+
+ # Generate the Python binding code (pytsk3.c).
+ libtsk_header_files = [
+ os.path.join(libtsk_path, "libtsk.h"),
+ os.path.join(libtsk_path, "base", "tsk_base.h"),
+ os.path.join(libtsk_path, "fs", "tsk_fs.h"),
+ os.path.join(libtsk_path, "img", "tsk_img.h"),
+ os.path.join(libtsk_path, "vs", "tsk_vs.h"),
+ "tsk3.h"]
+
+ print("Generating bindings...")
+ generate_bindings.generate_bindings(
+ "pytsk3.c", libtsk_header_files, initialization="tsk_init();")
+
+
+class ProjectBuilder(object):
+ """Class to help build the project."""
+
+ def __init__(self, project_config, argv):
+ """Initializes a project builder object."""
+ self._project_config = project_config
+ self._argv = argv
+
+ # The path to the sleuthkit/tsk directory.
+ self._libtsk_path = os.path.join("sleuthkit", "tsk")
+
+ # Paths under the sleuthkit/tsk directory which contain files we need
+ # to compile.
+ self._sub_library_names = ["base", "docs", "fs", "img", "vs"]
+
+ # The args for the extension builder.
+ self.extension_args = {
+ "define_macros": [],
+ "include_dirs": ["talloc", self._libtsk_path, "sleuthkit", "."],
+ "library_dirs": [],
+ "libraries": []}
+
+ # The sources to build.
+ self._source_files = [
+ "class.c", "error.c", "tsk3.c", "pytsk3.c", "talloc/talloc.c"]
+
+ # Path to the top of the unpacked sleuthkit sources.
+ self._sleuthkit_path = "sleuthkit"
+
+ def build(self):
+ """Build everything."""
+ # Fetch all c and cpp files from the subdirs to compile.
+ for library_name in self._sub_library_names:
+ for extension in ("*.c", "*.cpp"):
+ extension_glob = os.path.join(
+ self._libtsk_path, library_name, extension)
+ self._source_files.extend(glob.glob(extension_glob))
+
+ # Sort the soure files to make sure they are in consistent order when
+ # building.
+ source_files = sorted(self._source_files)
+ ext_modules = [Extension("pytsk3", source_files, **self.extension_args)]
+
+ setup(
+ cmdclass={
+ "build_ext": BuildExtCommand,
+ "bdist_msi": BdistMSICommand,
+ "bdist_rpm": BdistRPMCommand,
+ "sdist": SDistCommand,
+ "update": UpdateCommand},
+ ext_modules=ext_modules,
+ **self._project_config)
+
+
+if __name__ == "__main__":
+ __version__ = open("version.txt").read().strip()
+
+ setup_args = dict(
+ name="pytsk3",
+ version=__version__,
+ description="Python bindings for the sleuthkit",
+ long_description=(
+ "Python bindings for the sleuthkit (http://www.sleuthkit.org/)"),
+ license="Apache 2.0",
+ url="https://github.com/py4n6/pytsk/",
+ author="Michael Cohen and Joachim Metz",
+ author_email="scudette@gmail.com, joachim.metz@gmail.com",
+ zip_safe=False)
+
+ ProjectBuilder(setup_args, sys.argv).build()