/// <summary> /// Resign application using local executable and update debug symbols /// </summary> void ResignApplication(UnrealAppConfig AppConfig) { // check that we have the signing stuff we need string SignProvision = Globals.Params.ParseValue("signprovision", String.Empty); string SignEntitlements = Globals.Params.ParseValue("signentitlements", String.Empty); string SigningIdentity = Globals.Params.ParseValue("signidentity", String.Empty); // handle signing provision if (string.IsNullOrEmpty(SignProvision) || !File.Exists(SignProvision)) { throw new AutomationException("Absolute path to existing provision must be specified, example: -signprovision=/path/to/myapp.provision"); } // handle entitlements // Note this extracts entitlements: which may be useful when using same provision/entitlements?: codesign -d --entitlements :entitlements.plist ~/.gauntletappcache/Payload/Example.app/ if (string.IsNullOrEmpty(SignEntitlements) || !File.Exists(SignEntitlements)) { throw new AutomationException("Absolute path to existing entitlements must be specified, example: -signprovision=/path/to/entitlements.plist"); } // signing identity if (string.IsNullOrEmpty(SigningIdentity)) { throw new AutomationException("Signing identity must be specified, example: -signidentity=\"iPhone Developer: John Smith\""); } string ProjectName = AppConfig.ProjectName; string BundleName = Path.GetFileNameWithoutExtension(LocalAppBundle); string ExecutableName = UnrealHelpers.GetExecutableName(ProjectName, UnrealTargetPlatform.IOS, AppConfig.Configuration, AppConfig.ProcessType, ""); string CachedAppPath = Path.Combine(GauntletAppCache, "Payload", string.Format("{0}.app", BundleName)); string LocalExecutable = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}", ExecutableName)); if (!File.Exists(LocalExecutable)) { throw new AutomationException("Local executable not found for -dev argument: {0}", LocalExecutable); } File.WriteAllText(CacheResignedFilename, "The application has been resigned"); // copy local executable FileInfo SrcInfo = new FileInfo(LocalExecutable); string DestPath = Path.Combine(CachedAppPath, BundleName); SrcInfo.CopyTo(DestPath, true); Log.Verbose("Copied local executable from {0} to {1}", LocalExecutable, DestPath); // copy provision SrcInfo = new FileInfo(SignProvision); DestPath = Path.Combine(CachedAppPath, "embedded.mobileprovision"); SrcInfo.CopyTo(DestPath, true); Log.Verbose("Copied provision from {0} to {1}", SignProvision, DestPath); // handle symbols string LocalSymbolsDir = Path.Combine(Environment.CurrentDirectory, ProjectName, string.Format("Binaries/IOS/{0}.dSYM", ExecutableName)); DestPath = Path.Combine(GauntletAppCache, string.Format("Symbols/{0}.dSYM", ExecutableName)); if (Directory.Exists(DestPath)) { Directory.Delete(DestPath, true); } if (Directory.Exists(LocalSymbolsDir)) { CommandUtils.CopyDirectory_NoExceptions(LocalSymbolsDir, DestPath, true); } else { Log.Warning("No symbols found for local build at {0}, removing cached app symbols", LocalSymbolsDir); } // resign application // @todo: this asks for password unless "Always Allow" is selected, also for builders, document how to permanently grant codesign access to keychain string SignArgs = string.Format("-f -s \"{0}\" --entitlements \"{1}\" \"{2}\"", SigningIdentity, SignEntitlements, CachedAppPath); Log.Info("\nResigning app, please enter keychain password if prompted:\n\ncodesign {0}", SignArgs); var Result = IOSBuild.ExecuteCommand("codesign", SignArgs); if (Result.ExitCode != 0) { throw new AutomationException("Failed to resign application"); } }
virtual public UnrealAppConfig CreateConfiguration(UnrealSessionRole Role) { List <string> Issues = new List <string>(); Log.Verbose("Creating configuration Role {0}", Role); if (!CanSupportRole(Role, ref Issues)) { Issues.ForEach(S => Log.Error(S)); return(null); } UnrealAppConfig Config = new UnrealAppConfig(); Config.Name = this.BuildName; Config.ProjectName = ProjectName; Config.ProcessType = Role.RoleType; Config.Platform = Role.Platform; Config.Configuration = Role.Configuration; Config.CommandLine = ""; Config.FilesToCopy = new List <UnrealFileToCopy>(); // new system of retrieving and encapsulating the info needed to install/launch. Android & Mac Config.Build = GetMatchingBuilds(Role.RoleType, Role.Platform, Role.Configuration, Role.RequiredBuildFlags).FirstOrDefault(); if (Config.Build == null && Role.IsNullRole() == false) { var SupportedBuilds = String.Join("\n", DiscoveredBuilds.Select(B => B.ToString())); Log.Info("Available builds:\n{0}", SupportedBuilds); throw new AutomationException("No build found that can support a role of {0}.", Role); } if (Role.Options != null) { Role.Options.ApplyToConfig(Config); } if (string.IsNullOrEmpty(Role.CommandLine) == false) { Config.CommandLine += " " + Role.CommandLine; } bool IsContentOnlyProject = (Config.Build.Flags & BuildFlags.ContentOnlyProject) == BuildFlags.ContentOnlyProject; // Add in editor - TODO, should this be in the editor build? if (Role.RoleType.UsesEditor() || IsContentOnlyProject) { string ProjectParam = ProjectName; // if content only we need to provide a relative path to the uproject. if (IsContentOnlyProject) { ProjectParam = string.Format("../../../{0}/{0}.uproject", ProjectName); } // project must be first Config.CommandLine = ProjectParam + " " + Config.CommandLine; // add in -game or -server if (Role.RoleType.IsClient()) { Config.CommandLine += " -game"; } else if (Role.RoleType.IsServer()) { Config.CommandLine += " -server"; } } if (Role.FilesToCopy != null) { Config.FilesToCopy = Role.FilesToCopy; } return(Config); }
protected IAppInstall InstallStagedBuild(UnrealAppConfig AppConfig, StagedBuild InBuild) { bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy"); string BuildPath = InBuild.BuildPath; if (CanRunFromPath(BuildPath) == false) { string SubDir = string.IsNullOrEmpty(AppConfig.Sandbox) ? AppConfig.ProjectName : AppConfig.Sandbox; string DestPath = Path.Combine(this.TempDir, SubDir, AppConfig.ProcessType.ToString()); if (!SkipDeploy) { Log.Info("Installing {0} to {1}", AppConfig.Name, ToString()); Log.Verbose("\tCopying {0} to {1}", BuildPath, DestPath); Gauntlet.Utils.SystemHelpers.CopyDirectory(BuildPath, DestPath, Utils.SystemHelpers.CopyOptions.Mirror); } else { Log.Info("Skipping install of {0} (-skipdeploy)", BuildPath); } Utils.SystemHelpers.MarkDirectoryForCleanup(DestPath); BuildPath = DestPath; } WindowsAppInstall WinApp = new WindowsAppInstall(AppConfig.Name, AppConfig.ProjectName, this); WinApp.RunOptions = RunOptions; // Set commandline replace any InstallPath arguments with the path we use WinApp.CommandArguments = Regex.Replace(AppConfig.CommandLine, @"\$\(InstallPath\)", BuildPath, RegexOptions.IgnoreCase); if (string.IsNullOrEmpty(UserDir) == false) { WinApp.CommandArguments += string.Format(" -userdir=\"{0}\"", UserDir); WinApp.ArtifactPath = Path.Combine(UserDir, @"Saved"); Utils.SystemHelpers.MarkDirectoryForCleanup(UserDir); } else { // e.g d:\Unreal\GameName\Saved WinApp.ArtifactPath = Path.Combine(BuildPath, AppConfig.ProjectName, @"Saved"); } // clear artifact path if (Directory.Exists(WinApp.ArtifactPath)) { try { Directory.Delete(WinApp.ArtifactPath, true); } catch (Exception Ex) { Log.Warning("Failed to delete {0}. {1}", WinApp.ArtifactPath, Ex.Message); } } if (LocalDirectoryMappings.Count == 0) { PopulateDirectoryMappings(Path.Combine(BuildPath, AppConfig.ProjectName), UserDir); } if (AppConfig.FilesToCopy != null) { foreach (UnrealFileToCopy FileToCopy in AppConfig.FilesToCopy) { string PathToCopyTo = Path.Combine(LocalDirectoryMappings[FileToCopy.TargetBaseDirectory], FileToCopy.TargetRelativeLocation); if (File.Exists(FileToCopy.SourceFileLocation)) { FileInfo SrcInfo = new FileInfo(FileToCopy.SourceFileLocation); SrcInfo.IsReadOnly = false; string DirectoryToCopyTo = Path.GetDirectoryName(PathToCopyTo); if (!Directory.Exists(DirectoryToCopyTo)) { Directory.CreateDirectory(DirectoryToCopyTo); } if (File.Exists(PathToCopyTo)) { FileInfo ExistingFile = new FileInfo(PathToCopyTo); ExistingFile.IsReadOnly = false; } SrcInfo.CopyTo(PathToCopyTo, true); Log.Info("Copying {0} to {1}", FileToCopy.SourceFileLocation, PathToCopyTo); } else { Log.Warning("File to copy {0} not found", FileToCopy); } } } if (Path.IsPathRooted(InBuild.ExecutablePath)) { WinApp.ExecutablePath = InBuild.ExecutablePath; } else { // TODO - this check should be at a higher level.... string BinaryPath = Path.Combine(BuildPath, InBuild.ExecutablePath); // check for a local newer executable if (Globals.Params.ParseParam("dev") && AppConfig.ProcessType.UsesEditor() == false) { string LocalBinary = Path.Combine(Environment.CurrentDirectory, InBuild.ExecutablePath); bool LocalFileExists = File.Exists(LocalBinary); bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalBinary) > File.GetLastWriteTime(BinaryPath); Log.Verbose("Checking for newer binary at {0}", LocalBinary); Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer); if (LocalFileExists && LocalFileNewer) { // need to -basedir to have our exe load content from the path WinApp.CommandArguments += string.Format(" -basedir={0}", Path.GetDirectoryName(BinaryPath)); BinaryPath = LocalBinary; } } WinApp.ExecutablePath = BinaryPath; } return(WinApp); }
protected IAppInstall InstallStagedBuild(UnrealAppConfig AppConfig, StagedBuild InBuild) { bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy"); string BuildPath = InBuild.BuildPath; // Must be on our volume to run string BuildVolume = GetVolumeName(BuildPath); string LocalRoot = GetVolumeName(Environment.CurrentDirectory); if (BuildVolume.Equals(LocalRoot, StringComparison.OrdinalIgnoreCase) == false) { string SubDir = string.IsNullOrEmpty(AppConfig.Sandbox) ? AppConfig.ProjectName : AppConfig.Sandbox; string DestPath = Path.Combine(this.TempDir, SubDir, AppConfig.ProcessType.ToString()); if (!SkipDeploy) { Log.Info("Installing {0} to {1}", AppConfig.Name, ToString()); Log.Verbose("\tCopying {0} to {1}", BuildPath, DestPath); Gauntlet.Utils.SystemHelpers.CopyDirectory(BuildPath, DestPath, Utils.SystemHelpers.CopyOptions.Mirror); } else { Log.Info("Skipping install of {0} (-skipdeploy)", BuildPath); } Utils.SystemHelpers.MarkDirectoryForCleanup(DestPath); BuildPath = DestPath; } MacAppInstall MacApp = new MacAppInstall(AppConfig.Name, this); MacApp.LocalPath = BuildPath; MacApp.WorkingDirectory = MacApp.LocalPath; MacApp.RunOptions = RunOptions; // Set commandline replace any InstallPath arguments with the path we use MacApp.CommandArguments = Regex.Replace(AppConfig.CommandLine, @"\$\(InstallPath\)", BuildPath, RegexOptions.IgnoreCase); // Mac always forces this to stop logs and other artifacts going to different places // Mac always forces this to stop logs and other artifacts going to different places MacApp.CommandArguments += string.Format(" -userdir=\"{0}\"", UserDir); MacApp.ArtifactPath = Path.Combine(UserDir, @"Saved"); // temp - Mac doesn't support -userdir? //MacApp.ArtifactPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library/Logs", AppConfig.ProjectName); // clear artifact path if (Directory.Exists(MacApp.ArtifactPath)) { try { Directory.Delete(MacApp.ArtifactPath, true); } catch (Exception Ex) { Log.Warning("Failed to delete {0}. {1}", MacApp.ArtifactPath, Ex.Message); } } // For Mac turn Foo.app into Foo/Content/MacOS/Foo string AppPath = Path.GetDirectoryName(InBuild.ExecutablePath); string FileName = Path.GetFileNameWithoutExtension(InBuild.ExecutablePath); MacApp.ExecutablePath = Path.Combine(InBuild.ExecutablePath, "Contents", "MacOS", FileName); if (!Path.IsPathRooted(MacApp.ExecutablePath)) { // TODO - this check should be at a higher level.... string BinaryPath = Path.Combine(BuildPath, MacApp.ExecutablePath); // check for a local newer executable if (Globals.Params.ParseParam("dev") && AppConfig.ProcessType.UsesEditor() == false) { string LocalBinary = Path.Combine(Environment.CurrentDirectory, MacApp.ExecutablePath); bool LocalFileExists = File.Exists(LocalBinary); bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalBinary) > File.GetLastWriteTime(BinaryPath); Log.Verbose("Checking for newer binary at {0}", LocalBinary); Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer); if (LocalFileExists && LocalFileNewer) { string LocalAppPath = Path.Combine(Environment.CurrentDirectory, InBuild.ExecutablePath); // need to -basedir to have our exe load content from the path MacApp.CommandArguments += string.Format(" -basedir={0}", Path.GetDirectoryName(LocalAppPath)); BinaryPath = LocalBinary; } } MacApp.ExecutablePath = BinaryPath; } return(MacApp); }
/// <summary> /// When running parallel tests using staged builds, local desktop devices only need one copy of client/server /// this method is used to coordinate the copy across multiple local devices /// </summary> public static void InstallBuildParallel(UnrealAppConfig AppConfig, StagedBuild InBuild, string BuildPath, string DestPath, string Desc) { // In parallel tests, we only want to copy the client/server once bool Install = false; InstallStatus Status = InstallStatus.Error; lock (Globals.MainLock) { if (!LocalInstalls.ContainsKey(InBuild)) { Install = true; LocalInstalls[InBuild] = Status = InstallStatus.Installing; } else { Status = LocalInstalls[InBuild]; } } // check if we've already errored in another thread if (Status == InstallStatus.Error) { throw new AutomationException("Parallel build error installing {0} to {1}", BuildPath, DestPath); } if (Install) { try { Log.Info("Installing {0} to {1}", AppConfig.Name, Desc); Log.Verbose("\tCopying {0} to {1}", BuildPath, DestPath); Utils.SystemHelpers.CopyDirectory(BuildPath, DestPath, Utils.SystemHelpers.CopyOptions.Mirror); } catch (Exception Ex) { lock (Globals.MainLock) { LocalInstalls[InBuild] = InstallStatus.Error; } throw Ex; } lock (Globals.MainLock) { LocalInstalls[InBuild] = InstallStatus.Installed; } } else { DateTime StartTime = DateTime.Now; while (true) { if ((DateTime.Now - StartTime).TotalMinutes > 60) { throw new AutomationException("Parallel build error installing {0} to {1}, timed out after an hour", BuildPath, DestPath); } Thread.Sleep(1000); lock (Globals.MainLock) { Status = LocalInstalls[InBuild]; } // install process failed in other thread, disk full, etc if (Status == InstallStatus.Error) { throw new AutomationException("Error installing parallel build from {0} to {1}", BuildPath, DestPath); } if (Status == InstallStatus.Installed) { Log.Verbose("Parallel build successfully installed from {0} to {1}", BuildPath, DestPath); break; } } } }
public IAppInstall InstallApplication(UnrealAppConfig AppConfig) { LuminBuild Build = AppConfig.Build as LuminBuild; string QualifiedPackageName = GetQualifiedProjectName(AppConfig.ProjectName); InstalledAppName = AppConfig.ProjectName; // Ensure MPK exists if (Build == null) { throw new AutomationException("Invalid build for Lumin!"); } // kill any currently running instance: KillRunningProcess(QualifiedPackageName); bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy"); if (SkipDeploy == false) { // remote dir used to save things string RemoteDir = "/documents/c2/" + AppConfig.ProjectName.ToLower(); //string DependencyDir = RemoteDir + "/deps"; // device artifact path, always clear between runs // This clear is from andorid, but currently not needed on mldb. Everything is removed when you remove the pak // If in the future mldb supports a "sidecar" of data like the obb on Android, might need to delete that here DeviceArtifactPath = string.Format("{0}/{1}/saved", RemoteDir.ToLower(), AppConfig.ProjectName.ToLower()); if (Globals.Params.ParseParam("cleandevice")) { Log.Info("Cleaning previous builds due to presence of -cleandevice"); // we need to ununstall then install the mpk - don't care if it fails, may have been deleted Log.Info("Uninstalling {0}", QualifiedPackageName); RunMldbDeviceCommand(string.Format("uninstall {0}", QualifiedPackageName)); } // path to the MPK to install. string MpkPath = Build.SourceMpkPath; // check for a local newer executable if (Globals.Params.ParseParam("dev")) { string MpkFileName = UnrealHelpers.GetExecutableName(AppConfig.ProjectName, UnrealTargetPlatform.Lumin, AppConfig.Configuration, AppConfig.ProcessType, "mpk"); string LocalMPK = Path.Combine(Environment.CurrentDirectory, AppConfig.ProjectName, "Binaries/Lumin", MpkFileName); bool LocalFileExists = File.Exists(LocalMPK); bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalMPK) > File.GetLastWriteTime(MpkPath); Log.Verbose("Checking for newer binary at {0}", LocalMPK); Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer); if (LocalFileExists && LocalFileNewer) { MpkPath = LocalMPK; } } // first install the MPK RunMldbDeviceCommand(string.Format("install -u {0}", MpkPath)); // If in the future mldb supports a "sidecar" of data like the obb on Android, might need to install that here // The Gauntlet.TargetDeviceAndroid.cs implementation should be a good starting point } LuminAppInstall AppInstall = new LuminAppInstall(this, AppConfig.ProjectName, Build.LuminPackageName, AppConfig.CommandLine); return(AppInstall); }