Fix the System.String.Replace throwing NotImplementedException (#20960) (#20978)
[mono-project.git] / packaging / MacSDK / profile.py
blob6fc5ac9c2dee37e65643111f1f734e3b0277155b
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',
51 # Mono
52 'mono',
53 'msbuild',
54 'pcl-reference-assemblies',
55 'libgdiplus',
56 'xsp',
57 'gtk-sharp',
58 'ironlangs',
59 'fsharp',
60 'mono-basic',
61 'nuget'
64 def attach (self, bockbuild):
65 self.min_version = 9
66 DarwinProfile.attach (self, bockbuild)
68 # quick disk space check (http://stackoverflow.com/questions/787776/)
69 s = os.statvfs(bockbuild.root)
70 free_space = (s.f_bavail * s.f_frsize) / (1024 * 1024 * 1024) # in GB
72 if free_space < 10:
73 error('Low disk space (less than 10GB), aborting')
75 # check for XQuartz installation (needed for libgdiplus)
76 if not os.path.exists('/opt/X11/include/X11/Xlib.h'):
77 error(
78 'XQuartz is required to be installed (download from http://xquartz.macosforge.org/) ')
80 self.MONO_ROOT = "/Library/Frameworks/Mono.framework"
81 self.BUILD_NUMBER = "0"
82 self.MDK_GUID = "964ebddd-1ffe-47e7-8128-5ce17ffffb05"
84 system_mono_dir = '/Library/Frameworks/Mono.framework/Versions/Current'
85 self.env.set('system_mono', os.path.join(
86 system_mono_dir, 'bin', 'mono'))
87 self.env.set('system_mcs', os.path.join(system_mono_dir, 'bin', 'mcs'))
89 self.env.set('system_mono_version', backtick(
90 '%s --version' % self.env.system_mono)[0])
92 # config overrides for some programs to be functional while staged
94 self.env.set('GDK_PIXBUF_MODULE_FILE',
95 '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache')
96 self.env.set('GDK_PIXBUF_MODULEDIR',
97 '%{staged_prefix}/lib/gdk-pixbuf-2.0/2.10.0/loaders')
98 self.env.set('PANGO_SYSCONFDIR', '%{staged_prefix}/etc')
99 self.env.set('PANGO_LIBDIR', '%{staged_prefix}/lib')
100 # self.env.set ('MONO_PATH', '%{staged_prefix}/lib/mono/4.0')
101 self.debug_info = ['gtk', 'cairo', 'glib',
102 'pango', 'mono', 'llvm', 'libgdiplus']
103 self.cache_host = None
105 def setup_release(self):
106 self.mono_package = self.release_packages['mono']
107 dest = os.path.join(self.bockbuild.build_root, self.mono_package.source_dir_name)
108 self.mono_package.fetch(dest)
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)
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)
119 self.calculate_updateid()
121 self.mono_package.custom_version_str = self.FULL_VERSION
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()
138 # make the MDK
139 self.apply_blacklist(working, 'mdk_blacklist.sh')
140 self.make_updateinfo(working, self.MDK_GUID)
141 mdk_pkg = self.run_pkgbuild(working, "MDK")
142 title(mdk_pkg)
144 shutil.rmtree(working)
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)
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)
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 # Info{_sdk}.plist - used by packagemaker to make the installer
174 # resources/ - other resources used by packagemaker for the installer
175 def setup_working_dir(self):
176 def make_package_symlinks(root):
177 os.symlink(self.prefix, os.path.join(root, "Versions", "Current"))
178 currentlink = os.path.join(self.MONO_ROOT, "Versions", "Current")
179 links = [
180 ("bin", "Commands"),
181 ("include", "Headers"),
182 ("lib", "Libraries"),
183 ("", "Home"),
184 (os.path.join("lib", "libmono-2.0.dylib"), "Mono")
186 for srcname, destname in links:
187 src = os.path.join(currentlink, srcname)
188 dest = os.path.join(root, destname)
189 # If the symlink exists, we remove it so we can create a fresh
190 # one
191 if os.path.exists(dest):
192 os.unlink(dest)
193 os.symlink(src, dest)
195 tmpdir = tempfile.mkdtemp()
196 monoroot = os.path.join(tmpdir, "PKGROOT", self.MONO_ROOT[1:])
197 versions = os.path.join(monoroot, "Versions")
198 os.makedirs(versions)
200 print "Setting up temporary package directory:", tmpdir
202 # setup metadata
203 self.packaging_dir = os.path.join(self.directory, "packaging")
204 run_shell('rsync -aPq %s/* %s' % (self.packaging_dir, tmpdir), False)
206 packages_list = string.join(
207 [pkg.desc for pkg in self.release_packages.values()], "\\\n")
208 deps_list = 'bockbuild (rev. %s)\\\n' % bockbuild.bockbuild_rev + string.join(
209 [pkg.desc for pkg in self.toolchain_packages.values()], "\\\n")
211 parameter_map = {
212 '@@MONO_VERSION@@': self.RELEASE_VERSION,
213 '@@MONO_RELEASE@@': self.BUILD_NUMBER,
214 '@@MONO_VERSION_RELEASE@@': self.RELEASE_VERSION + '_' + self.BUILD_NUMBER,
215 '@@MONO_CSDK_GUID@@': self.MDK_GUID,
216 '@@MONO_VERSION_RELEASE_INT@@': self.updateid,
217 '@@PACKAGES@@': packages_list,
218 '@@DEP_PACKAGES@@': deps_list
220 for dirpath, d, files in os.walk(tmpdir):
221 for name in files:
222 if not name.startswith('.'):
223 replace_in_file(os.path.join(dirpath, name), parameter_map)
225 make_package_symlinks(monoroot)
227 # copy to package root
228 run_shell('rsync -aPq "%s"/* "%s/%s"' %
229 (bockbuild.package_root, versions, self.RELEASE_VERSION), False)
231 return tmpdir
233 def apply_blacklist(self, working_dir, blacklist_name):
234 print "Applying blacklist script:", blacklist_name
235 blacklist = os.path.join(self.packaging_dir, blacklist_name)
236 root = os.path.join(working_dir, "PKGROOT", self.prefix[1:])
237 run_shell('%s "%s" > /dev/null' % (blacklist, root), print_cmd=False)
239 def run_pkgbuild(self, working_dir, package_type):
240 print 'Running pkgbuild & productbuild...',
241 info = self.package_info(package_type)
242 output = os.path.join(self.directory, info["filename"])
243 identifier = "com.xamarin.mono-" + info["type"] + ".pkg"
244 resources_dir = os.path.join(working_dir, "resources")
245 distribution_xml = os.path.join(resources_dir, "distribution.xml")
247 old_cwd = os.getcwd()
248 os.chdir(working_dir)
249 pkgbuild = "/usr/bin/pkgbuild"
250 pkgbuild_cmd = ' '.join([pkgbuild,
251 "--identifier " + identifier,
252 "--root '%s/PKGROOT'" % working_dir,
253 "--version '%s'" % self.RELEASE_VERSION,
254 "--install-location '/'",
255 "--scripts '%s'" % resources_dir,
256 "--quiet",
257 os.path.join(working_dir, "mono.pkg")])
259 run_shell(pkgbuild_cmd)
261 productbuild = "/usr/bin/productbuild"
262 productbuild_cmd = ' '.join([productbuild,
263 "--resources %s" % resources_dir,
264 "--distribution %s" % distribution_xml,
265 "--package-path %s" % working_dir,
266 "--quiet",
267 output])
269 run_shell(productbuild_cmd)
271 assert_exists(output)
272 os.chdir(old_cwd)
273 print output
274 return output
276 def make_updateinfo(self, working_dir, guid):
277 updateinfo = os.path.join(
278 working_dir, "PKGROOT", self.prefix[1:], "updateinfo")
279 with open(updateinfo, "w") as updateinfo:
280 updateinfo.write(guid + ' ' + self.updateid + "\n")
281 version_file = os.path.join(
282 working_dir, "PKGROOT", self.prefix[1:], "VERSION")
283 with open(version_file, "w") as version_file:
284 version_file.write(self.FULL_VERSION + "\n")
286 def package_info(self, pkg_type):
287 arch = self.bockbuild.cmd_options.arch
288 arch_str = None
289 if arch == "darwin-32":
290 arch_str = "x86"
291 elif arch == "darwin-64":
292 arch_str = "x64"
293 elif arch == "darwin-universal":
294 arch_str = "universal"
295 else:
296 error ("Unknown architecture")
298 if self.bockbuild.cmd_options.release_build:
299 info = (pkg_type, self.FULL_VERSION, arch_str)
300 else:
301 info = (pkg_type, '%s-%s' % (git_shortid(self.bockbuild,
302 self.mono_package.workspace), self.FULL_VERSION), arch_str)
304 filename = "MonoFramework-%s-%s.macos10.xamarin.%s.pkg" % info
305 return {
306 "type": pkg_type,
307 "filename": filename
310 def fix_line(self, line, matcher):
311 def insert_install_root(matches):
312 root = self.prefix
313 captures = matches.groupdict()
314 return 'target="%s"' % os.path.join(root, "lib", captures["lib"])
316 if matcher(line):
317 pattern = r'target="(?P<lib>.+\.dylib)"'
318 result = re.sub(pattern, insert_install_root, line)
319 return result
320 else:
321 return line
323 def fix_dllmap(self, config, matcher):
324 handle, temp = tempfile.mkstemp()
325 with open(config) as c:
326 with open(temp, "w") as output:
327 for line in c:
328 output.write(self.fix_line(line, matcher))
329 os.rename(temp, config)
330 os.system('chmod a+r %s' % config)
332 def fix_gtksharp_configs(self):
333 print 'Fixing GTK# configuration files...',
334 count = 0
335 libs = [
336 'atk-sharp',
337 'gdk-sharp',
338 'glade-sharp',
339 'glib-sharp',
340 'gtk-dotnet',
341 'gtk-sharp',
342 'pango-sharp'
344 gac = os.path.join(bockbuild.package_root, "lib", "mono", "gac")
345 confs = [glob.glob(os.path.join(gac, x, "*", "*.dll.config")) for x in libs]
346 for c in itertools.chain(*confs):
347 count = count + 1
348 self.fix_dllmap(c, lambda line: "dllmap" in line)
349 print count
351 def verify(self, f):
352 result = " ".join(backtick("otool -L " + f))
353 regex = os.path.join(self.MONO_ROOT, "Versions", r"(\d+\.\d+\.\d+)")
355 match = re.search(regex, result)
356 if match is None:
357 return
358 token = match.group(1)
359 trace(token)
360 if self.RELEASE_VERSION not in token:
361 raise Exception("%s references Mono %s\n%s" % (f, token, text))
363 def verify_binaries(self):
364 bindir = os.path.join(bockbuild.package_root, "bin")
365 for path, dirs, files in os.walk(bindir):
366 for name in files:
367 f = os.path.join(path, name)
368 file_type = backtick('file "%s"' % f)
369 if "Mach-O executable" in "".join(file_type):
370 self.verify(f)
372 def shell(self):
373 envscript = '''#!/bin/sh
374 PROFNAME="%s"
375 INSTALLDIR="%s"
376 ROOT="%s"
377 export DYLD_FALLBACK_LIBRARY_PATH="$INSTALLDIR/lib:/lib:/usr/lib"
378 export ACLOCAL_PATH="$INSTALLDIR/share/aclocal"
379 export CONFIG_SITE="$INSTALLDIR/$PROFNAME-config.site"
380 export MONO_GAC_PREFIX="$INSTALLDIR"
381 export MONO_ADDINS_REGISTRY="$ROOT/addinreg"
382 export MONO_INSTALL_PREFIX="$INSTALLDIR"
384 export PS1="\[\e[1;3m\][$PROFNAME] \w @ "
385 bash -i
386 ''' % (self.profile_name, self.staged_prefix, self.root)
388 path = os.path.join(self.root, self.profile_name + '.sh')
390 with open(path, 'w') as f:
391 f.write(envscript)
393 os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
395 subprocess.call(['bash', '-c', path])
397 MonoReleaseProfile()