| #!/usr/bin/env python |
| |
| import errno, os, re, sys, tempfile |
| |
| from distutils import log |
| from distutils.cmd import Command |
| from distutils.command.build import build as _build |
| from distutils.command.clean import clean as _clean |
| from distutils.core import setup |
| from distutils.dir_util import remove_tree |
| from distutils.errors import DistutilsExecError |
| from distutils.errors import DistutilsOptionError |
| |
| from glob import glob |
| from tempfile import mkdtemp |
| |
| class clean(_clean): |
| """Special distutils command for cleaning the Subversion ctypes bindings.""" |
| description = "clean the Subversion ctypes Python bindings" |
| |
| def initialize_options (self): |
| _clean.initialize_options(self) |
| |
| # initialize_options() |
| |
| def finalize_options (self): |
| _clean.finalize_options(self) |
| |
| # finalize_options() |
| |
| def run(self): |
| functions_py = os.path.join(os.path.dirname(__file__), "csvn", "core", |
| "functions.py") |
| functions_pyc = os.path.join(os.path.dirname(__file__), "csvn", "core", |
| "functions.pyc") |
| |
| if os.path.exists(functions_py): |
| log.info("removing '%s'", os.path.normpath(functions_py)) |
| |
| if not self.dry_run: |
| os.remove(functions_py) |
| else: |
| log.warn("'%s' does not exist -- can't clean it", |
| os.path.normpath(functions_py)) |
| |
| if os.path.exists(functions_pyc): |
| log.info("removing '%s'" % os.path.normpath(functions_pyc)) |
| |
| if not self.dry_run: |
| os.remove(functions_pyc) |
| else: |
| log.warn("'%s' does not exist -- can't clean it", |
| os.path.normpath(functions_pyc)) |
| |
| |
| # Run standard clean command |
| _clean.run(self) |
| |
| # run() |
| |
| # class clean |
| |
| class build(_build): |
| """Special distutils command for building the Subversion ctypes bindings.""" |
| |
| description = "build the Subversion ctypes Python bindings" |
| |
| _build.user_options.append(("apr=", None, "full path to where apr is " |
| "installed or the full path to the apr-config or " |
| "apr-1-config file")) |
| _build.user_options.append(("apr-util=", None, "full path to where apr-util " |
| "is installed or the full path to the apu-config" |
| "apu-1-config file")) |
| _build.user_options.append(("subversion=", None, "full path to where " |
| "Subversion is installed")) |
| _build.user_options.append(("ctypesgen=", None, "full path to where ctypesgen " |
| "is installed")) |
| _build.user_options.append(("cppflags=", None, "extra flags to pass to the c " |
| "preprocessor")) |
| _build.user_options.append(("ldflags=", None, "extra flags to pass to the " |
| "ctypesgen linker")) |
| _build.user_options.append(("lib-dirs=", None, "colon-delimited list of paths " |
| "to append to the search path")) |
| _build.user_options.append(("save-preprocessed-headers=", None, "full path to " |
| "where to save the preprocessed headers")) |
| |
| def initialize_options (self): |
| _build.initialize_options(self) |
| self.apr = None |
| self.apr_util = None |
| self.ctypesgen = None |
| self.subversion = None |
| self.cppflags = "" |
| self.ldflags = "" |
| self.lib_dirs = None |
| self.save_preprocessed_headers = None |
| |
| # initialize_options() |
| |
| def finalize_options (self): |
| _build.finalize_options(self) |
| |
| # Distutils doesn't appear to like when you have --dry-run after the build |
| # build command so fail out if this is the case. |
| if self.dry_run != self.distribution.dry_run: |
| raise DistutilsOptionError, "The --dry-run flag must be specified " \ |
| "before the 'build' command" |
| |
| # finalize_options() |
| |
| ############################################################################## |
| # Get the APR configuration |
| ############################################################################## |
| def get_apr_config (self): |
| flags = [] |
| ldflags = [] |
| library_path = [] |
| ferr = None |
| apr_include_dir = None |
| |
| fout = self.run_cmd("%s --includes --cppflags" % self.apr_config) |
| if fout: |
| flags = fout.split() |
| apr_prefix = self.run_cmd("%s --prefix" % self.apr_config) |
| apr_prefix = apr_prefix.strip() |
| apr_include_dir = self.run_cmd("%s --includedir" % self.apr_config).strip() |
| apr_version = self.run_cmd("%s --version" % self.apr_config).strip() |
| cpp = self.run_cmd("%s --cpp" % self.apr_config).strip() |
| fout = self.run_cmd("%s --ldflags --link-ld" % self.apr_config) |
| if fout: |
| ldflags = fout.split() |
| else: |
| log.error(ferr) |
| raise DistutilsExecError("Problem running '%s'. Check the output " \ |
| "for details" % self.apr_config) |
| |
| fout = self.run_cmd("%s --includes" % self.apu_config) |
| if fout: |
| flags += fout.split() |
| fout = self.run_cmd("%s --ldflags --link-ld" % self.apu_config) |
| if fout: |
| ldflags += fout.split() |
| else: |
| log.error(ferr) |
| raise DistutilsExecError("Problem running '%s'. Check the output " \ |
| "for details" % self.apr_config) |
| |
| subversion_prefixes = [ |
| self.subversion, |
| "/usr/local", |
| "/usr" |
| ] |
| |
| svn_include_dir = "%s/include/subversion-1" % self.subversion |
| if self.subversion != "/usr": |
| ldflags.append("-L%s/lib" % self.subversion) |
| flags.append("-I%s" % svn_include_dir) |
| |
| # List the libraries in the order they should be loaded |
| libraries = [ |
| "svn_subr-1", |
| "svn_diff-1", |
| "svn_delta-1", |
| "svn_fs-1", |
| "svn_repos-1", |
| "svn_wc-1", |
| "svn_ra-1", |
| "svn_client-1", |
| ] |
| |
| for lib in libraries: |
| if glob("%s/lib/*%s*" % (self.subversion, lib)): |
| ldflags.append("-l%s" % lib) |
| |
| if apr_prefix != '/usr': |
| library_path.append("%s/lib" % apr_prefix) |
| if self.subversion != '/usr' and self.subversion != apr_prefix: |
| library_path.append("%s/lib" % self.subversion) |
| |
| return (apr_prefix, apr_include_dir, cpp + " " + self.cppflags, |
| " ".join(ldflags) + " " + self.ldflags, " ".join(flags), |
| ":".join(library_path)) |
| |
| # get_apr_config() |
| |
| ############################################################################## |
| # Build csvn/core/functions.py |
| ############################################################################## |
| def build_functions_py(self): |
| (apr_prefix, apr_include_dir, cpp, ldflags, flags, |
| library_path) = self.get_apr_config() |
| ctypesgen_path = os.path.join(self.ctypesgen, "ctypesgen.py") |
| tempdir = mkdtemp() |
| try: |
| includes = ('%s/include/subversion-1/svn_*.h ' |
| '%s/ap[ru]_*.h' % (self.subversion, apr_include_dir)) |
| cmd = ["cd %s && %s %s --cpp '%s %s' %s " |
| "%s -o svn_all.py --no-macro-warnings" % (tempdir, sys.executable, |
| ctypesgen_path, cpp, |
| flags, ldflags, |
| includes)] |
| if self.lib_dirs: |
| cmd.extend('-R ' + x for x in self.lib_dirs.split(":")) |
| cmd = ' '.join(cmd) |
| |
| if self.save_preprocessed_headers: |
| cmd += " --save-preprocessed-headers=%s" % \ |
| os.path.abspath(self.save_preprocessed_headers) |
| |
| if self.verbose or self.dry_run: |
| status = self.execute(os.system, (cmd,), cmd) |
| else: |
| f = os.popen(cmd, 'r') |
| f.read() # Required to avoide the 'Broken pipe' error. |
| status = f.close() # None is returned for the usual 0 return code |
| |
| if os.name == "posix" and status and status != 0: |
| if os.WIFEXITED(status): |
| status = os.WEXITSTATUS(status) |
| if status != 0: |
| sys.exit(status) |
| elif os.WIFSIGNALED(status): |
| log.error("ctypesgen.py killed with signal %d" % os.WTERMSIG(status)) |
| sys.exit(2) |
| elif os.WIFSTOPPED(status): |
| log.error("ctypesgen.py stopped with signal %d" % os.WSTOPSIG(status)) |
| sys.exit(2) |
| else: |
| log.error("ctypesgen.py exited with invalid status %d", status) |
| sys.exit(2) |
| |
| if not self.dry_run: |
| out = file("%s/svn_all2.py" % tempdir, "w") |
| for line in file("%s/svn_all.py" % tempdir): |
| line = line.replace("restype = POINTER(svn_error_t)", |
| "restype = SVN_ERR") |
| |
| if not line.startswith("FILE ="): |
| out.write(line) |
| out.close() |
| |
| cmd = ("cat csvn/core/functions.py.in %s/svn_all2.py " |
| "> csvn/core/functions.py" % tempdir) |
| self.execute(os.system, (cmd,), cmd) |
| |
| log.info("Generated csvn/core/functions.py successfully") |
| |
| finally: |
| # Remove temporary directory |
| remove_tree(tempdir) |
| |
| # build_functions_py() |
| |
| def run_cmd(self, cmd): |
| return os.popen(cmd).read() |
| |
| # run_cmd() |
| |
| def validate_options(self): |
| # Validate apr |
| if not self.apr: |
| raise DistutilsOptionError, "The --apr option is mandatory and " \ |
| "must point to a valid apr installation " \ |
| "or to either the apr-config file or the " \ |
| "apr-1-config file" |
| |
| if os.path.exists(self.apr): |
| if os.path.isdir(self.apr): |
| if os.path.exists(os.path.join(self.apr, "bin", "apr-config")): |
| self.apr_config = os.path.join(self.apr, "bin", "apr-config") |
| elif os.path.exists(os.path.join(self.apr, "bin", "apr-1-config")): |
| self.apr_config = os.path.join(self.apr, "bin", "apr-1-config") |
| else: |
| self.apr_config = None |
| else: |
| self.apr_config = self.apr |
| else: |
| self.apr_config = None |
| |
| if not self.apr_config: |
| raise DistutilsOptionError, "The --apr option is not valid. It must " \ |
| "point to a valid apr installation or " \ |
| "to either the apr-config file or the " \ |
| "apr-1-config file" |
| |
| # Validate apr-util |
| if not self.apr_util: |
| raise DistutilsOptionError, "The --apr-util option is mandatory and " \ |
| "must point to a valid apr-util " \ |
| "installation or to either the apu-config " \ |
| "file or the apu-1-config file" |
| |
| if os.path.exists(self.apr_util): |
| if os.path.isdir(self.apr_util): |
| if os.path.exists(os.path.join(self.apr_util, "bin", "apu-config")): |
| self.apu_config = os.path.join(self.apr_util, "bin", "apu-config") |
| elif os.path.exists(os.path.join(self.apr_util, "bin", "apu-1-config")): |
| self.apu_config = os.path.join(self.apr_util, "bin", "apu-1-config") |
| else: |
| self.apu_config = None |
| else: |
| self.apu_config = self.apr_util |
| else: |
| self.apu_config = None |
| |
| if not self.apu_config: |
| raise DistutilsOptionError, "The --apr-util option is not valid. It " \ |
| "must point to a valid apr-util " \ |
| "installation or to either the apu-config " \ |
| "file or the apu-1-config file" |
| |
| # Validate subversion |
| if not self.subversion: |
| raise DistutilsOptionError, "The --subversion option is mandatory and " \ |
| "must point to a valid Subversion " \ |
| "installation" |
| |
| if not os.path.exists(os.path.join(self.subversion, "include", |
| "subversion-1", "svn_client.h")): |
| raise DistutilsOptionError, "The --subversion option is not valid. " \ |
| "Could not locate %s/include/" \ |
| "subversion-1/svn_client.h" % self.subversion |
| |
| # Validate ctypesgen |
| if not self.ctypesgen: |
| raise DistutilsOptionError, "The --ctypesgen option is mandatory and " \ |
| "must point to a valid ctypesgen " \ |
| "installation" |
| |
| if not os.path.exists(os.path.join(self.ctypesgen, "ctypesgen.py")): |
| raise DistutilsOptionError, "The --ctypesgen option is not valid. " \ |
| "Could not locate %s/ctypesgen.py" \ |
| % self.ctypesgen |
| |
| # validate_functions() |
| |
| def run (self): |
| # We only want to build if functions.py is not present. |
| if not os.path.exists(os.path.join(os.path.dirname(__file__), |
| "csvn", "core", |
| "functions.py")): |
| if 'build' not in self.distribution.commands: |
| raise DistutilsOptionError, "You must run 'build' explicitly before " \ |
| "you can proceed" |
| |
| # Validate the command line options |
| self.validate_options() |
| |
| # Generate functions.py |
| self.build_functions_py() |
| else: |
| log.info("csvn/core/functions.py was not regenerated (output up-to-date)") |
| |
| # Run the standard build command. |
| _build.run(self) |
| |
| # run() |
| |
| # class build |
| |
| setup(cmdclass={'build': build, 'clean': clean}, |
| name='svn-ctypes-python-bindings', |
| version='0.1', |
| description='Python bindings for the Subversion version control system.', |
| author='The Subversion Team', |
| author_email='dev@subversion.tigris.org', |
| url='http://subversion.tigris.org', |
| packages=['csvn', 'csvn.core', 'csvn.ext'], |
| ) |
| |
| # TODO: We need to create our own bdist_rpm implementation so that we can pass |
| # our required arguments to the build command being called by bdist_rpm. |