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