import itertools import os import re import shutil import string import sys import tempfile import subprocess import stat from bockbuild.darwinprofile import DarwinProfile from bockbuild.util.util import * from glob import glob class MonoReleaseProfile(DarwinProfile): description = 'The Mono Framework for MacOS' packages = [ 'gettext', 'pkg-config', # Base Libraries 'libpng', 'libjpeg', 'libtiff', 'libgif', 'libxml2', 'freetype', 'fontconfig', 'pixman', 'cairo', 'libffi', 'glib', 'pango', 'atk', 'intltool', 'gdk-pixbuf', 'gtk+', 'libglade', 'sqlite', 'expat', 'ige-mac-integration', # Theme 'libcroco', 'librsvg', 'hicolor-icon-theme', 'gtk-engines', 'murrine', 'xamarin-gtk-theme', 'gtk-quartz-engine', # Mono 'mono-llvm', 'mono', 'msbuild', 'pcl-reference-assemblies', 'libgdiplus', 'xsp', 'gtk-sharp', 'ironlangs', 'fsharp', 'mono-basic', 'nuget' ] def attach (self, bockbuild): self.min_version = 7 DarwinProfile.attach (self, bockbuild) # quick disk space check (http://stackoverflow.com/questions/787776/) s = os.statvfs(bockbuild.root) free_space = (s.f_bavail * s.f_frsize) / (1024 * 1024 * 1024) # in GB if free_space < 10: error('Low disk space (less than 10GB), aborting') # check for XQuartz installation (needed for libgdiplus) if not os.path.exists('/opt/X11/include/X11/Xlib.h'): error( 'XQuartz is required to be installed (download from http://xquartz.macosforge.org/) ') self.MONO_ROOT = "/Library/Frameworks/Mono.framework" self.BUILD_NUMBER = "0" self.MDK_GUID = "964ebddd-1ffe-47e7-8128-5ce17ffffb05" system_mono_dir = '/Library/Frameworks/Mono.framework/Versions/Current' self.env.set('system_mono', os.path.join( system_mono_dir, 'bin', 'mono')) self.env.set('system_mcs', os.path.join(system_mono_dir, 'bin', 'mcs')) self.env.set('system_mono_version', backtick( '%s --version' % self.env.system_mono)[0]) # config overrides for some programs to be functional while staged self.env.set('GDK_PIXBUF_MODULE_FILE', '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache') self.env.set('GDK_PIXBUF_MODULEDIR', '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders') self.env.set('PANGO_SYSCONFDIR', '%{staged_prefix}/etc') self.env.set('PANGO_LIBDIR', '%{staged_prefix}/lib') # self.env.set ('MONO_PATH', '%{staged_prefix}/lib/mono/4.0') self.debug_info = ['gtk+', 'cairo', 'pango', 'mono', 'llvm', 'libgdiplus'] self.cache_host = None def setup_release(self): self.mono_package = self.release_packages['mono'] dest = os.path.join(self.bockbuild.build_root, self.mono_package.source_dir_name) self.mono_package.fetch(dest) verbose('Mono version: %s' % self.mono_package.version) self.RELEASE_VERSION = self.mono_package.version self.prefix = os.path.join( self.MONO_ROOT, "Versions", self.RELEASE_VERSION) if os.path.exists(self.prefix): error('Prefix %s exists, and may interfere with the staged build. Please remove and try again.' % self.prefix) self.calculate_updateid() trace(self.package_info('MDK')) self.dont_optimize = ['pixman'] for p in self.release_packages.values(): if p.name in self.dont_optimize: continue self.gcc_flags.extend(['-O2']) # THIS IS THE MAIN METHOD FOR MAKING A PACKAGE def package(self): self.fix_gtksharp_configs() self.verify_binaries() working = self.setup_working_dir() uninstall_script = os.path.join(working, "uninstallMono.sh") # make the MDK self.apply_blacklist(working, 'mdk_blacklist.sh') self.make_updateinfo(working, self.MDK_GUID) mdk_pkg = self.run_pkgbuild(working, "MDK") title(mdk_pkg) # self.make_dmg(mdk_dmg, title, mdk_pkg, uninstall_script) shutil.rmtree(working) def calculate_updateid(self): # Create the updateid pwd = os.getcwd() git_bin = self.bockbuild.git_bin trace("cur path is %s and git is %s" % (pwd, git_bin)) blame_rev_str = 'cd %s; %s blame configure.ac HEAD | grep AC_INIT | sed \'s/ .*//\' ' % ( self.mono_package.workspace, git_bin) blame_rev = backtick(blame_rev_str)[0] trace("Last commit to the version string %s" % (blame_rev)) version_number_str = 'cd %s; %s log %s..HEAD --oneline | wc -l | sed \'s/ //g\'' % ( self.mono_package.workspace, git_bin, blame_rev) self.BUILD_NUMBER = backtick(version_number_str)[0] trace("Calculating commit distance, %s" % (self.BUILD_NUMBER)) self.FULL_VERSION = self.RELEASE_VERSION + "." + self.BUILD_NUMBER os.chdir(pwd) parts = self.RELEASE_VERSION.split(".") version_list = (parts + ["0"] * (3 - len(parts)))[:4] for i in range(1, 3): version_list[i] = version_list[i].zfill(2) self.updateid = "".join(version_list) self.updateid += self.BUILD_NUMBER.replace( ".", "").zfill(9 - len(self.updateid)) trace(self.updateid) # creates and returns the path to a working directory containing: # PKGROOT/ - this root will be bundled into the .pkg and extracted at / # uninstallMono.sh - copied onto the DMG # Info{_sdk}.plist - used by packagemaker to make the installer # resources/ - other resources used by packagemaker for the installer def setup_working_dir(self): def make_package_symlinks(root): os.symlink(self.prefix, os.path.join(root, "Versions", "Current")) currentlink = os.path.join(self.MONO_ROOT, "Versions", "Current") links = [ ("bin", "Commands"), ("include", "Headers"), ("lib", "Libraries"), ("", "Home"), (os.path.join("lib", "libmono-2.0.dylib"), "Mono") ] for srcname, destname in links: src = os.path.join(currentlink, srcname) dest = os.path.join(root, destname) # If the symlink exists, we remove it so we can create a fresh # one if os.path.exists(dest): os.unlink(dest) os.symlink(src, dest) tmpdir = tempfile.mkdtemp() monoroot = os.path.join(tmpdir, "PKGROOT", self.MONO_ROOT[1:]) versions = os.path.join(monoroot, "Versions") os.makedirs(versions) print "Setting up temporary package directory:", tmpdir # setup metadata self.packaging_dir = os.path.join(self.path, "packaging") run_shell('rsync -aPq %s/* %s' % (self.packaging_dir, tmpdir), False) packages_list = string.join( [pkg.desc for pkg in self.release_packages.values()], "\\\n") deps_list = 'bockbuild (rev. %s)\\\n' % bockbuild.bockbuild_rev + string.join( [pkg.desc for pkg in self.toolchain_packages.values()], "\\\n") parameter_map = { '@@MONO_VERSION@@': self.RELEASE_VERSION, '@@MONO_RELEASE@@': self.BUILD_NUMBER, '@@MONO_VERSION_RELEASE@@': self.RELEASE_VERSION + '_' + self.BUILD_NUMBER, '@@MONO_CSDK_GUID@@': self.MDK_GUID, '@@MONO_VERSION_RELEASE_INT@@': self.updateid, '@@PACKAGES@@': packages_list, '@@DEP_PACKAGES@@': deps_list } for dirpath, d, files in os.walk(tmpdir): for name in files: if not name.startswith('.'): replace_in_file(os.path.join(dirpath, name), parameter_map) make_package_symlinks(monoroot) # copy to package root run_shell('rsync -aPq "%s"/* "%s/%s"' % (bockbuild.package_root, versions, self.RELEASE_VERSION), False) return tmpdir def apply_blacklist(self, working_dir, blacklist_name): print "Applying blacklist script:", blacklist_name blacklist = os.path.join(self.packaging_dir, blacklist_name) root = os.path.join(working_dir, "PKGROOT", self.prefix[1:]) run_shell('%s "%s" > /dev/null' % (blacklist, root), print_cmd=False) def run_pkgbuild(self, working_dir, package_type): print 'Running pkgbuild & productbuild...', info = self.package_info(package_type) output = os.path.join(self.resource_path, info["filename"]) identifier = "com.xamarin.mono-" + info["type"] + ".pkg" resources_dir = os.path.join(working_dir, "resources") distribution_xml = os.path.join(resources_dir, "distribution.xml") old_cwd = os.getcwd() os.chdir(working_dir) pkgbuild = "/usr/bin/pkgbuild" pkgbuild_cmd = ' '.join([pkgbuild, "--identifier " + identifier, "--root '%s/PKGROOT'" % working_dir, "--version '%s'" % self.RELEASE_VERSION, "--install-location '/'", "--scripts '%s'" % resources_dir, "--quiet", os.path.join(working_dir, "mono.pkg")]) run_shell(pkgbuild_cmd) productbuild = "/usr/bin/productbuild" productbuild_cmd = ' '.join([productbuild, "--resources %s" % resources_dir, "--distribution %s" % distribution_xml, "--package-path %s" % working_dir, "--quiet", output]) run_shell(productbuild_cmd) assert_exists(output) os.chdir(old_cwd) print output return output def make_updateinfo(self, working_dir, guid): updateinfo = os.path.join( working_dir, "PKGROOT", self.prefix[1:], "updateinfo") with open(updateinfo, "w") as updateinfo: updateinfo.write(guid + ' ' + self.updateid + "\n") def package_info(self, pkg_type): arch = self.bockbuild.cmd_options.arch arch_str = None if arch == "darwin-32": arch_str = "x86" elif arch == "darwin-64": arch_str = "x64" elif arch == "darwin-universal": arch_str = "universal" else: error ("Unknown architecture") if self.bockbuild.cmd_options.release_build: info = (pkg_type, self.FULL_VERSION, arch_str) else: info = (pkg_type, '%s-%s' % (git_shortid(self.bockbuild, self.mono_package.workspace), self.FULL_VERSION), arch_str) filename = "MonoFramework-%s-%s.macos10.xamarin.%s.pkg" % info return { "type": pkg_type, "filename": filename } def fix_line(self, line, matcher): def insert_install_root(matches): root = self.prefix captures = matches.groupdict() return 'target="%s"' % os.path.join(root, "lib", captures["lib"]) if matcher(line): pattern = r'target="(?P.+\.dylib)"' result = re.sub(pattern, insert_install_root, line) return result else: return line def fix_dllmap(self, config, matcher): handle, temp = tempfile.mkstemp() with open(config) as c: with open(temp, "w") as output: for line in c: output.write(self.fix_line(line, matcher)) os.rename(temp, config) os.system('chmod a+r %s' % config) def fix_gtksharp_configs(self): print 'Fixing GTK# configuration files...', count = 0 libs = [ 'atk-sharp', 'gdk-sharp', 'glade-sharp', 'glib-sharp', 'gtk-dotnet', 'gtk-sharp', 'pango-sharp' ] gac = os.path.join(bockbuild.package_root, "lib", "mono", "gac") confs = [glob.glob(os.path.join(gac, x, "*", "*.dll.config")) for x in libs] for c in itertools.chain(*confs): count = count + 1 self.fix_dllmap(c, lambda line: "dllmap" in line) print count def verify(self, f): result = " ".join(backtick("otool -L " + f)) regex = os.path.join(self.MONO_ROOT, "Versions", r"(\d+\.\d+\.\d+)") match = re.search(regex, result) if match is None: return token = match.group(1) trace(token) if self.RELEASE_VERSION not in token: raise Exception("%s references Mono %s\n%s" % (f, token, text)) def verify_binaries(self): bindir = os.path.join(bockbuild.package_root, "bin") for path, dirs, files in os.walk(bindir): for name in files: f = os.path.join(path, name) file_type = backtick('file "%s"' % f) if "Mach-O executable" in "".join(file_type): self.verify(f) def shell(self): envscript = '''#!/bin/sh PROFNAME="%s" INSTALLDIR="%s" ROOT="%s" export DYLD_FALLBACK_LIBRARY_PATH="$INSTALLDIR/lib:/lib:/usr/lib" export ACLOCAL_PATH="$INSTALLDIR/share/aclocal" export CONFIG_SITE="$INSTALLDIR/$PROFNAME-config.site" export MONO_GAC_PREFIX="$INSTALLDIR" export MONO_ADDINS_REGISTRY="$ROOT/addinreg" export MONO_INSTALL_PREFIX="$INSTALLDIR" export PS1="\[\e[1;3m\][$PROFNAME] \w @ " bash -i ''' % (self.profile_name, self.staged_prefix, self.root) path = os.path.join(self.root, self.profile_name + '.sh') with open(path, 'w') as f: f.write(envscript) os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) subprocess.call(['bash', '-c', path]) MonoReleaseProfile()