public List <IBuild> GetBuildsAtPath(string InProjectName, string InPath, int MaxRecursion = 3) { List <DirectoryInfo> AllDirs = new List <DirectoryInfo>(); //AndroidBuildSource BuildSource = null; List <IBuild> Builds = new List <IBuild>(); // c:\path\to\build DirectoryInfo PathDI = new DirectoryInfo(InPath); if (PathDI.Exists) { if (PathDI.Name.IndexOf("Android", StringComparison.OrdinalIgnoreCase) >= 0) { AllDirs.Add(PathDI); } // find all directories that begin with Android DirectoryInfo[] AndroidDirs = PathDI.GetDirectories("Android*", SearchOption.TopDirectoryOnly); AllDirs.AddRange(AndroidDirs); List <DirectoryInfo> DirsToRecurse = AllDirs; // now get subdirs while (MaxRecursion-- > 0) { List <DirectoryInfo> DiscoveredDirs = new List <DirectoryInfo>(); DirsToRecurse.ToList().ForEach((D) => { DiscoveredDirs.AddRange(D.GetDirectories("*", SearchOption.TopDirectoryOnly)); }); AllDirs.AddRange(DiscoveredDirs); DirsToRecurse = DiscoveredDirs; } string AndroidBuildFilter = Globals.Params.ParseValue("AndroidBuildFilter", ""); foreach (DirectoryInfo Di in AllDirs) { IEnumerable <AndroidBuild> FoundBuilds = AndroidBuild.CreateFromPath(InProjectName, Di.FullName); if (FoundBuilds != null) { if (!string.IsNullOrEmpty(AndroidBuildFilter)) { //IndexOf used because Contains must be case-sensitive FoundBuilds = FoundBuilds.Where(B => B.SourceApkPath.IndexOf(AndroidBuildFilter, StringComparison.OrdinalIgnoreCase) >= 0); } Builds.AddRange(FoundBuilds); } } } return(Builds); }
public IAppInstall InstallApplication(UnrealAppConfig AppConfig) { // todo - pass this through AndroidBuild Build = AppConfig.Build as AndroidBuild; // Ensure APK exists if (Build == null) { throw new AutomationException("Invalid build for Android!"); } // kill any currently running instance: KillRunningProcess(Build.AndroidPackageName); bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy"); if (SkipDeploy == false) { // Establish remote directory locations string DeviceStorageQueryCommand = AndroidPlatform.GetStorageQueryCommand(); IProcessResult StorageQueryResult = RunAdbDeviceCommand(DeviceStorageQueryCommand); string StorageLocation = StorageQueryResult.Output.Trim(); // "/mnt/sdcard"; // remote dir used to save things string RemoteDir = StorageLocation + "/UE4Game/" + AppConfig.ProjectName; // if not a bulk/dev build, remote dir will be under /{StorageLocation}/Android/data/{PackageName} if ((Build.Flags & (BuildFlags.Bulk | BuildFlags.CanReplaceExecutable)) == 0) { RemoteDir = StorageLocation + "/Android/data/" + Build.AndroidPackageName + "/files/UE4Game/" + AppConfig.ProjectName; } string DependencyDir = RemoteDir + "/deps"; // device artifact path, always clear between runs DeviceArtifactPath = string.Format("{0}/{1}/Saved", RemoteDir, AppConfig.ProjectName); RunAdbDeviceCommand(string.Format("shell rm -r {0}", DeviceArtifactPath)); // path for OBB files string OBBRemoteDestination = string.Format("{0}/obb/{1}", StorageLocation, Build.AndroidPackageName); if (Globals.Params.ParseParam("cleandevice")) { Log.Info("Cleaning previous builds due to presence of -cleandevice"); // we need to ununstall then install the apk - don't care if it fails, may have been deleted Log.Info("Uninstalling {0}", Build.AndroidPackageName); RunAdbDeviceCommand(string.Format("uninstall {0}", Build.AndroidPackageName)); Log.Info("Removing {0}", RemoteDir); RunAdbDeviceCommand(string.Format("shell rm -r {0}", RemoteDir)); Log.Info("Removing {0}", OBBRemoteDestination); RunAdbDeviceCommand(string.Format("shell rm -r {0}", OBBRemoteDestination)); } // remote dir on the device, create it if it doesn't exist RunAdbDeviceCommand(string.Format("shell mkdir -p {0}/", RemoteDir)); IProcessResult AdbResult; string AdbCommand; // path to the APK to install. string ApkPath = Build.SourceApkPath; // check for a local newer executable if (Globals.Params.ParseParam("dev")) { //string ApkFileName = Path.GetFileName(ApkPath); string ApkFileName2 = UnrealHelpers.GetExecutableName(AppConfig.ProjectName, UnrealTargetPlatform.Android, AppConfig.Configuration, AppConfig.ProcessType, "apk"); string LocalAPK = Path.Combine(Environment.CurrentDirectory, AppConfig.ProjectName, "Binaries/Android", ApkFileName2); bool LocalFileExists = File.Exists(LocalAPK); bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalAPK) > File.GetLastWriteTime(ApkPath); Log.Verbose("Checking for newer binary at {0}", LocalAPK); Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer); if (LocalFileExists && LocalFileNewer) { ApkPath = LocalAPK; } } // first install the APK CopyFileToDevice(Build.AndroidPackageName, ApkPath, ""); // obb files need to be named based on APK version (grrr), so find that out. This should return something like // versionCode=2 minSdk=21 targetSdk=21 string PackageInfo = RunAdbDeviceCommand(string.Format("shell dumpsys package {0} | grep versionCode", Build.AndroidPackageName)).Output; var Match = Regex.Match(PackageInfo, @"versionCode=([\d\.]+)\s"); if (Match.Success == false) { throw new AutomationException("Failed to find version info for APK!"); } string PackageVersion = Match.Groups[1].ToString(); // Convert the files from the source to final destination names Dictionary <string, string> FilesToInstall = new Dictionary <string, string>(); Console.WriteLine("trying to copy files over."); if (AppConfig.FilesToCopy != null) { if (LocalDirectoryMappings.Count == 0) { Console.WriteLine("Populating Directory"); PopulateDirectoryMappings(DeviceArtifactPath); } Console.WriteLine("trying to copy files over."); 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; FilesToInstall.Add(FileToCopy.SourceFileLocation, PathToCopyTo.Replace("\\", "/")); Console.WriteLine("Copying {0} to {1}", FileToCopy.SourceFileLocation, PathToCopyTo); } else { Log.Warning("File to copy {0} not found", FileToCopy); } } } Build.FilesToInstall.Keys.ToList().ForEach(K => { string SrcPath = K; string DestPath = Build.FilesToInstall[K]; string DestFile = Path.GetFileName(DestPath); // If we installed a new APK we need to change the package version Match OBBMatch = Regex.Match(DestFile, @"main\.(\d+)\.com.*\.obb"); if (OBBMatch.Success) { string NewFileName = DestFile.Replace(OBBMatch.Groups[1].ToString(), PackageVersion); DestPath = DestPath.Replace(DestFile, NewFileName); } DestPath = Regex.Replace(DestPath, "%STORAGE%", StorageLocation, RegexOptions.IgnoreCase); FilesToInstall.Add(SrcPath, DestPath); }); // get a list of files in the destination OBB directory AdbResult = RunAdbDeviceCommand(string.Format("shell ls {0}", OBBRemoteDestination)); // if != 0 then no folder exists if (AdbResult.ExitCode == 0) { IEnumerable <string> CurrentRemoteFileList = AdbResult.Output.Replace("\r\n", "\n").Split('\n'); IEnumerable <string> NewRemoteFileList = FilesToInstall.Values.Select(F => Path.GetFileName(F)); // delete any files that should not be there foreach (string FileName in CurrentRemoteFileList) { if (FileName.StartsWith(".") || FileName.Length == 0) { continue; } if (NewRemoteFileList.Contains(FileName) == false) { RunAdbDeviceCommand(string.Format("shell rm {0}/{1}", OBBRemoteDestination, FileName)); } } } foreach (var KV in FilesToInstall) { string LocalFile = KV.Key; string RemoteFile = KV.Value; CopyFileToDevice(Build.AndroidPackageName, LocalFile, RemoteFile); } // create a tempfile, insert the command line, and push it over string TmpFile = Path.GetTempFileName(); CommandLineFilePath = string.Format("{0}/UE4CommandLine.txt", RemoteDir); // I've seen a weird thing where adb push truncates by a byte, so add some padding... File.WriteAllText(TmpFile, AppConfig.CommandLine + " "); AdbCommand = string.Format("push {0} {1}", TmpFile, CommandLineFilePath); RunAdbDeviceCommand(AdbCommand); File.Delete(TmpFile); } else { Log.Info("Skipping install of {0} (-skipdeploy)", Build.AndroidPackageName); } AndroidAppInstall AppInstall = new AndroidAppInstall(this, AppConfig.ProjectName, Build.AndroidPackageName, AppConfig.CommandLine); return(AppInstall); }
public static IEnumerable <AndroidBuild> CreateFromPath(string InProjectName, string InPath) { string BuildPath = InPath; List <AndroidBuild> DiscoveredBuilds = new List <AndroidBuild>(); DirectoryInfo Di = new DirectoryInfo(BuildPath); // find all install batchfiles FileInfo[] InstallFiles = Di.GetFiles("Install_*"); foreach (FileInfo Fi in InstallFiles) { // todo if (Fi.FullName.Contains("armv7")) { continue; } // Unreal naming. In test & shipping the platform name and config will be includedn string RegEx = "Install_(.+?)(client|game|server)-(Android)?-?(test|shipping)?-?(.+).bat"; Match Info = Regex.Match(Fi.Name, RegEx, RegexOptions.IgnoreCase); bool TestInstall = Fi.Name.EndsWith("_TEST.bat", StringComparison.OrdinalIgnoreCase); // filter out non-matching or test installation batch files // test installation scripts are intended to be manually invoked if (Info.Success == false || TestInstall) { if (TestInstall) { Log.Verbose("Ignoring test installation batch file {0}", Fi.Name); } continue; } string TargetName = Info.Groups[1].ToString(); string TypeName = Info.Groups[2].ToString(); string ConfigName = Info.Groups[4].ToString(); if (string.IsNullOrEmpty(ConfigName)) { ConfigName = "Development"; } Log.Verbose("Pulling install data from {0}", Fi.FullName); string AbsPath = Fi.Directory.FullName; // read contents and replace linefeeds (regex doesn't stop on them :(( string BatContents = File.ReadAllText(Fi.FullName).Replace(Environment.NewLine, "\n"); // Replace .bat with .apk and strip up to and including the first _, that is then our APK name string SourceApkPath = Regex.Replace(Fi.Name, ".bat", ".apk", RegexOptions.IgnoreCase); SourceApkPath = SourceApkPath.Substring(SourceApkPath.IndexOf("_") + 1); SourceApkPath = Path.Combine(AbsPath, SourceApkPath); // save com.companyname.product string AndroidPackageName = Regex.Match(BatContents, @"uninstall\s+(com\..+)").Groups[1].ToString(); // pull all OBBs (probably just one..) var OBBMatches = Regex.Matches(BatContents, @"push\s+(.+?)\s+(.+)"); // save them as a dict of full paths as keys and dest paths as values Dictionary <string, string> FilesToInstall = OBBMatches.Cast <Match>().ToDictionary(M => Path.Combine(AbsPath, M.Groups[1].ToString()), M => M.Groups[2].ToString()); if (string.IsNullOrEmpty(SourceApkPath)) { Log.Warning("No APK found for build at {0}", Fi.FullName); continue; } if (string.IsNullOrEmpty(AndroidPackageName)) { Log.Warning("No product name found for build at {0}", Fi.FullName); continue; } UnrealTargetConfiguration UnrealConfig; if (Enum.TryParse(ConfigName, true, out UnrealConfig) == false) { Log.Warning("Could not determine Unreal Config type from '{0}'", ConfigName); continue; } // Android builds are always packaged, and we can always replace the command line BuildFlags Flags = BuildFlags.Packaged | BuildFlags.CanReplaceCommandLine; // if there's data then the pak files are in an obb and we can sub in a new exe if (FilesToInstall.Count() > 0) { Flags |= BuildFlags.CanReplaceExecutable; } if (AbsPath.Contains("Bulk")) { Flags |= BuildFlags.Bulk; } AndroidBuild NewBuild = new AndroidBuild(UnrealConfig, AndroidPackageName, SourceApkPath, FilesToInstall, Flags); DiscoveredBuilds.Add(NewBuild); Log.Verbose("Found {0} {1} build at {2}", UnrealConfig, ((Flags & BuildFlags.Bulk) == BuildFlags.Bulk) ? "(bulk)" : "(not bulk)", AbsPath); } return(DiscoveredBuilds); }
/// <summary> /// Given a platform, a build config, and true/false for client, returns the path to the binary for that config. E.g. /// Win64, Shipping, false = Binaries\Win64\FooServer-Win64-Shipping.exe /// </summary> /// <param name="TargetPlatform"></param> /// <param name="BuildConfig"></param> /// <param name="IsClient"></param> /// <returns></returns> virtual public string GetRelativeExecutablePath(UnrealTargetRole TargetType, UnrealTargetPlatform TargetPlatform, UnrealTargetConfiguration TargetConfiguration) { string ExePath; if (TargetType.UsesEditor()) { ExePath = string.Format("Engine/Binaries/{0}/UE4Editor{1}", BuildHostPlatform.Current.Platform, Platform.GetExeExtension(TargetPlatform)); } else { string BuildType = ""; if (TargetType == UnrealTargetRole.Client) { if (!UsesSharedBuildType) { BuildType = "Client"; } } else if (TargetType == UnrealTargetRole.Server) { if (!UsesSharedBuildType) { BuildType = "Server"; } } bool IsRunningDev = Globals.Params.ParseParam("dev"); // Turn FooGame into Foo string ExeBase = ProjectName.Replace("Game", ""); if (TargetPlatform == UnrealTargetPlatform.Android) { // use the archive results for android. //var AndroidSource = new AndroidBuild(ProjectName, GetPlatformPath(TargetType, TargetPlatform), TargetConfiguration); // We always (currently..) need to be able to replace the command line BuildFlags Flags = BuildFlags.CanReplaceCommandLine; if (IsRunningDev) { Flags |= BuildFlags.CanReplaceExecutable; } if (Globals.Params.ParseParam("bulk")) { Flags |= BuildFlags.Bulk; } else if (Globals.Params.ParseParam("notbulk")) { Flags |= BuildFlags.NotBulk; } var Build = GetMatchingBuilds(TargetType, TargetPlatform, TargetConfiguration, Flags).FirstOrDefault(); if (Build != null) { AndroidBuild AndroidBuild = Build as AndroidBuild; ExePath = AndroidBuild.SourceApkPath; } else { throw new AutomationException("No suitable build for {0} found at {1}", TargetPlatform, string.Join(",", BuildPaths)); } //ExePath = AndroidSource.SourceApkPath; } else { string ExeFileName = string.Format("{0}{1}", ExeBase, BuildType); if (TargetConfiguration != UnrealTargetConfiguration.Development) { ExeFileName += string.Format("-{0}-{1}", TargetPlatform.ToString(), TargetConfiguration.ToString()); } ExeFileName += Platform.GetExeExtension(TargetPlatform); string BasePath = GetPlatformPath(TargetType, TargetPlatform); string ProjectBinary = string.Format("{0}\\Binaries\\{1}\\{2}", ProjectName, TargetPlatform.ToString(), ExeFileName); string StubBinary = Path.Combine(BasePath, ExeFileName); string DevBinary = Path.Combine(Environment.CurrentDirectory, ProjectBinary); string NonCodeProjectName = "UE4Game" + Platform.GetExeExtension(TargetPlatform); string NonCodeProjectBinary = Path.Combine(BasePath, "Engine", "Binaries", TargetPlatform.ToString()); NonCodeProjectBinary = Path.Combine(NonCodeProjectBinary, NonCodeProjectName); // check the project binaries folder if (File.Exists(Path.Combine(BasePath, ProjectBinary))) { ExePath = ProjectBinary; } else if (File.Exists(StubBinary)) { ExePath = Path.Combine(BasePath, ExeFileName); } else if (IsRunningDev && File.Exists(DevBinary)) { ExePath = DevBinary; } else if (File.Exists(NonCodeProjectBinary)) { ExePath = NonCodeProjectBinary; } else { List <string> CheckedFiles = new List <String>() { Path.Combine(BasePath, ProjectBinary), StubBinary, NonCodeProjectBinary }; if (IsRunningDev) { CheckedFiles.Add(DevBinary); } throw new AutomationException("Executable not found, upstream compile job may have failed. Could not find executable {0} within {1}, binaries checked: {2}", ExeFileName, BasePath, String.Join(" - ", CheckedFiles)); } } } return(Utils.SystemHelpers.CorrectDirectorySeparators(ExePath)); }
public static IEnumerable <AndroidBuild> CreateFromPath(string InProjectName, string InPath) { string BuildPath = InPath; List <AndroidBuild> DiscoveredBuilds = new List <AndroidBuild>(); DirectoryInfo Di = new DirectoryInfo(BuildPath); // find all install batchfiles FileInfo[] InstallFiles = Di.GetFiles("Install_*"); foreach (FileInfo Fi in InstallFiles) { bool PackageIs32Bit = Fi.FullName.Contains("armv7"); UnrealTargetConfiguration UnrealConfig = UnrealHelpers.GetConfigurationFromExecutableName(InProjectName, Fi.FullName); UnrealTargetRole UnrealRole = UnrealHelpers.GetRoleFromExecutableName(InProjectName, Fi.FullName); if (UnrealConfig == UnrealTargetConfiguration.Unknown) { Log.Info("Skipping unrecognized build {0}", Fi.FullName); continue; } bool TestInstall = Fi.Name.EndsWith("_TEST.bat", StringComparison.OrdinalIgnoreCase); bool PatchInstall = Fi.Name.EndsWith("_Patch.bat", StringComparison.OrdinalIgnoreCase); // filter out non-matching or test installation batch files // test installation scripts are intended to be manually invoked if (TestInstall || PatchInstall) { if (TestInstall || PatchInstall) { Log.Verbose("Ignoring {0} installation batch file {1}", TestInstall ? "test" : "patch", Fi.Name); } continue; } Log.Verbose("Pulling install data from {0}", Fi.FullName); string AbsPath = Fi.Directory.FullName; // read contents and replace linefeeds (regex doesn't stop on them :(( string BatContents = File.ReadAllText(Fi.FullName).Replace(Environment.NewLine, "\n"); // Replace .bat with .apk and strip up to and including the first _, that is then our APK name var SourceApkMatch = Regex.Match(BatContents, @" install\s+(.+\.apk)"); if (SourceApkMatch.Groups.Count <= 0) { Log.Warning("Could not parse install command from {0}", Fi.FullName); continue; } string SourceApkPath = Path.Combine(AbsPath, SourceApkMatch.Groups[1].ToString()); // save com.companyname.product string AndroidPackageName = Regex.Match(BatContents, @"uninstall\s+(com\..+)").Groups[1].ToString(); // pull all OBBs (probably just one..) var OBBMatches = Regex.Matches(BatContents, @"push\s+(.+?)\s+(.+)"); // save them as a dict of full paths as keys and dest paths as values Dictionary <string, string> FilesToInstall = OBBMatches.Cast <Match>().ToDictionary(M => Path.Combine(AbsPath, M.Groups[1].ToString()), M => M.Groups[2].ToString()); if (string.IsNullOrEmpty(SourceApkPath)) { Log.Warning("No APK found for build at {0}", Fi.FullName); continue; } if (!File.Exists(SourceApkPath)) { Log.Warning("Resolved APK name but it doesn't exist {0}", SourceApkPath); continue; } if (string.IsNullOrEmpty(AndroidPackageName)) { Log.Warning("No product name found for build at {0}", Fi.FullName); continue; } // Android builds are always packaged, and we can always replace the command line BuildFlags Flags = BuildFlags.Packaged | BuildFlags.CanReplaceCommandLine; // if there's data then the pak files are in an obb and we can sub in a new exe if (FilesToInstall.Count() > 0) { Flags |= BuildFlags.CanReplaceExecutable; } if (AbsPath.Contains("Bulk")) { Flags |= BuildFlags.Bulk; } else { Flags |= BuildFlags.NotBulk; } AndroidBuild NewBuild = new AndroidBuild(UnrealConfig, AndroidPackageName, SourceApkPath, FilesToInstall, Flags, PackageIs32Bit); DiscoveredBuilds.Add(NewBuild); Log.Verbose("Found {0} {1} build at {2}", UnrealConfig, ((Flags & BuildFlags.Bulk) == BuildFlags.Bulk) ? "(bulk)" : "(not bulk)", AbsPath); } // If we have both 32 and 64-bit builds, prefer 64-bit if (DiscoveredBuilds.Where(B => B.Is32Bit == false).Any()) { DiscoveredBuilds = DiscoveredBuilds.Where(B => !B.Is32Bit).ToList(); } return(DiscoveredBuilds); }