示例#1
0
		static PlistDocument LoadSdkSettings (IPhoneSdkVersion sdk)
		{
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile ("/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS" + sdk.ToString ()
			                     + ".sdk/SDKSettings.plist");
			return doc;
		}
示例#2
0
		public ProjectFile GetInfoPlist ()
		{
			var name = BaseDirectory.Combine ("Info.plist");
			var pf = Files.GetFile (name);
			if (pf != null)
				return pf;
			
			var doc = new PlistDocument ();
			doc.Root = new PlistDictionary ();
			doc.WriteToFile (name);
			return AddFile (name);
		}
示例#3
0
		static PlistDocument LoadDTSettings ()
		{
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile ("/Developer/Platforms/iPhoneOS.platform/Info.plist");
			return doc;
		}
		public static BuildResult CreateMergedPlist (IProgressMonitor monitor, 
			ProjectFile template, string outPath,
			Func<PlistDocument,BuildResult> merge)
		{
			var result = new BuildResult ();
			
			var doc = new PlistDocument ();
			if (template != null) {
				try {
					doc.LoadFromXmlFile (template.FilePath);
				} catch (Exception ex) {
					if (ex is XmlException)
						result.AddError (template.FilePath, ((XmlException)ex).LineNumber,
						                 ((XmlException)ex).LinePosition, null, ex.Message);
					else
						result.AddError (template.FilePath, 0, 0, null, ex.Message);
					monitor.ReportError (GettextCatalog.GetString ("Could not load file '{0}': {1}",
					                                               template.FilePath, ex.Message), null);
					return result;
				}
			}
			
			try {
				if (result.Append (merge (doc)).ErrorCount > 0)
					return result;
			} catch (Exception ex) {
				result.AddError ("Error merging Info.plist: " + ex.Message);
				LoggingService.LogError ("Error merging Info.plist", ex);
				return result;
			}
			
			try {
				EnsureDirectoryForFile (outPath);
				doc.WriteToFile (outPath);
			} catch (Exception ex) {
				result.AddError (outPath, 0, 0, null, ex.Message);
				monitor.ReportError (GettextCatalog.GetString ("Could not write file '{0}'", outPath), ex);
			}
			return result;
		}
示例#5
0
		public static bool BuildPackage (IProgressMonitor monitor, MonoMacProject project,
			ConfigurationSelector conf, MonoMacPackagingSettings settings, FilePath target)
		{
			string bundleKey = settings.BundleSigningKey;
			string packageKey = settings.PackageSigningKey;
			
			if (settings.SignBundle || (settings.CreatePackage && settings.SignPackage)) {
				var identities = Keychain.GetAllSigningIdentities ();
				
				if (string.IsNullOrEmpty (bundleKey)) {
					bundleKey = identities.FirstOrDefault (k => k.StartsWith (MonoMacPackagingSettingsWidget.APPLICATION_PREFIX));
					if (string.IsNullOrEmpty (bundleKey)) {
						monitor.ReportError ("Did not find default app signing key", null);
						return false;
					} else if (!identities.Any (k => k == bundleKey)) {
						monitor.ReportError ("Did not find app signing key in keychain", null);
						return false;
					}
				}
				
				if (string.IsNullOrEmpty (packageKey)) {
					packageKey = identities.FirstOrDefault (k => k.StartsWith (MonoMacPackagingSettingsWidget.INSTALLER_PREFIX));
					if (string.IsNullOrEmpty (packageKey)) {
						monitor.ReportError ("Did not find default package signing key", null);
						return false;
					} else if (!identities.Any (k => k == packageKey)) {
						monitor.ReportError ("Did not find package signing key in keychain", null);
						return false;
					}
				}
			}
			
			if (project.NeedsBuilding (conf)) {
				BuildResult res = project.Build (monitor, conf);
				if (res.ErrorCount > 0) {
					foreach (BuildError e in res.Errors)
						monitor.ReportError (e.ToString (), null);
					monitor.ReportError (GettextCatalog.GetString ("The project failed to build."), null);
					return false;
				}
			}
			
			var cfg = (MonoMacProjectConfiguration) project.GetConfiguration (conf);
			
			FilePath tempDir = "/tmp/monomac-build-" + DateTime.Now.Ticks;
			FilePath workingApp = tempDir.Combine (cfg.AppDirectory.FileName);
			
			try {
				//user will have agreed to overwrite when they picked the target
				if (Directory.Exists (target))
					Directory.Delete (target, true);
				else if (File.Exists (target))
					File.Delete (target);
				
				monitor.BeginTask (GettextCatalog.GetString ("Creating app bundle"), 0);
				var files = Directory.GetFiles (cfg.AppDirectory, "*", SearchOption.AllDirectories);
				HashSet<string> createdDirs = new HashSet<string> ();
				foreach (FilePath f in files) {
					var rel = f.ToRelative (cfg.AppDirectory);
					var parentDir = rel.ParentDirectory;
					if (settings.IncludeMono) {
						if (parentDir.IsNullOrEmpty || parentDir == "." || parentDir == "Contents/MacOS")
							continue;
						var ext = rel.Extension;
						if (ext == ".mdb" || ext == ".exe" || ext == ".dll")
							continue;
					}
					if (monitor.IsCancelRequested)
						return false;
					if (createdDirs.Add (parentDir))
						Directory.CreateDirectory (workingApp.Combine (parentDir));
					monitor.Log.WriteLine (rel);
					File.Copy (f, workingApp.Combine (rel));
				}
				monitor.EndTask ();
				
				if (settings.IncludeMono) {
					monitor.BeginTask (GettextCatalog.GetString ("Merging Mono into app bundle"), 0);
					
					var args = new ProcessArgumentBuilder ();
					switch (settings.LinkerMode){
					case MonoMacLinkerMode.LinkNone:
						args.Add ("--nolink");
						break;
						
					case MonoMacLinkerMode.LinkFramework:
						args.Add ("--linksdkonly");
						break;
						
					case MonoMacLinkerMode.LinkAll:
						// nothing
						break;
					}
					
					args.Add ("-o");
					args.AddQuoted (tempDir);
					args.Add ("-n");
					args.AddQuoted (cfg.AppName);
					
					var assemblies = project.GetReferencedAssemblies (conf, true);
					foreach (var a in assemblies) {
						args.Add ("-a");
						args.AddQuoted (a);
					}
					args.AddQuoted (cfg.CompiledOutputName);
					
					string mmpPath = Mono.Addins.AddinManager.CurrentAddin.GetFilePath ("mmp");
					
					//FIXME: workaround for Mono.Addins losing the executable bit during packaging
					var mmpInfo = new Mono.Unix.UnixFileInfo (mmpPath);
					if ((mmpInfo.FileAccessPermissions & Mono.Unix.FileAccessPermissions.UserExecute) == 0)
						mmpInfo.FileAccessPermissions |=  Mono.Unix.FileAccessPermissions.UserExecute;
					
					var psi = new ProcessStartInfo (mmpPath, args.ToString ());
					if (MacBuildUtilities.ExecuteBuildCommand (monitor, psi) != 0) {
						monitor.ReportError ("Merging Mono failed", null);
						return false;
					}
					
					var plistFile = workingApp.Combine ("Contents", "Info.plist");
					var plistDoc = new PlistDocument ();
					plistDoc.LoadFromXmlFile (plistFile);
					((PlistDictionary)plistDoc.Root)["MonoBundleExecutable"] = cfg.CompiledOutputName.FileName;
					plistDoc.WriteToFile (plistFile);
					
					monitor.EndTask ();
				}
				
				//TODO: verify bundle details if for app store?
					
				if (settings.SignBundle) {
					monitor.BeginTask (GettextCatalog.GetString ("Signing app bundle"), 0);

					// Sign any dynamic libraries, before we sign the bundle
					var dylibs = Directory.GetFiles (workingApp, "*.dylib", SearchOption.AllDirectories);
					foreach (var dylib in dylibs){
						if (!Sign (monitor, bundleKey, dylib))
							return false;
					}
					
					if (!Sign (monitor, bundleKey, workingApp))
						return false;
					
					monitor.EndTask ();
				}
				
				if (settings.CreatePackage) {
					monitor.BeginTask (GettextCatalog.GetString ("Creating installer"), 0);
					
					var args = new ProcessArgumentBuilder ();
					args.Add ("--component");
					args.AddQuoted (workingApp);
					args.Add ("/Applications");
					if (settings.SignPackage) {
						args.Add ("--sign");
						args.AddQuoted (packageKey);
					}
					if (!settings.ProductDefinition.IsNullOrEmpty) {
						args.Add ("--product");
						args.AddQuoted (settings.ProductDefinition);
					}
					args.AddQuoted (target);
					
					var psi = new ProcessStartInfo ("productbuild", args.ToString ());
					try {
						if (MacBuildUtilities.ExecuteBuildCommand (monitor, psi) != 0) {
							monitor.ReportError ("Package creation failed", null);
							return false;
						}
					} catch (System.ComponentModel.Win32Exception) {
						monitor.ReportError ("productbuild not found", null);
						return false;
					}
					monitor.EndTask ();
				} else {
					Directory.Move (workingApp, target);
				}
			} finally {
				try {
					if (Directory.Exists (tempDir))
						Directory.Delete (tempDir, true);
				} catch (Exception ex) {
					LoggingService.LogError ("Error removing temp directory", ex);
				}
			}
			
			return true;
		}
示例#6
0
		static string GrabRootString (string file, string key)
		{
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile (file);
			return ((PlistString) ((PlistDictionary)doc.Root)[key]).Value;
		}
示例#7
0
		public static DTSdkSettings GetSdkSettings (IPhoneSdkVersion sdk)
		{
			DTSdkSettings settings;
			if (sdkSettingsCache.TryGetValue (sdk.ToString (), out settings))
				return settings;
			
			settings = new DTSdkSettings ();
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile (GetSdkPlistFilename (sdk.ToString ()));
			var dict = (PlistDictionary) doc.Root;
			
			settings.AlternateSDK = ((PlistString)dict["AlternateSDK"]).Value;
			settings.CanonicalName = ((PlistString)dict["CanonicalName"]).Value;
			var props = (PlistDictionary) dict["DefaultProperties"];
			settings.DTCompiler = ((PlistString)props["GCC_VERSION"]).Value;
			
			var sdkPath = GetSdkPath (sdk.ToString ());
			var file = sdkPath + "/System/Library/CoreServices/SystemVersion.plist";
			settings.DTPlatformBuild = GrabRootString (file, "ProductBuildVersion");
			
			sdkSettingsCache[sdk.ToString ()] = settings;
			return settings;
		}
		public void Store (IPhoneProject proj)
		{
			proj.BundleDevelopmentRegion = NullIfEmpty (devRegionEntry.Text);
			proj.BundleIdentifier = NullIfEmpty (bundleIdEntry.Text);
			proj.BundleVersion = NullIfEmpty (bundleVersionEntry.Text);
			proj.BundleDisplayName = NullIfEmpty (displayNameEntry.Text);
			proj.MainNibFile = NullIfEmpty (mainNibPicker.SelectedFile);
			proj.MainNibFileIPad = NullIfEmpty (iPadNibPicker.SelectedFile);
			
			proj.BundleIcon = NullIfEmpty (iphoneIconPicker.SelectedFile);
			proj.BundleIconHigh = NullIfEmpty (iphoneIconHighPicker.SelectedFile);
			proj.BundleIconIPad = NullIfEmpty (ipadIconPicker.SelectedFile);
			proj.BundleIconSpotlight = NullIfEmpty (settingsIconPicker.SelectedFile);
			proj.BundleIconSpotlightHigh = NullIfEmpty (settingsIconHighPicker.SelectedFile);
			proj.BundleIconIPadSpotlight = NullIfEmpty (ipadSpotlightIconPicker.SelectedFile);
			
			proj.SupportedDevices = SupportedDevices;
			
			if (badPlist)
				return;
			try {
				var pf = proj.GetInfoPlist ();
				var doc = new PlistDocument ();
				doc.LoadFromXmlFile (pf.FilePath);
				var dict = doc.Root as PlistDictionary;
				if (dict == null)
					doc.Root = dict = new PlistDictionary ();
				
				var orientations = SaveOrientationsCombo (supportedOrientationsCombo);
				if (orientations != null)
					dict [OrientationUtil.KEY] = orientations;
				else
					dict.Remove (OrientationUtil.KEY);
				
				var iPadOrientations = SaveOrientationsCombo (iPadOrientationsCombo);
				if (proj.SupportedDevices == TargetDevice.IPhoneAndIPad && iPadOrientations != null)
					dict [OrientationUtil.KEY_IPAD] = iPadOrientations;
				else
					dict.Remove (OrientationUtil.KEY_IPAD);
				
				doc.WriteToFile (pf.FilePath);
			} catch (Exception ex) {
				badPlist = true;
				MonoDevelop.Ide.MessageService.ShowException (ex, "Error saving Info.plist.");
			}
		}
示例#9
0
		public static DTSettings GetDTSettings ()
		{
			if (dtSettings != null)
				return dtSettings;
			
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile (PLAT_PLIST);
			var dict = (PlistDictionary) doc.Root;
			var infos = (PlistDictionary) dict["AdditionalInfo"];
			var vals = new DTSettings ();
			
			vals.DTPlatformVersion = ((PlistString)infos["DTPlatformVersion"]).Value;
			
			var xcodeVersion = GrabRootString ("/Developer/Applications/Xcode.app/Contents/Info.plist", "CFBundleShortVersionString");
			vals.DTXcode = "0" + xcodeVersion.Replace (".", "");
			
			vals.DTXcodeBuild = GrabRootString ("/Developer/Library/version.plist", "ProductBuildVersion");
			
			return (dtSettings = vals);
		}
		public void Load (IPhoneProject proj)
		{
			devRegionEntry.Text = proj.BundleDevelopmentRegion ?? "";
			bundleIdEntry.Text = proj.BundleIdentifier ?? "";
			bundleVersionEntry.Text = proj.BundleVersion ?? "";
			displayNameEntry.Text = proj.BundleDisplayName ?? "";
			
			mainNibPicker.Project         = iPadNibPicker.Project         = proj;
			mainNibPicker.EntryIsEditable = iPadNibPicker.EntryIsEditable = true;
			mainNibPicker.DefaultFilter   = iPadNibPicker.DefaultFilter   = "*.xib";
			
			mainNibPicker.DialogTitle = GettextCatalog.GetString ("Select main interface file...");
			mainNibPicker.SelectedFile = proj.MainNibFile.ToString () ?? "";
			
			iPadNibPicker.DialogTitle = GettextCatalog.GetString ("Select iPad interface file...");
			iPadNibPicker.SelectedFile = proj.MainNibFileIPad.ToString () ?? "";
			
			targetDevicesCombo.AppendText (GettextCatalog.GetString ("iPhone and iPad"));
			targetDevicesCombo.AppendText (GettextCatalog.GetString ("iPhone only"));
			targetDevicesCombo.AppendText (GettextCatalog.GetString ("iPad only"));
			
			SupportedDevices = proj.SupportedDevices;
			
			ProjectFileEntry [] pickers = {
				iphoneIconPicker,
				iphoneIconHighPicker,
				ipadIconPicker,
				settingsIconPicker,
				settingsIconHighPicker,
				ipadSpotlightIconPicker,
			};
			
			foreach (var p in pickers) {
				p.Project = proj;
				p.DefaultFilter = "*.png";
				p.EntryIsEditable = true;
				p.DialogTitle = GettextCatalog.GetString ("Select icon...");
			}
			
			iphoneIconPicker.SelectedFile = proj.BundleIcon.ToString () ?? "";
			iphoneIconHighPicker.SelectedFile = proj.BundleIconHigh.ToString () ?? "";
			ipadIconPicker.SelectedFile = proj.BundleIconIPad.ToString () ?? "";
			settingsIconPicker.SelectedFile = proj.BundleIconSpotlight.ToString () ?? "";
			settingsIconHighPicker.SelectedFile = proj.BundleIconSpotlightHigh.ToString () ?? "";
			ipadSpotlightIconPicker.SelectedFile = proj.BundleIconIPadSpotlight.ToString () ?? "";
			
			badPlist = false;
			try {
				var pf = proj.GetInfoPlist ();
				var doc = new PlistDocument ();
				doc.LoadFromXmlFile (pf.FilePath);
				var dict = doc.Root as PlistDictionary;
				if (dict == null)
					doc.Root = dict = new PlistDictionary ();
				
				var orientationArr = dict.TryGetValue (OrientationUtil.KEY) as PlistArray;
				var ipadOrientationArr = dict.TryGetValue (OrientationUtil.KEY_IPAD) as PlistArray;
				
				LoadOrientationsCombo (supportedOrientationsCombo, orientationArr);
				LoadOrientationsCombo (iPadOrientationsCombo, ipadOrientationArr);
			} catch (Exception ex) {
				badPlist = true;
				MonoDevelop.Ide.MessageService.ShowException (ex, "Error reading Info.plist. Some settings may not be saved.");
			}
			
			HandleTargetDevicesComboChanged (null, null);
		}
示例#11
0
		public static DTSettings GetDTSettings ()
		{
			if (dtSettings != null)
				return dtSettings;
			
			var doc = new PlistDocument ();
			doc.LoadFromXmlFile (PLAT_PLIST);
			var dict = (PlistDictionary) doc.Root;
			var infos = (PlistDictionary) dict["AdditionalInfo"];
			var vals = new DTSettings ();
			
			vals.DTPlatformVersion = ((PlistString)infos["DTPlatformVersion"]).Value;
			
			var pool = SendMessage (GetClass ("NSAutoreleasePool"), GetSelector ("new"));
			var bundle = SendMessage (GetClass ("NSString"), GetSelector ("stringWithUTF8String:"), "CFBundleShortVersionString");
			var plist = SendMessage (GetClass ("NSString"), GetSelector ("stringWithUTF8String:"), "/Developer/Applications/Xcode.app/Contents/Info.plist");
			var data = SendMessage (GetClass ("NSDictionary"), GetSelector ("dictionaryWithContentsOfFile:"), plist);
			var val = SendMessage (data, GetSelector ("objectForKey:"), bundle);

			var xcodeVersion = Marshal.PtrToStringAuto (SendMessage (val, GetSelector ("UTF8String")));

			SendMessage (pool, GetSelector ("release"));

			vals.DTXcode = "0" + xcodeVersion.Replace (".", "");
			
			vals.DTXcodeBuild = GrabRootString (VERSION_PLIST, "ProductBuildVersion");
			
			return (dtSettings = vals);
		}
示例#12
0
		static void WriteXcent (PlistDocument doc, string file)
		{
			//write the plist to a byte[] as UTF8 without a BOM
			var ms = new MemoryStream ();
			var xmlSettings = new XmlWriterSettings () {
				Encoding = new UTF8Encoding (false), //no BOM
				CloseOutput = false,
				Indent = true,
				IndentChars = "\t",
				NewLineChars = "\n",
			};
			using (var writer = XmlTextWriter.Create (ms, xmlSettings))
				doc.Write (writer);
			
			//HACK: workaround for bug in Apple's entitlements XML parser
			//having written to a UTF8 stream to convince the xmlwriter to do the right thing,
			//we now convert to string and back to do some substitutions to work around bugs
			//in Apple's braindead entitlements XML parser.
			//Specifically, it chokes on "<true />" but accepts "<true/>"
			//Hence, to be on the safe side, we produce EXACTLY the same format
			var sb = new StringBuilder (Encoding.UTF8.GetString (ms.GetBuffer ()));
			sb.Replace ("-//Apple Computer//DTD PLIST 1.0//EN", "-//Apple//DTD PLIST 1.0//EN");
			sb.Replace ("<?xml version=\"1.0\" encoding=\"utf-8\"?>", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			sb.Replace ("\n\t", "\n");
			sb.Replace (" />\n", "/>\n");
			sb.Append ("\n");
			var buf = Encoding.UTF8.GetBytes (sb.ToString ());
			
			//write the xcent file with the magic header, length, and the plist
			byte[] magic = new byte[] { 0xfa, 0xde, 0x71, 0x71 };
			byte[] fileLen = Mono.DataConverter.BigEndian.GetBytes ((uint)buf.Length + 8); // 8 = magic.length + magicLen.Length
			using (var fs = File.Open (file, FileMode.Create)) {
				fs.Write (magic, 0, magic.Length);
				fs.Write (fileLen, 0, fileLen.Length);
				fs.Write (buf, 0, (int)buf.Length);
			}
		}
示例#13
0
		static BuildResult GenXcent (IProgressMonitor monitor, IPhoneSdkVersion sdkVersion, IPhoneProject proj, 
			IPhoneProjectConfiguration conf, IPhoneAppIdentity identity, out string xcentName)
		{
			xcentName = conf.CompiledOutputName.ChangeExtension (".xcent");
			
			monitor.BeginTask (GettextCatalog.GetString ("Processing entitlements file"), 0);
			
			string srcFile;
			
			if (!string.IsNullOrEmpty (conf.CodesignEntitlements)) {
				if (!File.Exists (conf.CodesignEntitlements))
					return BuildError ("Entitlements file \"" + conf.CodesignEntitlements + "\" not found.");
				srcFile = conf.CodesignEntitlements;
			} else {
				srcFile = "/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS" + sdkVersion.ToString ()
					+ ".sdk/Entitlements.plist";
			}
			
			var doc = new PlistDocument ();
			try {
				doc.LoadFromXmlFile (srcFile);
			} catch (Exception ex) {
				monitor.Log.WriteLine (ex.ToString ());
				return BuildError ("Error loading entitlements source file '" + srcFile +"'.");
			}
			
			//insert the app ID into the plist at the beginning
			var oldDict = doc.Root as PlistDictionary;
			var newDict = new PlistDictionary ();
			doc.Root = newDict;
			newDict["application-identifier"] = identity.AppID;
			var keychainGroups = new PlistArray (new [] { identity.AppID } );
			newDict["keychain-access-groups"] = keychainGroups;
			
			//merge in the user's values
			foreach (var item in oldDict) {
				//FIXME: we currently ignore these items, and write our own, but maybe we should do substitutes
				//i.e. $(AppIdentifierPrefix)$(CFBundleIdentifier)
				if (item.Key == "application-identifier") {
					var str = item.Value as PlistString;
					if (str == null || string.IsNullOrEmpty (str.Value) || str.Value.Contains ('$'))
						continue;
				} else if (item.Key == "keychain-access-groups") {
					//special handling, merge into the array
					var keyArr = item.Value as PlistArray;
					foreach (var key in keyArr) {
						var str = key as PlistString;
						if (str != null && !string.IsNullOrEmpty (str.Value) && !str.Value.Contains ('$')) {
							keychainGroups.Add (str.Value);
						}
					}
					continue;
				}
				newDict[item.Key] = item.Value;
			}
			
			//merge in the settings from the provisioning profile, skipping some
			foreach (var item in identity.Profile.Entitlements)
				if (item.Key != "application-identifier" && item.Key != "keychain-access-groups")
					newDict[item.Key] = item.Value;
			
			try {
				WriteXcent (doc, xcentName);
			} catch (Exception ex) {
				monitor.Log.WriteLine (ex.ToString ());
				return BuildError ("Error writing entitlements file '" + xcentName +"'.");
			}
			
			monitor.EndTask ();
			return null;
		}
示例#14
0
		static bool BuildPackage (IProgressMonitor monitor, MonoMacProject project,
			ConfigurationSelector conf, MonoMacPackagingSettings settings, FilePath target)
		{
			string bundleKey = settings.BundleSigningKey;
			string packageKey = settings.PackageSigningKey;
			
			if (settings.SignBundle || settings.SignPackage) {
				var identities = Keychain.GetAllSigningIdentities ();
				
				if (string.IsNullOrEmpty (bundleKey)) {
					bundleKey = identities.FirstOrDefault (k => k.StartsWith (MonoMacPackagingSettingsWidget.APPLICATION_PREFIX));
					if (string.IsNullOrEmpty (bundleKey)) {
						monitor.ReportError ("Did not find default app signing key", null);
						return false;
					} else if (!identities.Any (k => k == bundleKey)) {
						monitor.ReportError ("Did not find app signing key in keychain", null);
						return false;
					}
				}
				
				if (string.IsNullOrEmpty (packageKey)) {
					packageKey = identities.FirstOrDefault (k => k.StartsWith (MonoMacPackagingSettingsWidget.INSTALLER_PREFIX));
					if (string.IsNullOrEmpty (packageKey)) {
						monitor.ReportError ("Did not find default package signing key", null);
						return false;
					} else if (!identities.Any (k => k == packageKey)) {
						monitor.ReportError ("Did not find package signing key in keychain", null);
						return false;
					}
				}
			}
			
			if (project.NeedsBuilding (conf)) {
				BuildResult res = project.Build (monitor, conf);
				if (res.ErrorCount > 0) {
					foreach (BuildError e in res.Errors)
						monitor.ReportError (e.ToString (), null);
					monitor.ReportError (GettextCatalog.GetString ("The project failed to build."), null);
					return false;
				}
			}
			
			var cfg = (MonoMacProjectConfiguration) project.GetConfiguration (conf);
			
			FilePath tempDir = "/tmp/monomac-build-" + DateTime.Now.Ticks;
			FilePath workingApp = tempDir.Combine (cfg.AppDirectory.FileName);
			
			try {
				monitor.BeginTask (GettextCatalog.GetString ("Creating app bundle"), 0);
				var files = Directory.GetFiles (cfg.AppDirectory, "*", SearchOption.AllDirectories);
				HashSet<string> createdDirs = new HashSet<string> ();
				foreach (FilePath f in files) {
					var rel = f.ToRelative (cfg.AppDirectory);
					var parentDir = rel.ParentDirectory;
					if (settings.IncludeMono) {
						if (parentDir.IsNullOrEmpty || parentDir == "." || parentDir == "Contents/MacOS")
							continue;
						var ext = rel.Extension;
						if (ext == ".mdb" || ext == ".exe" || ext == ".dll")
							continue;
					}
					if (monitor.IsCancelRequested)
						return false;
					if (createdDirs.Add (parentDir))
						Directory.CreateDirectory (workingApp.Combine (parentDir));
					monitor.Log.WriteLine (rel);
					File.Copy (f, workingApp.Combine (rel));
				}
				monitor.EndTask ();
				
				if (settings.IncludeMono) {
					monitor.BeginTask (GettextCatalog.GetString ("Merging Mono into app bundle"), 0);
					
					var args = new ProcessArgumentBuilder ();
					args.Add ("-o");
					args.AddQuoted (tempDir);
					args.Add ("-n");
					args.AddQuoted (cfg.AppName);
					
					var assemblies = project.GetReferencedAssemblies (conf, true);
					foreach (var a in assemblies) {
						args.Add ("-a");
						args.AddQuoted (a);
					}
					args.AddQuoted (cfg.CompiledOutputName);
					
					string mmpPath = Mono.Addins.AddinManager.CurrentAddin.GetFilePath ("mmp");
					var psi = new ProcessStartInfo (mmpPath, args.ToString ());
					monitor.Log.WriteLine ("mmp " + psi.Arguments);
					
					string err;
					if (MacBuildUtilities.ExecuteCommand (monitor, psi, out err) != 0) {
						monitor.Log.WriteLine (err);
						monitor.ReportError ("Merging Mono failed", null);
						return false;
					}
					
					var plistFile = workingApp.Combine ("Contents", "Info.plist");
					var plistDoc = new PlistDocument ();
					plistDoc.LoadFromXmlFile (plistFile);
					((PlistDictionary)plistDoc.Root)["MonoBundleExecutable"] = cfg.CompiledOutputName.FileName;
					plistDoc.WriteToFile (plistFile);
					
					monitor.EndTask ();
				}
				
				//TODO: verify bundle details if for app store?
					
				if (settings.SignBundle) {
					monitor.BeginTask (GettextCatalog.GetString ("Signing app bundle"), 0);
					
					var args = new ProcessArgumentBuilder ();
					args.Add ("-v", "-f", "-s");
					args.AddQuoted (bundleKey, workingApp);
					
					var psi = new ProcessStartInfo ("codesign", args.ToString ());
					monitor.Log.WriteLine ("codesign " + psi.Arguments);
					
					string err;
					if (MacBuildUtilities.ExecuteCommand (monitor, psi, out err) != 0) {
						monitor.Log.WriteLine (err);
						monitor.ReportError ("Signing failed", null);
						return false;
					}
					
					monitor.EndTask ();
				}
				
				if (settings.CreatePackage) {
					monitor.BeginTask (GettextCatalog.GetString ("Creating installer"), 0);
					
					var args = new ProcessArgumentBuilder ();
					args.Add ("--component");
					args.AddQuoted (workingApp);
					args.Add ("/Applications", "--sign");
					args.AddQuoted (packageKey);
					if (!settings.ProductDefinition.IsNullOrEmpty) {
						args.Add ("--product");
						args.AddQuoted (settings.ProductDefinition);
					}
					args.AddQuoted (target);
					
					var psi = new ProcessStartInfo ("productbuild", args.ToString ());
					monitor.Log.WriteLine ("productbuild " + psi.Arguments);
					
					string err;
					if (MacBuildUtilities.ExecuteCommand (monitor, psi, out err) != 0) {
						monitor.Log.WriteLine (err);
						monitor.ReportError ("Package creation failed", null);
						return false;
					}
					monitor.EndTask ();
				} else {
					Directory.Move (workingApp, target);
				}
			} finally {
				try {
					if (Directory.Exists (tempDir))
						Directory.Delete (tempDir, true);
				} catch (Exception ex) {
					LoggingService.LogError ("Error removing temp directory", ex);
				}
			}
			
			return true;
		}