1#!/usr/bin/env python 2 3import sys 4import subprocess 5import shutil 6import re 7import os 8import plistlib 9 10# We need at least Python 2.5 11MIN_PYTHON = (2, 6) 12 13# FIXME: autodetect default values for USE_* variables: 14# both should be false by default, unless 15# 1) python is /usr/bin/python: both should be true 16# 2) python build with --with-system-libffi: USE_SYSTEM_FFI 17# should be true. 18 19# Set USE_SYSTEM_FFI to True to link to the system version 20# of libffi 21USE_SYSTEM_FFI = True 22 23SDKROOT = os.environ.get('SDKROOT') 24if SDKROOT is None or SDKROOT is '': 25 SDKROOT = '/' 26 27if sys.version_info < MIN_PYTHON: 28 vstr = '.'.join(map(str, MIN_PYTHON)) 29 raise SystemExit('PyObjC: Need at least Python ' + vstr) 30 31 32try: 33 import setuptools 34 35except ImportError: 36 import distribute_setup 37 distribute_setup.use_setuptools() 38 39 40#extra_args=dict( 41 #use_2to3 = True, 42#) 43 44def get_os_level(): 45 pl = plistlib.readPlist('/System/Library/CoreServices/SystemVersion.plist') 46 v = pl['ProductVersion'] 47 return '.'.join(v.split('.')[:2]) 48 49 50 51 52 53from setuptools.command import build_py 54from setuptools.command import test 55from distutils import log 56from distutils.core import Command 57 58 59class oc_build_py (build_py.build_py): 60 def run_2to3(self, files, doctests=True): 61 files = [ fn for fn in files if not os.path.basename(fn).startswith('test3_') ] 62 build_py.build_py.run_2to3(self, files, doctests) 63 64 def build_packages(self): 65 log.info("Overriding build_packages to copy PyObjCTest") 66 p = self.packages 67 self.packages = list(self.packages) + ['PyObjCTest'] 68 try: 69 build_py.build_py.build_packages(self) 70 finally: 71 self.packages = p 72 73from pkg_resources import working_set, normalize_path, add_activation_listener, require 74 75class oc_test (test.test): 76 description = "run test suite" 77 user_options = [ 78 ('verbosity=', None, "print what tests are run"), 79 ] 80 81 def initialize_options(self): 82 self.verbosity='1' 83 84 def finalize_options(self): 85 if isinstance(self.verbosity, str): 86 self.verbosity = int(self.verbosity) 87 88 89 def cleanup_environment(self): 90 ei_cmd = self.get_finalized_command('egg_info') 91 egg_name = ei_cmd.egg_name.replace('-', '_') 92 93 to_remove = [] 94 for dirname in sys.path: 95 bn = os.path.basename(dirname) 96 if bn.startswith(egg_name + "-"): 97 to_remove.append(dirname) 98 99 for dirname in to_remove: 100 log.info("removing installed %r from sys.path before testing"%( 101 dirname,)) 102 sys.path.remove(dirname) 103 104 from pkg_resources import add_activation_listener 105 add_activation_listener(lambda dist: dist.activate()) 106 working_set.__init__() 107 108 def add_project_to_sys_path(self): 109 from pkg_resources import normalize_path, add_activation_listener 110 from pkg_resources import working_set, require 111 112 if getattr(self.distribution, 'use_2to3', False): 113 114 # Using 2to3, cannot do this inplace: 115 self.reinitialize_command('build_py', inplace=0) 116 self.run_command('build_py') 117 bpy_cmd = self.get_finalized_command("build_py") 118 build_path = normalize_path(bpy_cmd.build_lib) 119 120 self.reinitialize_command('egg_info', egg_base=build_path) 121 self.run_command('egg_info') 122 123 self.reinitialize_command('build_ext', inplace=0) 124 self.run_command('build_ext') 125 126 else: 127 self.reinitialize_command('egg_info') 128 self.run_command('egg_info') 129 self.reinitialize_command('build_ext', inplace=1) 130 self.run_command('build_ext') 131 132 self.__old_path = sys.path[:] 133 self.__old_modules = sys.modules.copy() 134 135 if 'PyObjCTools' in sys.modules: 136 del sys.modules['PyObjCTools'] 137 138 139 ei_cmd = self.get_finalized_command('egg_info') 140 sys.path.insert(0, normalize_path(ei_cmd.egg_base)) 141 sys.path.insert(1, os.path.dirname(__file__)) 142 143 add_activation_listener(lambda dist: dist.activate()) 144 working_set.__init__() 145 require('%s==%s'%(ei_cmd.egg_name, ei_cmd.egg_version)) 146 147 def remove_from_sys_path(self): 148 from pkg_resources import working_set 149 sys.path[:] = self.__old_path 150 sys.modules.clear() 151 sys.modules.update(self.__old_modules) 152 working_set.__init__() 153 154 155 def run(self): 156 import unittest 157 158 # Ensure that build directory is on sys.path (py3k) 159 import sys 160 161 self.cleanup_environment() 162 self.add_project_to_sys_path() 163 164 from PyObjCTest.loader import makeTestSuite 165 import PyObjCTools.TestSupport as mod 166 167 try: 168 meta = self.distribution.metadata 169 name = meta.get_name() 170 test_pkg = name + "_tests" 171 suite = makeTestSuite() 172 173 runner = unittest.TextTestRunner(verbosity=self.verbosity) 174 result = runner.run(suite) 175 176 # Print out summary. This is a structured format that 177 # should make it easy to use this information in scripts. 178 summary = dict( 179 count=result.testsRun, 180 fails=len(result.failures), 181 errors=len(result.errors), 182 xfails=len(getattr(result, 'expectedFailures', [])), 183 xpass=len(getattr(result, 'expectedSuccesses', [])), 184 skip=len(getattr(result, 'skipped', [])), 185 ) 186 print("SUMMARY: %s"%(summary,)) 187 188 finally: 189 self.remove_from_sys_path() 190 191from setuptools.command import egg_info 192 193def write_header(cmd, basename, filename): 194 with open(os.path.join('Modules/objc/', os.path.basename(basename)), 'rU') as fp: 195 data = fp.read() 196 if not cmd.dry_run: 197 if not os.path.exists(os.path.dirname(filename)): 198 os.makedirs(os.path.dirname(filename)) 199 200 cmd.write_file(basename, filename, data) 201 202 203# This is a workaround for a bug in setuptools: I'd like 204# to use the 'egg_info.writers' entry points in the setup() 205# call, but those don't work when also using a package_base 206# argument as we do. 207# (issue 123 in the distribute tracker) 208class my_egg_info (egg_info.egg_info): 209 def run(self): 210 self.mkpath(self.egg_info) 211 212 for hdr in ("pyobjc-compat.h", "pyobjc-api.h"): 213 fn = os.path.join("include", hdr) 214 215 write_header(self, fn, os.path.join(self.egg_info, fn)) 216 217 egg_info.egg_info.run(self) 218 219 220if sys.version_info[0] == 3: 221 # FIXME: add custom test command that does the work. 222 # - Patch sys.path 223 # - Ensure PyObjCTest gets translated by 2to3 224 from distutils.util import get_platform 225 build_dir = 'build/lib.%s-%d.%d'%( 226 get_platform(), sys.version_info[0], sys.version_info[1]) 227 if hasattr(sys, 'gettotalrefcount'): 228 build_dir += '-pydebug' 229 sys.path.insert(0, build_dir) 230 231 232import os 233import glob 234import site 235import platform 236 237if 'MallocStackLogging' in os.environ: 238 del os.environ['MallocStackLogging'] 239if 'MallocStackLoggingNoCompact' in os.environ: 240 del os.environ['MallocStackLoggingNoCompact'] 241 242# See the news file: 243#os.environ['MACOSX_DEPLOYMENT_TARGET']='10.5' 244 245 246 247#if int(os.uname()[2].split('.')[0]) >= 10: 248# USE_SYSTEM_FFI = True 249 250 251# Some PiPy stuff 252LONG_DESCRIPTION=""" 253PyObjC is a bridge between Python and Objective-C. It allows full 254featured Cocoa applications to be written in pure Python. It is also 255easy to use other frameworks containing Objective-C class libraries 256from Python and to mix in Objective-C, C and C++ source. 257 258Python is a highly dynamic programming language with a shallow learning 259curve. It combines remarkable power with very clear syntax. 260 261The installer package installs a number of Xcode templates for 262easily creating new Cocoa-Python projects. 263 264PyObjC also supports full introspection of Objective-C classes and 265direct invocation of Objective-C APIs from the interactive interpreter. 266""" 267 268from setuptools import setup, Extension, find_packages 269from setuptools.command import build_ext, install_lib 270import os 271 272class pyobjc_install_lib (install_lib.install_lib): 273 def get_exclusions(self): 274 result = install_lib.install_lib.get_exclusions(self) 275 for fn in install_lib._install_lib.get_outputs(self): 276 if 'PyObjCTest' in fn: 277 result[fn] = 1 278 279 for fn in os.listdir('PyObjCTest'): 280 result[os.path.join('PyObjCTest', fn)] = 1 281 result[os.path.join(self.install_dir, 'PyObjCTest', fn)] = 1 282 283 284 return result 285 286 287def _find_executable(executable): 288 if os.path.isfile(executable): 289 return executable 290 291 else: 292 for p in os.environ['PATH'].split(os.pathsep): 293 f = os.path.join(p, executable) 294 if os.path.isfile(f): 295 return f 296 return None 297 298def _working_compiler(executable): 299 import tempfile, subprocess, shlex 300 with tempfile.NamedTemporaryFile(mode='w', suffix='.c') as fp: 301 fp.write('#include <stdarg.h>\nint main(void) { return 0; }\n') 302 fp.flush() 303 304 cflags = get_config_var('CFLAGS') 305 cflags = shlex.split(cflags) 306 cflags += CFLAGS 307 308 p = subprocess.Popen([ 309 executable, '-c', fp.name] + cflags, 310 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 311 exit = p.wait() 312 if exit != 0: 313 return False 314 315 binfile = fp.name[:-1] + 'o' 316 if os.path.exists(binfile): 317 os.unlink(binfile) 318 319 binfile = os.path.basename(binfile) 320 if os.path.exists(binfile): 321 os.unlink(binfile) 322 323 return True 324 325def _fixup_compiler(): 326 if 'CC' in os.environ: 327 # CC is in the environment, always use explicit 328 # overrides. 329 return 330 331 cc = oldcc = get_config_var('CC').split()[0] 332 cc = _find_executable(cc) 333 if cc is not None and os.path.basename(cc).startswith('gcc'): 334 # Check if compiler is LLVM-GCC, that's known to 335 # generate bad code. 336 data = os.popen("'%s' --version 2>/dev/null"%( 337 cc.replace("'", "'\"'\"'"),)).read() 338 if 'llvm-gcc' in data: 339 cc = None 340 341 if cc is not None and not _working_compiler(cc): 342 cc = None 343 344 if cc is None: 345 # Default compiler is not useable, try finding 'clang' 346 cc = _find_executable('clang') 347 if cc is None: 348 cc = os.popen("/usr/bin/xcrun -find clang").read() 349 350 if not cc: 351 raise SystemExit("Cannot locate compiler candidate") 352 353 if not _working_compiler(cc): 354 raise SystemExit("Cannot locate a working compiler") 355 356 if cc != oldcc: 357 print("Use '%s' instead of '%s' as the compiler"%( 358 cc, oldcc)) 359 360 vars = get_config_vars() 361 for env in ('BLDSHARED', 'LDSHARED', 'CC', 'CXX'): 362 if env in vars and env not in os.environ: 363 split = vars[env].split() 364 split[0] = cc if env != 'CXX' else cc + '++' 365 vars[env] = ' '.join(split) 366 367 368class pyobjc_build_ext (build_ext.build_ext): 369 def run(self): 370 _fixup_compiler() 371 build_ext.build_ext.run(self) 372 extensions = self.extensions 373 self.extensions = [ 374 e for e in extensions if e.name.startswith('PyObjCTest') ] 375 self.copy_extensions_to_source() 376 self.extensions = extensions 377 378def frameworks(*args): 379 lst = [] 380 for arg in args: 381 lst.extend(['-framework', arg]) 382 return lst 383 384def IfFrameWork(name, packages, extensions, headername=None): 385 """ 386 Return the packages and extensions if the framework exists, or 387 two empty lists if not. 388 """ 389 import os 390 for pth in ('/System/Library/Frameworks', '/Library/Frameworks'): 391 basedir = os.path.join(pth, name) 392 if os.path.exists(basedir): 393 if (headername is None) or os.path.exists(os.path.join(basedir, "Headers", headername)): 394 return packages, extensions 395 return [], [] 396 397# Double-check 398if sys.platform != 'darwin': 399 print("You're not running on MacOS X, and don't use GNUstep") 400 print("I don't know how to build PyObjC on such a platform.") 401 print("Please read the ReadMe.") 402 print("") 403 raise SystemExit("ObjC runtime not found") 404 405from distutils.sysconfig import get_config_var, get_config_vars 406 407CFLAGS=[ ] 408 409# Enable 'PyObjC_STRICT_DEBUGGING' to enable some costly internal 410# assertions. 411CFLAGS.extend([ 412 #"-fdiagnostics-show-option", 413 414 # Use this to analyze with clang 415 #"--analyze", 416 417# The following flags are an attempt at getting rid of /usr/local 418# in the compiler search path. 419 "-DPyObjC_STRICT_DEBUGGING", 420 "-DMACOSX", # For libffi 421 "-DPyObjC_BUILD_RELEASE=%02d%02d"%(tuple(map(int, platform.mac_ver()[0].split('.')[:2]))), 422# "-no-cpp-precomp", 423 "-DMACOSX", 424 "-g", 425 "-fexceptions", 426 427 428 # Loads of warning flags 429 "-Wall", "-Wstrict-prototypes", "-Wmissing-prototypes", 430 "-Wformat=2", "-W", 431 #"-Wshadow", # disabled due to warnings from Python headers 432 "-Wpointer-arith", #"-Wwrite-strings", 433 "-Wmissing-declarations", 434 "-Wnested-externs", 435# "-Wno-long-long", 436 "-W", 437 438 #"-fcatch-undefined-behavior", 439 #"-Wno-missing-method-return-type", # XXX 440 "-Wno-import", 441 "-DPyObjC_BUILD_RELEASE=%02d%02d"%(tuple(map(int, get_os_level().split('.')))), 442 #"-Warray-bounds", # XXX: Needed to avoid False positives for PyTuple access macros 443 ]) 444 445## Arghh, a stupid compiler flag can cause problems. Don't 446## enable -O0 if you value your sanity. With -O0 PyObjC will crash 447## on i386 systems when a method returns a struct that isn't returned 448## in registers. 449if '-O0' in get_config_var('CFLAGS'): 450 print ("Change -O0 to -O1") 451 vars = get_config_vars() 452 for k in vars: 453 if isinstance(vars[k], str) and '-O0' in vars[k]: 454 vars[k] = vars[k].replace('-O0', '-O1') 455 456OBJC_LDFLAGS = frameworks('CoreFoundation', 'Foundation', 'Carbon') 457 458if not os.path.exists(os.path.join(SDKROOT, 'usr/include/objc/runtime.h')): 459 CFLAGS.append('-DNO_OBJC2_RUNTIME') 460 461# Force compilation with the local SDK, compilation of PyObC will result in 462# a binary that runs on other releases of the OS without using a particular SDK. 463pass 464pass 465CFLAGS.append('-Ibuild/codegen/') 466 467# Patch distutils: it needs to compile .S files as well. 468from distutils.unixccompiler import UnixCCompiler 469UnixCCompiler.src_extensions.append('.S') 470del UnixCCompiler 471 472 473# 474# Support for an embedded copy of libffi 475# 476FFI_CFLAGS=['-Ilibffi-src/include', '-Ilibffi-src/powerpc'] 477 478# The list below includes the source files for all CPU types that we run on 479# this makes it easier to build fat binaries on Mac OS X. 480FFI_SOURCE=[ 481 "libffi-src/ffi.c", 482 "libffi-src/types.c", 483 "libffi-src/powerpc/ppc-darwin.S", 484 "libffi-src/powerpc/ppc-darwin_closure.S", 485 "libffi-src/powerpc/ppc-ffi_darwin.c", 486 "libffi-src/powerpc/ppc64-darwin_closure.S", 487 "libffi-src/x86/darwin64.S", 488 "libffi-src/x86/x86-darwin.S", 489 "libffi-src/x86/x86-ffi64.c", 490 "libffi-src/x86/x86-ffi_darwin.c", 491] 492 493 494 495# 496# Calculate the list of extensions: objc._objc + extensions for the unittests 497# 498 499if USE_SYSTEM_FFI: 500 ExtensionList = [ 501 Extension("objc._objc", 502 list(glob.glob(os.path.join('Modules', 'objc', '*.m'))), 503 extra_compile_args=CFLAGS + ['-I' + os.path.join(SDKROOT, "usr/include/ffi")], 504 extra_link_args=OBJC_LDFLAGS + ["-lffi"], 505 depends=list(glob.glob(os.path.join('Modules', 'objc', '*.h'))), 506 ), 507 ] 508 509else: 510 ExtensionList = [ 511 Extension("objc._objc", 512 FFI_SOURCE + list(glob.glob(os.path.join('Modules', 'objc', '*.m'))), 513 extra_compile_args=CFLAGS + FFI_CFLAGS, 514 extra_link_args=OBJC_LDFLAGS, 515 depends=list(glob.glob(os.path.join('Modules', 'objc', '*.h'))), 516 ), 517 ] 518 519for test_source in glob.glob(os.path.join('Modules', 'objc', 'test', '*.m')): 520 name, ext = os.path.splitext(os.path.basename(test_source)) 521 522 ExtensionList.append(Extension('PyObjCTest.' + name, 523 [test_source], 524 extra_compile_args=['-IModules/objc'] + CFLAGS, 525 extra_link_args=OBJC_LDFLAGS)) 526 527def package_version(): 528 fp = open('Modules/objc/pyobjc.h', 'r') 529 for ln in fp.readlines(): 530 if ln.startswith('#define OBJC_VERSION'): 531 fp.close() 532 return ln.split()[-1][1:-1] 533 534 raise ValueError("Version not found") 535 536CLASSIFIERS = filter(None, 537""" 538Development Status :: 5 - Production/Stable 539Environment :: Console 540Environment :: MacOS X :: Cocoa 541Intended Audience :: Developers 542License :: OSI Approved :: MIT License 543Natural Language :: English 544Operating System :: MacOS :: MacOS X 545Programming Language :: Python 546Programming Language :: Python :: 2 547Programming Language :: Python :: 2.6 548Programming Language :: Python :: 2.7 549Programming Language :: Python :: 3 550Programming Language :: Python :: 3.1 551Programming Language :: Python :: 3.2 552Programming Language :: Python :: 3.3 553Programming Language :: Objective C 554Topic :: Software Development :: Libraries :: Python Modules 555Topic :: Software Development :: User Interfaces 556""".splitlines()) 557 558 559dist = setup( 560 name = "pyobjc-core", 561 version = package_version(), 562 description = "Python<->ObjC Interoperability Module", 563 long_description = LONG_DESCRIPTION, 564 author = "Ronald Oussoren, bbum, SteveM, LeleG, many others stretching back through the reaches of time...", 565 author_email = "pyobjc-dev@lists.sourceforge.net", 566 url = "http://pyobjc.sourceforge.net/", 567 platforms = [ 'MacOS X' ], 568 ext_modules = ExtensionList, 569 packages = [ 'objc', 'PyObjCTools', ], 570 #namespace_packages = ['PyObjCTools'], 571 package_dir = { '': 'Lib', 'PyObjCTest': 'PyObjCTest' }, 572 extra_path = "PyObjC", 573 cmdclass = {'build_ext': pyobjc_build_ext, 'install_lib': pyobjc_install_lib, 'build_py': oc_build_py, 'test': oc_test, 'egg_info':my_egg_info }, 574 options = {'egg_info': {'egg_base': 'Lib'}}, 575 classifiers = CLASSIFIERS, 576 license = 'MIT License', 577 download_url = 'http://pyobjc.sourceforge.net/software/index.php', 578 zip_safe = False, 579) 580