#!/usr/bin/python import commands import cPickle import glob import optparse import os import shutil import string import sys import types import subprocess import cfg_version as cfg from distutils.dep_util import newer version2 = "%d.%d" % (cfg.VER_MAJOR, cfg.VER_MINOR) version3 = "%d.%d.%d" % (cfg.VER_MAJOR, cfg.VER_MINOR, cfg.VER_RELEASE) version2_nodot = version2.replace(".", "") version3_nodot = version3.replace(".", "") CPU = os.environ.get('CPU', '') scriptDir = os.path.abspath(sys.path[0]) scriptName = os.path.basename(sys.argv[0]) WXPYDIR = scriptDir if os.environ.has_key("WXWIN"): WXWIN = os.environ["WXWIN"] else: if os.path.exists('../wxWidgets'): WXWIN = '../wxWidgets' # assumes in parallel SVN tree else: WXWIN = '..' # assumes wxPython is in a subdir of wxWidgets WXWIN = os.path.abspath(os.path.join(WXPYDIR, WXWIN)) try: # Import the wxWidgets build script wxscript = os.path.join(WXWIN, "build/tools/build-wxwidgets.py") sys.path.insert(0, os.path.dirname(wxscript)) wxbuild = __import__('build-wxwidgets') except: print "Can't find or import %s/build/tools/build-wxwidgets.py, exiting." % WXWIN sys.exit(1) def optionCleanCallback(option, opt_str, value, parser): if value is None: value = "all" if not value in ["all", "wx", "py", "pyext"]: raise optparse.OptionValueError("Invalid clean option") setattr(parser.values, option.dest, value) # Set some default values for cmd-line options, to help keep the code below # somewhat readable. defJobs = str(wxbuild.numCPUs()) defFwPrefix = '/Library/Frameworks' defPrefix = '/usr/local' option_dict = { "clean" : ("", "Clean files from build directories. Default is all build files. " "Specify 'wx' to clean just the wx build, 'py' for just the " "wxPython build, and 'pyext' for just the built extension modules.", optionCleanCallback), "debug" : (False, "Build wxPython with debug symbols"), "reswig" : (False, "Allow SWIG to regenerate the wrappers"), "jobs" : (defJobs, "Number of make jobs to run at one time, if supported. Default: %s" % defJobs), "unicode" : (True, "Build wxPython with unicode support (always on for wx3.0)"), "gtk3" : (False, "On Linux build for gtk3 (default gtk2)"), "osx_cocoa" : (False, "Build the OS X Cocoa port on Mac"), "osx_carbon" : (True, "Build the Carbon port on Mac (default)"), "mac_arch" : ("", "Build the specified architectures on Mac, (comma-separated list)"), "mac_framework" : (False, "Build wxWidgets as a Mac framework."), "mac_framework_prefix" : (defFwPrefix, "Prefix where the framework should be installed. Default: %s" % defFwPrefix), "mac_universal_binary" : (False, "Build Mac version as a universal binary"), "cairo" : (False, "Enable dynamicly loading the Cairo lib for wxGraphicsContext on MSW"), "force_config" : (False, "Run configure when building even if the script determines it's not necessary."), "no_config" : (False, "Turn off wx configure step on autoconf builds"), "no_wxbuild" : (False, "Turn off the wx build step (assumes that wx is already " "built with the options and and in the location expected " "based on the other flags."), "prefix" : (defPrefix, "Prefix value to pass to the wx build. Default: %s" % defPrefix), "install" : (False, "Install the built wxPython into installdir or standard location"), "installdir" : ("", "Installation root for wxWidgets, files will go to {installdir}/{prefix}"), "build_dir" : ("", "Directory to store wx build files. (Not used on Windows)"), "wxpy_installdir":("", "Installation root for wxPython, defaults to Python's site-packages."), "multiversion" : (False, "Use the old-style multi-version install option."), "extra_setup" : ("", "Extra args to pass on setup.py's command line."), "extra_make" : ("", "Extra args to pass on [n]make's command line."), } options_changed = True old_options = None if os.path.exists("build-options.cache"): cache_file = open("build-options.cache") old_options = set(cPickle.load(cache_file)) cache_file.close() parser = optparse.OptionParser(usage="usage: %prog [options]", version="%prog 1.0") keys = option_dict.keys() keys.sort() for opt in keys: default = option_dict[opt][0] action = "store" if type(default) == types.BooleanType: action = "store_true" if len(option_dict[opt]) > 2: # Even with the callback action we still have to workaround optparse's # checking for presense of a value or not. Check for a '=' and set # type accordingly. vtype = None for a in sys.argv: if a.startswith('--'+opt+'='): vtype = 'string' break parser.add_option("--" + opt, action='callback', type=vtype, default=default, dest=opt, help=option_dict[opt][1], callback=option_dict[opt][2], nargs=1) else: parser.add_option("--" + opt, default=default, action=action, dest=opt, help=option_dict[opt][1]) options, arguments = parser.parse_args() if options.osx_cocoa: options.osx_carbon = False # Compare current command line options to the saved set. If they match then we # can save some time by skipping parts of the build. if set(sys.argv[1:]) == old_options: options_changed = False print "old_options = %r\nsys.argv = %r" % (old_options, sys.argv[1:]) cache_file = open("build-options.cache", "wb") cPickle.dump(sys.argv[1:], cache_file) cache_file.close() #--------------------------------------------------------------------------- # Utility functions def deleteIfExists(deldir, verbose=True): if os.path.exists(deldir) and os.path.isdir(deldir): if verbose: print "Removing folder: %s" % deldir shutil.rmtree(deldir) def delFiles(fileList, verbose=True): for afile in fileList: if verbose: print "Removing file: %s" % afile os.remove(afile) def runCmd(cmd): print '****', cmd return os.system(cmd) def exitIfError(code, msg): if code != 0: print msg sys.exit(1) def getoutput(cmd): sp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = None output = sp.stdout.read() if sys.version_info > (3,): output = output.decode('utf-8') # TODO: is utf-8 okay here? output = output.rstrip() rval = sp.wait() if rval: # Failed! print("Command '%s' failed with exit code %d." % (cmd, rval)) sys.exit(rval) return output def getVisCVersion(): text = getoutput("cl.exe") if 'Version 15' in text: return '90' # TODO: Add more tests to get the other versions... else: return 'FIXME' #--------------------------------------------------------------------------- build_options = ['--wxpython', '--jobs=' + options.jobs] wxpy_build_options = [] if os.environ.has_key("SWIG"): SWIG_BIN = os.environ["SWIG"] elif sys.platform.startswith("win"): SWIG_BIN = 'C:\\SWIG-1.3.29\\swig.exe' else: # WARNING: This is may not be the patched version of SWIG if the # user has installed a stock package SWIG_BIN = commands.getoutput("which swig") if options.reswig: if not os.path.exists(SWIG_BIN) and not sys.platform.startswith("win"): wxpy_build_options.append('SWIG="%s"' % "/opt/swig/bin/swig") if os.path.exists(SWIG_BIN): wxpy_build_options.append('SWIG="%s"' % SWIG_BIN) wxpy_build_options.append("USE_SWIG=%d" % 1) else: wxpy_build_options.append("USE_SWIG=%d" % 0) print "WARNING: Unable to find SWIG binary. Not re-SWIGing files." # Windows extension build stuff build_type_ext = "" if options.debug: build_type_ext = "d" dll_type = build_type_ext if options.unicode: dll_type = "u" + dll_type build_base = 'build' if sys.platform.startswith("darwin"): if options.osx_cocoa: build_base += '/cocoa' else: build_base += '/carbon' # Clean the wxPython build files and other things created or copied by previous builds. # Cleaning of the wxWidgets build files is done below. if options.clean in ['all', 'py']: if options.unicode: deleteIfExists(os.path.join(WXPYDIR, build_base + '.unicode')) else: deleteIfExists(os.path.join(WXPYDIR, build_base)) if options.clean in ['all', 'py', 'pyext']: files = glob.glob(os.path.join(WXPYDIR, "wx", "*.so")) files += glob.glob(os.path.join(WXPYDIR, "wx", "*.py")) files += glob.glob(os.path.join(WXPYDIR, "wx", "*.pyc")) if options.debug: files += glob.glob(os.path.join(WXPYDIR, "wx", "*_d.pyd")) else: allpyd = glob.glob(os.path.join(WXPYDIR, "wx", "*.pyd")) files += list( [pyd for pyd in allpyd if not pyd.endswith("_d.pyd")] ) files += glob.glob(os.path.join(WXPYDIR, "wx", "wx*" + version2_nodot + dll_type + "*.dll")) files += glob.glob(os.path.join(WXPYDIR, "wx", "wx*" + version3_nodot + dll_type + "*.dll")) delFiles(files) print "wxWidgets directory is: %s" % WXWIN if sys.platform.startswith("win"): if CPU in ['AMD64', 'X64']: dllDir = os.path.join(WXWIN, "lib", "vc%s_x64_dll" % getVisCVersion()) else: dllDir = os.path.join(WXWIN, "lib", "vc%s_dll" % getVisCVersion()) buildDir = os.path.join(WXWIN, "build", "msw") if options.cairo: build_options.append("--cairo") if not os.environ.get("CAIRO_ROOT"): print "WARNING: Expected CAIRO_ROOT set in the environment!" if options.clean in ['all', 'wx']: deleteIfExists(os.path.join(dllDir, "msw" + dll_type + "")) delFiles(glob.glob(os.path.join(dllDir, "wx*" + version2_nodot + dll_type + "*.*"))) delFiles(glob.glob(os.path.join(dllDir, "wx*" + version3_nodot + dll_type + "*.*"))) delFiles(glob.glob(os.path.join(dllDir, "*%s.*" % dll_type))) delFiles(glob.glob(os.path.join(dllDir, "*%s.*" % build_type_ext))) deleteIfExists(os.path.join(buildDir, "vc%s_msw%sdll" % (getVisCVersion(), dll_type))) sys.exit(0) else: WXPY_BUILD_DIR = os.path.join(os.getcwd(), "wxpy-bld") if options.build_dir != "": WXPY_BUILD_DIR = os.path.abspath(options.build_dir) if sys.platform.startswith("darwin"): port = "carbon" if options.osx_cocoa: port = "cocoa" WXPY_BUILD_DIR = os.path.join(WXPY_BUILD_DIR, port) DESTDIR = options.installdir PREFIX = options.prefix if options.prefix: build_options.append('--prefix=%s' % options.prefix) if options.clean in ['all', 'wx']: deleteIfExists(WXPY_BUILD_DIR) if options.clean in ['all', 'py'] and options.wxpy_installdir: deleteIfExists(options.wxpy_installdir) if options.clean: sys.exit(0) if not os.path.exists(WXPY_BUILD_DIR): os.makedirs(WXPY_BUILD_DIR) if options.mac_universal_binary: build_options.append("--mac_universal_binary=default") elif options.mac_arch: build_options.append("--mac_universal_binary=%s" % options.mac_arch) wxpy_build_options.append("ARCH=%s" % options.mac_arch) if options.gtk3: build_options.append('--gtk3') wxpy_build_options.append('WXPORT=gtk3') # now that we've done platform setup, start the common build process if options.unicode: build_options.append("--unicode") wxpy_build_options.append("UNICODE=1") if options.debug: build_options.append("--debug") if options.extra_make: build_options.append('--extra_make="%s"' % options.extra_make) # Only disable configure if we don't need it if not sys.platform.startswith("win") and options.no_config: build_options.append("--no_config") elif ( not sys.platform.startswith("win") and not options.force_config and not options_changed ): dependencies = [ os.path.join(WXWIN, 'Makefile.in'), os.path.join(WXWIN, 'configure'), os.path.join(WXWIN, 'setup.h.in'), os.path.join(WXWIN, 'version-script.in'), os.path.join(WXWIN, 'wx-config.in'), ] blddir = WXPY_BUILD_DIR for dep in dependencies: if newer(dep, os.path.join(blddir, "Makefile")): break else: build_options.append("--no_config") if sys.platform.startswith("darwin") and options.osx_cocoa: build_options.append("--osx_cocoa") wxpy_build_options.append("WXPORT=osx_cocoa") if sys.platform.startswith("darwin") and options.osx_carbon: build_options.append("--osx_carbon") wxpy_build_options.append("WXPORT=osx_carbon") if not sys.platform.startswith("win") and options.install: build_options.append('--installdir=%s' % DESTDIR) build_options.append("--install") if options.mac_framework and sys.platform.startswith("darwin"): build_options.append("--mac_framework") build_options.append("--mac_framework_prefix=%s" % options.mac_framework_prefix) # We'll automatically add the --install option because for the wxPython # build to be able to use the framework it must be fully constructed. if "--install" not in build_options: build_options.append("--install") PREFIX = wxbuild.getPrefixInFramework(options, WXWIN) if options.multiversion: wxpy_build_options.append("INSTALL_MULTIVERSION=1") if not sys.platform.startswith("win"): build_options.append('--builddir=%s' % WXPY_BUILD_DIR) if options.no_wxbuild: print 'Skipping wxWidgets build, assuming it is already done.' else: try: print 'wxWidgets build options:', build_options wxbuild.main(wxscript, build_options) except: print "ERROR: failed building wxWidgets" import traceback traceback.print_exc() sys.exit(1) #----------------------------------------------------------------------- # wxPython build def macFixupInstallNames2(destprefix, # prefix where the files are currently located instprefix, # prefix to write into the files oldprefix # the prefix to be replaced, defaults to destprefix ): return # TODO: is this function still needed? print "**** macFixupInstallNames2(%s, %s, %s)" % ( destprefix, instprefix, oldprefix) pwd = os.getcwd() os.chdir(destprefix+'/lib') dylibs = glob.glob('*.dylib') # ('*[0-9].[0-9].[0-9].[0-9]*.dylib') for lib in dylibs: if not os.path.islink(lib): cmd = 'install_name_tool -id %s/lib/%s %s/lib/%s' % \ (instprefix,lib, destprefix,lib) runCmd(cmd) for dep in dylibs: if not os.path.islink(dep): cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s/lib/%s' % \ (oldprefix,dep, instprefix,dep, destprefix,lib) runCmd(cmd) os.chdir(pwd) def macFixDependencyInstallName(destdir, prefix, extension, buildDir): return # TODO: is this function still needed? print "**** macFixDependencyInstallName(%s, %s, %s, %s)" % (destdir, prefix, extension, buildDir) pwd = os.getcwd() os.chdir(destdir+prefix+'/lib') dylibs = glob.glob('*.dylib') for lib in dylibs: cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ (destdir+prefix,lib, prefix,lib, extension) #cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ # (buildDir,lib, prefix,lib, extension) runCmd(cmd) os.chdir(pwd) if options.install: # only add the --prefix flag if we have an explicit request to do # so, otherwise let distutils install in the default location. install_dir = DESTDIR or PREFIX WXPY_PREFIX = "" if options.wxpy_installdir: install_dir = options.wxpy_installdir WXPY_PREFIX = "--prefix=%s" % options.wxpy_installdir if sys.platform.startswith("win"): # Copy the wxWidgets DLLs to the wxPython package folder dlls = glob.glob(os.path.join(dllDir, "wx*" + version2_nodot + dll_type + "*.dll")) + \ glob.glob(os.path.join(dllDir, "wx*" + version3_nodot + dll_type + "*.dll")) if options.debug: dlls += glob.glob(os.path.join(dllDir, "wx*" + "*.pdb")) # Also copy the cairo DLLs if needed if options.cairo: dlls += glob.glob(os.path.join(os.environ['CAIRO_ROOT'], 'bin', '*.dll')) for dll in dlls: shutil.copyfile(dll, os.path.join(WXPYDIR, "wx", os.path.basename(dll))) wxpy_build_options.append("BUILD_BASE=%s" % build_base) build_mode = "build_ext --inplace" if options.install: build_mode = "build" if options.debug: build_mode += " --debug" if not sys.platform.startswith("win"): if options.install: wxlocation = DESTDIR + PREFIX wxcfg = "%s/bin/wx-config" % wxlocation wxcfgsrc = glob.glob(wxlocation + '/lib/wx/config/*')[0] print '-='*20 print 'DESTDIR:', DESTDIR print 'PREFIX:', PREFIX print 'wxlocation:', wxlocation print 'wxcfg:', wxcfg print 'wxcfgsrc:', wxcfgsrc print '-='*20 try: os.unlink(wxcfg) except IOError: pass os.symlink(wxcfgsrc.replace(wxlocation, '..'), wxcfg) wxpy_build_options.append('WX_CONFIG="%s --prefix=%s"' % (wxcfg, wxlocation)) elif options.mac_framework: wxpy_build_options.append("WX_CONFIG=%s/bin/wx-config" % PREFIX) else: wxpy_build_options.append("WX_CONFIG=%s/wx-config" % WXPY_BUILD_DIR) os.chdir(WXPYDIR) if options.install and sys.platform.startswith("darwin") and DESTDIR: # Adjust the install_names in the wx libs so we can link with # the wx binaries in their temporary DESTDIR location macFixupInstallNames2(DESTDIR+PREFIX, DESTDIR+PREFIX, PREFIX) command = sys.executable + " -u ./setup.py %s %s %s" % \ (build_mode, " ".join(wxpy_build_options), options.extra_setup) exitIfError(runCmd(command), "ERROR: failed building wxPython.") if options.install: command = sys.executable + " -u ./setup.py install %s %s %s --record installed_files.txt" % \ (WXPY_PREFIX, " ".join(wxpy_build_options), options.extra_setup) exitIfError(runCmd(command), "ERROR: failed installing wxPython.") if sys.platform.startswith("darwin") and DESTDIR: # Now that we are finished with the build fix the ids and # dependency names in the wx libs macFixupInstallNames2(DESTDIR+PREFIX, PREFIX, DESTDIR+PREFIX) # and also adjust the dependency names in the wxPython extensions for line in file("installed_files.txt"): line = line.strip() if line.endswith('.so'): macFixDependencyInstallName(DESTDIR, PREFIX, line, WXPY_BUILD_DIR) # update the language files TODO: this needs fixed... command = sys.executable + " -u " + os.path.join(WXPYDIR, "distrib", "makemo.py") exitIfError(runCmd(command), "ERROR: failed generating language files") print "------------ BUILD FINISHED ------------" print "" print "To run the wxPython demo you may need to:" print " - set your PYTHONPATH variable to %s" % WXPYDIR if options.mac_framework: print " - set your DYLD_FRAMEWORK_PATH to %s" % os.path.abspath(options.mac_framework_prefix) elif sys.platform.startswith("darwin"): print " - set your DYLD_LIBRARY_PATH to %s" % WXPY_BUILD_DIR + "/lib" elif not sys.platform.startswith("win") and not options.install: print " - set your LD_LIBRARY_PATH to %s" % WXPY_BUILD_DIR + "/lib" print "\nAnd then:" print " - Run python demo/demo.py" print ""