Add glib to the list of packages with debugging symbols.
[mono.git] / packaging / MacSDK / profile.py
1 import itertools
2 import os
3 import re
4 import shutil
5 import string
6 import sys
7 import tempfile
8 import subprocess
9 import stat
10
11 from bockbuild.darwinprofile import DarwinProfile
12 from bockbuild.util.util import *
13 from glob import glob
14
15 class MonoReleaseProfile(DarwinProfile):
16     description = 'The Mono Framework for MacOS'
17     packages = [
18         'gettext',
19         'pkg-config',
20
21         # Base Libraries
22         'libpng',
23         'libjpeg',
24         'libtiff',
25         'libgif',
26         'libxml2',
27         'freetype',
28         'fontconfig',
29         'pixman',
30         'cairo',
31         'libffi',
32         'glib',
33         'pango',
34         'atk',
35         'intltool',
36         'gdk-pixbuf',
37         'gtk+',
38         'libglade',
39         'sqlite',
40         'expat',
41         'ige-mac-integration',
42
43         # Theme
44         'libcroco',
45         'librsvg',
46         'hicolor-icon-theme',
47         'gtk-engines',
48         'murrine',
49         'xamarin-gtk-theme',
50         'gtk-quartz-engine',
51
52         # Mono
53         'mono-llvm',
54         'mono',
55         'msbuild',
56         'pcl-reference-assemblies',
57         'libgdiplus',
58         'xsp',
59         'gtk-sharp',
60         'ironlangs',
61         'fsharp',
62         'mono-basic',
63         'nuget'
64     ]
65
66     def attach (self, bockbuild):
67         self.min_version = 7
68         DarwinProfile.attach (self, bockbuild)
69
70         # quick disk space check (http://stackoverflow.com/questions/787776/)
71         s = os.statvfs(bockbuild.root)
72         free_space = (s.f_bavail * s.f_frsize) / (1024 * 1024 * 1024)  # in GB
73
74         if free_space < 10:
75             error('Low disk space (less than 10GB), aborting')
76
77         # check for XQuartz installation (needed for libgdiplus)
78         if not os.path.exists('/opt/X11/include/X11/Xlib.h'):
79             error(
80                 'XQuartz is required to be installed (download from http://xquartz.macosforge.org/) ')
81
82         self.MONO_ROOT = "/Library/Frameworks/Mono.framework"
83         self.BUILD_NUMBER = "0"
84         self.MDK_GUID = "964ebddd-1ffe-47e7-8128-5ce17ffffb05"
85
86         system_mono_dir = '/Library/Frameworks/Mono.framework/Versions/Current'
87         self.env.set('system_mono', os.path.join(
88             system_mono_dir, 'bin', 'mono'))
89         self.env.set('system_mcs', os.path.join(system_mono_dir, 'bin', 'mcs'))
90
91         self.env.set('system_mono_version', backtick(
92             '%s --version' % self.env.system_mono)[0])
93
94         # config overrides for some programs to be functional while staged
95
96         self.env.set('GDK_PIXBUF_MODULE_FILE',
97                      '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache')
98         self.env.set('GDK_PIXBUF_MODULEDIR',
99                      '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders')
100         self.env.set('PANGO_SYSCONFDIR', '%{staged_prefix}/etc')
101         self.env.set('PANGO_LIBDIR', '%{staged_prefix}/lib')
102         # self.env.set ('MONO_PATH', '%{staged_prefix}/lib/mono/4.0')
103         self.debug_info = ['gtk+', 'cairo', 'glib',
104                            'pango', 'mono', 'llvm', 'libgdiplus']
105         self.cache_host = None
106
107     def setup_release(self):
108         self.mono_package = self.release_packages['mono']
109         dest = os.path.join(self.bockbuild.build_root, self.mono_package.source_dir_name)
110         self.mono_package.fetch(dest)
111
112
113         verbose('Mono version: %s' % self.mono_package.version)
114         self.RELEASE_VERSION = self.mono_package.version
115         self.prefix = os.path.join(
116             self.MONO_ROOT, "Versions", self.RELEASE_VERSION)
117
118         if os.path.exists(self.prefix):
119             error('Prefix %s exists, and may interfere with the staged build. Please remove and try again.' % self.prefix)
120
121         self.calculate_updateid()
122
123         self.mono_package.custom_version_str = self.FULL_VERSION
124         trace(self.package_info('MDK'))
125
126         self.dont_optimize = ['pixman']
127
128         for p in self.release_packages.values():
129             if p.name in self.dont_optimize:
130                 continue
131             self.gcc_flags.extend(['-O2'])
132
133     # THIS IS THE MAIN METHOD FOR MAKING A PACKAGE
134     def package(self):
135         self.fix_gtksharp_configs()
136         self.verify_binaries()
137
138         working = self.setup_working_dir()
139
140         # make the MDK
141         self.apply_blacklist(working, 'mdk_blacklist.sh')
142         self.make_updateinfo(working, self.MDK_GUID)
143         mdk_pkg = self.run_pkgbuild(working, "MDK")
144         title(mdk_pkg)
145
146         shutil.rmtree(working)
147
148     def calculate_updateid(self):
149         # Create the updateid
150         pwd = os.getcwd()
151         git_bin = self.bockbuild.git_bin
152         trace("cur path is %s and git is %s" % (pwd, git_bin))
153         blame_rev_str = 'cd %s; %s blame configure.ac HEAD | grep AC_INIT | sed \'s/ .*//\' ' % (
154             self.mono_package.workspace, git_bin)
155         blame_rev = backtick(blame_rev_str)[0]
156         trace("Last commit to the version string %s" % (blame_rev))
157         version_number_str = 'cd %s; %s log %s..HEAD --oneline | wc -l | sed \'s/ //g\'' % (
158             self.mono_package.workspace, git_bin, blame_rev)
159         self.BUILD_NUMBER = backtick(version_number_str)[0]
160         trace("Calculating commit distance, %s" % (self.BUILD_NUMBER))
161         self.FULL_VERSION = self.RELEASE_VERSION + "." + self.BUILD_NUMBER
162         os.chdir(pwd)
163
164         parts = self.RELEASE_VERSION.split(".")
165         version_list = (parts + ["0"] * (3 - len(parts)))[:4]
166         for i in range(1, 3):
167             version_list[i] = version_list[i].zfill(2)
168             self.updateid = "".join(version_list)
169             self.updateid += self.BUILD_NUMBER.replace(
170                 ".", "").zfill(9 - len(self.updateid))
171         trace(self.updateid)
172
173     # creates and returns the path to a working directory containing:
174     #   PKGROOT/ - this root will be bundled into the .pkg and extracted at /
175     #   Info{_sdk}.plist - used by packagemaker to make the installer
176     #   resources/ - other resources used by packagemaker for the installer
177     def setup_working_dir(self):
178         def make_package_symlinks(root):
179             os.symlink(self.prefix, os.path.join(root, "Versions", "Current"))
180             currentlink = os.path.join(self.MONO_ROOT, "Versions", "Current")
181             links = [
182                 ("bin", "Commands"),
183                 ("include", "Headers"),
184                 ("lib", "Libraries"),
185                 ("", "Home"),
186                 (os.path.join("lib", "libmono-2.0.dylib"), "Mono")
187             ]
188             for srcname, destname in links:
189                 src = os.path.join(currentlink, srcname)
190                 dest = os.path.join(root, destname)
191                 # If the symlink exists, we remove it so we can create a fresh
192                 # one
193                 if os.path.exists(dest):
194                     os.unlink(dest)
195                 os.symlink(src, dest)
196
197         tmpdir = tempfile.mkdtemp()
198         monoroot = os.path.join(tmpdir, "PKGROOT", self.MONO_ROOT[1:])
199         versions = os.path.join(monoroot, "Versions")
200         os.makedirs(versions)
201
202         print "Setting up temporary package directory:", tmpdir
203
204         # setup metadata
205         self.packaging_dir = os.path.join(self.directory, "packaging")
206         run_shell('rsync -aPq %s/* %s' % (self.packaging_dir, tmpdir), False)
207
208         packages_list = string.join(
209             [pkg.desc for pkg in self.release_packages.values()], "\\\n")
210         deps_list = 'bockbuild (rev. %s)\\\n' % bockbuild.bockbuild_rev + string.join(
211             [pkg.desc for pkg in self.toolchain_packages.values()], "\\\n")
212
213         parameter_map = {
214             '@@MONO_VERSION@@': self.RELEASE_VERSION,
215             '@@MONO_RELEASE@@': self.BUILD_NUMBER,
216             '@@MONO_VERSION_RELEASE@@': self.RELEASE_VERSION + '_' + self.BUILD_NUMBER,
217             '@@MONO_CSDK_GUID@@': self.MDK_GUID,
218             '@@MONO_VERSION_RELEASE_INT@@': self.updateid,
219             '@@PACKAGES@@': packages_list,
220             '@@DEP_PACKAGES@@': deps_list
221         }
222         for dirpath, d, files in os.walk(tmpdir):
223             for name in files:
224                 if not name.startswith('.'):
225                     replace_in_file(os.path.join(dirpath, name), parameter_map)
226
227         make_package_symlinks(monoroot)
228
229         # copy to package root
230         run_shell('rsync -aPq "%s"/* "%s/%s"' %
231                   (bockbuild.package_root, versions, self.RELEASE_VERSION), False)
232
233         return tmpdir
234
235     def apply_blacklist(self, working_dir, blacklist_name):
236         print "Applying blacklist script:", blacklist_name
237         blacklist = os.path.join(self.packaging_dir, blacklist_name)
238         root = os.path.join(working_dir, "PKGROOT", self.prefix[1:])
239         run_shell('%s "%s" > /dev/null' % (blacklist, root), print_cmd=False)
240
241     def run_pkgbuild(self, working_dir, package_type):
242         print 'Running pkgbuild & productbuild...',
243         info = self.package_info(package_type)
244         output = os.path.join(self.directory, info["filename"])
245         identifier = "com.xamarin.mono-" + info["type"] + ".pkg"
246         resources_dir = os.path.join(working_dir, "resources")
247         distribution_xml = os.path.join(resources_dir, "distribution.xml")
248
249         old_cwd = os.getcwd()
250         os.chdir(working_dir)
251         pkgbuild = "/usr/bin/pkgbuild"
252         pkgbuild_cmd = ' '.join([pkgbuild,
253                                  "--identifier " + identifier,
254                                  "--root '%s/PKGROOT'" % working_dir,
255                                  "--version '%s'" % self.RELEASE_VERSION,
256                                  "--install-location '/'",
257                                  "--scripts '%s'" % resources_dir,
258                                  "--quiet",
259                                  os.path.join(working_dir, "mono.pkg")])
260
261         run_shell(pkgbuild_cmd)
262
263         productbuild = "/usr/bin/productbuild"
264         productbuild_cmd = ' '.join([productbuild,
265                                      "--resources %s" % resources_dir,
266                                      "--distribution %s" % distribution_xml,
267                                      "--package-path %s" % working_dir,
268                                      "--quiet",
269                                      output])
270
271         run_shell(productbuild_cmd)
272
273         assert_exists(output)
274         os.chdir(old_cwd)
275         print output
276         return output
277
278     def make_updateinfo(self, working_dir, guid):
279         updateinfo = os.path.join(
280             working_dir, "PKGROOT", self.prefix[1:], "updateinfo")
281         with open(updateinfo, "w") as updateinfo:
282             updateinfo.write(guid + ' ' + self.updateid + "\n")
283         version_file = os.path.join(
284             working_dir, "PKGROOT", self.prefix[1:], "VERSION")
285         with open(version_file, "w") as version_file:
286             version_file.write(self.FULL_VERSION + "\n")
287
288     def package_info(self, pkg_type):
289         arch = self.bockbuild.cmd_options.arch
290         arch_str = None
291         if  arch == "darwin-32":
292             arch_str = "x86"
293         elif arch == "darwin-64":
294             arch_str = "x64"
295         elif arch == "darwin-universal":
296             arch_str = "universal"
297         else:
298             error ("Unknown architecture")
299
300         if self.bockbuild.cmd_options.release_build:
301             info = (pkg_type, self.FULL_VERSION, arch_str)
302         else:
303             info = (pkg_type, '%s-%s' % (git_shortid(self.bockbuild,
304                                                      self.mono_package.workspace), self.FULL_VERSION), arch_str)
305
306         filename = "MonoFramework-%s-%s.macos10.xamarin.%s.pkg" % info
307         return {
308             "type": pkg_type,
309             "filename": filename
310         }
311
312     def fix_line(self, line, matcher):
313         def insert_install_root(matches):
314             root = self.prefix
315             captures = matches.groupdict()
316             return 'target="%s"' % os.path.join(root, "lib", captures["lib"])
317
318         if matcher(line):
319             pattern = r'target="(?P<lib>.+\.dylib)"'
320             result = re.sub(pattern, insert_install_root, line)
321             return result
322         else:
323             return line
324
325     def fix_dllmap(self, config, matcher):
326         handle, temp = tempfile.mkstemp()
327         with open(config) as c:
328             with open(temp, "w") as output:
329                 for line in c:
330                     output.write(self.fix_line(line, matcher))
331         os.rename(temp, config)
332         os.system('chmod a+r %s' % config)
333
334     def fix_gtksharp_configs(self):
335         print 'Fixing GTK# configuration files...',
336         count = 0
337         libs = [
338             'atk-sharp',
339             'gdk-sharp',
340             'glade-sharp',
341             'glib-sharp',
342             'gtk-dotnet',
343             'gtk-sharp',
344             'pango-sharp'
345         ]
346         gac = os.path.join(bockbuild.package_root, "lib", "mono", "gac")
347         confs = [glob.glob(os.path.join(gac, x, "*", "*.dll.config")) for x in libs]
348         for c in itertools.chain(*confs):
349             count = count + 1
350             self.fix_dllmap(c, lambda line: "dllmap" in line)
351         print count
352
353     def verify(self, f):
354         result = " ".join(backtick("otool -L " + f))
355         regex = os.path.join(self.MONO_ROOT, "Versions", r"(\d+\.\d+\.\d+)")
356
357         match = re.search(regex, result)
358         if match is None:
359             return
360         token = match.group(1)
361         trace(token)
362         if self.RELEASE_VERSION not in token:
363             raise Exception("%s references Mono %s\n%s" % (f, token, text))
364
365     def verify_binaries(self):
366         bindir = os.path.join(bockbuild.package_root, "bin")
367         for path, dirs, files in os.walk(bindir):
368             for name in files:
369                 f = os.path.join(path, name)
370                 file_type = backtick('file "%s"' % f)
371                 if "Mach-O executable" in "".join(file_type):
372                     self.verify(f)
373
374     def shell(self):
375         envscript = '''#!/bin/sh
376         PROFNAME="%s"
377         INSTALLDIR="%s"
378         ROOT="%s"
379         export DYLD_FALLBACK_LIBRARY_PATH="$INSTALLDIR/lib:/lib:/usr/lib"
380         export ACLOCAL_PATH="$INSTALLDIR/share/aclocal"
381         export CONFIG_SITE="$INSTALLDIR/$PROFNAME-config.site"
382         export MONO_GAC_PREFIX="$INSTALLDIR"
383         export MONO_ADDINS_REGISTRY="$ROOT/addinreg"
384         export MONO_INSTALL_PREFIX="$INSTALLDIR"
385
386         export PS1="\[\e[1;3m\][$PROFNAME] \w @ "
387         bash -i
388         ''' % (self.profile_name, self.staged_prefix, self.root)
389
390         path = os.path.join(self.root, self.profile_name + '.sh')
391
392         with open(path, 'w') as f:
393             f.write(envscript)
394
395         os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
396
397         subprocess.call(['bash', '-c', path])
398
399 MonoReleaseProfile()