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