1#!/usr/bin/env python 2 3import sys 4import subprocess 5import shutil 6import re 7import os 8 9# We need at least Python 2.5 10MIN_PYTHON = (2, 5) 11 12# FIXME: autodetect default values for USE_* variables: 13# both should be false by default, unless 14# 1) python is /usr/bin/python: both should be true 15# 2) python build with --with-system-libffi: USE_SYSTEM_FFI 16# should be true. 17 18# Set USE_SYSTEM_FFI to True to link to the system version 19# of libffi 20USE_SYSTEM_FFI = True 21 22# Set USE_SYSTEM_LIBXML to True to link to the system version 23# of libxml2 (defaults to False to avoid problems when building 24# on 10.6 and running on an earlier release) 25USE_SYSTEM_LIBXML = True 26 27SDKROOT = os.environ.get('SDKROOT') 28if SDKROOT is None or SDKROOT is '': 29 SDKROOT = '/' 30 31if sys.version_info < MIN_PYTHON: 32 vstr = '.'.join(map(str, MIN_PYTHON)) 33 raise SystemExit('PyObjC: Need at least Python ' + vstr) 34 35 36try: 37 import setuptools 38 39except ImportError: 40 import distribute_setup 41 distribute_setup.use_setuptools() 42 43 44extra_args=dict( 45 use_2to3 = True, 46) 47 48from setuptools.command import build_py 49from setuptools.command import test 50from distutils import log 51class oc_build_py (build_py.build_py): 52 def run_2to3(self, files, doctests=True): 53 files = [ fn for fn in files if not os.path.basename(fn).startswith('test3_') ] 54 build_py.build_py.run_2to3(self, files, doctests) 55 56 def build_packages(self): 57 log.info("Overriding build_packages to copy PyObjCTest") 58 p = self.packages 59 self.packages = list(self.packages) + ['PyObjCTest'] 60 try: 61 build_py.build_py.build_packages(self) 62 finally: 63 self.packages = p 64 65from pkg_resources import working_set, normalize_path, add_activation_listener, require 66 67class oc_test (test.test): 68 def run_tests(self): 69 import sys, os 70 71 if sys.version_info[0] == 3: 72 rootdir = os.path.dirname(os.path.abspath(__file__)) 73 if rootdir in sys.path: 74 sys.path.remove(rootdir) 75 76 ei_cmd = self.get_finalized_command('egg_info') 77 egg_name = ei_cmd.egg_name.replace('-', '_') 78 79 to_remove = [] 80 for dirname in sys.path: 81 bn = os.path.basename(dirname) 82 if bn.startswith(egg_name + "-"): 83 to_remove.append(dirname) 84 85 for dirname in to_remove: 86 log.info("removing installed %r from sys.path before testing"%(dirname,)) 87 sys.path.remove(dirname) 88 89 from PyObjCTest.loader import makeTestSuite 90 import unittest 91 92 #import pprint; pprint.pprint(sys.path) 93 94 unittest.main(None, None, [unittest.__file__]+self.test_args) 95 96from setuptools.command import egg_info as orig_egg_info 97 98def write_header(cmd, basename, filename): 99 data = open(os.path.join('Modules/objc/', os.path.basename(basename)), 'rU').read() 100 if not cmd.dry_run: 101 if not os.path.exists(os.path.dirname(filename)): 102 os.makedirs(os.path.dirname(filename)) 103 104 cmd.write_file(basename, filename, data) 105 106 107# This is a workaround for a bug in setuptools: I'd like 108# to use the 'egg_info.writers' entry points in the setup() 109# call, but those don't work when also using a package_base 110# argument as we do. 111# (issue 123 in the distribute tracker) 112class egg_info (orig_egg_info.egg_info): 113 def run(self): 114 orig_egg_info.egg_info.run(self) 115 116 for hdr in ("pyobjc-compat.h", "pyobjc-api.h"): 117 fn = os.path.join("include", hdr) 118 119 write_header(self, fn, os.path.join(self.egg_info, fn)) 120 121if sys.version_info[0] == 3: 122 # FIXME: add custom test command that does the work. 123 # - Patch sys.path 124 # - Ensure PyObjCTest gets translated by 2to3 125 from distutils.util import get_platform 126 build_dir = 'build/lib.%s-%d.%d'%( 127 get_platform(), sys.version_info[0], sys.version_info[1]) 128 if hasattr(sys, 'gettotalrefcount'): 129 build_dir += '-pydebug' 130 sys.path.insert(0, build_dir) 131 132 133import os 134import glob 135import site 136import platform 137 138if 'MallocStackLogging' in os.environ: 139 del os.environ['MallocStackLogging'] 140if 'MallocStackLoggingNoCompact' in os.environ: 141 del os.environ['MallocStackLoggingNoCompact'] 142 143# See the news file: 144#os.environ['MACOSX_DEPLOYMENT_TARGET']='10.5' 145 146 147 148#if int(os.uname()[2].split('.')[0]) >= 10: 149# USE_SYSTEM_FFI = True 150 151 152# Some PiPy stuff 153LONG_DESCRIPTION=""" 154PyObjC is a bridge between Python and Objective-C. It allows full 155featured Cocoa applications to be written in pure Python. It is also 156easy to use other frameworks containing Objective-C class libraries 157from Python and to mix in Objective-C, C and C++ source. 158 159Python is a highly dynamic programming language with a shallow learning 160curve. It combines remarkable power with very clear syntax. 161 162The installer package installs a number of Xcode templates for 163easily creating new Cocoa-Python projects. 164 165PyObjC also supports full introspection of Objective-C classes and 166direct invocation of Objective-C APIs from the interactive interpreter. 167 168PyObjC requires MacOS X 10.4 or later. This beta release requires 169MacOS X 10.5. 170""" 171 172from setuptools import setup, Extension, find_packages 173from setuptools.command import build_ext, install_lib 174import os 175 176class pyobjc_install_lib (install_lib.install_lib): 177 def get_exclusions(self): 178 result = install_lib.install_lib.get_exclusions(self) 179 for fn in install_lib._install_lib.get_outputs(self): 180 if 'PyObjCTest' in fn: 181 result[fn] = 1 182 183 for fn in os.listdir('PyObjCTest'): 184 result[os.path.join('PyObjCTest', fn)] = 1 185 result[os.path.join(self.install_dir, 'PyObjCTest', fn)] = 1 186 187 188 return result 189 190class pyobjc_build_ext (build_ext.build_ext): 191 def run(self): 192 if not USE_SYSTEM_LIBXML: 193 build = self.get_finalized_command('build') 194 xmldir=os.path.join(build.build_base, 'libxml') 195 if not os.path.exists(xmldir): 196 builddir=os.path.join(build.build_base, 'libxml-build') 197 if os.path.exists(builddir): 198 shutil.rmtree(builddir) 199 os.makedirs(builddir) 200 201 cflags = get_config_var('CFLAGS') 202 if '-isysroot' in cflags: 203 cflags = re.sub(r'-isysroot\s*\S*', '-isysroot /', cflags) 204 205 p = subprocess.Popen( 206 ['../../libxml2-src/configure', 207 '--disable-dependency-tracking', 208 '--enable-static', 209 '--disable-shared', 210 '--prefix=%s'%(os.path.abspath(xmldir),), 211 '--with-minimum', 212 'CFLAGS=%s'%(cflags,), 213 'CC=%s'%(get_config_var('CC')), 214 ], 215 cwd=builddir) 216 xit=p.wait() 217 if xit != 0: 218 shutil.rmtree(builddir) 219 raise RuntimeError("libxml configure failed") 220 p = subprocess.Popen( 221 ['make', 'install'], 222 cwd=builddir) 223 xit=p.wait() 224 if xit != 0: 225 raise RuntimeError("libxml install failed") 226 227 os.environ['PATH'] = xmldir + "/bin:" + os.environ['PATH'] 228 229 for ext in self.extensions: 230 if ext.name == "objc._objc": 231 if not USE_SYSTEM_LIBXML: 232 ext.extra_link_args.append('-Wl,-search_paths_first') 233 ext.extra_compile_args.extend(xml2config('--cflags')) 234 ext.extra_link_args.extend(xml2config('--libs')) 235 236 build_ext.build_ext.run(self) 237 extensions = self.extensions 238 self.extensions = [ 239 e for e in extensions if e.name.startswith('PyObjCTest') ] 240 self.copy_extensions_to_source() 241 self.extensions = extensions 242 243def frameworks(*args): 244 lst = [] 245 for arg in args: 246 lst.extend(['-framework', arg]) 247 return lst 248 249def IfFrameWork(name, packages, extensions, headername=None): 250 """ 251 Return the packages and extensions if the framework exists, or 252 two empty lists if not. 253 """ 254 import os 255 for pth in ('/System/Library/Frameworks', '/Library/Frameworks'): 256 basedir = os.path.join(pth, name) 257 if os.path.exists(basedir): 258 if (headername is None) or os.path.exists(os.path.join(basedir, "Headers", headername)): 259 return packages, extensions 260 return [], [] 261 262# Double-check 263if sys.platform != 'darwin': 264 print("You're not running on MacOS X, and don't use GNUstep") 265 print("I don't know how to build PyObjC on such a platform.") 266 print("Please read the ReadMe.") 267 print("") 268 raise SystemExit("ObjC runtime not found") 269 270from distutils.sysconfig import get_config_var 271 272CFLAGS=[ ] 273 274# Enable 'PyObjC_STRICT_DEBUGGING' to enable some costly internal 275# assertions. 276CFLAGS.extend([ 277 278 # Use this to analyze with clang 279 #"--analyze", 280 281# The following flags are an attempt at getting rid of /usr/local 282# in the compiler search path. 283 "-DPyObjC_STRICT_DEBUGGING", 284 "-DMACOSX", # For libffi 285 "-DPyObjC_BUILD_RELEASE=%02d%02d"%(tuple(map(int, platform.mac_ver()[0].split('.')[:2]))), 286 "-no-cpp-precomp", 287 "-DMACOSX", 288 "-g", 289 "-fexceptions", 290 291 292 # Loads of warning flags 293 "-Wall", "-Wstrict-prototypes", "-Wmissing-prototypes", 294 "-Wformat=2", "-W", 295 #"-Wshadow", # disabled due to warnings from Python headers 296 "-Wpointer-arith", #"-Wwrite-strings", 297 "-Wmissing-declarations", 298 "-Wnested-externs", 299 "-Wno-long-long", 300 301 "-Wno-import", 302 ]) 303 304## Arghh, a stupid compiler flag can cause problems. Don't 305## enable -O0 if you value your sanity. With -O0 PyObjC will crash 306## on i386 systems when a method returns a struct that isn't returned 307## in registers. 308if '-O0' in get_config_var('CFLAGS'): 309 print ("Change -O0 to -O1") 310 CFLAGS.append('-O1') 311 312OBJC_LDFLAGS = frameworks('CoreFoundation', 'Foundation', 'Carbon') 313 314if not os.path.exists(os.path.join(SDKROOT, 'usr/include/objc/runtime.h')): 315 CFLAGS.append('-DNO_OBJC2_RUNTIME') 316 317# Force compilation with the local SDK, compilation of PyObC will result in 318# a binary that runs on other releases of the OS without using a particular SDK. 319pass 320pass 321 322 323# We're using xml2, check for the flags to use: 324def xml2config(arg): 325 import os, shlex 326 ln = os.popen('xml2-config %s'%(arg,), 'r').readline() 327 ln = ln.strip() 328 329 return shlex.split(ln) 330 331#CFLAGS.extend(xml2config('--cflags')) 332#OBJC_LDFLAGS.extend(xml2config('--libs')) 333 334 335 336CFLAGS.append('-Ibuild/codegen/') 337 338# Patch distutils: it needs to compile .S files as well. 339from distutils.unixccompiler import UnixCCompiler 340UnixCCompiler.src_extensions.append('.S') 341del UnixCCompiler 342 343 344# 345# Support for an embedded copy of libffi 346# 347FFI_CFLAGS=['-Ilibffi-src/include', '-Ilibffi-src/powerpc'] 348 349# The list below includes the source files for all CPU types that we run on 350# this makes it easier to build fat binaries on Mac OS X. 351FFI_SOURCE=[ 352 "libffi-src/ffi.c", 353 "libffi-src/types.c", 354 "libffi-src/powerpc/ppc-darwin.S", 355 "libffi-src/powerpc/ppc-darwin_closure.S", 356 "libffi-src/powerpc/ppc-ffi_darwin.c", 357 "libffi-src/powerpc/ppc64-darwin_closure.S", 358 "libffi-src/x86/darwin64.S", 359 "libffi-src/x86/x86-darwin.S", 360 "libffi-src/x86/x86-ffi64.c", 361 "libffi-src/x86/x86-ffi_darwin.c", 362] 363 364 365 366# 367# Calculate the list of extensions: objc._objc + extensions for the unittests 368# 369 370if USE_SYSTEM_FFI: 371 ExtensionList = [ 372 Extension("objc._objc", 373 list(glob.glob(os.path.join('Modules', 'objc', '*.m'))), 374 extra_compile_args=CFLAGS + ['-I' + os.path.join(SDKROOT, "usr/include/ffi")], 375 extra_link_args=OBJC_LDFLAGS + ["-lffi"], 376 depends=list(glob.glob(os.path.join('Modules', 'objc', '*.h'))), 377 ), 378 ] 379 380else: 381 ExtensionList = [ 382 Extension("objc._objc", 383 FFI_SOURCE + list(glob.glob(os.path.join('Modules', 'objc', '*.m'))), 384 extra_compile_args=CFLAGS + FFI_CFLAGS, 385 extra_link_args=OBJC_LDFLAGS, 386 depends=list(glob.glob(os.path.join('Modules', 'objc', '*.h'))), 387 ), 388 ] 389 390for test_source in glob.glob(os.path.join('Modules', 'objc', 'test', '*.m')): 391 name, ext = os.path.splitext(os.path.basename(test_source)) 392 393 ExtensionList.append(Extension('PyObjCTest.' + name, 394 [test_source], 395 extra_compile_args=['-IModules/objc'] + CFLAGS, 396 extra_link_args=OBJC_LDFLAGS)) 397 398def package_version(): 399 fp = open('Modules/objc/pyobjc.h', 'r') 400 for ln in fp.readlines(): 401 if ln.startswith('#define OBJC_VERSION'): 402 fp.close() 403 return ln.split()[-1][1:-1] 404 405 raise ValueError("Version not found") 406 407CLASSIFIERS = filter(None, 408""" 409Development Status :: 5 - Production/Stable 410Environment :: Console 411Environment :: MacOS X :: Cocoa 412Intended Audience :: Developers 413License :: OSI Approved :: MIT License 414Natural Language :: English 415Operating System :: MacOS :: MacOS X 416Programming Language :: Python 417Programming Language :: Python :: 2 418Programming Language :: Python :: 2.6 419Programming Language :: Python :: 2.7 420Programming Language :: Python :: 3 421Programming Language :: Python :: 3.1 422Programming Language :: Python :: 3.2 423Programming Language :: Objective C 424Topic :: Software Development :: Libraries :: Python Modules 425Topic :: Software Development :: User Interfaces 426""".splitlines()) 427 428 429dist = setup( 430 name = "pyobjc-core", 431 version = package_version(), 432 description = "Python<->ObjC Interoperability Module", 433 long_description = LONG_DESCRIPTION, 434 author = "Ronald Oussoren, bbum, SteveM, LeleG, many others stretching back through the reaches of time...", 435 author_email = "pyobjc-dev@lists.sourceforge.net", 436 url = "http://pyobjc.sourceforge.net/", 437 platforms = [ 'MacOS X' ], 438 ext_modules = ExtensionList, 439 packages = [ 'objc', 'PyObjCTools', ], 440 #namespace_packages = ['PyObjCTools'], 441 package_dir = { '': 'Lib', 'PyObjCTest': 'PyObjCTest' }, 442 extra_path = "PyObjC", 443 cmdclass = {'build_ext': pyobjc_build_ext, 'install_lib': pyobjc_install_lib, 'build_py': oc_build_py, 'test': oc_test, 'egg_info':egg_info }, 444 options = {'egg_info': {'egg_base': 'Lib'}}, 445 classifiers = CLASSIFIERS, 446 license = 'MIT License', 447 download_url = 'http://pyobjc.sourceforge.net/software/index.php', 448 test_suite='PyObjCTest.loader.makeTestSuite', 449 zip_safe = False, 450# entry_points = { 451# "egg_info.writers": [ 452# "include/pyobjc-api.h = __main__:write_header", 453# "include/pyobjc-compat.h = __main__:write_header", 454# ], 455# }, 456 **extra_args 457) 458