#!/usr/bin/env python # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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. 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 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") svn_all_py = os.path.join(os.path.dirname(__file__), "svn_all.py") svn_all2_py = os.path.join(os.path.dirname(__file__), "svn_all2.py") for f in (functions_py, functions_pyc, svn_all_py, svn_all2_py): if os.path.exists(f): log.info("removing '%s'", os.path.normpath(f)) if not self.dry_run: os.remove(f) else: log.warn("'%s' does not exist -- can't clean it", os.path.normpath(f)) # 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-1-config or " "apr-config file")) _build.user_options.append(("apr-util=", None, "full path to where apr-util " "is installed or the full path to the apu-1-config or " "apu-config file")) _build.user_options.append(("subversion=", None, "full path to where " "Subversion is installed")) _build.user_options.append(("svn-headers=", None, "Full path to the " "Subversion header files, if they are not in a " "standard location")) _build.user_options.append(("ctypesgen=", None, "full path to where ctypesgen " "is installed, to the ctypesgen source tree or " "the full path to ctypesgen.py")) _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.svn_headers = 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 build configuration ############################################################################## def get_build_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" ] if self.subversion != "/usr": ldflags.append("-L%s/lib" % self.subversion) if self.svn_include_dir[-18:] == "subversion/include": flags.append("-Isubversion/include") else: flags.append("-I%s" % self.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: 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_build_config() ############################################################################## # Build csvn/core/functions.py ############################################################################## def build_functions_py(self): (apr_prefix, apr_include_dir, cpp, ldflags, flags, library_path) = self.get_build_config() cwd = os.getcwd() if self.svn_include_dir[-18:] == "subversion/include": includes = ('subversion/include/svn_*.h ' '%s/ap[ru]_*.h' % apr_include_dir) cmd = ["%s %s --cpp '%s %s' %s " "%s -o subversion/bindings/ctypes-python/svn_all.py " "--no-macro-warnings --strip-build-path=%s" % (sys.executable, self.ctypesgen_py, cpp, flags, ldflags, includes, self.svn_include_dir[:-19])] os.chdir(self.svn_include_dir[:-19]) else: includes = ('%s/svn_*.h ' '%s/ap[ru]_*.h' % (self.svn_include_dir, apr_include_dir)) cmd = ["%s %s --cpp '%s %s' %s " "%s -o svn_all.py --no-macro-warnings" % (sys.executable, self.ctypesgen_py, 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 avoid the 'Broken pipe' error. status = f.close() # None is returned for the usual 0 return code os.chdir(cwd) 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: r = re.compile(r"(\s+\w+)\.restype = POINTER\(svn_error_t\)") out = open("svn_all2.py", "w") for line in open("svn_all.py"): line = r.sub("\\1.restype = POINTER(svn_error_t)\n\\1.errcheck = _svn_errcheck", line) if not line.startswith("FILE ="): out.write(line) out.close() cmd = "cat csvn/core/functions.py.in svn_all2.py > csvn/core/functions.py" self.execute(os.system, (cmd,), cmd) log.info("Generated csvn/core/functions.py successfully") # 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: self.apr = find_in_path('apr-1-config') if not self.apr: self.apr = find_in_path('apr-config') if self.apr: log.info("Found %s" % self.apr) else: raise DistutilsOptionError("Could not find apr-1-config or " \ "apr-config. Please rerun with the " \ "--apr option.") if os.path.exists(self.apr): if os.path.isdir(self.apr): if os.path.exists(os.path.join(self.apr, "bin", "apr-1-config")): self.apr_config = os.path.join(self.apr, "bin", "apr-1-config") elif os.path.exists(os.path.join(self.apr, "bin", "apr-config")): self.apr_config = os.path.join(self.apr, "bin", "apr-config") else: self.apr_config = None elif os.path.basename(self.apr) in ("apr-1-config", "apr-config"): self.apr_config = self.apr else: self.apr_config = None 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-1-config file or the " \ "apr-config file") # Validate apr-util if not self.apr_util: self.apr_util = find_in_path('apu-1-config') if not self.apr_util: self.apr_util = find_in_path('apu-config') if self.apr_util: log.info("Found %s" % self.apr_util) else: raise DistutilsOptionError("Could not find apu-1-config or " \ "apu-config. Please rerun with the " \ "--apr-util option.") 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-1-config")): self.apu_config = os.path.join(self.apr_util, "bin", "apu-1-config") elif os.path.exists(os.path.join(self.apr_util, "bin", "apu-config")): self.apu_config = os.path.join(self.apr_util, "bin", "apu-config") else: self.apu_config = None elif os.path.basename(self.apr_util) in ("apu-1-config", "apu-config"): self.apu_config = self.apr_util else: self.apu_config = None 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-1-config " \ "file or the apu-config file") # Validate subversion if not self.subversion: self.subversion = find_in_path('svn') if self.subversion: log.info("Found %s" % self.subversion) # Get the installation root instead of path to 'svn' self.subversion = os.path.normpath(os.path.join(self.subversion, "..", "..")) else: raise DistutilsOptionError("Could not find Subversion. Please rerun " \ "with the --subversion option.") # Validate svn-headers, if present if self.svn_headers: if os.path.isdir(self.svn_headers): if os.path.exists(os.path.join(self.svn_headers, "svn_client.h")): self.svn_include_dir = self.svn_headers elif os.path.exists(os.path.join(self.svn_headers, "subversion-1", "svn_client.h")): self.svn_include_dir = os.path.join(self.svn_headers, "subversion-1") else: self.svn_include_dir = None else: self.svn_include_dir = None elif os.path.exists(os.path.join(self.subversion, "include", "subversion-1")): self.svn_include_dir = "%s/include/subversion-1" % self.subversion else: self.svn_include_dir = None if not self.svn_include_dir: msg = "" if self.svn_headers: msg = "The --svn-headers options is not valid. It must point to " \ "either a Subversion include directory or the Subversion " \ "include/subversion-1 directory." else: msg = "The --subversion option is not valid. " \ "Could not locate %s/include/" \ "subversion-1/svn_client.h" % self.subversion raise DistutilsOptionError(msg) # Validate ctypesgen if not self.ctypesgen: self.ctypesgen = find_in_path('ctypesgen.py') if self.ctypesgen: log.info("Found %s" % self.ctypesgen) else: raise DistutilsOptionError("Could not find ctypesgen. Please rerun " \ "with the --ctypesgen option.") if os.path.exists(self.ctypesgen): if os.path.isdir(self.ctypesgen): if os.path.exists(os.path.join(self.ctypesgen, "ctypesgen.py")): self.ctypesgen_py = os.path.join(self.ctypesgen, "ctypesgen.py") elif os.path.exists(os.path.join(self.ctypesgen, "bin", "ctypesgen.py")): self.ctypesgen_py = os.path.join(self.ctypesgen, "bin", "ctypesgen.py") else: self.ctypesgen_py = None elif os.path.basename(self.ctypesgen) == "ctypesgen.py": self.ctypesgen_py = self.ctypesgen else: self.ctypesgen_py = None else: self.ctypesgen_py = None if not self.ctypesgen_py: raise DistutilsOptionError("The --ctypesgen option is not valid. It " \ "must point to a valid ctypesgen " \ "installation, a ctypesgen source tree or " \ "to the ctypesgen.py script") # 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 def find_in_path(file): paths = [] if os.environ.has_key('PATH'): paths = os.environ['PATH'].split(os.pathsep) for path in paths: file_path = os.path.join(path, file) if os.path.exists(file_path): # Return the path to the first existing file found in PATH return os.path.abspath(file_path) return None # find_in_path() 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.apache.org', url='http://subversion.apache.org', packages=['csvn', 'csvn.core', 'csvn.ext'], license='Apache License, Version 2.0', ) # 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.