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