/// <summary> /// Do a build configured for UWP Applications to the specified path, returns the error from <see cref="BuildPlayer(UwpBuildInfo)"/> /// </summary> /// <param name="buildDirectory"></param> /// <param name="showDialog"></param> /// <param name="buildAppx"></param> /// <returns>True, if build was successful.</returns> public static async Task <bool> BuildPlayer(string buildDirectory, bool showDialog = true, bool buildAppx = false) { if (UnityPlayerBuildTools.CheckBuildScenes() == false) { return(false); } var buildInfo = new UwpBuildInfo { OutputDirectory = buildDirectory, Scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path), BuildAppx = buildAppx, AppIconPath = UwpBuildDeployPreferences._3DAppIconPath, // Configure a post build action that will compile the generated solution PostBuildAction = PostBuildAction }; async void PostBuildAction(IBuildInfo innerBuildInfo, BuildReport buildReport) { if (buildReport.summary.result != BuildResult.Succeeded) { EditorUtility.DisplayDialog($"{PlayerSettings.productName} WindowsStoreApp Build {buildReport.summary.result}!", "See console for details", "OK"); } else { if (showDialog && !EditorUtility.DisplayDialog(PlayerSettings.productName, "Build Complete", "OK", "Build AppX")) { var _buildInfo = innerBuildInfo as UwpBuildInfo; Debug.Assert(_buildInfo != null); EditorAssemblyReloadManager.LockReloadAssemblies = true; await UwpAppxBuildTools.BuildAppxAsync(_buildInfo); EditorAssemblyReloadManager.LockReloadAssemblies = false; } } } return(await BuildPlayer(buildInfo)); }
/// <summary> /// Build the Uwp Player. /// </summary> /// <param name="buildInfo"></param> public static async Task <bool> BuildPlayer(UwpBuildInfo buildInfo) { #region Gather Build Data if (buildInfo.IsCommandLine) { ParseBuildCommandLine(ref buildInfo); } #endregion Gather Build Data BuildReport buildReport = UnityPlayerBuildTools.BuildUnityPlayer(buildInfo); bool success = buildReport != null && buildReport.summary.result == BuildResult.Succeeded; if (success && buildInfo.BuildAppx) { success &= await UwpAppxBuildTools.BuildAppxAsync(buildInfo); } return(success); }
private static void ParseBuildCommandLine(ref UwpBuildInfo buildInfo) { IBuildInfo iBuildInfo = buildInfo; UnityPlayerBuildTools.ParseBuildCommandLine(ref iBuildInfo); string[] arguments = Environment.GetCommandLineArgs(); for (int i = 0; i < arguments.Length; ++i) { switch (arguments[i]) { case "-buildAppx": buildInfo.BuildAppx = true; break; case "-rebuildAppx": buildInfo.RebuildAppx = true; break; } } }
/// <summary> /// Build the UWP appx bundle for this project. Requires that <see cref="UwpPlayerBuildTools.BuildPlayer(string,bool,bool)"/> has already be run or a user has /// previously built the Unity Player with the WSA Player as the Build Target. /// </summary> /// <param name="buildInfo"></param> /// <returns>True, if the appx build was successful.</returns> public static async Task <bool> BuildAppxAsync(UwpBuildInfo buildInfo) { if (!EditorAssemblyReloadManager.LockReloadAssemblies) { Debug.LogError("Lock Reload assemblies before attempting to build appx!"); return(false); } if (IsBuilding) { Debug.LogWarning("Build already in progress!"); return(false); } if (Application.isBatchMode) { // We don't need stack traces on all our logs. Makes things a lot easier to read. Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None); } Debug.Log("Starting Unity Appx Build..."); IsBuilding = true; string slnFilename = Path.Combine(buildInfo.OutputDirectory, $"{PlayerSettings.productName}.sln"); if (!File.Exists(slnFilename)) { Debug.LogError("Unable to find Solution to build from!"); return(IsBuilding = false); } // Get and validate the msBuild path... var msBuildPath = await FindMsBuildPathAsync(); if (!File.Exists(msBuildPath)) { Debug.LogError($"MSBuild.exe is missing or invalid!\n{msBuildPath}"); return(IsBuilding = false); } // Ensure that the generated .appx version increments by modifying Package.appxmanifest if (!UpdateAppxManifest(buildInfo)) { Debug.LogError("Failed to increment package version!"); return(IsBuilding = false); } string storagePath = Path.GetFullPath(Path.Combine(Path.Combine(Application.dataPath, ".."), buildInfo.OutputDirectory)); string solutionProjectPath = Path.GetFullPath(Path.Combine(storagePath, $@"{PlayerSettings.productName}.sln")); // Now do the actual appx build var processResult = await new Process().StartProcessAsync( msBuildPath, $"\"{solutionProjectPath}\" /t:{(buildInfo.RebuildAppx ? "Rebuild" : "Build")} /p:Configuration={buildInfo.Configuration} /p:Platform={buildInfo.BuildPlatform} /verbosity:m", !Application.isBatchMode); switch (processResult.ExitCode) { case 0: Debug.Log("Appx Build Successful!"); if (Application.isBatchMode) { Debug.Log(string.Join("\n", processResult.Output)); } break; case -1073741510: Debug.LogWarning("The build was terminated either by user's keyboard input CTRL+C or CTRL+Break or closing command prompt window."); break; default: { if (processResult.ExitCode != 0) { Debug.LogError($"{PlayerSettings.productName} appx build Failed! (ErrorCode: {processResult.ExitCode})"); if (Application.isBatchMode) { Debug.LogError(string.Join("\n", processResult.Errors)); } } break; } } AssetDatabase.SaveAssets(); IsBuilding = false; return(processResult.ExitCode == 0); }
private static bool UpdateAppxManifest(UwpBuildInfo buildInfo) { // Find the manifest, assume the one we want is the first one string[] manifests = Directory.GetFiles(BuildDeployPreferences.AbsoluteBuildDirectory, "Package.appxmanifest", SearchOption.AllDirectories); if (manifests.Length == 0) { Debug.LogError($"Unable to find Package.appxmanifest file for build (in path - {BuildDeployPreferences.AbsoluteBuildDirectory})"); return(false); } if (manifests.Length > 1) { Debug.LogWarning("Found more than one appxmanifest in the target build folder!"); } const string uap5 = "uap5"; var rootNode = XElement.Load(manifests[0]); var identityNode = rootNode.Element(rootNode.GetDefaultNamespace() + "Identity"); if (identityNode == null) { Debug.LogError($"Package.appxmanifest for build (in path - {BuildDeployPreferences.AbsoluteBuildDirectory}) is missing an <Identity /> node"); return(false); } var dependencies = rootNode.Element(rootNode.GetDefaultNamespace() + "Dependencies"); if (dependencies == null) { Debug.LogError($"Package.appxmanifest for build (in path - {BuildDeployPreferences.AbsoluteBuildDirectory}) is missing <Dependencies /> node."); return(false); } UpdateDependenciesElement(dependencies, rootNode.GetDefaultNamespace()); // Setup the 3d app icon. if (!string.IsNullOrWhiteSpace(UwpBuildDeployPreferences._3DAppIconPath)) { // Add the uap5 namespace if it doesn't exist. if (rootNode.GetNamespaceOfPrefix(uap5) == null) { rootNode.Add(new XAttribute(XNamespace.Xmlns + uap5, Uap5NameSpace)); } var ignorable = rootNode.Attribute(XName.Get("IgnorableNamespaces")); if (ignorable != null) { if (!ignorable.Value.Contains(uap5)) { ignorable.Value = $"{ignorable.Value} {uap5}"; } } // find mixed reality model var modelContainer = rootNode.Descendants(Uap5NameSpace + "MixedRealityModel").ToArray(); if (modelContainer.Any()) { var element = modelContainer.First(); var path = element.Attribute(XName.Get("Path")); if (path != null) { path.Value = buildInfo.AppIconPath; } else { element.Add(new XAttribute("Path", buildInfo.AppIconPath)); } } else { var modelElement = new XElement(Uap5NameSpace + "MixedRealityModel"); var defaultTile = rootNode.Descendants(UapNameSpace + "DefaultTile").First(); defaultTile.Add(modelElement); modelElement.Add(new XAttribute("Path", buildInfo.AppIconPath)); } } // We use XName.Get instead of string -> XName implicit conversion because // when we pass in the string "Version", the program doesn't find the attribute. // Best guess as to why this happens is that implicit string conversion doesn't set the namespace to empty var versionAttr = identityNode.Attribute(XName.Get("Version")); if (versionAttr == null) { Debug.LogError($"Package.appxmanifest for build (in path - {BuildDeployPreferences.AbsoluteBuildDirectory}) is missing a Version attribute in the <Identity /> node."); return(false); } // Assume package version always has a '.' between each number. // According to https://msdn.microsoft.com/en-us/library/windows/apps/br211441.aspx // Package versions are always of the form Major.Minor.Build.Revision. // Note: Revision number reserved for Windows Store, and a value other than 0 will fail WACK. var version = PlayerSettings.WSA.packageVersion; var newVersion = new Version(version.Major, version.Minor, buildInfo.AutoIncrement ? version.Build + 1 : version.Build, version.Revision); PlayerSettings.WSA.packageVersion = newVersion; versionAttr.Value = newVersion.ToString(); rootNode.Save(manifests[0]); return(true); }