1""" 2Generic setup.py file for PyObjC framework wrappers. 3 4This file should only be changed in pyobjc-core and then copied 5to all framework wrappers. 6""" 7 8__all__ = ('setup', 'Extension', 'Command') 9 10import sys 11from pkg_resources import Distribution 12 13try: 14 import setuptools 15 16except ImportError: 17 import distribute_setup 18 distribute_setup.use_setuptools() 19 20from setuptools.command import test 21from setuptools.command import build_py 22from distutils.sysconfig import get_config_var, get_config_vars 23 24 25from distutils import log 26 27class oc_build_py (build_py.build_py): 28 def build_packages(self): 29 log.info("overriding build_packages to copy PyObjCTest") 30 p = self.packages 31 self.packages = list(self.packages) + ['PyObjCTest'] 32 try: 33 build_py.build_py.build_packages(self) 34 finally: 35 self.packages = p 36 37 38from pkg_resources import working_set, normalize_path, add_activation_listener, require 39 40class oc_test (test.test): 41 description = "run test suite" 42 user_options = [ 43 ('verbosity=', None, "print what tests are run"), 44 ] 45 46 def initialize_options(self): 47 test.test.initialize_options(self) 48 self.verbosity='1' 49 50 def finalize_options(self): 51 test.test.finalize_options(self) 52 if isinstance(self.verbosity, str): 53 self.verbosity = int(self.verbosity) 54 55 56 def cleanup_environment(self): 57 ei_cmd = self.get_finalized_command('egg_info') 58 egg_name = ei_cmd.egg_name.replace('-', '_') 59 60 to_remove = [] 61 for dirname in sys.path: 62 bn = os.path.basename(dirname) 63 if bn.startswith(egg_name + "-"): 64 to_remove.append(dirname) 65 66 for dirname in to_remove: 67 log.info("removing installed %r from sys.path before testing"%( 68 dirname,)) 69 sys.path.remove(dirname) 70 71 from pkg_resources import add_activation_listener 72 add_activation_listener(lambda dist: dist.activate()) 73 working_set.__init__() 74 75 def add_project_to_sys_path(self): 76 from pkg_resources import normalize_path, add_activation_listener 77 from pkg_resources import working_set, require 78 79 self.reinitialize_command('egg_info') 80 self.run_command('egg_info') 81 self.reinitialize_command('build_ext', inplace=1) 82 self.run_command('build_ext') 83 84 self.__old_path = sys.path[:] 85 self.__old_modules = sys.modules.copy() 86 87 if 'PyObjCTools' in sys.modules: 88 del sys.modules['PyObjCTools'] 89 90 91 ei_cmd = self.get_finalized_command('egg_info') 92 sys.path.insert(0, normalize_path(ei_cmd.egg_base)) 93 sys.path.insert(1, os.path.dirname(__file__)) 94 95 add_activation_listener(lambda dist: dist.activate()) 96 working_set.__init__() 97 require('%s==%s'%(ei_cmd.egg_name, ei_cmd.egg_version)) 98 99 def remove_from_sys_path(self): 100 from pkg_resources import working_set 101 sys.path[:] = self.__old_path 102 sys.modules.clear() 103 sys.modules.update(self.__old_modules) 104 working_set.__init__() 105 106 107 def run(self): 108 import unittest 109 110 # Ensure that build directory is on sys.path (py3k) 111 import sys 112 113 self.cleanup_environment() 114 self.add_project_to_sys_path() 115 116 import PyObjCTools.TestSupport as modo 117 118 from pkg_resources import EntryPoint 119 loader_ep = EntryPoint.parse("x="+self.test_loader) 120 loader_class = loader_ep.load(require=False) 121 122 try: 123 meta = self.distribution.metadata 124 name = meta.get_name() 125 test_pkg = name + "_tests" 126 suite = loader_class().loadTestsFromName(self.distribution.test_suite) 127 128 runner = unittest.TextTestRunner(verbosity=self.verbosity) 129 result = runner.run(suite) 130 131 # Print out summary. This is a structured format that 132 # should make it easy to use this information in scripts. 133 summary = dict( 134 count=result.testsRun, 135 fails=len(result.failures), 136 errors=len(result.errors), 137 xfails=len(getattr(result, 'expectedFailures', [])), 138 xpass=len(getattr(result, 'unexpectedSuccesses', [])), 139 skip=len(getattr(result, 'skipped', [])), 140 ) 141 print("SUMMARY: %s"%(summary,)) 142 143 finally: 144 self.remove_from_sys_path() 145 146 147from setuptools import setup as _setup, Extension as _Extension, Command 148from distutils.errors import DistutilsPlatformError 149from distutils.command import build, install 150from setuptools.command import develop, test, build_ext, install_lib 151import pkg_resources 152import shutil 153import os 154import plistlib 155import sys 156import __main__ 157 158CLASSIFIERS = filter(None, 159""" 160Development Status :: 5 - Production/Stable 161Environment :: Console 162Environment :: MacOS X :: Cocoa 163Intended Audience :: Developers 164License :: OSI Approved :: MIT License 165Natural Language :: English 166Operating System :: MacOS :: MacOS X 167Programming Language :: Python 168Programming Language :: Python :: 2 169Programming Language :: Python :: 2.6 170Programming Language :: Python :: 2.7 171Programming Language :: Python :: 3 172Programming Language :: Python :: 3.1 173Programming Language :: Python :: 3.2 174Programming Language :: Python :: 3.3 175Programming Language :: Objective C 176Topic :: Software Development :: Libraries :: Python Modules 177Topic :: Software Development :: User Interfaces 178""".splitlines()) 179 180 181def get_os_level(): 182 pl = plistlib.readPlist('/System/Library/CoreServices/SystemVersion.plist') 183 v = pl['ProductVersion'] 184 return tuple(map(int, v.split('.')[:2])) 185 186class pyobjc_install_lib (install_lib.install_lib): 187 def get_exclusions(self): 188 result = install_lib.install_lib.get_exclusions(self) 189 for fn in install_lib._install_lib.get_outputs(self): 190 if 'PyObjCTest' in fn: 191 result[fn] = 1 192 193 result['PyObjCTest'] = 1 194 result[os.path.join(self.install_dir, 'PyObjCTest')] = 1 195 for fn in os.listdir('PyObjCTest'): 196 result[os.path.join('PyObjCTest', fn)] = 1 197 result[os.path.join(self.install_dir, 'PyObjCTest', fn)] = 1 198 199 return result 200 201def _find_executable(executable): 202 if os.path.isfile(executable): 203 return executable 204 205 else: 206 for p in os.environ['PATH'].split(os.pathsep): 207 f = os.path.join(p, executable) 208 if os.path.isfile(f): 209 return f 210 return None 211 212def _working_compiler(executable): 213 import tempfile, subprocess, shlex 214 with tempfile.NamedTemporaryFile(mode='w', suffix='.c') as fp: 215 fp.write('#include <stdarg.h>\nint main(void) { return 0; }\n') 216 fp.flush() 217 218 cflags = get_config_var('CFLAGS') 219 cflags = shlex.split(cflags) 220 221 p = subprocess.Popen([ 222 executable, '-c', fp.name] + cflags, 223 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 224 exit = p.wait() 225 if exit != 0: 226 return False 227 228 binfile = fp.name[:-1] + 'o' 229 if os.path.exists(binfile): 230 os.unlink(binfile) 231 232 233 binfile = os.path.basename(binfile) 234 if os.path.exists(binfile): 235 os.unlink(binfile) 236 237 return True 238 239def _fixup_compiler(): 240 if 'CC' in os.environ: 241 # CC is in the environment, always use explicit 242 # overrides. 243 return 244 245 cc = oldcc = get_config_var('CC').split()[0] 246 cc = _find_executable(cc) 247 if cc is not None and os.path.basename(cc).startswith('gcc'): 248 # Check if compiler is LLVM-GCC, that's known to 249 # generate bad code. 250 data = os.popen("'%s' --version 2>/dev/null"%( 251 cc.replace("'", "'\"'\"'"),)).read() 252 if 'llvm-gcc' in data: 253 cc = None 254 255 if cc is not None and not _working_compiler(cc): 256 cc = None 257 258 if cc is None: 259 # Default compiler is not useable, try finding 'clang' 260 cc = _find_executable('clang') 261 if cc is None: 262 cc = os.popen("/usr/bin/xcrun -find clang").read() 263 264 if not cc: 265 raise SystemExit("Cannot locate compiler candidate") 266 267 if not _working_compiler(cc): 268 raise SystemExit("Cannot locate a working compiler") 269 270 if cc != oldcc: 271 print("Use '%s' instead of '%s' as the compiler"%( 272 cc, oldcc)) 273 274 vars = get_config_vars() 275 for env in ('BLDSHARED', 'LDSHARED', 'CC', 'CXX'): 276 if env in vars and env not in os.environ: 277 split = vars[env].split() 278 split[0] = cc if env != 'CXX' else cc + '++' 279 vars[env] = ' '.join(split) 280 281class pyobjc_build_ext (build_ext.build_ext): 282 def run(self): 283 _fixup_compiler() 284 285 # Ensure that the PyObjC header files are available 286 # in 2.3 and later the headers are in the egg, 287 # before that we ship a copy. 288 dist, = pkg_resources.require('pyobjc-core') 289 290 include_root = os.path.join(self.build_temp, 'pyobjc-include') 291 if os.path.exists(include_root): 292 shutil.rmtree(include_root) 293 294 os.makedirs(include_root) 295 if dist.has_metadata('include'): 296 for fn in dist.metadata_listdir('include'): 297 data = dist.get_metadata('include/%s'%(fn,)) 298 fp = open(os.path.join(include_root, fn), 'w') 299 try: 300 fp.write(data) 301 finally: 302 fp.close() 303 304 else: 305 raise SystemExit("pyobjc-core egg-info does not include header files") 306 307 for e in self.extensions: 308 if include_root not in e.include_dirs: 309 e.include_dirs.append(include_root) 310 311 # Run the actual build 312 build_ext.build_ext.run(self) 313 314 # Then tweak the copy_extensions bit to ensure PyObjCTest gets 315 # copied to the right place. 316 extensions = self.extensions 317 self.extensions = [ 318 e for e in extensions if e.name.startswith('PyObjCTest') ] 319 self.copy_extensions_to_source() 320 self.extensions = extensions 321 322 323 324def Extension(*args, **kwds): 325 """ 326 Simple wrapper about distutils.core.Extension that adds additional PyObjC 327 specific flags. 328 """ 329 os_level = get_os_level() 330 cflags = ["-DPyObjC_BUILD_RELEASE=%02d%02d"%os_level] 331 ldflags = [] 332 if os_level != (10, 4): 333 pass 334 pass 335 else: 336 cflags.append('-DNO_OBJC2_RUNTIME') 337 338 if 'extra_compile_args' in kwds: 339 kwds['extra_compile_args'] = kwds['extra_compile_args'] + cflags 340 else: 341 kwds['extra_compile_args'] = cflags 342 343 if 'extra_link_args' in kwds: 344 kwds['extra_link_args'] = kwds['extra_link_args'] + ldflags 345 else: 346 kwds['extra_link_args'] = ldflags 347 348 return _Extension(*args, **kwds) 349 350 351def setup( 352 min_os_level=None, 353 max_os_level=None, 354 cmdclass=None, 355 **kwds): 356 357 358 k = kwds.copy() 359 360 os_level = get_os_level() 361 os_compatible = True 362 if sys.platform != 'darwin': 363 os_compatible = False 364 365 else: 366 if min_os_level is not None: 367 if os_level < tuple(map(int, min_os_level.split('.'))): 368 os_compatible = False 369 if max_os_level is not None: 370 if os_level > tuple(map(int, max_os_level.split('.'))): 371 os_compatible = False 372 373 if cmdclass is None: 374 cmdclass = {} 375 else: 376 cmdclass = cmdclass.copy() 377 378 if not os_compatible: 379 if min_os_level != None: 380 if max_os_level != None: 381 msg = "This distribution is only supported on MacOSX versions %s upto and including %s"%( 382 min_os_level, max_os_level) 383 else: 384 msg = "This distribution is only supported on MacOSX >= %s"%(min_os_level,) 385 elif max_os_level != None: 386 msg = "This distribution is only supported on MacOSX <= %s"%(max_os_level,) 387 else: 388 msg = "This distribution is only supported on MacOSX" 389 390 def create_command_subclass(base_class): 391 392 class subcommand (base_class): 393 def run(self): 394 raise DistutilsPlatformError(msg) 395 396 return subcommand 397 398 class no_test (oc_test): 399 def run(self): 400 print("WARNING: %s\n"%(msg,)) 401 print("SUMMARY: {'count': 0, 'fails': 0, 'errors': 0, 'xfails': 0, 'skip': 65, 'xpass': 0, 'message': msg }\n") 402 403 cmdclass['build'] = create_command_subclass(build.build) 404 cmdclass['test'] = no_test 405 cmdclass['install'] = create_command_subclass(install.install) 406 cmdclass['install_lib'] = create_command_subclass(pyobjc_install_lib) 407 cmdclass['develop'] = create_command_subclass(develop.develop) 408 cmdclass['build_py'] = create_command_subclass(oc_build_py) 409 else: 410 cmdclass['build_ext'] = pyobjc_build_ext 411 cmdclass['install_lib'] = pyobjc_install_lib 412 cmdclass['test'] = oc_test 413 cmdclass['build_py'] = oc_build_py 414 415 plat_name = "MacOS X" 416 plat_versions = [] 417 if min_os_level is not None and min_os_level == max_os_level: 418 plat_versions.append("==%s"%(min_os_level,)) 419 else: 420 if min_os_level is not None: 421 plat_versions.append(">=%s"%(min_os_level,)) 422 if max_os_level is not None: 423 plat_versions.append("<=%s"%(max_os_level,)) 424 if plat_versions: 425 plat_name += " (%s)"%(", ".join(plat_versions),) 426 427 _setup( 428 cmdclass=cmdclass, 429 long_description=__main__.__doc__, 430 author='Ronald Oussoren', 431 author_email='pyobjc-dev@lists.sourceforge.net', 432 url='http://pyobjc.sourceforge.net', 433 platforms = [ plat_name ], 434 package_dir = { '': 'Lib', 'PyObjCTest': 'PyObjCTest' }, 435 dependency_links = [], 436 package_data = { '': ['*.bridgesupport'] }, 437 test_suite='PyObjCTest', 438 zip_safe = False, 439 license = 'MIT License', 440 classifiers = CLASSIFIERS, 441 **k 442 ) 443