Merge pull request #4431 from vkargov/vk-leaking-points
[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',
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         uninstall_script = os.path.join(working, "uninstallMono.sh")
140
141         # make the MDK
142         self.apply_blacklist(working, 'mdk_blacklist.sh')
143         self.make_updateinfo(working, self.MDK_GUID)
144         mdk_pkg = self.run_pkgbuild(working, "MDK")
145         title(mdk_pkg)
146         # self.make_dmg(mdk_dmg, title, mdk_pkg, uninstall_script)
147
148         shutil.rmtree(working)
149
150     def calculate_updateid(self):
151         # Create the updateid
152         pwd = os.getcwd()
153         git_bin = self.bockbuild.git_bin
154         trace("cur path is %s and git is %s" % (pwd, git_bin))
155         blame_rev_str = 'cd %s; %s blame configure.ac HEAD | grep AC_INIT | sed \'s/ .*//\' ' % (
156             self.mono_package.workspace, git_bin)
157         blame_rev = backtick(blame_rev_str)[0]
158         trace("Last commit to the version string %s" % (blame_rev))
159         version_number_str = 'cd %s; %s log %s..HEAD --oneline | wc -l | sed \'s/ //g\'' % (
160             self.mono_package.workspace, git_bin, blame_rev)
161         self.BUILD_NUMBER = backtick(version_number_str)[0]
162         trace("Calculating commit distance, %s" % (self.BUILD_NUMBER))
163         self.FULL_VERSION = self.RELEASE_VERSION + "." + self.BUILD_NUMBER
164         os.chdir(pwd)
165
166         parts = self.RELEASE_VERSION.split(".")
167         version_list = (parts + ["0"] * (3 - len(parts)))[:4]
168         for i in range(1, 3):
169             version_list[i] = version_list[i].zfill(2)
170             self.updateid = "".join(version_list)
171             self.updateid += self.BUILD_NUMBER.replace(
172                 ".", "").zfill(9 - len(self.updateid))
173         trace(self.updateid)
174
175     # creates and returns the path to a working directory containing:
176     #   PKGROOT/ - this root will be bundled into the .pkg and extracted at /
177     #   uninstallMono.sh - copied onto the DMG
178     #   Info{_sdk}.plist - used by packagemaker to make the installer
179     #   resources/ - other resources used by packagemaker for the installer
180     def setup_working_dir(self):
181         def make_package_symlinks(root):
182             os.symlink(self.prefix, os.path.join(root, "Versions", "Current"))
183             currentlink = os.path.join(self.MONO_ROOT, "Versions", "Current")
184             links = [
185                 ("bin", "Commands"),
186                 ("include", "Headers"),
187                 ("lib", "Libraries"),
188                 ("", "Home"),
189                 (os.path.join("lib", "libmono-2.0.dylib"), "Mono")
190             ]
191             for srcname, destname in links:
192                 src = os.path.join(currentlink, srcname)
193                 dest = os.path.join(root, destname)
194                 # If the symlink exists, we remove it so we can create a fresh
195                 # one
196                 if os.path.exists(dest):
197                     os.unlink(dest)
198                 os.symlink(src, dest)
199
200         tmpdir = tempfile.mkdtemp()
201         monoroot = os.path.join(tmpdir, "PKGROOT", self.MONO_ROOT[1:])
202         versions = os.path.join(monoroot, "Versions")
203         os.makedirs(versions)
204
205         print "Setting up temporary package directory:", tmpdir
206
207         # setup metadata
208         self.packaging_dir = os.path.join(self.directory, "packaging")
209         run_shell('rsync -aPq %s/* %s' % (self.packaging_dir, tmpdir), False)
210
211         packages_list = string.join(
212             [pkg.desc for pkg in self.release_packages.values()], "\\\n")
213         deps_list = 'bockbuild (rev. %s)\\\n' % bockbuild.bockbuild_rev + string.join(
214             [pkg.desc for pkg in self.toolchain_packages.values()], "\\\n")
215
216         parameter_map = {
217             '@@MONO_VERSION@@': self.RELEASE_VERSION,
218             '@@MONO_RELEASE@@': self.BUILD_NUMBER,
219             '@@MONO_VERSION_RELEASE@@': self.RELEASE_VERSION + '_' + self.BUILD_NUMBER,
220             '@@MONO_CSDK_GUID@@': self.MDK_GUID,
221             '@@MONO_VERSION_RELEASE_INT@@': self.updateid,
222             '@@PACKAGES@@': packages_list,
223             '@@DEP_PACKAGES@@': deps_list
224         }
225         for dirpath, d, files in os.walk(tmpdir):
226             for name in files:
227                 if not name.startswith('.'):
228                     replace_in_file(os.path.join(dirpath, name), parameter_map)
229
230         make_package_symlinks(monoroot)
231
232         # copy to package root
233         run_shell('rsync -aPq "%s"/* "%s/%s"' %
234                   (bockbuild.package_root, versions, self.RELEASE_VERSION), False)
235
236         return tmpdir
237
238     def apply_blacklist(self, working_dir, blacklist_name):
239         print "Applying blacklist script:", blacklist_name
240         blacklist = os.path.join(self.packaging_dir, blacklist_name)
241         root = os.path.join(working_dir, "PKGROOT", self.prefix[1:])
242         run_shell('%s "%s" > /dev/null' % (blacklist, root), print_cmd=False)
243
244     def run_pkgbuild(self, working_dir, package_type):
245         print 'Running pkgbuild & productbuild...',
246         info = self.package_info(package_type)
247         output = os.path.join(self.directory, info["filename"])
248         identifier = "com.xamarin.mono-" + info["type"] + ".pkg"
249         resources_dir = os.path.join(working_dir, "resources")
250         distribution_xml = os.path.join(resources_dir, "distribution.xml")
251
252         old_cwd = os.getcwd()
253         os.chdir(working_dir)
254         pkgbuild = "/usr/bin/pkgbuild"
255         pkgbuild_cmd = ' '.join([pkgbuild,
256                                  "--identifier " + identifier,
257                                  "--root '%s/PKGROOT'" % working_dir,
258                                  "--version '%s'" % self.RELEASE_VERSION,
259                                  "--install-location '/'",
260                                  "--scripts '%s'" % resources_dir,
261                                  "--quiet",
262                                  os.path.join(working_dir, "mono.pkg")])
263
264         run_shell(pkgbuild_cmd)
265
266         productbuild = "/usr/bin/productbuild"
267         productbuild_cmd = ' '.join([productbuild,
268                                      "--resources %s" % resources_dir,
269                                      "--distribution %s" % distribution_xml,
270                                      "--package-path %s" % working_dir,
271                                      "--quiet",
272                                      output])
273
274         run_shell(productbuild_cmd)
275
276         assert_exists(output)
277         os.chdir(old_cwd)
278         print output
279         return output
280
281     def make_updateinfo(self, working_dir, guid):
282         updateinfo = os.path.join(
283             working_dir, "PKGROOT", self.prefix[1:], "updateinfo")
284         with open(updateinfo, "w") as updateinfo:
285             updateinfo.write(guid + ' ' + self.updateid + "\n")
286         version_file = os.path.join(
287             working_dir, "PKGROOT", self.prefix[1:], "VERSION")
288         with open(version_file, "w") as version_file:
289             version_file.write(self.FULL_VERSION + "\n")
290
291     def package_info(self, pkg_type):
292         arch = self.bockbuild.cmd_options.arch
293         arch_str = None
294         if  arch == "darwin-32":
295             arch_str = "x86"
296         elif arch == "darwin-64":
297             arch_str = "x64"
298         elif arch == "darwin-universal":
299             arch_str = "universal"
300         else:
301             error ("Unknown architecture")
302
303         if self.bockbuild.cmd_options.release_build:
304             info = (pkg_type, self.FULL_VERSION, arch_str)
305         else:
306             info = (pkg_type, '%s-%s' % (git_shortid(self.bockbuild,
307                                                      self.mono_package.workspace), self.FULL_VERSION), arch_str)
308
309         filename = "MonoFramework-%s-%s.macos10.xamarin.%s.pkg" % info
310         return {
311             "type": pkg_type,
312             "filename": filename
313         }
314
315     def fix_line(self, line, matcher):
316         def insert_install_root(matches):
317             root = self.prefix
318             captures = matches.groupdict()
319             return 'target="%s"' % os.path.join(root, "lib", captures["lib"])
320
321         if matcher(line):
322             pattern = r'target="(?P<lib>.+\.dylib)"'
323             result = re.sub(pattern, insert_install_root, line)
324             return result
325         else:
326             return line
327
328     def fix_dllmap(self, config, matcher):
329         handle, temp = tempfile.mkstemp()
330         with open(config) as c:
331             with open(temp, "w") as output:
332                 for line in c:
333                     output.write(self.fix_line(line, matcher))
334         os.rename(temp, config)
335         os.system('chmod a+r %s' % config)
336
337     def fix_gtksharp_configs(self):
338         print 'Fixing GTK# configuration files...',
339         count = 0
340         libs = [
341             'atk-sharp',
342             'gdk-sharp',
343             'glade-sharp',
344             'glib-sharp',
345             'gtk-dotnet',
346             'gtk-sharp',
347             'pango-sharp'
348         ]
349         gac = os.path.join(bockbuild.package_root, "lib", "mono", "gac")
350         confs = [glob.glob(os.path.join(gac, x, "*", "*.dll.config")) for x in libs]
351         for c in itertools.chain(*confs):
352             count = count + 1
353             self.fix_dllmap(c, lambda line: "dllmap" in line)
354         print count
355
356     def verify(self, f):
357         result = " ".join(backtick("otool -L " + f))
358         regex = os.path.join(self.MONO_ROOT, "Versions", r"(\d+\.\d+\.\d+)")
359
360         match = re.search(regex, result)
361         if match is None:
362             return
363         token = match.group(1)
364         trace(token)
365         if self.RELEASE_VERSION not in token:
366             raise Exception("%s references Mono %s\n%s" % (f, token, text))
367
368     def verify_binaries(self):
369         bindir = os.path.join(bockbuild.package_root, "bin")
370         for path, dirs, files in os.walk(bindir):
371             for name in files:
372                 f = os.path.join(path, name)
373                 file_type = backtick('file "%s"' % f)
374                 if "Mach-O executable" in "".join(file_type):
375                     self.verify(f)
376
377     def shell(self):
378         envscript = '''#!/bin/sh
379         PROFNAME="%s"
380         INSTALLDIR="%s"
381         ROOT="%s"
382         export DYLD_FALLBACK_LIBRARY_PATH="$INSTALLDIR/lib:/lib:/usr/lib"
383         export ACLOCAL_PATH="$INSTALLDIR/share/aclocal"
384         export CONFIG_SITE="$INSTALLDIR/$PROFNAME-config.site"
385         export MONO_GAC_PREFIX="$INSTALLDIR"
386         export MONO_ADDINS_REGISTRY="$ROOT/addinreg"
387         export MONO_INSTALL_PREFIX="$INSTALLDIR"
388
389         export PS1="\[\e[1;3m\][$PROFNAME] \w @ "
390         bash -i
391         ''' % (self.profile_name, self.staged_prefix, self.root)
392
393         path = os.path.join(self.root, self.profile_name + '.sh')
394
395         with open(path, 'w') as f:
396             f.write(envscript)
397
398         os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
399
400         subprocess.call(['bash', '-c', path])
401
402 MonoReleaseProfile()