[bockbuild] Fix a packaging regression
[mono-project.git] / packaging / MacSDK / profile.py
blob119678f9e7575bc876ce092d0e9ce4e6d26262e7
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
11 from bockbuild.darwinprofile import DarwinProfile
12 from bockbuild.util.util import *
13 from glob import glob
15 class MonoReleaseProfile(DarwinProfile):
16 description = 'The Mono Framework for MacOS'
17 packages = [
18 'gettext',
19 'pkg-config',
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',
43 # Theme
44 'libcroco',
45 'librsvg',
46 'hicolor-icon-theme',
47 'gtk-engines',
48 'murrine',
49 'xamarin-gtk-theme',
50 'gtk-quartz-engine',
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'
66 def attach (self, bockbuild):
67 self.min_version = 7
68 DarwinProfile.attach (self, bockbuild)
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
74 if free_space < 10:
75 error('Low disk space (less than 10GB), aborting')
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/) ')
82 self.MONO_ROOT = "/Library/Frameworks/Mono.framework"
83 self.BUILD_NUMBER = "0"
84 self.MDK_GUID = "964ebddd-1ffe-47e7-8128-5ce17ffffb05"
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'))
91 self.env.set('system_mono_version', backtick(
92 '%s --version' % self.env.system_mono)[0])
94 # config overrides for some programs to be functional while staged
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
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)
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)
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)
121 self.calculate_updateid()
122 trace(self.package_info('MDK'))
124 self.dont_optimize = ['pixman']
126 for p in self.release_packages.values():
127 if p.name in self.dont_optimize:
128 continue
129 self.gcc_flags.extend(['-O2'])
131 # THIS IS THE MAIN METHOD FOR MAKING A PACKAGE
132 def package(self):
133 self.fix_gtksharp_configs()
134 self.verify_binaries()
136 working = self.setup_working_dir()
137 uninstall_script = os.path.join(working, "uninstallMono.sh")
139 # make the MDK
140 self.apply_blacklist(working, 'mdk_blacklist.sh')
141 self.make_updateinfo(working, self.MDK_GUID)
142 mdk_pkg = self.run_pkgbuild(working, "MDK")
143 title(mdk_pkg)
144 # self.make_dmg(mdk_dmg, title, mdk_pkg, uninstall_script)
146 shutil.rmtree(working)
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)
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)
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 # uninstallMono.sh - copied onto the DMG
176 # Info{_sdk}.plist - used by packagemaker to make the installer
177 # resources/ - other resources used by packagemaker for the installer
178 def setup_working_dir(self):
179 def make_package_symlinks(root):
180 os.symlink(self.prefix, os.path.join(root, "Versions", "Current"))
181 currentlink = os.path.join(self.MONO_ROOT, "Versions", "Current")
182 links = [
183 ("bin", "Commands"),
184 ("include", "Headers"),
185 ("lib", "Libraries"),
186 ("", "Home"),
187 (os.path.join("lib", "libmono-2.0.dylib"), "Mono")
189 for srcname, destname in links:
190 src = os.path.join(currentlink, srcname)
191 dest = os.path.join(root, destname)
192 # If the symlink exists, we remove it so we can create a fresh
193 # one
194 if os.path.exists(dest):
195 os.unlink(dest)
196 os.symlink(src, dest)
198 tmpdir = tempfile.mkdtemp()
199 monoroot = os.path.join(tmpdir, "PKGROOT", self.MONO_ROOT[1:])
200 versions = os.path.join(monoroot, "Versions")
201 os.makedirs(versions)
203 print "Setting up temporary package directory:", tmpdir
205 # setup metadata
206 self.packaging_dir = os.path.join(self.directory, "packaging")
207 run_shell('rsync -aPq %s/* %s' % (self.packaging_dir, tmpdir), False)
209 packages_list = string.join(
210 [pkg.desc for pkg in self.release_packages.values()], "\\\n")
211 deps_list = 'bockbuild (rev. %s)\\\n' % bockbuild.bockbuild_rev + string.join(
212 [pkg.desc for pkg in self.toolchain_packages.values()], "\\\n")
214 parameter_map = {
215 '@@MONO_VERSION@@': self.RELEASE_VERSION,
216 '@@MONO_RELEASE@@': self.BUILD_NUMBER,
217 '@@MONO_VERSION_RELEASE@@': self.RELEASE_VERSION + '_' + self.BUILD_NUMBER,
218 '@@MONO_CSDK_GUID@@': self.MDK_GUID,
219 '@@MONO_VERSION_RELEASE_INT@@': self.updateid,
220 '@@PACKAGES@@': packages_list,
221 '@@DEP_PACKAGES@@': deps_list
223 for dirpath, d, files in os.walk(tmpdir):
224 for name in files:
225 if not name.startswith('.'):
226 replace_in_file(os.path.join(dirpath, name), parameter_map)
228 make_package_symlinks(monoroot)
230 # copy to package root
231 run_shell('rsync -aPq "%s"/* "%s/%s"' %
232 (bockbuild.package_root, versions, self.RELEASE_VERSION), False)
234 return tmpdir
236 def apply_blacklist(self, working_dir, blacklist_name):
237 print "Applying blacklist script:", blacklist_name
238 blacklist = os.path.join(self.packaging_dir, blacklist_name)
239 root = os.path.join(working_dir, "PKGROOT", self.prefix[1:])
240 run_shell('%s "%s" > /dev/null' % (blacklist, root), print_cmd=False)
242 def run_pkgbuild(self, working_dir, package_type):
243 print 'Running pkgbuild & productbuild...',
244 info = self.package_info(package_type)
245 output = os.path.join(self.directory, info["filename"])
246 identifier = "com.xamarin.mono-" + info["type"] + ".pkg"
247 resources_dir = os.path.join(working_dir, "resources")
248 distribution_xml = os.path.join(resources_dir, "distribution.xml")
250 old_cwd = os.getcwd()
251 os.chdir(working_dir)
252 pkgbuild = "/usr/bin/pkgbuild"
253 pkgbuild_cmd = ' '.join([pkgbuild,
254 "--identifier " + identifier,
255 "--root '%s/PKGROOT'" % working_dir,
256 "--version '%s'" % self.RELEASE_VERSION,
257 "--install-location '/'",
258 "--scripts '%s'" % resources_dir,
259 "--quiet",
260 os.path.join(working_dir, "mono.pkg")])
262 run_shell(pkgbuild_cmd)
264 productbuild = "/usr/bin/productbuild"
265 productbuild_cmd = ' '.join([productbuild,
266 "--resources %s" % resources_dir,
267 "--distribution %s" % distribution_xml,
268 "--package-path %s" % working_dir,
269 "--quiet",
270 output])
272 run_shell(productbuild_cmd)
274 assert_exists(output)
275 os.chdir(old_cwd)
276 print output
277 return output
279 def make_updateinfo(self, working_dir, guid):
280 updateinfo = os.path.join(
281 working_dir, "PKGROOT", self.prefix[1:], "updateinfo")
282 with open(updateinfo, "w") as updateinfo:
283 updateinfo.write(guid + ' ' + self.updateid + "\n")
285 def package_info(self, pkg_type):
286 arch = self.bockbuild.cmd_options.arch
287 arch_str = None
288 if arch == "darwin-32":
289 arch_str = "x86"
290 elif arch == "darwin-64":
291 arch_str = "x64"
292 elif arch == "darwin-universal":
293 arch_str = "universal"
294 else:
295 error ("Unknown architecture")
297 if self.bockbuild.cmd_options.release_build:
298 info = (pkg_type, self.FULL_VERSION, arch_str)
299 else:
300 info = (pkg_type, '%s-%s' % (git_shortid(self.bockbuild,
301 self.mono_package.workspace), self.FULL_VERSION), arch_str)
303 filename = "MonoFramework-%s-%s.macos10.xamarin.%s.pkg" % info
304 return {
305 "type": pkg_type,
306 "filename": filename
309 def fix_line(self, line, matcher):
310 def insert_install_root(matches):
311 root = self.prefix
312 captures = matches.groupdict()
313 return 'target="%s"' % os.path.join(root, "lib", captures["lib"])
315 if matcher(line):
316 pattern = r'target="(?P<lib>.+\.dylib)"'
317 result = re.sub(pattern, insert_install_root, line)
318 return result
319 else:
320 return line
322 def fix_dllmap(self, config, matcher):
323 handle, temp = tempfile.mkstemp()
324 with open(config) as c:
325 with open(temp, "w") as output:
326 for line in c:
327 output.write(self.fix_line(line, matcher))
328 os.rename(temp, config)
329 os.system('chmod a+r %s' % config)
331 def fix_gtksharp_configs(self):
332 print 'Fixing GTK# configuration files...',
333 count = 0
334 libs = [
335 'atk-sharp',
336 'gdk-sharp',
337 'glade-sharp',
338 'glib-sharp',
339 'gtk-dotnet',
340 'gtk-sharp',
341 'pango-sharp'
343 gac = os.path.join(bockbuild.package_root, "lib", "mono", "gac")
344 confs = [glob.glob(os.path.join(gac, x, "*", "*.dll.config")) for x in libs]
345 for c in itertools.chain(*confs):
346 count = count + 1
347 self.fix_dllmap(c, lambda line: "dllmap" in line)
348 print count
350 def verify(self, f):
351 result = " ".join(backtick("otool -L " + f))
352 regex = os.path.join(self.MONO_ROOT, "Versions", r"(\d+\.\d+\.\d+)")
354 match = re.search(regex, result)
355 if match is None:
356 return
357 token = match.group(1)
358 trace(token)
359 if self.RELEASE_VERSION not in token:
360 raise Exception("%s references Mono %s\n%s" % (f, token, text))
362 def verify_binaries(self):
363 bindir = os.path.join(bockbuild.package_root, "bin")
364 for path, dirs, files in os.walk(bindir):
365 for name in files:
366 f = os.path.join(path, name)
367 file_type = backtick('file "%s"' % f)
368 if "Mach-O executable" in "".join(file_type):
369 self.verify(f)
371 def shell(self):
372 envscript = '''#!/bin/sh
373 PROFNAME="%s"
374 INSTALLDIR="%s"
375 ROOT="%s"
376 export DYLD_FALLBACK_LIBRARY_PATH="$INSTALLDIR/lib:/lib:/usr/lib"
377 export ACLOCAL_PATH="$INSTALLDIR/share/aclocal"
378 export CONFIG_SITE="$INSTALLDIR/$PROFNAME-config.site"
379 export MONO_GAC_PREFIX="$INSTALLDIR"
380 export MONO_ADDINS_REGISTRY="$ROOT/addinreg"
381 export MONO_INSTALL_PREFIX="$INSTALLDIR"
383 export PS1="\[\e[1;3m\][$PROFNAME] \w @ "
384 bash -i
385 ''' % (self.profile_name, self.staged_prefix, self.root)
387 path = os.path.join(self.root, self.profile_name + '.sh')
389 with open(path, 'w') as f:
390 f.write(envscript)
392 os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
394 subprocess.call(['bash', '-c', path])
396 MonoReleaseProfile()