1#!/usr/bin/env python 2# Licensed to the Apache Software Foundation (ASF) under one 3# or more contributor license agreements. See the NOTICE file 4# distributed with this work for additional information 5# regarding copyright ownership. The ASF licenses this file 6# to you under the Apache License, Version 2.0 (the 7# "License"); you may not use this file except in compliance 8# with the License. You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, 13# software distributed under the License is distributed on an 14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15# KIND, either express or implied. See the License for the 16# specific language governing permissions and limitations 17# under the License. 18 19import errno, os, re, sys, tempfile 20 21from distutils import log 22from distutils.cmd import Command 23from distutils.command.build import build as _build 24from distutils.command.clean import clean as _clean 25from distutils.core import setup 26from distutils.dir_util import remove_tree 27from distutils.errors import DistutilsExecError 28from distutils.errors import DistutilsOptionError 29 30from glob import glob 31 32class clean(_clean): 33 """Special distutils command for cleaning the Subversion ctypes bindings.""" 34 description = "clean the Subversion ctypes Python bindings" 35 36 def initialize_options (self): 37 _clean.initialize_options(self) 38 39 # initialize_options() 40 41 def finalize_options (self): 42 _clean.finalize_options(self) 43 44 # finalize_options() 45 46 def run(self): 47 functions_py = os.path.join(os.path.dirname(__file__), "csvn", "core", 48 "functions.py") 49 functions_pyc = os.path.join(os.path.dirname(__file__), "csvn", "core", 50 "functions.pyc") 51 svn_all_py = os.path.join(os.path.dirname(__file__), "svn_all.py") 52 svn_all2_py = os.path.join(os.path.dirname(__file__), "svn_all2.py") 53 54 for f in (functions_py, functions_pyc, svn_all_py, svn_all2_py): 55 if os.path.exists(f): 56 log.info("removing '%s'", os.path.normpath(f)) 57 58 if not self.dry_run: 59 os.remove(f) 60 else: 61 log.debug("'%s' does not exist -- can't clean it", os.path.normpath(f)) 62 63 # Run standard clean command 64 _clean.run(self) 65 66 # run() 67 68# class clean 69 70class build(_build): 71 """Special distutils command for building the Subversion ctypes bindings.""" 72 73 description = "build the Subversion ctypes Python bindings" 74 75 _build.user_options.append(("apr=", None, "full path to where apr is " 76 "installed or the full path to the apr-1-config or " 77 "apr-config file")) 78 _build.user_options.append(("apr-util=", None, "full path to where apr-util " 79 "is installed or the full path to the apu-1-config or " 80 "apu-config file")) 81 _build.user_options.append(("subversion=", None, "full path to where " 82 "Subversion is installed")) 83 _build.user_options.append(("svn-headers=", None, "Full path to the " 84 "Subversion header files, if they are not in a " 85 "standard location")) 86 _build.user_options.append(("ctypesgen=", None, "full path to where ctypesgen " 87 "is installed, to the ctypesgen source tree or " 88 "the full path to ctypesgen.py")) 89 _build.user_options.append(("cppflags=", None, "extra flags to pass to the c " 90 "preprocessor")) 91 _build.user_options.append(("ldflags=", None, "extra flags to pass to the " 92 "ctypesgen linker")) 93 _build.user_options.append(("lib-dirs=", None, "colon-delimited list of paths " 94 "to append to the search path")) 95 _build.user_options.append(("save-preprocessed-headers=", None, "full path to " 96 "where to save the preprocessed headers")) 97 98 def initialize_options (self): 99 _build.initialize_options(self) 100 self.apr = None 101 self.apr_util = None 102 self.ctypesgen = None 103 self.subversion = None 104 self.svn_headers = None 105 self.cppflags = "" 106 self.ldflags = "" 107 self.lib_dirs = None 108 self.save_preprocessed_headers = None 109 110 # initialize_options() 111 112 def finalize_options (self): 113 _build.finalize_options(self) 114 115 # Distutils doesn't appear to like when you have --dry-run after the build 116 # build command so fail out if this is the case. 117 if self.dry_run != self.distribution.dry_run: 118 raise DistutilsOptionError("The --dry-run flag must be specified " \ 119 "before the 'build' command") 120 121 # finalize_options() 122 123 ############################################################################## 124 # Get build configuration 125 ############################################################################## 126 def get_build_config (self): 127 flags = [] 128 ldflags = [] 129 library_path = [] 130 ferr = None 131 apr_include_dir = None 132 133 fout = self.run_cmd("%s --includes --cppflags" % self.apr_config) 134 if fout: 135 flags = fout.split() 136 apr_prefix = self.run_cmd("%s --prefix" % self.apr_config) 137 apr_prefix = apr_prefix.strip() 138 apr_include_dir = self.run_cmd("%s --includedir" % self.apr_config).strip() 139 apr_version = self.run_cmd("%s --version" % self.apr_config).strip() 140 cpp = self.run_cmd("%s --cpp" % self.apr_config).strip() 141 fout = self.run_cmd("%s --ldflags --link-ld" % self.apr_config) 142 if fout: 143 ldflags = fout.split() 144 else: 145 log.error(ferr) 146 raise DistutilsExecError("Problem running '%s'. Check the output " \ 147 "for details" % self.apr_config) 148 149 fout = self.run_cmd("%s --includes" % self.apu_config) 150 if fout: 151 flags += fout.split() 152 fout = self.run_cmd("%s --ldflags --link-ld" % self.apu_config) 153 if fout: 154 ldflags += fout.split() 155 else: 156 log.error(ferr) 157 raise DistutilsExecError("Problem running '%s'. Check the output " \ 158 "for details" % self.apr_config) 159 160 subversion_prefixes = [ 161 self.subversion, 162 "/usr/local", 163 "/usr" 164 ] 165 166 if self.subversion != "/usr": 167 ldflags.append("-L%s/lib" % self.subversion) 168 if self.svn_include_dir[-18:] == "subversion/include": 169 flags.append("-Isubversion/include") 170 else: 171 flags.append("-I%s" % self.svn_include_dir) 172 173 # List the libraries in the order they should be loaded 174 libraries = [ 175 "svn_subr-1", 176 "svn_diff-1", 177 "svn_delta-1", 178 "svn_fs-1", 179 "svn_repos-1", 180 "svn_wc-1", 181 "svn_ra-1", 182 "svn_client-1", 183 ] 184 185 for lib in libraries: 186 ldflags.append("-l%s" % lib) 187 188 if apr_prefix != '/usr': 189 library_path.append("%s/lib" % apr_prefix) 190 if self.subversion != '/usr' and self.subversion != apr_prefix: 191 library_path.append("%s/lib" % self.subversion) 192 193 return (apr_prefix, apr_include_dir, cpp + " " + self.cppflags, 194 " ".join(ldflags) + " " + self.ldflags, " ".join(flags), 195 ":".join(library_path)) 196 197 # get_build_config() 198 199 ############################################################################## 200 # Build csvn/core/functions.py 201 ############################################################################## 202 def build_functions_py(self): 203 (apr_prefix, apr_include_dir, cpp, ldflags, flags, 204 library_path) = self.get_build_config() 205 cwd = os.getcwd() 206 if self.svn_include_dir[-18:] == "subversion/include": 207 includes = ('subversion/include/svn_*.h ' 208 '%s/ap[ru]_*.h' % apr_include_dir) 209 cmd = ["%s %s --cpp '%s %s' %s " 210 "%s -o subversion/bindings/ctypes-python/svn_all.py " 211 "--no-macro-warnings --strip-build-path=%s" % (sys.executable, 212 self.ctypesgen_py, cpp, 213 flags, ldflags, 214 includes, self.svn_include_dir[:-19])] 215 os.chdir(self.svn_include_dir[:-19]) 216 else: 217 includes = ('%s/svn_*.h ' 218 '%s/ap[ru]_*.h' % (self.svn_include_dir, apr_include_dir)) 219 cmd = ["%s %s --cpp '%s %s' %s " 220 "%s -o svn_all.py --no-macro-warnings" % (sys.executable, 221 self.ctypesgen_py, cpp, 222 flags, ldflags, 223 includes)] 224 if self.lib_dirs: 225 cmd.extend('-R ' + x for x in self.lib_dirs.split(":")) 226 cmd = ' '.join(cmd) 227 228 if self.save_preprocessed_headers: 229 cmd += " --save-preprocessed-headers=%s" % \ 230 os.path.abspath(self.save_preprocessed_headers) 231 232 if self.verbose or self.dry_run: 233 status = self.execute(os.system, (cmd,), cmd) 234 else: 235 f = os.popen(cmd, 'r') 236 f.read() # Required to avoid the 'Broken pipe' error. 237 status = f.close() # None is returned for the usual 0 return code 238 239 os.chdir(cwd) 240 241 if os.name == "posix" and status and status != 0: 242 if os.WIFEXITED(status): 243 status = os.WEXITSTATUS(status) 244 if status != 0: 245 sys.exit(status) 246 elif os.WIFSIGNALED(status): 247 log.error("ctypesgen.py killed with signal %d" % os.WTERMSIG(status)) 248 sys.exit(2) 249 elif os.WIFSTOPPED(status): 250 log.error("ctypesgen.py stopped with signal %d" % os.WSTOPSIG(status)) 251 sys.exit(2) 252 else: 253 log.error("ctypesgen.py exited with invalid status %d", status) 254 sys.exit(2) 255 256 if not self.dry_run: 257 r = re.compile(r"(\s+\w+)\.restype = POINTER\(svn_error_t\)") 258 out = open("svn_all2.py", "w") 259 for line in open("svn_all.py"): 260 line = r.sub("\\1.restype = POINTER(svn_error_t)\n\\1.errcheck = _svn_errcheck", line) 261 262 if not line.startswith("FILE ="): 263 out.write(line) 264 out.close() 265 266 cmd = "cat csvn/core/functions.py.in svn_all2.py > csvn/core/functions.py" 267 self.execute(os.system, (cmd,), cmd) 268 269 log.info("Generated csvn/core/functions.py successfully") 270 271 # build_functions_py() 272 273 def run_cmd(self, cmd): 274 return os.popen(cmd).read() 275 276 # run_cmd() 277 278 def validate_options(self): 279 # Validate apr 280 if not self.apr: 281 self.apr = find_in_path('apr-1-config') 282 283 if not self.apr: 284 self.apr = find_in_path('apr-config') 285 286 if self.apr: 287 log.info("Found %s" % self.apr) 288 else: 289 raise DistutilsOptionError("Could not find apr-1-config or " \ 290 "apr-config. Please rerun with the " \ 291 "--apr option.") 292 293 if os.path.exists(self.apr): 294 if os.path.isdir(self.apr): 295 if os.path.exists(os.path.join(self.apr, "bin", "apr-1-config")): 296 self.apr_config = os.path.join(self.apr, "bin", "apr-1-config") 297 elif os.path.exists(os.path.join(self.apr, "bin", "apr-config")): 298 self.apr_config = os.path.join(self.apr, "bin", "apr-config") 299 else: 300 self.apr_config = None 301 elif os.path.basename(self.apr) in ("apr-1-config", "apr-config"): 302 self.apr_config = self.apr 303 else: 304 self.apr_config = None 305 else: 306 self.apr_config = None 307 308 if not self.apr_config: 309 raise DistutilsOptionError("The --apr option is not valid. It must " \ 310 "point to a valid apr installation or " \ 311 "to either the apr-1-config file or the " \ 312 "apr-config file") 313 314 # Validate apr-util 315 if not self.apr_util: 316 self.apr_util = find_in_path('apu-1-config') 317 318 if not self.apr_util: 319 self.apr_util = find_in_path('apu-config') 320 321 if self.apr_util: 322 log.info("Found %s" % self.apr_util) 323 else: 324 raise DistutilsOptionError("Could not find apu-1-config or " \ 325 "apu-config. Please rerun with the " \ 326 "--apr-util option.") 327 328 if os.path.exists(self.apr_util): 329 if os.path.isdir(self.apr_util): 330 if os.path.exists(os.path.join(self.apr_util, "bin", "apu-1-config")): 331 self.apu_config = os.path.join(self.apr_util, "bin", "apu-1-config") 332 elif os.path.exists(os.path.join(self.apr_util, "bin", "apu-config")): 333 self.apu_config = os.path.join(self.apr_util, "bin", "apu-config") 334 else: 335 self.apu_config = None 336 elif os.path.basename(self.apr_util) in ("apu-1-config", "apu-config"): 337 self.apu_config = self.apr_util 338 else: 339 self.apu_config = None 340 else: 341 self.apu_config = None 342 343 if not self.apu_config: 344 raise DistutilsOptionError("The --apr-util option is not valid. It " \ 345 "must point to a valid apr-util " \ 346 "installation or to either the apu-1-config " \ 347 "file or the apu-config file") 348 349 # Validate subversion 350 if not self.subversion: 351 self.subversion = find_in_path('svn') 352 353 if self.subversion: 354 log.info("Found %s" % self.subversion) 355 # Get the installation root instead of path to 'svn' 356 self.subversion = os.path.normpath(os.path.join(self.subversion, "..", 357 "..")) 358 else: 359 raise DistutilsOptionError("Could not find Subversion. Please rerun " \ 360 "with the --subversion option.") 361 362 # Validate svn-headers, if present 363 if self.svn_headers: 364 if os.path.isdir(self.svn_headers): 365 if os.path.exists(os.path.join(self.svn_headers, "svn_client.h")): 366 self.svn_include_dir = self.svn_headers 367 elif os.path.exists(os.path.join(self.svn_headers, "subversion-1", 368 "svn_client.h")): 369 self.svn_include_dir = os.path.join(self.svn_headers, "subversion-1") 370 else: 371 self.svn_include_dir = None 372 else: 373 self.svn_include_dir = None 374 elif os.path.exists(os.path.join(self.subversion, "include", 375 "subversion-1")): 376 self.svn_include_dir = "%s/include/subversion-1" % self.subversion 377 else: 378 self.svn_include_dir = None 379 380 if not self.svn_include_dir: 381 msg = "" 382 383 if self.svn_headers: 384 msg = "The --svn-headers options is not valid. It must point to " \ 385 "either a Subversion include directory or the Subversion " \ 386 "include/subversion-1 directory." 387 else: 388 msg = "The --subversion option is not valid. " \ 389 "Could not locate %s/include/" \ 390 "subversion-1/svn_client.h" % self.subversion 391 392 raise DistutilsOptionError(msg) 393 394 # Validate ctypesgen 395 if not self.ctypesgen: 396 self.ctypesgen = find_in_path('ctypesgen.py') 397 398 if self.ctypesgen: 399 log.info("Found %s" % self.ctypesgen) 400 else: 401 raise DistutilsOptionError("Could not find ctypesgen. Please rerun " \ 402 "with the --ctypesgen option.") 403 404 if os.path.exists(self.ctypesgen): 405 if os.path.isdir(self.ctypesgen): 406 if os.path.exists(os.path.join(self.ctypesgen, "ctypesgen.py")): 407 self.ctypesgen_py = os.path.join(self.ctypesgen, "ctypesgen.py") 408 elif os.path.exists(os.path.join(self.ctypesgen, "bin", 409 "ctypesgen.py")): 410 self.ctypesgen_py = os.path.join(self.ctypesgen, "bin", 411 "ctypesgen.py") 412 else: 413 self.ctypesgen_py = None 414 elif os.path.basename(self.ctypesgen) == "ctypesgen.py": 415 self.ctypesgen_py = self.ctypesgen 416 else: 417 self.ctypesgen_py = None 418 else: 419 self.ctypesgen_py = None 420 421 if not self.ctypesgen_py: 422 raise DistutilsOptionError("The --ctypesgen option is not valid. It " \ 423 "must point to a valid ctypesgen " \ 424 "installation, a ctypesgen source tree or " \ 425 "to the ctypesgen.py script") 426 427 # validate_functions() 428 429 def run (self): 430 # We only want to build if functions.py is not present. 431 if not os.path.exists(os.path.join(os.path.dirname(__file__), 432 "csvn", "core", 433 "functions.py")): 434 if 'build' not in self.distribution.commands: 435 raise DistutilsOptionError("You must run 'build' explicitly before " \ 436 "you can proceed") 437 438 # Validate the command line options 439 self.validate_options() 440 441 # Generate functions.py 442 self.build_functions_py() 443 else: 444 log.info("csvn/core/functions.py was not regenerated (output up-to-date)") 445 446 # Run the standard build command. 447 _build.run(self) 448 449 # run() 450 451# class build 452 453def find_in_path(file): 454 paths = [] 455 if os.environ.has_key('PATH'): 456 paths = os.environ['PATH'].split(os.pathsep) 457 458 for path in paths: 459 file_path = os.path.join(path, file) 460 461 if os.path.exists(file_path): 462 # Return the path to the first existing file found in PATH 463 return os.path.abspath(file_path) 464 465 return None 466# find_in_path() 467 468setup(cmdclass={'build': build, 'clean': clean}, 469 name='svn-ctypes-python-bindings', 470 version='0.1', 471 description='Python bindings for the Subversion version control system.', 472 author='The Subversion Team', 473 author_email='dev@subversion.apache.org', 474 url='http://subversion.apache.org', 475 packages=['csvn', 'csvn.core', 'csvn.ext'], 476 license='Apache License, Version 2.0', 477 ) 478 479# TODO: We need to create our own bdist_rpm implementation so that we can pass 480# our required arguments to the build command being called by bdist_rpm. 481