1#!/usr/bin/env python 2 3################################### 4# Author: Kevin Ollivier 5# Licence: wxWindows licence 6################################### 7 8import os 9import re 10import sys 11import builder 12import glob 13import optparse 14import platform 15import shutil 16import types 17import subprocess 18 19# builder object 20wxBuilder = None 21 22# other globals 23scriptDir = None 24wxRootDir = None 25contribDir = None 26options = None 27configure_opts = None 28exitWithException = True 29nmakeCommand = 'nmake.exe' 30 31verbose = False 32 33 34def numCPUs(): 35 """ 36 Detects the number of CPUs on a system. 37 This approach is from detectCPUs here: http://www.artima.com/weblogs/viewpost.jsp?thread=230001 38 """ 39 # Linux, Unix and MacOS: 40 if hasattr(os, "sysconf"): 41 if "SC_NPROCESSORS_ONLN" in os.sysconf_names: 42 # Linux & Unix: 43 ncpus = os.sysconf("SC_NPROCESSORS_ONLN") 44 if isinstance(ncpus, int) and ncpus > 0: 45 return ncpus 46 else: # OSX: 47 p = subprocess.Popen("sysctl -n hw.ncpu", shell=True, stdout=subprocess.PIPE) 48 return p.stdout.read() 49 50 # Windows: 51 if "NUMBER_OF_PROCESSORS" in os.environ: 52 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]); 53 if ncpus > 0: 54 return ncpus 55 return 1 # Default 56 57 58def getXcodePaths(): 59 base = getoutput("xcode-select -print-path") 60 return [base, base+"/Platforms/MacOSX.platform/Developer"] 61 62 63def getVisCVersion(): 64 text = getoutput("cl.exe") 65 if 'Version 13' in text: 66 return '71' 67 if 'Version 15' in text: 68 return '90' 69 if 'Version 16' in text: 70 return '100' 71 # TODO: Add more tests to get the other versions... 72 else: 73 return 'FIXME' 74 75 76def exitIfError(code, msg): 77 if code != 0: 78 print(msg) 79 if exitWithException: 80 raise builder.BuildError(msg) 81 else: 82 sys.exit(1) 83 84 85def getWxRelease(wxRoot=None): 86 if not wxRoot: 87 global wxRootDir 88 wxRoot = wxRootDir 89 90 configureText = open(os.path.join(wxRoot, "configure.in"), "r").read() 91 majorVersion = re.search("wx_major_version_number=(\d+)", configureText).group(1) 92 minorVersion = re.search("wx_minor_version_number=(\d+)", configureText).group(1) 93 94 versionText = "%s.%s" % (majorVersion, minorVersion) 95 96 if int(minorVersion) % 2: 97 releaseVersion = re.search("wx_release_number=(\d+)", configureText).group(1) 98 versionText += ".%s" % (releaseVersion) 99 100 return versionText 101 102 103def getFrameworkName(options): 104 # the name of the framework is based on the wx port being built 105 name = "wxOSX" 106 if options.osx_cocoa: 107 name += "Cocoa" 108 else: 109 name += "Carbon" 110 return name 111 112 113def getPrefixInFramework(options, wxRoot=None): 114 # the path inside the framework that is the wx --prefix 115 fwPrefix = os.path.join( 116 os.path.abspath(options.mac_framework_prefix), 117 "%s.framework/Versions/%s" % (getFrameworkName(options), getWxRelease(wxRoot))) 118 return fwPrefix 119 120 121def macFixupInstallNames(destdir, prefix, buildDir=None): 122 # When an installdir is used then the install_names embedded in 123 # the dylibs are not correct. Reset the IDs and the dependencies 124 # to use just the prefix. 125 print("**** macFixupInstallNames(%s, %s, %s)" % (destdir, prefix, buildDir)) 126 pwd = os.getcwd() 127 os.chdir(destdir+prefix+'/lib') 128 dylibs = glob.glob('*.dylib') # ('*[0-9].[0-9].[0-9].[0-9]*.dylib') 129 for lib in dylibs: 130 cmd = 'install_name_tool -id %s/lib/%s %s/lib/%s' % \ 131 (prefix,lib, destdir+prefix,lib) 132 print(cmd) 133 run(cmd) 134 for dep in dylibs: 135 if buildDir is not None: 136 cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s/lib/%s' % \ 137 (buildDir,dep, prefix,dep, destdir+prefix,lib) 138 else: 139 cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s/lib/%s' % \ 140 (destdir+prefix,dep, prefix,dep, destdir+prefix,lib) 141 print(cmd) 142 run(cmd) 143 os.chdir(pwd) 144 145 146def run(cmd): 147 global verbose 148 if verbose: 149 print("Running %s" % cmd) 150 return exitIfError(os.system(cmd), "Error running %s" % cmd) 151 152 153def getoutput(cmd): 154 sp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 155 output = None 156 output = sp.stdout.read() 157 if sys.version_info > (3,): 158 output = output.decode('utf-8') # TODO: is utf-8 okay here? 159 output = output.rstrip() 160 rval = sp.wait() 161 if rval: 162 # Failed! 163 print("Command '%s' failed with exit code %d." % (cmd, rval)) 164 sys.exit(rval) 165 return output 166 167 168def main(scriptName, args): 169 global scriptDir 170 global wxRootDir 171 global contribDir 172 global options 173 global configure_opts 174 global wxBuilder 175 global nmakeCommand 176 177 scriptDir = os.path.dirname(os.path.abspath(scriptName)) 178 wxRootDir = os.path.abspath(os.path.join(scriptDir, "..", "..")) 179 180 contribDir = os.path.join("contrib", "src") 181 installDir = None 182 183 VERSION = tuple([int(i) for i in getWxRelease().split('.')[:2]]) 184 185 if sys.platform.startswith("win"): 186 contribDir = os.path.join(wxRootDir, "contrib", "build") 187 188 if sys.platform.startswith("win"): 189 toolkit = "msvc" 190 else: 191 toolkit = "autoconf" 192 193 defJobs = str(numCPUs()) 194 defFwPrefix = '/Library/Frameworks' 195 196 option_dict = { 197 "clean" : (False, "Clean all files from the build directory"), 198 "debug" : (False, "Build the library in debug symbols"), 199 "builddir" : ("", "Directory where the build will be performed for autoconf builds."), 200 "prefix" : ("", "Configured prefix to use for autoconf builds. Defaults to installdir if set. Ignored for framework builds."), 201 "jobs" : (defJobs, "Number of jobs to run at one time in make. Default: %s" % defJobs), 202 "install" : (False, "Install the toolkit to the installdir directory, or the default dir."), 203 "installdir" : ("", "Directory where built wxWidgets will be installed"), 204 "mac_distdir" : (None, "If set on Mac, will create an installer package in the specified dir."), 205 "mac_universal_binary" 206 : ("", "Comma separated list of architectures to include in the Mac universal binary"), 207 "mac_framework" : (False, "Install the Mac build as a framework"), 208 "mac_framework_prefix" 209 : (defFwPrefix, "Prefix where the framework should be installed. Default: %s" % defFwPrefix), 210 "cairo" : (False, "Enable dynamically loading the Cairo lib for wxGraphicsContext on MSW"), 211 "no_config" : (False, "Turn off configure step on autoconf builds"), 212 "config_only" : (False, "Only run the configure step and then exit"), 213 "rebake" : (False, "Regenerate Bakefile and autoconf files"), 214 "unicode" : (False, "Build the library with unicode support"), 215 "wxpython" : (False, "Build the wxWidgets library with all options needed by wxPython"), 216 "cocoa" : (False, "Build the old Mac Cocoa port."), 217 "osx_cocoa" : (False, "Build the new Cocoa port"), 218 "shared" : (False, "Build wx as a dynamic library"), 219 "extra_make" : ("", "Extra args to pass on [n]make's command line."), 220 "features" : ("", "A comma-separated list of wxUSE_XYZ defines on Win, or a list of configure flags on unix."), 221 "verbose" : (False, "Print commands as they are run, (to aid with debugging this script)"), 222 "jom" : (False, "Use jom.exe instead of nmake for MSW builds."), 223 } 224 225 parser = optparse.OptionParser(usage="usage: %prog [options]", version="%prog 1.0") 226 227 keys = option_dict.keys() 228 for opt in sorted(keys): 229 default = option_dict[opt][0] 230 action = "store" 231 if type(default) == bool: 232 action = "store_true" 233 parser.add_option("--" + opt, default=default, action=action, dest=opt, 234 help=option_dict[opt][1]) 235 236 options, arguments = parser.parse_args(args=args) 237 238 global verbose 239 if options.verbose: 240 verbose = True 241 242 # compiler / build system specific args 243 buildDir = options.builddir 244 args = [] 245 installDir = options.installdir 246 prefixDir = options.prefix 247 248 if toolkit == "autoconf": 249 if not buildDir: 250 buildDir = os.getcwd() 251 configure_opts = [] 252 if options.features != "": 253 configure_opts.extend(options.features.split(" ")) 254 255 if options.unicode: 256 configure_opts.append("--enable-unicode") 257 258 if options.debug: 259 configure_opts.append("--enable-debug") 260 261 if options.cocoa: 262 configure_opts.append("--with-old_cocoa") 263 264 if options.osx_cocoa: 265 configure_opts.append("--with-osx_cocoa") 266 267 wxpy_configure_opts = [ 268 "--with-opengl", 269 "--enable-sound", 270 "--enable-graphics_ctx", 271 "--enable-mediactrl", 272 "--enable-display", 273 "--enable-geometry", 274 "--enable-debug_flag", 275 "--enable-optimise", 276 "--disable-debugreport", 277 "--enable-uiactionsim", 278 ] 279 280 if sys.platform.startswith("darwin"): 281 wxpy_configure_opts.append("--enable-monolithic") 282 else: 283 wxpy_configure_opts.append("--with-sdl") 284 285 # Try to use use lowest available SDK back to 10.5. Both Carbon and 286 # Cocoa builds require at least the 10.5 SDK now. We only add it to 287 # the wxpy options because this is a hard-requirement for wxPython, 288 # but other cases it is optional and is left up to the developer. 289 # TODO: there should be a command line option to set the SDK... 290 if sys.platform.startswith("darwin"): 291 for xcodePath in getXcodePaths(): 292 sdks = [ 293 xcodePath+"/SDKs/MacOSX10.5.sdk", 294 xcodePath+"/SDKs/MacOSX10.6.sdk", 295 xcodePath+"/SDKs/MacOSX10.7.sdk", 296 xcodePath+"/SDKs/MacOSX10.8.sdk", 297 ] 298 299 # use the lowest available sdk 300 for sdk in sdks: 301 if os.path.exists(sdk): 302 wxpy_configure_opts.append( 303 "--with-macosx-sdk=%s" % sdk) 304 break 305 306 if not options.mac_framework: 307 if installDir and not prefixDir: 308 prefixDir = installDir 309 if prefixDir: 310 prefixDir = os.path.abspath(prefixDir) 311 configure_opts.append("--prefix=" + prefixDir) 312 313 314 if options.wxpython: 315 configure_opts.extend(wxpy_configure_opts) 316 if options.debug: 317 # wxPython likes adding these debug options too 318 configure_opts.append("--enable-debug_gdb") 319 configure_opts.append("--disable-optimise") 320 configure_opts.remove("--enable-optimise") 321 322 323 if options.rebake: 324 retval = run("make -f autogen.mk") 325 exitIfError(retval, "Error running autogen.mk") 326 327 if options.mac_framework: 328 # TODO: Should options.install be automatically turned on if the 329 # mac_framework flag is given? 330 331 # framework builds always need to be monolithic 332 if not "--enable-monolithic" in configure_opts: 333 configure_opts.append("--enable-monolithic") 334 335 # The --prefix given to configure will be the framework prefix 336 # plus the framework specific dir structure. 337 prefixDir = getPrefixInFramework(options) 338 configure_opts.append("--prefix=" + prefixDir) 339 340 # the framework build adds symlinks above the installDir + prefixDir folder 341 # so we need to wipe from the framework root instead of inside the prefixDir. 342 frameworkRootDir = os.path.abspath(os.path.join(installDir + prefixDir, "..", "..")) 343 if os.path.exists(frameworkRootDir): 344 if os.path.exists(frameworkRootDir): 345 shutil.rmtree(frameworkRootDir) 346 347 if options.mac_universal_binary: 348 if options.mac_universal_binary == 'default': 349 if options.osx_cocoa: 350 configure_opts.append("--enable-universal_binary=i386,x86_64") 351 else: 352 configure_opts.append("--enable-universal_binary") 353 else: 354 configure_opts.append("--enable-universal_binary=%s" % options.mac_universal_binary) 355 356 357 print("Configure options: " + repr(configure_opts)) 358 wxBuilder = builder.AutoconfBuilder() 359 if not options.no_config and not options.clean: 360 olddir = os.getcwd() 361 if buildDir: 362 os.chdir(buildDir) 363 exitIfError(wxBuilder.configure(dir=wxRootDir, options=configure_opts), 364 "Error running configure") 365 os.chdir(olddir) 366 367 if options.config_only: 368 print("Exiting after configure") 369 return 370 371 elif toolkit in ["msvc", "msvcProject"]: 372 flags = {} 373 buildDir = os.path.abspath(os.path.join(scriptDir, "..", "msw")) 374 375 print("creating wx/msw/setup.h") 376 if options.unicode: 377 flags["wxUSE_UNICODE"] = "1" 378 379 if options.cairo: 380 if not os.environ.get("CAIRO_ROOT"): 381 print("WARNING: Expected CAIRO_ROOT set in the environment!") 382 flags["wxUSE_CAIRO"] = "1" 383 384 if options.wxpython: 385 flags["wxDIALOG_UNIT_COMPATIBILITY "] = "0" 386 flags["wxUSE_DEBUGREPORT"] = "0" 387 flags["wxUSE_DIALUP_MANAGER"] = "0" 388 flags["wxUSE_GRAPHICS_CONTEXT"] = "1" 389 flags["wxUSE_DISPLAY"] = "1" 390 flags["wxUSE_GLCANVAS"] = "1" 391 flags["wxUSE_POSTSCRIPT"] = "1" 392 flags["wxUSE_AFM_FOR_POSTSCRIPT"] = "0" 393 flags["wxUSE_DATEPICKCTRL_GENERIC"] = "1" 394 395 # Remove this when Windows XP finally dies, or when there is a 396 # solution for ticket #13116... 397 flags["wxUSE_COMPILER_TLS"] = "0" 398 399 if VERSION < (2,9): 400 flags["wxUSE_DIB_FOR_BITMAP"] = "1" 401 402 if VERSION >= (2,9): 403 flags["wxUSE_UIACTIONSIMULATOR"] = "1" 404 405 406 mswIncludeDir = os.path.join(wxRootDir, "include", "wx", "msw") 407 setupFile = os.path.join(mswIncludeDir, "setup.h") 408 setupText = open(setupFile, "rb").read() 409 410 for flag in flags: 411 setupText, subsMade = re.subn(flag + "\s+?\d", "%s %s" % (flag, flags[flag]), setupText) 412 if subsMade == 0: 413 print("Flag %s wasn't found in setup.h!" % flag) 414 sys.exit(1) 415 416 setupFile = open(os.path.join(mswIncludeDir, "setup.h"), "wb") 417 setupFile.write(setupText) 418 setupFile.close() 419 args = [] 420 if toolkit == "msvc": 421 print("setting build options...") 422 args.append("-f makefile.vc") 423 if options.unicode: 424 args.append("UNICODE=1") 425 426 if options.wxpython: 427 args.append("OFFICIAL_BUILD=1") 428 args.append("COMPILER_VERSION=%s" % getVisCVersion()) 429 args.append("SHARED=1") 430 args.append("MONOLITHIC=0") 431 args.append("USE_OPENGL=1") 432 args.append("USE_GDIPLUS=1") 433 434 if not options.debug: 435 args.append("BUILD=release") 436 else: 437 args.append("BUILD=debug") 438 439 if options.shared: 440 args.append("SHARED=1") 441 442 if options.cairo: 443 args.append( 444 "CPPFLAGS=/I%s" % 445 os.path.join(os.environ.get("CAIRO_ROOT", ""), 'include\\cairo')) 446 447 if options.jom: 448 nmakeCommand = 'jom.exe' 449 450 wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) 451 452 if toolkit == "msvcProject": 453 args = [] 454 if options.shared or options.wxpython: 455 args.append("wx_dll.dsw") 456 else: 457 args.append("wx.dsw") 458 459 # TODO: 460 wxBuilder = builder.MSVCProjectBuilder() 461 462 463 if not wxBuilder: 464 print("Builder not available for your specified platform/compiler.") 465 sys.exit(1) 466 467 if options.clean: 468 print("Performing cleanup.") 469 wxBuilder.clean(dir=buildDir, options=args) 470 471 sys.exit(0) 472 473 if options.extra_make: 474 args.append(options.extra_make) 475 476 if not sys.platform.startswith("win"): 477 args.append("--jobs=" + options.jobs) 478 exitIfError(wxBuilder.build(dir=buildDir, options=args), "Error building") 479 480 if options.install: 481 extra=None 482 if installDir: 483 extra = ['DESTDIR='+installDir] 484 wxBuilder.install(dir=buildDir, options=extra) 485 486 if options.install and options.mac_framework: 487 488 def renameLibrary(libname, frameworkname): 489 reallib = libname 490 links = [] 491 while os.path.islink(reallib): 492 links.append(reallib) 493 reallib = "lib/" + os.readlink(reallib) 494 495 #print("reallib is %s" % reallib) 496 run("mv -f %s lib/%s.dylib" % (reallib, frameworkname)) 497 498 for link in links: 499 run("ln -s -f %s.dylib %s" % (frameworkname, link)) 500 501 frameworkRootDir = prefixDir 502 if installDir: 503 print("installDir = %s" % installDir) 504 frameworkRootDir = installDir + prefixDir 505 os.chdir(frameworkRootDir) 506 build_string = "" 507 if options.debug: 508 build_string = "d" 509 510 fwname = getFrameworkName(options) 511 version = getoutput("bin/wx-config --release") 512 version_full = getoutput("bin/wx-config --version") 513 basename = getoutput("bin/wx-config --basename") 514 configname = getoutput("bin/wx-config --selected-config") 515 516 os.makedirs("Resources") 517 wxplist = dict( 518 CFBundleDevelopmentRegion="English", 519 CFBundleIdentifier='org.wxwidgets.wxosxcocoa', 520 CFBundleName=fwname, 521 CFBundleVersion=version_full, 522 CFBundleExecutable=fwname, 523 CFBundleGetInfoString="%s %s" % (fwname, version_full), 524 CFBundlePackageType="FMWK", 525 CFBundleSignature="WXCO", 526 CFBundleShortVersionString=version_full, 527 CFBundleInfoDictionaryVersion="6.0", 528 ) 529 530 import plistlib 531 plistlib.writePlist(wxplist, os.path.join(frameworkRootDir, "Resources", "Info.plist")) 532 533 # we make wx the "actual" library file and link to it from libwhatever.dylib 534 # so that things can link to wx and survive minor version changes 535 renameLibrary("lib/lib%s-%s.dylib" % (basename, version), fwname) 536 run("ln -s -f lib/%s.dylib %s" % (fwname, fwname)) 537 538 run("ln -s -f include Headers") 539 540 for lib in ["GL", "STC", "Gizmos", "Gizmos_xrc"]: 541 libfile = "lib/lib%s_%s-%s.dylib" % (basename, lib.lower(), version) 542 if os.path.exists(libfile): 543 frameworkDir = "framework/wx%s/%s" % (lib, version) 544 if not os.path.exists(frameworkDir): 545 os.makedirs(frameworkDir) 546 renameLibrary(libfile, "wx" + lib) 547 run("ln -s -f ../../../%s %s/wx%s" % (libfile, frameworkDir, lib)) 548 549 for lib in glob.glob("lib/*.dylib"): 550 if not os.path.islink(lib): 551 corelibname = "lib/lib%s-%s.0.dylib" % (basename, version) 552 run("install_name_tool -id %s %s" % (os.path.join(prefixDir, lib), lib)) 553 run("install_name_tool -change %s %s %s" % (os.path.join(frameworkRootDir, corelibname), os.path.join(prefixDir, corelibname), lib)) 554 555 os.chdir("include") 556 557 header_template = """ 558#ifndef __WX_FRAMEWORK_HEADER__ 559#define __WX_FRAMEWORK_HEADER__ 560 561%s 562 563#endif // __WX_FRAMEWORK_HEADER__ 564""" 565 headers = "" 566 header_dir = "wx-%s/wx" % version 567 for include in glob.glob(header_dir + "/*.h"): 568 headers += "#include <wx/" + os.path.basename(include) + ">\n" 569 570 framework_header = open("%s.h" % fwname, "w") 571 framework_header.write(header_template % headers) 572 framework_header.close() 573 574 run("ln -s -f %s wx" % header_dir) 575 os.chdir("wx-%s/wx" % version) 576 run("ln -s -f ../../../lib/wx/include/%s/wx/setup.h setup.h" % configname) 577 578 os.chdir(os.path.join(frameworkRootDir, "..")) 579 run("ln -s -f %s Current" % getWxRelease()) 580 os.chdir("..") 581 run("ln -s -f Versions/Current/Headers Headers") 582 run("ln -s -f Versions/Current/Resources Resources") 583 run("ln -s -f Versions/Current/%s %s" % (fwname, fwname)) 584 585 # sanity check to ensure the symlink works 586 os.chdir("Versions/Current") 587 588 # put info about the framework into wx-config 589 os.chdir(frameworkRootDir) 590 text = file('lib/wx/config/%s' % configname).read() 591 text = text.replace("MAC_FRAMEWORK=", "MAC_FRAMEWORK=%s" % getFrameworkName(options)) 592 if options.mac_framework_prefix not in ['/Library/Frameworks', 593 '/System/Library/Frameworks']: 594 text = text.replace("MAC_FRAMEWORK_PREFIX=", 595 "MAC_FRAMEWORK_PREFIX=%s" % options.mac_framework_prefix) 596 file('lib/wx/config/%s' % configname, 'w').write(text) 597 598 # The framework is finished! 599 print("wxWidgets framework created at: " + 600 os.path.join( installDir, 601 options.mac_framework_prefix, 602 '%s.framework' % fwname)) 603 604 605 # adjust the install_name if needed 606 if sys.platform.startswith("darwin") and \ 607 options.install and \ 608 options.installdir and \ 609 not options.mac_framework and \ 610 not options.wxpython: # wxPython's build will do this later if needed 611 if not prefixDir: 612 prefixDir = '/usr/local' 613 macFixupInstallNames(options.installdir, prefixDir)#, buildDir) 614 615 # make a package if a destdir was set. 616 if options.mac_framework and \ 617 options.install and \ 618 options.installdir and \ 619 options.mac_distdir: 620 621 if os.path.exists(options.mac_distdir): 622 shutil.rmtree(options.mac_distdir) 623 624 packagedir = os.path.join(options.mac_distdir, "packages") 625 os.makedirs(packagedir) 626 basename = os.path.basename(prefixDir.split(".")[0]) 627 packageName = basename + "-" + getWxRelease() 628 packageMakerPath = getXcodePaths()[0]+"/usr/bin/packagemaker " 629 args = [] 630 args.append("--root %s" % options.installdir) 631 args.append("--id org.wxwidgets.%s" % basename.lower()) 632 args.append("--title %s" % packageName) 633 args.append("--version %s" % getWxRelease()) 634 args.append("--out %s" % os.path.join(packagedir, packageName + ".pkg")) 635 cmd = packageMakerPath + ' '.join(args) 636 print("cmd = %s" % cmd) 637 run(cmd) 638 639 os.chdir(options.mac_distdir) 640 641 run('hdiutil create -srcfolder %s -volname "%s" -imagekey zlib-level=9 %s.dmg' % (packagedir, packageName, packageName)) 642 643 shutil.rmtree(packagedir) 644 645if __name__ == '__main__': 646 exitWithException = False # use sys.exit instead 647 main(sys.argv[0], sys.argv[1:]) 648 649