chunk 2: remaining non-audio non-NewImport assets
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc9a7cb46c21c41a0804f2aed443cd5f
|
||||
timeCreated: 1581039935
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b913d9b216abf4e9b8172d1b11816e86
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Google.Android.AppBundle.Editor",
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 995913c7e5ef24040bbef8815aeac049
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1fead41d7b3d49afb964adac65da097
|
||||
folderAsset: yes
|
||||
timeCreated: 1580752771
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities related to
|
||||
/// <a href="https://developer.android.com/platform/technology/app-bundle/">Android App Bundle</a>.
|
||||
/// </summary>
|
||||
public static class AndroidAppBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved name for the base module that contains the Unity game engine.
|
||||
/// </summary>
|
||||
public const string BaseModuleName = "base";
|
||||
|
||||
/// <summary>
|
||||
/// Reserved name for the module that separates the base module's assets into an install-time asset pack.
|
||||
/// </summary>
|
||||
public const string BaseAssetsModuleName = "base_assets";
|
||||
|
||||
/// <summary>
|
||||
/// Regex used to determine whether a module name is valid.
|
||||
/// See https://github.com/google/bundletool/blob/master/src/main/java/com/android/tools/build/bundletool/model/BundleModuleName.java#L38
|
||||
/// </summary>
|
||||
private static readonly Regex NameRegex = RegexHelper.CreateCompiled(@"^[a-zA-Z][a-zA-Z0-9_]*$");
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified name is a valid Android App Bundle module name, false otherwise.
|
||||
/// Certain names like "base" are reserved, so also return false in those cases.
|
||||
/// </summary>
|
||||
public static bool IsValidModuleName(string name)
|
||||
{
|
||||
// TODO(b/131241163): enforce a name length limit if we make it much smaller than 65535.
|
||||
return name != null
|
||||
&& NameRegex.IsMatch(name)
|
||||
&& CheckReservedName(name, BaseModuleName)
|
||||
&& CheckReservedName(name, BaseAssetsModuleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns true. Previously indicated if this version of the Unity Editor has native support for
|
||||
/// building an Android App Bundle.
|
||||
/// </summary>
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
[Obsolete("This is always true")]
|
||||
public static bool HasNativeBuildSupport()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns EditorUserBuildSettings.buildAppBundle if it is defined and false otherwise.
|
||||
/// </summary>
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
[Obsolete("Use EditorUserBuildSettings.buildAppBundle directly instead")]
|
||||
public static bool IsNativeBuildEnabled()
|
||||
{
|
||||
return EditorUserBuildSettings.buildAppBundle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the EditorUserBuildSettings.buildAppBundle field if it is defined.
|
||||
/// </summary>
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
[Obsolete("Use EditorUserBuildSettings.buildAppBundle directly instead")]
|
||||
public static void EnableNativeBuild()
|
||||
{
|
||||
EditorUserBuildSettings.buildAppBundle = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the EditorUserBuildSettings.buildAppBundle field if it is defined.
|
||||
/// </summary>
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
[Obsolete("Use EditorUserBuildSettings.buildAppBundle directly instead")]
|
||||
public static void DisableNativeBuild()
|
||||
{
|
||||
EditorUserBuildSettings.buildAppBundle = false;
|
||||
}
|
||||
|
||||
private static bool CheckReservedName(string name, string reserved)
|
||||
{
|
||||
return !name.Equals(reserved, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6f956b5a57f949f8bbdc5c0bdd5fc6e
|
||||
timeCreated: 1550109878
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
using System;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown in case of certain Android build failures.
|
||||
/// </summary>
|
||||
public class AndroidBuildException : Exception
|
||||
{
|
||||
internal AndroidBuildException(string errorMessage, AndroidBuildReport androidBuildReport) : base(errorMessage)
|
||||
{
|
||||
AndroidBuildReport = androidBuildReport;
|
||||
}
|
||||
|
||||
internal AndroidBuildException(Exception exception, AndroidBuildReport androidBuildReport)
|
||||
: base(exception.Message, exception)
|
||||
{
|
||||
AndroidBuildReport = androidBuildReport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="AndroidBuildReport"/> associated with this build failure. Note that the build may fail
|
||||
/// after the Android Player is built but before the AAB is ready; in this case the BuildReport will
|
||||
/// likely indicate a BuildResult of Succeeded since it's reporting the result of calling BuildPlayer().
|
||||
/// </summary>
|
||||
public AndroidBuildReport AndroidBuildReport { get; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50f90eb905884a578d699e13668b1692
|
||||
timeCreated: 1618864941
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper methods for accessing or persisting build-related settings.
|
||||
/// </summary>
|
||||
public static class AndroidBuildHelper
|
||||
{
|
||||
// Allowed characters for splitting PlayerSettings.GetScriptingDefineSymbolsForGroup().
|
||||
private static readonly char[] ScriptingDefineSymbolsSplitChars = {';', ',', ' '};
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of enabled scenes from the "Scenes In Build" section of Unity's Build Settings window.
|
||||
/// </summary>
|
||||
public static string[] GetEditorBuildEnabledScenes()
|
||||
{
|
||||
return EditorBuildSettings.scenes
|
||||
.Where(scene => scene.enabled && !string.IsNullOrEmpty(scene.path))
|
||||
.Select(scene => scene.path)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="BuildPlayerOptions"/> for the specified output file path with all other fields set to
|
||||
/// reasonable default values.
|
||||
/// </summary>
|
||||
/// <param name="locationPathName">
|
||||
/// The output file that will be created when building a player with <see cref="BuildPipeline"/>.
|
||||
/// </param>
|
||||
/// <returns>A new <see cref="BuildPlayerOptions"/> object.</returns>
|
||||
public static BuildPlayerOptions CreateBuildPlayerOptions(string locationPathName)
|
||||
{
|
||||
return new BuildPlayerOptions
|
||||
{
|
||||
assetBundleManifestPath = AndroidBuildConfiguration.AssetBundleManifestPath,
|
||||
locationPathName = locationPathName,
|
||||
options = EditorUserBuildSettings.development ? BuildOptions.Development : BuildOptions.None,
|
||||
scenes = GetEditorBuildEnabledScenes(),
|
||||
target = BuildTarget.Android,
|
||||
targetGroup = BuildTargetGroup.Android
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified scripting define symbol for Android, but only if it isn't already defined.
|
||||
/// </summary>
|
||||
public static void AddScriptingDefineSymbol(string symbol)
|
||||
{
|
||||
var scriptingDefineSymbols = GetScriptingDefineSymbols();
|
||||
if (!IsScriptingSymbolDefined(scriptingDefineSymbols, symbol))
|
||||
{
|
||||
SetScriptingDefineSymbols(scriptingDefineSymbols.Concat(new[] {symbol}));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified scripting define symbol if it exists.
|
||||
/// </summary>
|
||||
public static void RemoveScriptingDefineSymbol(string symbol)
|
||||
{
|
||||
var scriptingDefineSymbols = GetScriptingDefineSymbols();
|
||||
if (IsScriptingSymbolDefined(scriptingDefineSymbols, symbol))
|
||||
{
|
||||
SetScriptingDefineSymbols(scriptingDefineSymbols.Where(sym => sym != symbol));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified scripting symbol is defined for Android.
|
||||
/// </summary>
|
||||
public static bool IsScriptingSymbolDefined(string symbol)
|
||||
{
|
||||
return IsScriptingSymbolDefined(GetScriptingDefineSymbols(), symbol);
|
||||
}
|
||||
|
||||
private static bool IsScriptingSymbolDefined(string[] scriptingDefineSymbols, string symbol)
|
||||
{
|
||||
return Array.IndexOf(scriptingDefineSymbols, symbol) >= 0;
|
||||
}
|
||||
|
||||
private static string[] GetScriptingDefineSymbols()
|
||||
{
|
||||
var symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
|
||||
if (string.IsNullOrEmpty(symbols))
|
||||
{
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
return symbols.Split(ScriptingDefineSymbolsSplitChars, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private static void SetScriptingDefineSymbols(IEnumerable<string> scriptingDefineSymbols)
|
||||
{
|
||||
var symbols = string.Join(";", scriptingDefineSymbols.ToArray());
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, symbols);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35354282707b045b5b530c421d5a234e
|
||||
timeCreated: 1582318631
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Options that configure the behavior of an Android build.
|
||||
/// </summary>
|
||||
public class AndroidBuildOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="buildPlayerOptions">BuildPlayerOptions required for any build.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the BuildPlayerOptions build target isn't Android.</exception>
|
||||
public AndroidBuildOptions(BuildPlayerOptions buildPlayerOptions)
|
||||
{
|
||||
if (buildPlayerOptions.target != BuildTarget.Android)
|
||||
{
|
||||
throw new ArgumentException("Unexpected non-Android BuildTarget: " + buildPlayerOptions.target);
|
||||
}
|
||||
|
||||
if (buildPlayerOptions.targetGroup != BuildTargetGroup.Android)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Unexpected non-Android BuildTargetGroup: " + buildPlayerOptions.targetGroup);
|
||||
}
|
||||
|
||||
BuildPlayerOptions = buildPlayerOptions;
|
||||
CompressionOptions = new CompressionOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The build options passed to BuildPipeline.BuildPlayer(), including scenes and output location.
|
||||
/// </summary>
|
||||
public BuildPlayerOptions BuildPlayerOptions { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the AssetPackConfig to use for the build, or null.
|
||||
/// </summary>
|
||||
public AssetPackConfig AssetPackConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Options for overriding the default file compression strategy.
|
||||
/// </summary>
|
||||
public CompressionOptions CompressionOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, forces the entire build to run on the main thread, potentially freezing the Editor UI during some
|
||||
/// build steps. This setting doesn't affect batch mode builds, which always run on the main thread.
|
||||
/// </summary>
|
||||
public bool ForceSingleThreadedBuild { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90eabf8dd350c48c08d72f67b0082358
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
// Note: this class exists (versus using BuildReport directly) so that additional build info can be added later.
|
||||
/// <summary>
|
||||
/// Report containing the results of an Android build.
|
||||
/// </summary>
|
||||
public class AndroidBuildReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="buildReport">A BuildPlayer() BuildReport.</param>
|
||||
internal AndroidBuildReport(BuildReport buildReport)
|
||||
{
|
||||
Report = buildReport;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="BuildReport"/> generated during the creation of the Android Player. Note that since
|
||||
/// additional processing occurs after the Player is built, some information in this report may not be useful.
|
||||
/// For example, the BuildSummary.outputPath will contain an intermediate output file path, not the final
|
||||
/// AAB output file path.
|
||||
/// </summary>
|
||||
public BuildReport Report { get; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d0c6937ea0847ec9a639618b2ae2bb8
|
||||
timeCreated: 1617397690
|
||||
@@ -0,0 +1,377 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.AssetPacks;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper methods for building AssetBundles with various texture compression formats.
|
||||
/// </summary>
|
||||
public static class AssetBundleBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Run one or more AssetBundle builds with the specified texture compression formats.
|
||||
/// Notes about the <see cref="outputPath"/> parameter:
|
||||
/// - If a relative path is provided, the file paths in the returned AssetPackConfig will be relative paths.
|
||||
/// - If an absolute path is provided, the file paths in the returned object will be absolute paths.
|
||||
/// - AssetBundle builds for additional texture formats will be created in siblings of this directory. For
|
||||
/// example, for outputDirectory "a/b/c" and texture format ASTC, there will be a directory "a/b/c#tcf_astc".
|
||||
/// - If allowClearDirectory is false, this directory and any sibling directories must be empty or not exist,
|
||||
/// otherwise an exception is thrown.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The output directory for the ETC1 AssetBundles. See other notes above.</param>
|
||||
/// <param name="builds">The main argument to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="deliveryMode">A delivery mode to apply to every asset pack in the generated config.</param>
|
||||
/// <param name="additionalTextureFormats">Texture formats to build for in addition to ETC1.</param>
|
||||
/// <param name="assetBundleOptions">Options to pass to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="allowClearDirectory">Allows this method to clear the contents of the output directory.</param>
|
||||
/// <returns>An <see cref="AssetPackConfig"/> containing file paths to all generated AssetBundles.</returns>
|
||||
public static AssetPackConfig BuildAssetBundles(
|
||||
string outputPath,
|
||||
AssetBundleBuild[] builds,
|
||||
AssetPackDeliveryMode deliveryMode,
|
||||
IEnumerable<MobileTextureSubtarget> additionalTextureFormats = null,
|
||||
BuildAssetBundleOptions assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle,
|
||||
bool allowClearDirectory = false)
|
||||
{
|
||||
var nameToTextureFormatToPath = BuildAssetBundles(outputPath, builds, assetBundleOptions,
|
||||
MobileTextureSubtarget.ETC, additionalTextureFormats, allowClearDirectory);
|
||||
var assetPackConfig = new AssetPackConfig();
|
||||
foreach (var compressionToPath in nameToTextureFormatToPath.Values)
|
||||
{
|
||||
assetPackConfig.AddAssetBundles(compressionToPath, deliveryMode);
|
||||
}
|
||||
|
||||
return assetPackConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run one or more AssetBundle builds with the specified texture compression formats.
|
||||
/// This variant allows for overriding the base format and provides a different return type.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The output directory for base AssetBundles. See the other method for notes.</param>
|
||||
/// <param name="builds">The main argument to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="assetBundleOptions">Options to pass to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="baseTextureFormat">The default format. Note: ETC1 is supported by all devices.</param>
|
||||
/// <param name="additionalTextureFormats">Texture formats to generate for in addition to the base.</param>
|
||||
/// <param name="allowClearDirectory">Allows this method to clear the contents of the output directory.</param>
|
||||
/// <returns>A dictionary from AssetBundle name to TextureCompressionFormat to file outputPath.</returns>
|
||||
public static Dictionary<string, Dictionary<TextureCompressionFormat, string>> BuildAssetBundles(
|
||||
string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions,
|
||||
MobileTextureSubtarget baseTextureFormat, IEnumerable<MobileTextureSubtarget> additionalTextureFormats,
|
||||
bool allowClearDirectory)
|
||||
{
|
||||
if (builds == null || builds.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleBuild parameter cannot be null or empty");
|
||||
}
|
||||
|
||||
foreach (var build in builds)
|
||||
{
|
||||
if (!AndroidAppBundle.IsValidModuleName(build.assetBundleName))
|
||||
{
|
||||
throw new ArgumentException("Invalid AssetBundle name: " + build.assetBundleName);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
throw new ArgumentNullException("outputPath");
|
||||
}
|
||||
|
||||
CheckDirectory(outputPath, allowClearDirectory);
|
||||
|
||||
// Make unique and silently remove the base format, if it was present.
|
||||
var textureSubtargets = new HashSet<MobileTextureSubtarget>(
|
||||
additionalTextureFormats ?? Enumerable.Empty<MobileTextureSubtarget>());
|
||||
textureSubtargets.Remove(baseTextureFormat);
|
||||
|
||||
// Note: GetCompressionFormatSuffix() will throw for unsupported formats.
|
||||
var paths = textureSubtargets.Select(format => outputPath + GetCompressionFormatSuffix(format));
|
||||
foreach (var path in paths)
|
||||
{
|
||||
CheckDirectory(path, allowClearDirectory);
|
||||
}
|
||||
|
||||
// Throws if the base format is invalid.
|
||||
GetCompressionFormat(baseTextureFormat);
|
||||
|
||||
return BuildAssetBundlesInternal(
|
||||
outputPath, builds, assetBundleOptions, baseTextureFormat, textureSubtargets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run one or more AssetBundle builds for the specified device tiers.
|
||||
/// Notes about the <see cref="outputPath"/> parameter:
|
||||
/// - If a relative path is provided, the file paths in the returned AssetPackConfig will be relative paths.
|
||||
/// - If an absolute path is provided, the file paths in the returned object will be absolute paths.
|
||||
/// - AssetBundle builds for device tiers will be created in siblings of this directory. For
|
||||
/// example, for outputDirectory "a/b/c" and device tier HIGH, there will be a directory "a/b/c#tier_high".
|
||||
/// - If allowClearDirectory is false, this directory and any sibling directories must be empty or not exist,
|
||||
/// otherwise an exception is thrown.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The output directory for AssetBundles. See other notes above.</param>
|
||||
/// <param name="deliveryMode">A delivery mode to apply to every asset pack in the generated config.</param>
|
||||
/// <param name="deviceTierToBuilds">A dictionary from Device Tier to asset bundle builds. All device tiers
|
||||
/// should contains the same asset bundle names.</param>
|
||||
/// <param name="defaultDeviceTier">
|
||||
/// Default device tier to be used for standalone APKs. Set to zero if not specified.</param>
|
||||
/// <param name="assetBundleOptions">Options to pass to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="allowClearDirectory">Allows this method to clear the contents of the output directory.</param>
|
||||
/// <returns>An <see cref="AssetPackConfig"/> containing file paths to all generated AssetBundles.</returns>
|
||||
public static AssetPackConfig BuildAssetBundlesDeviceTier(
|
||||
string outputPath,
|
||||
AssetPackDeliveryMode deliveryMode,
|
||||
Dictionary<DeviceTier, AssetBundleBuild[]> deviceTierToBuilds,
|
||||
DeviceTier defaultDeviceTier = null,
|
||||
BuildAssetBundleOptions assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle,
|
||||
bool allowClearDirectory = false)
|
||||
{
|
||||
if (defaultDeviceTier == null)
|
||||
{
|
||||
defaultDeviceTier = DeviceTier.From(0);
|
||||
}
|
||||
|
||||
if (!deviceTierToBuilds.Keys.Contains(defaultDeviceTier))
|
||||
{
|
||||
throw new ArgumentException("Default device tier not present in deviceTierToBuilds.");
|
||||
}
|
||||
|
||||
var nameToDeviceTierToPath = BuildAssetBundlesDeviceTier(outputPath, deviceTierToBuilds,
|
||||
assetBundleOptions, allowClearDirectory);
|
||||
var assetPackConfig = new AssetPackConfig();
|
||||
foreach (var deviceTierToPath in nameToDeviceTierToPath.Values)
|
||||
{
|
||||
assetPackConfig.AddAssetBundles(deviceTierToPath, deliveryMode);
|
||||
}
|
||||
|
||||
assetPackConfig.DefaultDeviceTier = defaultDeviceTier;
|
||||
return assetPackConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run one or more AssetBundle builds for each device tier.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The output directory for base AssetBundles. See the other method for notes.</param>
|
||||
/// <param name="deviceTierToBuilds">A dictionary from Device Tier to asset bundle builds. All device tiers
|
||||
/// should contains the same asset bundle names.</param>
|
||||
/// <param name="assetBundleOptions">Options to pass to <see cref="BuildPipeline"/>.</param>
|
||||
/// <param name="allowClearDirectory">Allows this method to clear the contents of the output directory.</param>
|
||||
/// <returns>A dictionary from AssetBundle name to DeviceTier to file outputPath.</returns>
|
||||
public static Dictionary<string, Dictionary<DeviceTier, string>> BuildAssetBundlesDeviceTier(
|
||||
string outputPath, Dictionary<DeviceTier, AssetBundleBuild[]> deviceTierToBuilds,
|
||||
BuildAssetBundleOptions assetBundleOptions,
|
||||
bool allowClearDirectory)
|
||||
{
|
||||
if (deviceTierToBuilds == null || deviceTierToBuilds.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToBuild parameter cannot be null or empty");
|
||||
}
|
||||
|
||||
if (deviceTierToBuilds.Values.Any(builds => builds == null || builds.Length == 0))
|
||||
{
|
||||
throw new ArgumentException("AssetBundleBuilds value cannot be null or empty");
|
||||
}
|
||||
|
||||
var assetBundleNames = deviceTierToBuilds.Values.First().Select(build => build.assetBundleName).ToList();
|
||||
|
||||
if (deviceTierToBuilds.Values.Any(builds =>
|
||||
!(builds.Length == assetBundleNames.Count() &&
|
||||
builds.Select(build => build.assetBundleName).Except(assetBundleNames).ToList().Count == 0)))
|
||||
{
|
||||
throw new ArgumentException("AssetBundleBuild names cannot differ across device tiers.");
|
||||
}
|
||||
|
||||
foreach (var assetBundleName in assetBundleNames)
|
||||
{
|
||||
if (!AndroidAppBundle.IsValidModuleName(assetBundleName))
|
||||
{
|
||||
throw new ArgumentException("Invalid AssetBundle name: " + assetBundleName);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
throw new ArgumentNullException("outputPath");
|
||||
}
|
||||
|
||||
CheckDirectory(outputPath, allowClearDirectory);
|
||||
|
||||
var tiers = new HashSet<DeviceTier>(deviceTierToBuilds.Keys);
|
||||
var paths = tiers.Select(tier => outputPath + DeviceTierTargetingTools.GetTargetingSuffix(tier));
|
||||
foreach (var path in paths)
|
||||
{
|
||||
CheckDirectory(path, allowClearDirectory);
|
||||
}
|
||||
|
||||
return BuildAssetBundlesDeviceTierInternal(
|
||||
outputPath, assetBundleOptions, deviceTierToBuilds, assetBundleNames);
|
||||
}
|
||||
|
||||
|
||||
// The internal method assumes all parameter preconditions have already been checked.
|
||||
private static Dictionary<string, Dictionary<TextureCompressionFormat, string>> BuildAssetBundlesInternal(
|
||||
string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions,
|
||||
MobileTextureSubtarget baseTextureFormat, IEnumerable<MobileTextureSubtarget> textureSubtargets)
|
||||
{
|
||||
// Use a dictionary to capture the generated AssetBundles' file names.
|
||||
var nameToTextureFormatToPath = builds.ToDictionary(
|
||||
build => build.assetBundleName, _ => new Dictionary<TextureCompressionFormat, string>());
|
||||
|
||||
var originalAndroidBuildSubtarget = EditorUserBuildSettings.androidBuildSubtarget;
|
||||
try
|
||||
{
|
||||
// First build for the additional texture formats.
|
||||
foreach (var textureSubtarget in textureSubtargets)
|
||||
{
|
||||
// Build AssetBundles in the the base format's directory.
|
||||
EditorUserBuildSettings.androidBuildSubtarget = textureSubtarget;
|
||||
BuildAssetBundles(outputPath, builds, assetBundleOptions);
|
||||
|
||||
// Then move the files to a new directory with the compression format suffix.
|
||||
var outputPathWithSuffix = outputPath + GetCompressionFormatSuffix(textureSubtarget);
|
||||
Directory.Move(outputPath, outputPathWithSuffix);
|
||||
|
||||
var textureCompressionFormat = GetCompressionFormat(textureSubtarget);
|
||||
UpdateDictionary(nameToTextureFormatToPath, outputPathWithSuffix, textureCompressionFormat);
|
||||
}
|
||||
|
||||
// Then build for the base format.
|
||||
EditorUserBuildSettings.androidBuildSubtarget = baseTextureFormat;
|
||||
BuildAssetBundles(outputPath, builds, assetBundleOptions);
|
||||
|
||||
UpdateDictionary(nameToTextureFormatToPath, outputPath, TextureCompressionFormat.Default);
|
||||
return nameToTextureFormatToPath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUserBuildSettings.androidBuildSubtarget = originalAndroidBuildSubtarget;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<DeviceTier, string>> BuildAssetBundlesDeviceTierInternal(
|
||||
string outputPath, BuildAssetBundleOptions assetBundleOptions,
|
||||
Dictionary<DeviceTier, AssetBundleBuild[]> deviceTierToBuilds, IEnumerable<string> assetBundleNames)
|
||||
{
|
||||
// Use a dictionary to capture the generated AssetBundles' file names.
|
||||
var nameToDeviceTierToPath = assetBundleNames.ToDictionary(
|
||||
bundleName => bundleName, _ => new Dictionary<DeviceTier, string>());
|
||||
foreach (var deviceTier in deviceTierToBuilds.Keys)
|
||||
{
|
||||
var tierBuilds = deviceTierToBuilds[deviceTier];
|
||||
// Build AssetBundles in the the base format's directory.
|
||||
BuildAssetBundles(outputPath, tierBuilds, assetBundleOptions);
|
||||
|
||||
// Then move the files to a new directory with the device tier suffix.
|
||||
var outputPathWithSuffix = outputPath + DeviceTierTargetingTools.GetTargetingSuffix(deviceTier);
|
||||
Directory.Move(outputPath, outputPathWithSuffix);
|
||||
|
||||
UpdateDictionaryDeviceTier(nameToDeviceTierToPath, outputPathWithSuffix, deviceTier);
|
||||
}
|
||||
|
||||
return nameToDeviceTierToPath;
|
||||
}
|
||||
|
||||
private static void UpdateDictionaryDeviceTier(
|
||||
Dictionary<string, Dictionary<DeviceTier, string>> nameToDeviceTierToPath,
|
||||
string outputPath, DeviceTier deviceTier)
|
||||
{
|
||||
foreach (var entry in nameToDeviceTierToPath)
|
||||
{
|
||||
var filePath = Path.Combine(outputPath, entry.Key);
|
||||
entry.Value[deviceTier] = filePath;
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Missing AssetBundle file: " + filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void BuildAssetBundles(
|
||||
string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions)
|
||||
{
|
||||
// The output directory must exist before calling BuildAssetBundles(), so create it first.
|
||||
Directory.CreateDirectory(outputPath);
|
||||
var assetPackManifest =
|
||||
BuildPipeline.BuildAssetBundles(outputPath, builds, assetBundleOptions, BuildTarget.Android);
|
||||
if (assetPackManifest == null)
|
||||
{
|
||||
throw new InvalidOperationException("Error building AssetBundles");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateDictionary(
|
||||
Dictionary<string, Dictionary<TextureCompressionFormat, string>> nameToTextureFormatToPath,
|
||||
string outputPath, TextureCompressionFormat textureCompressionFormat)
|
||||
{
|
||||
foreach (var entry in nameToTextureFormatToPath)
|
||||
{
|
||||
var filePath = Path.Combine(outputPath, entry.Key);
|
||||
entry.Value[textureCompressionFormat] = filePath;
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Missing AssetBundle file: " + filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCompressionFormatSuffix(MobileTextureSubtarget subtarget)
|
||||
{
|
||||
var format = GetCompressionFormat(subtarget);
|
||||
return TextureTargetingTools.GetTargetingSuffix(format);
|
||||
}
|
||||
|
||||
private static TextureCompressionFormat GetCompressionFormat(MobileTextureSubtarget subtarget)
|
||||
{
|
||||
switch (subtarget)
|
||||
{
|
||||
case MobileTextureSubtarget.ASTC:
|
||||
return TextureCompressionFormat.Astc;
|
||||
case MobileTextureSubtarget.DXT:
|
||||
return TextureCompressionFormat.Dxt1;
|
||||
case MobileTextureSubtarget.ETC:
|
||||
return TextureCompressionFormat.Etc1;
|
||||
case MobileTextureSubtarget.ETC2:
|
||||
return TextureCompressionFormat.Etc2;
|
||||
case MobileTextureSubtarget.PVRTC:
|
||||
return TextureCompressionFormat.Pvrtc;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported MobileTextureSubtarget: " + subtarget);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckDirectory(string path, bool allowClearDirectory)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(path);
|
||||
if (!directoryInfo.Exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowClearDirectory && directoryInfo.GetFileSystemInfos().Any())
|
||||
{
|
||||
throw new ArgumentException("Directory is not empty: " + path);
|
||||
}
|
||||
|
||||
directoryInfo.Delete( /* recursive= */ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6103140ca35a146aebf90b0d5001cf9d
|
||||
timeCreated: 1582783480
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Options that configure the behavior of an asset-only app bundle build.
|
||||
/// </summary>
|
||||
public class AssetOnlyBuildOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="assetPackConfig">AssetPackConfig specifying which asset packs to include in the build.</param>>
|
||||
/// <exception cref="ArgumentException">Thrown if the AssetPackConfig contains install-time packs.</exception>
|
||||
public AssetOnlyBuildOptions(AssetPackConfig assetPackConfig)
|
||||
{
|
||||
foreach (var keyValue in assetPackConfig.AssetPacks)
|
||||
{
|
||||
var deliveryMode = keyValue.Value.DeliveryMode;
|
||||
if (deliveryMode == AssetPackDeliveryMode.InstallTime)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"AssetPackConfig contains install-time asset packs which are not supported in asset-only app bundles.");
|
||||
}
|
||||
}
|
||||
|
||||
AssetPackConfig = assetPackConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the AssetPackConfig to use for the build, or null.
|
||||
/// </summary>
|
||||
public AssetPackConfig AssetPackConfig { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version codes identifying which app versions should be served these asset packs.
|
||||
/// </summary>
|
||||
public IList<long> AppVersions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string that uniquely identifies this asset-only bundle.
|
||||
/// </summary>
|
||||
public string AssetVersionTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path where the asset only bundle will be built.
|
||||
/// </summary>
|
||||
public string LocationPathName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, forces the entire build to run on the main thread, potentially freezing the Editor UI during some
|
||||
/// build steps. This setting doesn't affect batch mode builds, which always run on the main thread.
|
||||
/// </summary>
|
||||
public bool ForceSingleThreadedBuild { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1bdcdd916a424a938fe54bd01767156
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains configuration about an asset pack including its <see cref="AssetPackDeliveryMode"/>
|
||||
/// and the location on disk of the files that should be included in the pack.
|
||||
/// </summary>
|
||||
public class AssetPack
|
||||
{
|
||||
private string _assetBundleFilePath;
|
||||
private string _assetPackDirectoryPath;
|
||||
private Dictionary<TextureCompressionFormat, string> _compressionFormatToAssetBundleFilePath;
|
||||
private Dictionary<TextureCompressionFormat, string> _compressionFormatToAssetPackDirectoryPath;
|
||||
private Dictionary<DeviceTier, string> _deviceTierToAssetBundleFilePath;
|
||||
private Dictionary<DeviceTier, string> _deviceTierToAssetPackDirectoryPath;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates how this asset pack will be delivered.
|
||||
/// </summary>
|
||||
public AssetPackDeliveryMode DeliveryMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Location on disk of a single AssetBundle file. This file can be fetched at runtime using the Play Asset
|
||||
/// Delivery APIs.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public string AssetBundleFilePath
|
||||
{
|
||||
get { return _assetBundleFilePath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_assetBundleFilePath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
_assetBundleFilePath = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location on disk of a folder containing raw asset files.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public string AssetPackDirectoryPath
|
||||
{
|
||||
get { return _assetPackDirectoryPath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_assetPackDirectoryPath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
_assetPackDirectoryPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from <see cref="TextureCompressionFormat"/> to the location on disk of a single AssetBundle file.
|
||||
/// The AssetBundles should be identical except for their texture compression format. When using Play
|
||||
/// Asset Delivery APIs, only the AssetBundle for the device's preferred texture compression format will
|
||||
/// be delivered.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public Dictionary<TextureCompressionFormat, string> CompressionFormatToAssetBundleFilePath
|
||||
{
|
||||
get { return _compressionFormatToAssetBundleFilePath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_compressionFormatToAssetBundleFilePath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_assetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
_compressionFormatToAssetBundleFilePath = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from <see cref="TextureCompressionFormat"/> to the location on disk of a folder containing
|
||||
/// raw asset files.
|
||||
/// When using Play Asset Delivery APIs, only the folder for the device's preferred texture compression format
|
||||
/// will be delivered.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public Dictionary<TextureCompressionFormat, string> CompressionFormatToAssetPackDirectoryPath
|
||||
{
|
||||
get { return _compressionFormatToAssetPackDirectoryPath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_compressionFormatToAssetPackDirectoryPath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_assetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_deviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
_compressionFormatToAssetPackDirectoryPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from <see cref="DeviceTier"/> to the location on disk of a single AssetBundle file.
|
||||
/// When using Play Asset Delivery APIs, only the AssetBundle for the device's tier will be delivered.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public Dictionary<DeviceTier, string> DeviceTierToAssetBundleFilePath
|
||||
{
|
||||
get { return _deviceTierToAssetBundleFilePath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_deviceTierToAssetBundleFilePath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_assetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
if (_deviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
_deviceTierToAssetBundleFilePath = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from <see cref="DeviceTier"/> to the location on disk of a folder containing
|
||||
/// raw asset files.
|
||||
/// When using Play Asset Delivery APIs, only the folder for the device's tier will be delivered.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown on set if any other path property is already set (non-null).
|
||||
/// </exception>
|
||||
public Dictionary<DeviceTier, string> DeviceTierToAssetPackDirectoryPath
|
||||
{
|
||||
get { return _deviceTierToAssetPackDirectoryPath; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
_deviceTierToAssetPackDirectoryPath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_assetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("AssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
if (_compressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
throw new ArgumentException("CompressionFormatToAssetPackDirectoryPath is already set.");
|
||||
}
|
||||
|
||||
|
||||
if (_deviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
throw new ArgumentException("DeviceTierToAssetBundleFilePath is already set.");
|
||||
}
|
||||
|
||||
_deviceTierToAssetPackDirectoryPath = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b308e30f8f2a419fa2d96ce08ab2697
|
||||
timeCreated: 1583258649
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for all asset packs that will be packaged in an Android App Bundle (AAB).
|
||||
/// </summary>
|
||||
public class AssetPackConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the assets in an AAB's base module should be split into a separate install-time asset
|
||||
/// pack. Usually when building a game, some assets are stored in the base module's "assets" directory.
|
||||
/// Games that run into APK size limits may have previously used the "Split Application Binary"
|
||||
/// option to create APK Expansion (*.obb) files, however this is unsupported with AABs.
|
||||
/// If Play Console indicates that the base module exceeds a download size limit, for example 150 MB,
|
||||
/// enabling this option may resolve the issue. See https://developer.android.com/guide/app-bundle.
|
||||
/// </summary>
|
||||
public bool SplitBaseModuleAssets;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from asset pack name to <see cref="AssetPack"/> object containing info such as delivery mode.
|
||||
/// Note: asset pack names must start with a letter and can contain only letters, numbers, and underscores.
|
||||
/// Note: convenience methods such as <see cref="AddAssetBundle"/> are recommended to more safely add new
|
||||
/// Asset packs, but this dictionary is available for direct modification for advanced use cases.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, AssetPack> AssetPacks = new Dictionary<string, AssetPack>();
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary containing the subset of <see cref="AssetPacks"/> that are marked for delivery.
|
||||
/// Note: the returned Dictionary doesn't indicate whether <see cref="SplitBaseModuleAssets"/> is enabled.
|
||||
/// </summary>
|
||||
public Dictionary<string, AssetPack> DeliveredAssetPacks
|
||||
{
|
||||
get
|
||||
{
|
||||
return AssetPacks
|
||||
.Where(pair => pair.Value.DeliveryMode != AssetPackDeliveryMode.DoNotPackage)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When asset packs for multiple texture compression formats are present, this specifies the format used
|
||||
/// when building standalone APKs for Android pre-Lollipop devices.
|
||||
/// </summary>
|
||||
public TextureCompressionFormat DefaultTextureCompressionFormat = TextureCompressionFormat.Default;
|
||||
|
||||
/// <summary>
|
||||
/// When asset packs for multiple device tiers are present, this specifies the tier used
|
||||
/// when building standalone APKs for Android pre-Lollipop devices.
|
||||
/// </summary>
|
||||
public DeviceTier DefaultDeviceTier = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this configuration includes at least 1 asset pack that may be packaged in an AAB.
|
||||
/// Return true if <see cref="SplitBaseModuleAssets"/> is enabled, even if there are no other asset packs.
|
||||
/// </summary>
|
||||
public bool HasDeliveredAssetPacks()
|
||||
{
|
||||
return SplitBaseModuleAssets || DeliveredAssetPacks.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package the specified AssetBundle file in its own <see cref="AssetPack"/> with the specified delivery mode.
|
||||
/// The name of the created asset pack will match that of the specified AssetBundle file.
|
||||
/// </summary>
|
||||
/// <param name="assetBundleFilePath">The path to a single AssetBundle file.</param>
|
||||
/// <param name="deliveryMode">The <see cref="AssetPackDeliveryMode"/> for the asset pack.</param>
|
||||
/// <exception cref="ArgumentException">If the asset pack name is invalid.</exception>
|
||||
/// <exception cref="FileNotFoundException">If the AssetBundle file doesn't exist.</exception>
|
||||
public void AddAssetBundle(string assetBundleFilePath, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
var assetPackName = GetAssetPackName(assetBundleFilePath);
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
AssetBundleFilePath = assetBundleFilePath
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package all raw assets in the specified folder in an <see cref="AssetPack"/> with the specified name and
|
||||
/// using the specified delivery mode.
|
||||
/// </summary>
|
||||
/// <param name="assetPackName">The name of the asset pack.</param>
|
||||
/// <param name="assetsFolderPath">
|
||||
/// The path to a directory whose files will be directly copied into the asset pack during app bundle creation.
|
||||
/// </param>
|
||||
/// <param name="deliveryMode">The <see cref="AssetPackDeliveryMode"/> for the asset pack.</param>
|
||||
/// <exception cref="ArgumentException">If the <see cref="assetPackName"/> is invalid.</exception>
|
||||
/// <exception cref="FileNotFoundException">If the <see cref="assetsFolderPath"/> doesn't exist.</exception>
|
||||
public void AddAssetsFolder(string assetPackName, string assetsFolderPath, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(assetsFolderPath);
|
||||
if (!directoryInfo.Exists)
|
||||
{
|
||||
throw new FileNotFoundException("Asset pack directory doesn't exist", assetsFolderPath);
|
||||
}
|
||||
|
||||
CheckAssetPackName(assetPackName);
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
AssetPackDirectoryPath = assetsFolderPath
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package the specified raw assets in the specified folders, keyed by <see cref="TextureCompressionFormat"/>,
|
||||
/// in an <see cref="AssetPack"/> with the specified delivery mode.
|
||||
/// When using Play Asset Delivery APIs, only the folder for the device's preferred texture compression format
|
||||
/// will be delivered.
|
||||
/// </summary>
|
||||
/// <param name="assetPackName">The name of the asset pack.</param>
|
||||
/// <param name="compressionFormatToAssetPackDirectoryPath">
|
||||
/// A dictionary from <see cref="TextureCompressionFormat"/> to the path of directories of files that will be
|
||||
/// directly copied into the asset pack during app bundle creation.</param>
|
||||
/// <param name="deliveryMode">The <see cref="AssetPackDeliveryMode"/> for the asset pack.</param>
|
||||
/// <exception cref="ArgumentException">If the dictionary or asset pack name is invalid.</exception>
|
||||
public void AddAssetsFolders(
|
||||
string assetPackName,
|
||||
IDictionary<TextureCompressionFormat, string> compressionFormatToAssetPackDirectoryPath,
|
||||
AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
if (compressionFormatToAssetPackDirectoryPath.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one path");
|
||||
}
|
||||
|
||||
if (compressionFormatToAssetPackDirectoryPath.All(kvp => kvp.Key != TextureCompressionFormat.Default))
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one Default compression path");
|
||||
}
|
||||
|
||||
CheckAssetPackName(assetPackName);
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
CompressionFormatToAssetPackDirectoryPath =
|
||||
new Dictionary<TextureCompressionFormat, string>(compressionFormatToAssetPackDirectoryPath)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package the specified AssetBundle files, which vary only by <see cref="TextureCompressionFormat"/>, in an
|
||||
/// <see cref="AssetPack"/> with the specified delivery mode.
|
||||
/// When using Play Asset Delivery APIs, only the AssetBundle for the device's preferred texture compression
|
||||
/// format will be delivered.
|
||||
/// </summary>
|
||||
/// <param name="compressionFormatToAssetBundleFilePath">
|
||||
/// A dictionary from <see cref="TextureCompressionFormat"/> to AssetBundle files.</param>
|
||||
/// <param name="deliveryMode">The <see cref="AssetPackDeliveryMode"/> for the asset pack.</param>
|
||||
/// <exception cref="ArgumentException">If the dictionary or asset pack name is invalid.</exception>
|
||||
/// <exception cref="FileNotFoundException">If any AssetBundle file doesn't exist.</exception>
|
||||
public void AddAssetBundles(
|
||||
IDictionary<TextureCompressionFormat, string> compressionFormatToAssetBundleFilePath,
|
||||
AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
if (compressionFormatToAssetBundleFilePath.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one AssetBundle");
|
||||
}
|
||||
|
||||
if (compressionFormatToAssetBundleFilePath.All(kvp => kvp.Key != TextureCompressionFormat.Default))
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one Default compression AssetBundle");
|
||||
}
|
||||
|
||||
var assetPackName = GetAssetPackName(compressionFormatToAssetBundleFilePath.Values.First());
|
||||
if (compressionFormatToAssetBundleFilePath.Any(kvp => assetPackName != GetAssetPackName(kvp.Value)))
|
||||
{
|
||||
throw new ArgumentException("All AssetBundles in the Dictionary must have the same name");
|
||||
}
|
||||
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
CompressionFormatToAssetBundleFilePath =
|
||||
new Dictionary<TextureCompressionFormat, string>(compressionFormatToAssetBundleFilePath)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package the specified raw assets in the specified folders, keyed by <see cref="DeviceTier"/>,
|
||||
/// in an <see cref="AssetPack"/> with the specified delivery mode.
|
||||
/// When using Play Asset Delivery APIs, only the AssetBundle for the device's tier will be delivered.
|
||||
/// </summary>
|
||||
public void AddAssetsFolders(
|
||||
string assetPackName,
|
||||
IDictionary<DeviceTier, string> deviceTierToAssetPackDirectoryPath,
|
||||
AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
if (deviceTierToAssetPackDirectoryPath.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one path");
|
||||
}
|
||||
|
||||
CheckAssetPackName(assetPackName);
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
DeviceTierToAssetPackDirectoryPath =
|
||||
new Dictionary<DeviceTier, string>(deviceTierToAssetPackDirectoryPath)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package the specified AssetBundle files, which vary only by <see cref="DeviceTier"/>, in an
|
||||
/// <see cref="AssetPack"/> with the specified delivery mode.
|
||||
/// When using Play Asset Delivery APIs, only the AssetBundle for the device's tier will be delivered.
|
||||
/// </summary>
|
||||
public void AddAssetBundles(
|
||||
IDictionary<DeviceTier, string> deviceTierToAssetBundleFilePath,
|
||||
AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
if (deviceTierToAssetBundleFilePath.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Dictionary should contain at least one AssetBundle");
|
||||
;
|
||||
}
|
||||
|
||||
var assetPackName = GetAssetPackName(deviceTierToAssetBundleFilePath.Values.First());
|
||||
if (deviceTierToAssetBundleFilePath.Any(kvp => assetPackName != GetAssetPackName(kvp.Value)))
|
||||
{
|
||||
throw new ArgumentException("All AssetBundles in the Dictionary must have the same name");
|
||||
}
|
||||
|
||||
AssetPacks[assetPackName] = new AssetPack
|
||||
{
|
||||
DeliveryMode = deliveryMode,
|
||||
DeviceTierToAssetBundleFilePath =
|
||||
new Dictionary<DeviceTier, string>(deviceTierToAssetBundleFilePath)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static string GetAssetPackName(string assetBundleFilePath)
|
||||
{
|
||||
var fileInfo = new FileInfo(assetBundleFilePath);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
throw new FileNotFoundException("AssetBundle file doesn't exist", assetBundleFilePath);
|
||||
}
|
||||
|
||||
var assetPackName = fileInfo.Name;
|
||||
CheckAssetPackName(assetPackName);
|
||||
return assetPackName;
|
||||
}
|
||||
|
||||
private static void CheckAssetPackName(string assetPackName)
|
||||
{
|
||||
if (!AndroidAppBundle.IsValidModuleName(assetPackName))
|
||||
{
|
||||
throw new ArgumentException("Invalid asset pack name: " + assetPackName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0d123655d73c4856b6f7c6790c62082
|
||||
timeCreated: 1582764640
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.IO;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for serializing <see cref="AssetPackConfig"/> to a JSON file and for deserializing the config.
|
||||
/// </summary>
|
||||
public static class AssetPackConfigSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Save the specified <see cref="AssetPackConfig"/> config to disk.
|
||||
/// </summary>
|
||||
public static void SaveConfig(
|
||||
AssetPackConfig assetPackConfig, string configurationFilePath = SerializationHelper.ConfigurationFilePath)
|
||||
{
|
||||
Debug.LogFormat("Saving {0}", configurationFilePath);
|
||||
var config = SerializationHelper.Serialize(assetPackConfig);
|
||||
var jsonText = JsonUtility.ToJson(config);
|
||||
File.WriteAllText(configurationFilePath, jsonText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="AssetPackConfig"/> loaded from disk.
|
||||
/// </summary>
|
||||
public static AssetPackConfig LoadConfig()
|
||||
{
|
||||
if (!File.Exists(SerializationHelper.ConfigurationFilePath))
|
||||
{
|
||||
Debug.LogFormat("Creating new config {0}", SerializationHelper.ConfigurationFilePath);
|
||||
return new AssetPackConfig();
|
||||
}
|
||||
|
||||
Debug.LogFormat("Loading config {0}", SerializationHelper.ConfigurationFilePath);
|
||||
var jsonText = File.ReadAllText(SerializationHelper.ConfigurationFilePath);
|
||||
var config = JsonUtility.FromJson<SerializableAssetPackConfig>(jsonText);
|
||||
return SerializationHelper.Deserialize(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="AssetPackConfig"/> loaded from the specified file on disk.
|
||||
/// </summary>
|
||||
public static AssetPackConfig LoadConfig(string configurationFilePath)
|
||||
{
|
||||
Debug.LogFormat("Loading custom config {0}", configurationFilePath);
|
||||
if (!File.Exists(configurationFilePath))
|
||||
{
|
||||
throw new FileNotFoundException("AssetPackConfig not found", configurationFilePath);
|
||||
}
|
||||
|
||||
var jsonText = File.ReadAllText(configurationFilePath);
|
||||
var config = JsonUtility.FromJson<SerializableAssetPackConfig>(jsonText);
|
||||
return SerializationHelper.Deserialize(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete config file.
|
||||
/// </summary>
|
||||
public static void DeleteConfig()
|
||||
{
|
||||
File.Delete(SerializationHelper.ConfigurationFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f277e21537064a51a435fbc4d4c713a
|
||||
timeCreated: 1582761043
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether an asset pack will be included in an Android App Bundle, and if so how it is delivered.
|
||||
/// </summary>
|
||||
public enum AssetPackDeliveryMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not include the corresponding asset pack in the Android App Bundle.
|
||||
/// </summary>
|
||||
DoNotPackage = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Deliver the corresponding asset pack when the app is installed.
|
||||
/// </summary>
|
||||
InstallTime = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Deliver the corresponding asset pack immediately after the app is installed and potentially before it is
|
||||
/// opened.
|
||||
/// </summary>
|
||||
FastFollow = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Only deliver the corresponding asset pack when it is explicitly requested by the app.
|
||||
/// </summary>
|
||||
OnDemand = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d15a50545f1c4c4586351a1e698bfdc
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal;
|
||||
using Google.Android.AppBundle.Editor.Internal.BuildTools;
|
||||
using UnityEditor;
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#endif
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build <a href="https://developer.android.com/platform/technology/app-bundle/">Android App Bundle</a>
|
||||
/// files suitable for publishing on <a href="https://play.google.com/console/">Google Play Console</a>.
|
||||
/// </summary>
|
||||
public static class Bundletool
|
||||
{
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle using the specified build options.
|
||||
///
|
||||
/// Before calling this method use PlayInstantBuildSettings.SetInstantBuildType() to decide whether to build an
|
||||
/// instant app bundle or a regular app bundle for installed apps.
|
||||
///
|
||||
/// This method returns an async Task because it partially runs on a background thread (except in Batch Mode).
|
||||
/// As with other methods that return async tasks, the result can be awaited on or accessed via ContinueWith().
|
||||
/// </summary>
|
||||
/// <param name="androidBuildOptions">Configuration options for the build.</param>
|
||||
/// <returns>An async task that provides an AndroidBuildReport.</returns>
|
||||
/// <exception cref="AndroidBuildException">
|
||||
/// Thrown in case of certain build failures. Includes an AndroidBuildReport when thrown.
|
||||
/// </exception>
|
||||
public static async Task<AndroidBuildReport> BuildBundle(AndroidBuildOptions androidBuildOptions)
|
||||
{
|
||||
return await AppBundlePublisher.BuildTask(androidBuildOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build an App Bundle containing only the specified asset packs.
|
||||
/// </summary>
|
||||
/// <param name="assetOnlyBuildOptions">
|
||||
/// Configuration options for the build, including what asset packs to include in the Android App Bundle.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the build completed successfully and false otherwise.
|
||||
/// </returns>
|
||||
public static async Task BuildAssetOnlyBundle(AssetOnlyBuildOptions assetOnlyBuildOptions)
|
||||
{
|
||||
await AppBundlePublisher.BuildAssetOnlyBundle(assetOnlyBuildOptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle using the specified build options.
|
||||
///
|
||||
/// Before calling this method use PlayInstantBuildSettings.SetInstantBuildType() to decide whether to build an
|
||||
/// instant app bundle or a regular app bundle for installed apps.
|
||||
///
|
||||
/// If this method is invoked in Batch Mode, for example from a command line build, then the entire build will
|
||||
/// run on the main thread. In this case the <see cref="forceSynchronousBuild"/> parameter has no effect.
|
||||
///
|
||||
/// If this method is invoked by a script running from an interactive Editor UI, some of the build will run on
|
||||
/// a background thread and this method will return before the full build is complete. This behavior can be
|
||||
/// overridden by setting the <see cref="forceSynchronousBuild"/> parameter to true; in this case the entire
|
||||
/// build will run on the main thread. Note that this freezes the Editor UI.
|
||||
///
|
||||
/// The asynchronous BuildBundle() method method should be preferred when building on Unity 2018.4 or higher.
|
||||
/// </summary>
|
||||
/// <param name="buildPlayerOptions">A Unity BuildPlayerOptions including the output file path.</param>
|
||||
/// <param name="assetPackConfig">The asset packs to include in the Android App Bundle, if any.</param>
|
||||
/// <param name="forceSynchronousBuild">If true, the build should only run on the main thread.</param>
|
||||
/// <returns>
|
||||
/// True if the build succeeded or began running in the background, false if it failed or was cancelled.
|
||||
/// </returns>
|
||||
public static bool BuildBundle(
|
||||
BuildPlayerOptions buildPlayerOptions,
|
||||
AssetPackConfig assetPackConfig = null,
|
||||
bool forceSynchronousBuild = false)
|
||||
{
|
||||
return AppBundlePublisher.Build(buildPlayerOptions, assetPackConfig, forceSynchronousBuild);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an APK Set (.apks) file from the specified Android App Bundle file (.aab).
|
||||
/// </summary>
|
||||
/// <param name="aabFilePath">An .aab file.</param>
|
||||
/// <param name="apksFilePath">An .apks output ZIP file containing APK files.</param>
|
||||
/// <param name="buildMode">The type of APKs to build from the Android App Bundle.</param>
|
||||
/// <param name="enableLocalTesting">
|
||||
/// If true, enables a testing mode where fast-follow and on-demand asset packs are fetched from local storage
|
||||
/// rather than downloaded.
|
||||
/// </param>
|
||||
/// <returns>An error message if there was a problem running bundletool, or null if successful.</returns>
|
||||
public static string BuildApks(
|
||||
string aabFilePath,
|
||||
string apksFilePath,
|
||||
BundletoolBuildMode buildMode = BundletoolBuildMode.Default,
|
||||
bool enableLocalTesting = false)
|
||||
{
|
||||
var bundletoolHelper = new BundletoolHelper(new JavaUtils());
|
||||
if (bundletoolHelper.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
return bundletoolHelper.BuildApkSet(aabFilePath, apksFilePath, buildMode, enableLocalTesting);
|
||||
}
|
||||
|
||||
return "Failed to initialize bundletool.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 801cb4138f93a4a9e9d7dfd5926fefba
|
||||
timeCreated: 1581697831
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the build mode when building an APK Set with <see cref="Bundletool"/>.
|
||||
/// </summary>
|
||||
public enum BundletoolBuildMode
|
||||
{
|
||||
Default,
|
||||
Universal,
|
||||
System,
|
||||
SystemCompressed,
|
||||
Persistent,
|
||||
Instant
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 316269c5a97a14720bbcfb91400cf2a8
|
||||
timeCreated: 1581700818
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Options that override the default Android App Bundle (AAB) compression settings for base APKs and install-time
|
||||
/// asset pack APKs. The asset files in on-demand and fast-follow asset packs are always stored uncompressed.
|
||||
///
|
||||
/// The default values for these options lead to APKs containing uncompressed files. This generally results in
|
||||
/// faster APK downloads, smaller update patches, and straightforward asset loading. These settings should
|
||||
/// generally only be changed in advanced use cases when the top priority is reducing on-disk APK size.
|
||||
/// </summary>
|
||||
public class CompressionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of file globs for matching files that should be kept uncompressed within the base APK or
|
||||
/// install-time asset pack APKs. Matching occurs against the path of files within a generated APK using
|
||||
/// forward slash ('/') as a separator, for example "assets/**/*.txt".
|
||||
/// </summary>
|
||||
public List<string> UncompressedGlobs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the compression strategy for asset files in install-time asset packs. By default (when false),
|
||||
/// asset files are stored uncompressed in the generated install-time asset pack APKs. If this setting is
|
||||
/// enabled and a file matches one of the <see cref="UncompressedGlobs"/>, the <see cref="UncompressedGlobs"/>
|
||||
/// setting takes precedence and the file is stored uncompressed in the generated APK.
|
||||
/// </summary>
|
||||
public bool CompressInstallTimeAssetPacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the compression strategy for asset files added to an AAB from the
|
||||
/// <a href="https://docs.unity3d.com/Manual/StreamingAssets.html">StreamingAssets folder</a>.
|
||||
/// By default (when false), the asset files are stored uncompressed in both the generated base APK and any
|
||||
/// install-time asset pack APKs. If this setting is enabled and a file matches one of the
|
||||
/// <see cref="UncompressedGlobs"/>, the <see cref="UncompressedGlobs"/> takes
|
||||
/// precedence and the file is stored uncompressed in the generated APK.
|
||||
/// </summary>
|
||||
public bool CompressStreamingAssets { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 874234f05df734fa594e364198f4e4ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// The device tier labels that can be recognized in an asset pack folder name.
|
||||
/// </summary>
|
||||
public class DeviceTier
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory method.
|
||||
/// </summary>
|
||||
/// <param name="tier">The tier level.</param>>
|
||||
/// <exception cref="ArgumentException">Thrown if the level is a negative int.</exception>
|
||||
public static DeviceTier From(int tier)
|
||||
{
|
||||
return new DeviceTier(tier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tier level.
|
||||
///
|
||||
/// Device tier levels determines the priority of what variation of app content gets
|
||||
/// served to a specific device, for device-targeted content.
|
||||
/// Tiers are evaluated from the highest to the lowest level; the highest
|
||||
/// tier matching a given device is selected for that device.
|
||||
/// </summary>
|
||||
public int Tier { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implicit conversion from DeviceTier to int.
|
||||
/// </summary>
|
||||
public static implicit operator int(DeviceTier deviceTier)
|
||||
{
|
||||
return deviceTier.Tier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicit conversion from int to DeviceTier.
|
||||
/// </summary>
|
||||
public static implicit operator DeviceTier(int tier)
|
||||
{
|
||||
return new DeviceTier(tier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToString conversion.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return Convert.ToString(Tier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equal comparision override for DeviceTier objects.
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
var otherTier = other as DeviceTier;
|
||||
if (otherTier != null)
|
||||
{
|
||||
return Tier == otherTier.Tier;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates hash code for DeviceTier object from underlying tier value.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Tier.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tier">The tier level.</param>>
|
||||
/// <exception cref="ArgumentException">Thrown if the level is a negative int.</exception>
|
||||
private DeviceTier(int tier)
|
||||
{
|
||||
if (tier < 0)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
String.Format("Tier levels are required to be non-negative integers, but found {0}.", tier));
|
||||
}
|
||||
|
||||
Tier = tier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9a5886ee61a408c939351e7a04afe20
|
||||
timeCreated: 1602848407
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7633a19d17e4c452a9c98b305f25746e
|
||||
folderAsset: yes
|
||||
timeCreated: 1580752771
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities for checking whether this version of Unity supports building ARM64 libraries.
|
||||
/// </summary>
|
||||
public static class AndroidArchitectureHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the current status of architecture-related build settings.
|
||||
/// </summary>
|
||||
public enum Status
|
||||
{
|
||||
/// <summary>
|
||||
/// No issues with selected architectures.
|
||||
/// </summary>
|
||||
Ok,
|
||||
|
||||
/// <summary>
|
||||
/// ARMv7 is disabled.
|
||||
/// </summary>
|
||||
ArmV7Disabled,
|
||||
|
||||
/// <summary>
|
||||
/// IL2CPP is disabled, also implying that ARM64 is unavailable.
|
||||
/// </summary>
|
||||
Il2CppDisabled,
|
||||
|
||||
/// <summary>
|
||||
/// IL2CPP is enabled, but ARM64 is disabled.
|
||||
/// </summary>
|
||||
Arm64Disabled
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this version of the Unity Editor has native support for building an Android App Bundle,
|
||||
/// and false otherwise.
|
||||
/// </summary>
|
||||
public static Status ArchitectureStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsArchitectureEnabled(AndroidArchitecture.ARMv7))
|
||||
{
|
||||
return Status.ArmV7Disabled;
|
||||
}
|
||||
|
||||
if (PlayerSettings.GetScriptingBackend(BuildTargetGroup.Android) != ScriptingImplementation.IL2CPP)
|
||||
{
|
||||
return Status.Il2CppDisabled;
|
||||
}
|
||||
|
||||
return IsArchitectureEnabled(AndroidArchitecture.ARM64)
|
||||
? Status.Ok
|
||||
: Status.Arm64Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables an IL2CPP build of ARMv7 and ARM64 without affecting whether X86 is enabled.
|
||||
/// IL2CPP is required since Unity's Mono scripting backend doesn't support ARM64.
|
||||
/// </summary>
|
||||
public static void FixTargetArchitectures()
|
||||
{
|
||||
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
|
||||
PlayerSettings.Android.targetArchitectures |= AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the IL2CPP scripting backend and all Android architectures supported by IL2CPP builds.
|
||||
/// </summary>
|
||||
public static void EnableIl2CppBuildArchitectures()
|
||||
{
|
||||
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
|
||||
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the Mono scripting backend and all Android architectures supported by Mono builds.
|
||||
/// Note: Unity 2019.4.31+ only supports x86 for IL2CPP builds.
|
||||
/// </summary>
|
||||
public static void EnableMonoBuildArchitectures()
|
||||
{
|
||||
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x);
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7;
|
||||
#else
|
||||
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.X86;
|
||||
#endif
|
||||
}
|
||||
|
||||
private static bool IsArchitectureEnabled(AndroidArchitecture androidArchitecture)
|
||||
{
|
||||
// Note: Enum.HasFlag() was introduced in .NET 4.x
|
||||
return (PlayerSettings.Android.targetArchitectures & androidArchitecture) == androidArchitecture;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f33b5abf4e34544e597d9b43a21c8741
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dac17760a2fd2434188c3671e822fcc2
|
||||
folderAsset: yes
|
||||
timeCreated: 1540331103
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Google.Android.AppBundle.Editor.AssetPacks;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AndroidManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class for creating asset pack AndroidManifest xml documents.
|
||||
/// </summary>
|
||||
public static class AssetPackManifestHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new XDocument representing the AndroidManifest.xml for an asset pack.
|
||||
/// </summary>
|
||||
/// <param name="packageName">Package name of this application.</param>
|
||||
/// <param name="assetPackName">The name of the asset pack.</param>
|
||||
/// <param name="deliveryMode">The <see cref="AssetPackDeliveryMode"/> of this asset pack.</param>
|
||||
public static XDocument CreateAssetPackManifestXDocument(
|
||||
string packageName, string assetPackName, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
// TODO(b/129943210): Add support for <dist:instant-delivery>
|
||||
var deliveryTypeXName = ManifestConstants.DistDeliveryXName;
|
||||
XName deliveryModeXName;
|
||||
switch (deliveryMode)
|
||||
{
|
||||
case AssetPackDeliveryMode.OnDemand:
|
||||
deliveryModeXName = ManifestConstants.DistOnDemandXName;
|
||||
break;
|
||||
case AssetPackDeliveryMode.InstallTime:
|
||||
deliveryModeXName = ManifestConstants.DistInstallTimeName;
|
||||
break;
|
||||
case AssetPackDeliveryMode.FastFollow:
|
||||
deliveryModeXName = ManifestConstants.DistFastFollowName;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unexpected delivery mode: " + deliveryMode, "deliveryMode");
|
||||
}
|
||||
|
||||
var moduleElement = new XElement(
|
||||
ManifestConstants.DistModuleXName,
|
||||
new XAttribute(ManifestConstants.DistTypeXName, ManifestConstants.AssetPack),
|
||||
new XElement(deliveryTypeXName,
|
||||
new XElement(deliveryModeXName)),
|
||||
new XElement(ManifestConstants.DistFusingXName,
|
||||
new XAttribute(ManifestConstants.DistIncludeXName, ManifestConstants.ValueTrue))
|
||||
);
|
||||
|
||||
return new XDocument(new XElement(
|
||||
ManifestConstants.Manifest,
|
||||
new XAttribute(ManifestConstants.AndroidXmlns, XNamespace.Get(ManifestConstants.AndroidNamespaceUrl)),
|
||||
new XAttribute(ManifestConstants.DistXmlns, XNamespace.Get(ManifestConstants.DistNamespaceUrl)),
|
||||
new XAttribute(ManifestConstants.Package, packageName),
|
||||
new XAttribute(ManifestConstants.Split, assetPackName),
|
||||
moduleElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3c4c13f33e6f48dc9e8130d699f1428
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Xml.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.BuildTools;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AndroidManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an interface to modify the AndroidManifest xml documents configured for asset packs.
|
||||
/// </summary>
|
||||
public interface IAssetPackManifestTransformer : IBuildTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Modifies the specified AndroidManifest xml document. Returns an error message if the operation fails and
|
||||
/// null otherwise.
|
||||
/// </summary>
|
||||
string Transform(XDocument document);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12432c2ade03483e9e956f11ddde2992
|
||||
timeCreated: 1574716612
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AndroidManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// String constants used by AndroidManifest.xml files.
|
||||
/// </summary>
|
||||
public static class ManifestConstants
|
||||
{
|
||||
public const string Action = "action";
|
||||
public const string Activity = "activity";
|
||||
public const string Application = "application";
|
||||
public const string AssetPack = "asset-pack";
|
||||
public const string Category = "category";
|
||||
public const string Data = "data";
|
||||
public const string IntentFilter = "intent-filter";
|
||||
public const string Manifest = "manifest";
|
||||
public const string Package = "package";
|
||||
public const string Service = "service";
|
||||
public const string Split = "split";
|
||||
public const string ValueTrue = "true";
|
||||
|
||||
public const string AndroidNamespaceAlias = "android";
|
||||
public const string AndroidNamespaceUrl = "http://schemas.android.com/apk/res/android";
|
||||
public static readonly XName AndroidXmlns = XNamespace.Xmlns + AndroidNamespaceAlias;
|
||||
public static readonly XName AndroidEnabledXName = XName.Get("enabled", AndroidNamespaceUrl);
|
||||
public static readonly XName AndroidIconXName = XName.Get("icon", AndroidNamespaceUrl);
|
||||
public static readonly XName AndroidLabelXName = XName.Get("label", AndroidNamespaceUrl);
|
||||
public static readonly XName AndroidNameXName = XName.Get("name", AndroidNamespaceUrl);
|
||||
|
||||
public static readonly XName AndroidTargetSandboxVersionXName =
|
||||
XName.Get("targetSandboxVersion", AndroidNamespaceUrl);
|
||||
|
||||
public const string DistNamespaceAlias = "dist";
|
||||
public const string DistNamespaceUrl = "http://schemas.android.com/apk/distribution";
|
||||
public static readonly XName DistXmlns = XNamespace.Xmlns + DistNamespaceAlias;
|
||||
public static readonly XName DistDeliveryXName = XName.Get("delivery", DistNamespaceUrl);
|
||||
public static readonly XName DistFastFollowName = XName.Get("fast-follow", DistNamespaceUrl);
|
||||
public static readonly XName DistFusingXName = XName.Get("fusing", DistNamespaceUrl);
|
||||
public static readonly XName DistIncludeXName = XName.Get("include", DistNamespaceUrl);
|
||||
public static readonly XName DistInstallTimeName = XName.Get("install-time", DistNamespaceUrl);
|
||||
public static readonly XName DistInstantXName = XName.Get("instant", DistNamespaceUrl);
|
||||
public static readonly XName DistModuleXName = XName.Get("module", DistNamespaceUrl);
|
||||
public static readonly XName DistOnDemandXName = XName.Get("on-demand", DistNamespaceUrl);
|
||||
public static readonly XName DistTypeXName = XName.Get("type", DistNamespaceUrl);
|
||||
|
||||
public const string ToolsNamespaceAlias = "tools";
|
||||
public const string ToolsNamespaceUrl = "http://schemas.android.com/tools";
|
||||
public static readonly XName ToolsXmlns = XNamespace.Xmlns + ToolsNamespaceAlias;
|
||||
public static readonly XName ToolsReplaceXName = XName.Get("replace", ToolsNamespaceUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16b5ed2898b684f67ab9286244de2e19
|
||||
timeCreated: 1556563916
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal.AssetPacks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides "Android App Bundle" and Android build related menu items for the Unity Editor.
|
||||
/// </summary>
|
||||
public static class AppBundleEditorMenu
|
||||
{
|
||||
private const string AppBundleMenuName = GoogleEditorMenu.MainMenuName + "/Android App Bundle/";
|
||||
private const string RootMenuName = GoogleEditorMenu.MainMenuName + "/";
|
||||
|
||||
private const int AboveLine = GoogleEditorMenu.AndroidAppBundlePriority;
|
||||
private const int BelowLine = GoogleEditorMenu.AndroidAppBundlePriority + GoogleEditorMenu.SeparatorSize;
|
||||
|
||||
[MenuItem(AppBundleMenuName + "Asset Delivery Settings...", false, AboveLine)]
|
||||
private static void ShowAssetDeliveryWindow()
|
||||
{
|
||||
AssetDeliveryWindow.ShowWindow();
|
||||
}
|
||||
|
||||
[MenuItem(AppBundleMenuName + GoogleEditorMenu.BuildSettings, false, AboveLine + 1)]
|
||||
private static void OpenEditorSettings()
|
||||
{
|
||||
BuildSettingsWindow.ShowWindow();
|
||||
}
|
||||
|
||||
[MenuItem(AppBundleMenuName + GoogleEditorMenu.ViewDocumentation, false, BelowLine)]
|
||||
private static void ViewDocumentation()
|
||||
{
|
||||
Application.OpenURL("https://developer.android.com/guide/app-bundle/asset-delivery/build-unity");
|
||||
}
|
||||
|
||||
[MenuItem(AppBundleMenuName + GoogleEditorMenu.ViewLicense, false, BelowLine + 1)]
|
||||
private static void ViewLicense()
|
||||
{
|
||||
// The guid is for GooglePlayPlugins/com.google.android.appbundle/LICENSE.md
|
||||
GoogleEditorMenu.OpenFileByGuid("4b995f494568e47259d526c5211d778b");
|
||||
}
|
||||
|
||||
[MenuItem(AppBundleMenuName + GoogleEditorMenu.FileBug, false, BelowLine + 2)]
|
||||
private static void ViewPlayPluginsIssuesPage()
|
||||
{
|
||||
GoogleEditorMenu.ViewPlayPluginsIssuesPage();
|
||||
}
|
||||
|
||||
[MenuItem(RootMenuName + "Build Android App Bundle...", false, GoogleEditorMenu.RootMenuPriority + 10)]
|
||||
private static void BuildAndroidAppBundle()
|
||||
{
|
||||
AppBundlePublisher.Build();
|
||||
}
|
||||
|
||||
[MenuItem(RootMenuName + "Build and Run #%r", false, GoogleEditorMenu.RootMenuPriority + 11)]
|
||||
private static void BuildAndRun()
|
||||
{
|
||||
BuildAndRunner.BuildAndRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79f87642d146d41d7bdaaa2328b529b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Google.Android.AppBundle.Editor.AssetPacks;
|
||||
using Google.Android.AppBundle.Editor.Internal.BuildTools;
|
||||
using UnityEditor;
|
||||
#if !UNITY_2018_3_OR_NEWER
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
#endif
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
using UnityEngine;
|
||||
#endif
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build <a href="https://developer.android.com/platform/technology/app-bundle/">Android App Bundles</a>.
|
||||
/// </summary>
|
||||
public static class AppBundlePublisher
|
||||
{
|
||||
private class AppBundleBuildSettings
|
||||
{
|
||||
public BuildPlayerOptions buildPlayerOptions;
|
||||
public AssetPackConfig assetPackConfig;
|
||||
public bool runOnDevice;
|
||||
public bool forceSynchronousBuild;
|
||||
}
|
||||
|
||||
#if !UNITY_2018_3_OR_NEWER
|
||||
/// <summary>
|
||||
/// A <see cref="PostBuildTask"/> that serializes state to resume an app bundle build after a script reload
|
||||
/// occurs. Starting with Unity 2018.3 player builds no longer reset the application domain.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class AppBundlePostBuildTask : PostBuildTask
|
||||
{
|
||||
public SerializableAssetPackConfig serializableAssetPackConfig;
|
||||
public string workingDirectoryPath;
|
||||
public string aabFilePath;
|
||||
public bool runOnDevice;
|
||||
|
||||
public override void RunPostBuildTask()
|
||||
{
|
||||
var buildToolLogger = new BuildToolLogger();
|
||||
var appBundleBuilder = CreateAppBundleBuilder(workingDirectoryPath);
|
||||
if (!appBundleBuilder.Initialize(buildToolLogger))
|
||||
{
|
||||
buildToolLogger.DisplayErrorDialog("Failed to initialize AppBundleBuilder in post-build task.");
|
||||
return;
|
||||
}
|
||||
|
||||
var assetPackConfig = SerializationHelper.Deserialize(serializableAssetPackConfig);
|
||||
CreateBundleAsync(appBundleBuilder, aabFilePath, assetPackConfig, runOnDevice);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static IBuildModeProvider _buildModeProvider;
|
||||
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle given the specified configuration.
|
||||
/// </summary>
|
||||
public static async Task<AndroidBuildReport> BuildTask(AndroidBuildOptions androidBuildOptions)
|
||||
{
|
||||
var appBundleBuilder = CreateAppBundleBuilder();
|
||||
if (!appBundleBuilder.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
throw new Exception("Failed to initialize AppBundleBuilder");
|
||||
}
|
||||
|
||||
return await appBundleBuilder.CreateBundleWithTask(androidBuildOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle containing only asset packs.
|
||||
/// </summary>
|
||||
public static async Task BuildAssetOnlyBundle(AssetOnlyBuildOptions assetOnlyBuildOptions)
|
||||
{
|
||||
var appBundleBuilder = CreateAppBundleBuilder();
|
||||
if (!appBundleBuilder.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
throw new Exception("Failed to initialize AppBundleBuilder");
|
||||
}
|
||||
|
||||
await appBundleBuilder.CreateAssetOnlyBundle(assetOnlyBuildOptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle given the specified configuration.
|
||||
/// </summary>
|
||||
public static bool Build(
|
||||
BuildPlayerOptions buildPlayerOptions, AssetPackConfig assetPackConfig, bool forceSynchronousBuild)
|
||||
{
|
||||
var appBundleBuilder = CreateAppBundleBuilder();
|
||||
if (!appBundleBuilder.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var buildSettings = new AppBundleBuildSettings
|
||||
{
|
||||
buildPlayerOptions = buildPlayerOptions,
|
||||
assetPackConfig = assetPackConfig ?? new AssetPackConfig(),
|
||||
forceSynchronousBuild = forceSynchronousBuild
|
||||
};
|
||||
return Build(appBundleBuilder, buildSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle at a location specified in a file save dialog.
|
||||
/// The AAB will include asset packs configured in Asset Delivery Settings.
|
||||
/// </summary>
|
||||
public static void Build()
|
||||
{
|
||||
// Check that we're ready to build before displaying the file save dialog.
|
||||
var appBundleBuilder = CreateAppBundleBuilder();
|
||||
if (!appBundleBuilder.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var aabFilePath = EditorUtility.SaveFilePanel("Create Android App Bundle", null, null, "aab");
|
||||
if (string.IsNullOrEmpty(aabFilePath))
|
||||
{
|
||||
// Assume cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
var buildSettings = new AppBundleBuildSettings
|
||||
{
|
||||
buildPlayerOptions = AndroidBuildHelper.CreateBuildPlayerOptions(aabFilePath),
|
||||
assetPackConfig = AssetPackConfigSerializer.LoadConfig()
|
||||
};
|
||||
Build(appBundleBuilder, buildSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle to a temp directory and then runs it on device.
|
||||
/// </summary>
|
||||
/// <param name="assetPackConfig">The asset pack configuration to use when building.</param>
|
||||
public static void BuildAndRun(AssetPackConfig assetPackConfig)
|
||||
{
|
||||
var appBundleBuilder = CreateAppBundleBuilder();
|
||||
if (!appBundleBuilder.Initialize(new BuildToolLogger()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tempOutputFilePath = Path.Combine(appBundleBuilder.WorkingDirectoryPath, "temp.aab");
|
||||
var buildSettings = new AppBundleBuildSettings
|
||||
{
|
||||
buildPlayerOptions = AndroidBuildHelper.CreateBuildPlayerOptions(tempOutputFilePath),
|
||||
assetPackConfig = assetPackConfig,
|
||||
runOnDevice = true
|
||||
};
|
||||
Build(appBundleBuilder, buildSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current BuildModeProvider. Should be called in a class annotated with "InitializeOnLoad" in order
|
||||
/// for it to be assigned after assembly reload.
|
||||
/// </summary>
|
||||
public static void SetBuildModeProvider(IBuildModeProvider buildModeProvider)
|
||||
{
|
||||
_buildModeProvider = buildModeProvider;
|
||||
}
|
||||
|
||||
private static bool Build(AppBundleBuilder appBundleBuilder, AppBundleBuildSettings buildSettings)
|
||||
{
|
||||
var androidBuildResult = appBundleBuilder.BuildAndroidPlayer(buildSettings.buildPlayerOptions);
|
||||
if (!androidBuildResult.Succeeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var aabFilePath = buildSettings.buildPlayerOptions.locationPathName;
|
||||
if (IsBatchMode || buildSettings.forceSynchronousBuild)
|
||||
{
|
||||
var createBundleOptions = new CreateBundleOptions
|
||||
{
|
||||
AabFilePath = aabFilePath, AssetPackConfig = buildSettings.assetPackConfig
|
||||
};
|
||||
var errorMessage = appBundleBuilder.CreateBundle(createBundleOptions);
|
||||
return errorMessage == null;
|
||||
}
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
CreateBundleAsync(appBundleBuilder, aabFilePath, buildSettings.assetPackConfig, buildSettings.runOnDevice);
|
||||
#else
|
||||
var task = new AppBundlePostBuildTask
|
||||
{
|
||||
serializableAssetPackConfig = SerializationHelper.Serialize(buildSettings.assetPackConfig),
|
||||
workingDirectoryPath = appBundleBuilder.WorkingDirectoryPath,
|
||||
aabFilePath = aabFilePath,
|
||||
runOnDevice = buildSettings.runOnDevice
|
||||
};
|
||||
PostBuildRunner.RunTask(task);
|
||||
#endif
|
||||
// This return value should be ignored since the build is being finished asynchronously.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void CreateBundleAsync(
|
||||
AppBundleBuilder appBundleBuilder, string aabFilePath, AssetPackConfig assetPackConfig, bool runOnDevice)
|
||||
{
|
||||
var callback =
|
||||
runOnDevice
|
||||
? (AppBundleBuilder.PostBuildCallback) RunBundle
|
||||
: EditorUtility.RevealInFinder;
|
||||
|
||||
var createBundleOptions = new CreateBundleOptions
|
||||
{
|
||||
AabFilePath = aabFilePath, AssetPackConfig = assetPackConfig
|
||||
};
|
||||
appBundleBuilder.CreateBundleAsync(createBundleOptions, callback);
|
||||
}
|
||||
|
||||
private static void RunBundle(string aabFile)
|
||||
{
|
||||
var buildToolLogger = new BuildToolLogger();
|
||||
var appBundleRunner = CreateAppBundleRunner();
|
||||
if (!appBundleRunner.Initialize(buildToolLogger))
|
||||
{
|
||||
buildToolLogger.DisplayErrorDialog("Failed to initialize AppBundleRunner.");
|
||||
return;
|
||||
}
|
||||
|
||||
appBundleRunner.RunBundle(aabFile, GetBuildMode());
|
||||
}
|
||||
|
||||
private static AppBundleBuilder CreateAppBundleBuilder()
|
||||
{
|
||||
return CreateAppBundleBuilder(GetTempFolder());
|
||||
}
|
||||
|
||||
private static AppBundleBuilder CreateAppBundleBuilder(string workingDirectoryPath)
|
||||
{
|
||||
var androidSdk = new AndroidSdk();
|
||||
var javaUtils = new JavaUtils();
|
||||
var androidSdkPlatform = new AndroidSdkPlatform(androidSdk);
|
||||
var androidBuildTools = new AndroidBuildTools(androidSdk);
|
||||
var jarSigner = new JarSigner(javaUtils);
|
||||
return new AppBundleBuilder(
|
||||
new AndroidAssetPackagingTool(androidBuildTools, androidSdkPlatform),
|
||||
new AndroidBuilder(androidSdkPlatform),
|
||||
new BundletoolHelper(javaUtils),
|
||||
jarSigner,
|
||||
workingDirectoryPath,
|
||||
new ZipUtils(javaUtils));
|
||||
}
|
||||
|
||||
private static AppBundleRunner CreateAppBundleRunner()
|
||||
{
|
||||
var androidSdk = new AndroidSdk();
|
||||
var javaUtils = new JavaUtils();
|
||||
var adb = new AndroidDebugBridge(androidSdk);
|
||||
var bundletool = new BundletoolHelper(javaUtils);
|
||||
return new AppBundleRunner(adb, bundletool);
|
||||
}
|
||||
|
||||
private static string GetTempFolder()
|
||||
{
|
||||
// Create a new temp folder with each build. Some developers prefer a random path here since there may be
|
||||
// multiple builds running concurrently, e.g. on an automated build machine. See Issue #69.
|
||||
// Note: this plugin doesn't clear out old temporary build folders, so disk usage will grow over time.
|
||||
// Note: we use the 2 argument Path.Combine() to support .NET 3.
|
||||
return Path.Combine(Path.Combine(Path.GetTempPath(), "play-unity-build"), Path.GetRandomFileName());
|
||||
}
|
||||
|
||||
private static BundletoolBuildMode GetBuildMode()
|
||||
{
|
||||
if (_buildModeProvider != null)
|
||||
{
|
||||
return _buildModeProvider.GetBuildMode();
|
||||
}
|
||||
|
||||
return BundletoolBuildMode.Persistent;
|
||||
}
|
||||
|
||||
private static bool IsBatchMode
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
return Application.isBatchMode;
|
||||
#else
|
||||
return Environment.CommandLine.Contains("-batchmode");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 067a2af83af994d2bab4a749cba47b73
|
||||
timeCreated: 1540331104
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Build Options for building asset-only app bundles.
|
||||
/// </summary>
|
||||
public class AssetOnlyOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of app versions that are eligible to update to this asset-only app bundle.
|
||||
/// </summary>
|
||||
public IList<long> AppVersions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string uniquely identifying this asset-only app bundle.
|
||||
/// </summary>
|
||||
public string AssetVersionTag { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36ce805c46a1445b5b499fbe670efae4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal.AndroidManifest;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a <see cref="Registry"/> of <see cref="IAssetPackManifestTransformer"/>s.
|
||||
/// </summary>
|
||||
public class AssetPackManifestTransformerRegistry : Registry<IAssetPackManifestTransformer>
|
||||
{
|
||||
private static AssetPackManifestTransformerRegistry _instance;
|
||||
|
||||
public static AssetPackManifestTransformerRegistry Registry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new AssetPackManifestTransformerRegistry();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02bb9eaf0454eeda560629d83c032ce
|
||||
timeCreated: 1574886319
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f68087b1bfbaa4196b496b765b6f3b91
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal-only representation of a folder containing AssetBundles.
|
||||
/// </summary>
|
||||
public class AssetBundleFolder
|
||||
{
|
||||
/// <summary>
|
||||
/// The error state of this folder or <see cref="AssetPackFolderState.Ok"/>.
|
||||
/// </summary>
|
||||
public AssetPackFolderState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path represented by the folder.
|
||||
/// </summary>
|
||||
public string FolderPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture compression format of AssetBundles inside this folder, based on the folder name.
|
||||
/// </summary>
|
||||
public TextureCompressionFormat TextureCompressionFormat { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of AssetBundles found in this folder.
|
||||
/// Note: this value is cached during calls to <see cref="ExtractAssetBundleVariant"/>.
|
||||
/// </summary>
|
||||
public int AssetBundleCount { get; private set; }
|
||||
|
||||
private string _searchedManifestFileName;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new AssetBundleFolder for a given path (which is then immutable).
|
||||
/// Call <see cref="Refresh"/> to update the folder state and texture compression format targeting.
|
||||
/// </summary>
|
||||
public AssetBundleFolder(string folderPath)
|
||||
{
|
||||
AssetBundleCount = 0;
|
||||
FolderPath = folderPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the folder state and texture compression format targeting, if any.
|
||||
/// </summary>
|
||||
/// <returns>true if the refresh was properly done (no errors)</returns>
|
||||
public bool Refresh()
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(FolderPath);
|
||||
if (!directoryInfo.Exists)
|
||||
{
|
||||
AssetBundleCount = 0;
|
||||
State = AssetPackFolderState.FolderMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
var files = directoryInfo.GetFiles();
|
||||
if (files.Length == 0)
|
||||
{
|
||||
AssetBundleCount = 0;
|
||||
State = AssetPackFolderState.FolderEmpty;
|
||||
return false;
|
||||
}
|
||||
|
||||
TextureCompressionFormat textureCompressionFormat;
|
||||
TextureTargetingTools.GetTextureCompressionFormatAndStripSuffix(
|
||||
directoryInfo.Name,
|
||||
out textureCompressionFormat,
|
||||
out _searchedManifestFileName);
|
||||
TextureCompressionFormat = textureCompressionFormat;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans and returns every <see cref="AssetBundleVariant"/> found in this folder.
|
||||
/// </summary>
|
||||
public IDictionary<string, AssetBundleVariant> ExtractAssetBundleVariant()
|
||||
{
|
||||
AssetBundleCount = 0;
|
||||
var assetBundleVariants = new Dictionary<string, AssetBundleVariant>();
|
||||
if (!Refresh())
|
||||
{
|
||||
return assetBundleVariants;
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(FolderPath);
|
||||
var files = directoryInfo.GetFiles();
|
||||
|
||||
var fileDictionary = files.ToDictionary(file => file.Name, file => file);
|
||||
|
||||
// Look for an AssetBundle file with the same name as the directory that contains it.
|
||||
// This AssetBundle file contains a single asset, which is of type AssetBundleManifest.
|
||||
// See https://unity3d.com/learn/tutorials/topics/best-practices/assetbundle-fundamentals
|
||||
FileInfo manifestFileInfo;
|
||||
if (!fileDictionary.TryGetValue(_searchedManifestFileName, out manifestFileInfo))
|
||||
{
|
||||
State = AssetPackFolderState.ManifestFileMissing;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
|
||||
var manifestFilePath = manifestFileInfo.FullName;
|
||||
AssetBundle manifestAssetBundle;
|
||||
try
|
||||
{
|
||||
manifestAssetBundle = AssetBundle.LoadFromFile(manifestFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogErrorFormat("Exception loading AssetBundle file containing manifest ({0}): {1}",
|
||||
manifestFilePath, ex);
|
||||
State = AssetPackFolderState.ManifestFileLoadError;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var manifest = manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
|
||||
if (manifest == null)
|
||||
{
|
||||
State = AssetPackFolderState.ManifestAssetMissing;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
|
||||
var allAssetBundles = manifest.GetAllAssetBundles();
|
||||
if (allAssetBundles.Length == 0)
|
||||
{
|
||||
State = AssetPackFolderState.AssetBundlesMissing;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
|
||||
foreach (var assetBundleName in allAssetBundles)
|
||||
{
|
||||
FileInfo assetBundleFileInfo;
|
||||
var fileSizeBytes = fileDictionary.TryGetValue(assetBundleName, out assetBundleFileInfo)
|
||||
? assetBundleFileInfo.Length
|
||||
: AssetBundleVariant.FileSizeIfMissing;
|
||||
|
||||
assetBundleVariants.Add(
|
||||
assetBundleName,
|
||||
AssetBundleVariant.CreateVariant(
|
||||
assetBundleName,
|
||||
fileSizeBytes,
|
||||
manifest.GetDirectDependencies(assetBundleName),
|
||||
manifest.GetAllDependencies(assetBundleName),
|
||||
Path.Combine(FolderPath, assetBundleName)));
|
||||
}
|
||||
|
||||
AssetBundleCount = assetBundleVariants.Count;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogErrorFormat(
|
||||
"Exception loading AssetBundleManifest from AssetBundle ({0}): {1}", manifestFilePath, ex);
|
||||
State = AssetPackFolderState.ManifestAssetLoadError;
|
||||
return assetBundleVariants;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// If an AssetBundle isn't unloaded, the Editor will have to be restarted to load it again.
|
||||
manifestAssetBundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c3bd268718fc41b48090fadb8eee0bd
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal-only wrapper around <see cref="AssetBundleVariant"/>s that are identical except for their
|
||||
/// texture compression formats.
|
||||
/// </summary>
|
||||
public class AssetBundlePack
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the asset pack, which is the same as the name of every included AssetBundle.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this pack will be included in an Android App Bundle, and if so how it is delivered.
|
||||
/// </summary>
|
||||
public AssetPackDeliveryMode DeliveryMode;
|
||||
|
||||
/// <summary>
|
||||
/// The AssetBundles, all with the same name but generated for different texture compression formats.
|
||||
/// </summary>
|
||||
public IDictionary<TextureCompressionFormat, AssetBundleVariant> Variants { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If non-empty, these errors will prevent this AssetBundlePack from being packaged in an App Bundle.
|
||||
/// </summary>
|
||||
public readonly HashSet<AssetPackError> Errors = new HashSet<AssetPackError>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AssetBundlePack(string name, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
Name = name;
|
||||
DeliveryMode = deliveryMode;
|
||||
Variants = new Dictionary<TextureCompressionFormat, AssetBundleVariant>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="AssetBundleVariant"/> to this asset pack.
|
||||
/// <returns>true if the AssetBundle was added, false if another AssetBundle with the same compression
|
||||
/// is already present.</returns>
|
||||
/// </summary>
|
||||
public bool Add(TextureCompressionFormat textureCompressionFormat, AssetBundleVariant variant)
|
||||
{
|
||||
AssetBundleVariant existingVariant;
|
||||
if (Variants.TryGetValue(textureCompressionFormat, out existingVariant))
|
||||
{
|
||||
Debug.LogErrorFormat("Multiple AssetBundles for texture format {0} are used for {1}.",
|
||||
textureCompressionFormat.ToString(), Name);
|
||||
existingVariant.Errors.Add(AssetPackError.DuplicateName);
|
||||
return false;
|
||||
}
|
||||
|
||||
Variants.Add(textureCompressionFormat, variant);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified error to all children.
|
||||
/// </summary>
|
||||
public void AddErrorToVariants(AssetPackError error)
|
||||
{
|
||||
foreach (var assetPack in Variants.Values)
|
||||
{
|
||||
assetPack.Errors.Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The names of any AssetBundles that the AssetBundles inside this AssetBundlePack directly depend on.
|
||||
/// </summary>
|
||||
public IList<string> DirectDependencies
|
||||
{
|
||||
get
|
||||
{
|
||||
var directDependencies =
|
||||
Variants.Values.SelectMany(pack => pack.DirectDependencies).Distinct().ToList();
|
||||
directDependencies.Sort();
|
||||
return directDependencies;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A UI friendly way of displaying the error with this AssetBundlePack (if one) or the number of errors
|
||||
/// (if more than one). If there are no errors, returns null.
|
||||
/// </summary>
|
||||
public string ErrorSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Errors.Count)
|
||||
{
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return NameAndDescriptionAttribute.GetAttribute(Errors.First()).Name;
|
||||
default:
|
||||
return Errors.Count + " Errors";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to find an AssetBundlePack when doing the dependencies check.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the other AssetBundlePack.</param>
|
||||
/// <param name="assetBundlePack">If found, will contain the AssetBundlePack.</param>
|
||||
public delegate bool TryGetAssetBundlePack(string name, out AssetBundlePack assetBundlePack);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates whether to set any <see cref="Errors"/> based on problems with the dependencies.
|
||||
/// </summary>
|
||||
/// <param name="tryGetAssetBundlePack">Function to get an <see cref="AssetBundlePack"/> given its name.</param>
|
||||
public void CheckDependencyErrors(TryGetAssetBundlePack tryGetAssetBundlePack)
|
||||
{
|
||||
if (DeliveryMode == AssetPackDeliveryMode.DoNotPackage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var variant in Variants)
|
||||
{
|
||||
var textureCompressionFormat = variant.Key;
|
||||
var assetPack = variant.Value;
|
||||
assetPack.CheckDependencyErrors(
|
||||
DeliveryMode,
|
||||
TryGetDependency(tryGetAssetBundlePack, textureCompressionFormat));
|
||||
}
|
||||
}
|
||||
|
||||
private static AssetBundleVariant.TryGetDependency TryGetDependency(
|
||||
TryGetAssetBundlePack tryGetAssetBundlePack, TextureCompressionFormat textureCompressionFormat)
|
||||
{
|
||||
return (string name, out AssetBundleVariant dependency, out AssetPackDeliveryMode deliveryMode) =>
|
||||
{
|
||||
deliveryMode = AssetPackDeliveryMode.DoNotPackage;
|
||||
dependency = null;
|
||||
AssetBundlePack assetBundlePack;
|
||||
if (tryGetAssetBundlePack(name, out assetBundlePack) &&
|
||||
assetBundlePack.Variants.TryGetValue(textureCompressionFormat, out dependency))
|
||||
{
|
||||
deliveryMode = assetBundlePack.DeliveryMode;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20e43cae4a4245538a68b920730857b6
|
||||
timeCreated: 1571324952
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal-only wrapper around an AssetBundle that by virtue of its texture compression format is a
|
||||
/// variant of the other AssetBundles contained in an <see cref="AssetBundlePack"/>.
|
||||
/// </summary>
|
||||
public class AssetBundleVariant
|
||||
{
|
||||
/// <summary>
|
||||
/// Special value for <see cref="FileSizeBytes"/> indicating the AssetBundle file does not exist.
|
||||
/// </summary>
|
||||
public const long FileSizeIfMissing = -1L;
|
||||
|
||||
// Visible for testing.
|
||||
public const string FileMissingText = "file missing";
|
||||
|
||||
// Visible for testing.
|
||||
public const string NoneText = "none";
|
||||
|
||||
private static readonly string[] EmptyStringArray = new string[0];
|
||||
|
||||
/// <summary>
|
||||
/// Constructor. Prefer <see cref="CreateVariant"/> for all cases except deserialization and testing.
|
||||
/// </summary>
|
||||
public AssetBundleVariant()
|
||||
{
|
||||
DirectDependencies = EmptyStringArray;
|
||||
AllDependencies = EmptyStringArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If non-empty, these errors will prevent this AssetBundle from being packaged in an app bundle.
|
||||
/// </summary>
|
||||
public readonly HashSet<AssetPackError> Errors = new HashSet<AssetPackError>();
|
||||
|
||||
/// <summary>
|
||||
/// The names of any AssetBundles that this one directly depends on.
|
||||
/// </summary>
|
||||
public string[] DirectDependencies { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The names of any AssetBundles that this one directly or transitively depends on.
|
||||
/// </summary>
|
||||
public string[] AllDependencies { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of the file in bytes, or -1L if the file doesn't exist.
|
||||
/// </summary>
|
||||
public long FileSizeBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to the AssetBundle represented by this wrapper.
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A UI friendly way of displaying dependency info.
|
||||
/// </summary>
|
||||
public string DependenciesText
|
||||
{
|
||||
// TODO: consider how to handle an extra long string in the UI.
|
||||
get { return DirectDependencies.Length == 0 ? NoneText : string.Join(", ", DirectDependencies); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A UI friendly way of displaying file size or that the file doesn't exist on disk.
|
||||
/// </summary>
|
||||
public string FileSizeText
|
||||
{
|
||||
get
|
||||
{
|
||||
// Display file size in Kibibytes using "JEDEC" standard. See https://en.wikipedia.org/wiki/Kilobyte
|
||||
return FileSizeBytes == FileSizeIfMissing ? FileMissingText : (FileSizeBytes / 1024) + " KB";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A UI friendly way of displaying the error with this AssetBundle (if there is one) or the number of errors
|
||||
/// (if there is more than one). If there are no errors, this is null.
|
||||
/// </summary>
|
||||
public string ErrorSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Errors.Count)
|
||||
{
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return NameAndDescriptionAttribute.GetAttribute(Errors.First()).Name;
|
||||
default:
|
||||
return Errors.Count + " Errors";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to find a variant when doing the dependencies check.
|
||||
/// See <see cref="AssetBundleVariant.CheckDependencyErrors"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The asset pack to search.</param>
|
||||
/// <param name="dependencyVariant">If found, will contain the searched asset pack.</param>
|
||||
/// <param name="dependencyDeliveryMode">If found, will contain the delivery mode of the searched asset pack.</param>
|
||||
/// <returns>true if the asset pack was found, false otherwise.</returns>
|
||||
public delegate bool TryGetDependency(string name, out AssetBundleVariant dependencyVariant,
|
||||
out AssetPackDeliveryMode dependencyDeliveryMode);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates whether to set any <see cref="Errors"/> based on problems with this variant's dependencies.
|
||||
/// </summary>
|
||||
/// <param name="deliveryMode">The delivery mode of this asset pack.</param>
|
||||
/// <param name="tryGetDependency">A function returning the asset pack for the given dependency name,
|
||||
/// along with the delivery mode of the asset pack.</param>
|
||||
public void CheckDependencyErrors(AssetPackDeliveryMode deliveryMode, TryGetDependency tryGetDependency)
|
||||
{
|
||||
if (deliveryMode == AssetPackDeliveryMode.DoNotPackage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dependencyName in AllDependencies)
|
||||
{
|
||||
AssetBundleVariant dependencyVariant;
|
||||
AssetPackDeliveryMode dependencyDeliveryMode;
|
||||
if (!tryGetDependency(dependencyName, out dependencyVariant, out dependencyDeliveryMode))
|
||||
{
|
||||
Errors.Add(AssetPackError.DependencyMissing);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dependencyVariant.Errors.Count > 0)
|
||||
{
|
||||
Errors.Add(AssetPackError.DependencyError);
|
||||
}
|
||||
|
||||
// If this asset pack is marked for delivery, all of its dependencies must be packaged for delivery.
|
||||
if (dependencyDeliveryMode == AssetPackDeliveryMode.DoNotPackage)
|
||||
{
|
||||
Errors.Add(AssetPackError.DependencyNotPackaged);
|
||||
continue;
|
||||
}
|
||||
|
||||
// An asset pack cannot have dependencies on asset packs with later delivery modes,
|
||||
// e.g. A FastFollow asset pack cannot depend on an OnDemand asset pack.
|
||||
if (deliveryMode < dependencyDeliveryMode)
|
||||
{
|
||||
Errors.Add(AssetPackError.DependencyIncompatibleDelivery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AssetBundleVariant"/>. Evaluates some conditions and may set <see cref="Errors"/>.
|
||||
/// </summary>
|
||||
/// <param name="assetBundleName">Name of the AssetBundle represented by this variant.</param>
|
||||
/// <param name="fileSizeBytes">Size of the file in bytes, or <see cref="FileSizeIfMissing"/>.</param>
|
||||
/// <param name="directDependencies">AssetBundles that are direct dependencies of this one.</param>
|
||||
/// <param name="allDependencies">AssetBundles that are direct or transitive dependencies of this one.</param>
|
||||
/// <param name="path">The path to the AssetBundle represented by this variant.</param>
|
||||
public static AssetBundleVariant CreateVariant(
|
||||
string assetBundleName,
|
||||
long fileSizeBytes,
|
||||
string[] directDependencies,
|
||||
string[] allDependencies,
|
||||
string path)
|
||||
{
|
||||
var variant = new AssetBundleVariant
|
||||
{
|
||||
DirectDependencies = directDependencies ?? EmptyStringArray,
|
||||
AllDependencies = allDependencies ?? EmptyStringArray,
|
||||
FileSizeBytes = fileSizeBytes,
|
||||
Path = path,
|
||||
};
|
||||
|
||||
if (fileSizeBytes == FileSizeIfMissing)
|
||||
{
|
||||
variant.Errors.Add(AssetPackError.FileMissing);
|
||||
}
|
||||
|
||||
if (!AndroidAppBundle.IsValidModuleName(assetBundleName))
|
||||
{
|
||||
variant.Errors.Add(AssetPackError.InvalidName);
|
||||
}
|
||||
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c348752fa8c94d3a8f6f085846aa228
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal-only representation of asset packs being considered for packaging in an Android App Bundle.
|
||||
/// </summary>
|
||||
public class AssetDeliveryConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Default texture compression format for building standalone APKs for Android pre-Lollipop devices.
|
||||
/// </summary>
|
||||
public TextureCompressionFormat DefaultTextureCompressionFormat = TextureCompressionFormat.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to split the assets in an AAB's base module into a separate install-time asset pack.
|
||||
/// </summary>
|
||||
public bool SplitBaseModuleAssets;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from a folder path to a AssetBundleFolder object describing the folder's contents.
|
||||
/// </summary>
|
||||
public readonly IDictionary<string, AssetBundleFolder> Folders =
|
||||
new SortedDictionary<string, AssetBundleFolder>();
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary from AssetBundle name to AssetBundlePacks.
|
||||
/// </summary>
|
||||
public IDictionary<string, AssetBundlePack> AssetBundlePacks { get; private set; }
|
||||
|
||||
// TODO(b/150701341): add support for managing this type of asset pack in the UI.
|
||||
public List<SerializableAssetPack> rawAssetsPacks;
|
||||
|
||||
private readonly IEnumerable<IAssetPackValidator> _packValidators;
|
||||
|
||||
public AssetDeliveryConfig()
|
||||
{
|
||||
AssetBundlePacks = new Dictionary<string, AssetBundlePack>();
|
||||
_packValidators = AssetPackValidatorRegistry.Registry.ConstructInstances();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the in-memory state based on the AssetBundles on disk.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
// Refresh the AssetBundlePacks according to AssetBundles contained in folders.
|
||||
RefreshAssetBundlePacks();
|
||||
|
||||
// Check for errors.
|
||||
foreach (var assetBundlePack in AssetBundlePacks.Values)
|
||||
{
|
||||
CheckBuildTypeErrors(assetBundlePack);
|
||||
assetBundlePack.CheckDependencyErrors(
|
||||
(string name, out AssetBundlePack pack) => AssetBundlePacks.TryGetValue(name, out pack));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Potentially refresh all folders and/or add and remove the specified folders.
|
||||
/// </summary>
|
||||
/// <returns>true if changes were made.</returns>
|
||||
public bool UpdateAndRefreshFolders(bool refreshFolders, List<string> foldersToAdd,
|
||||
List<string> foldersToRemove)
|
||||
{
|
||||
if (!refreshFolders && foldersToRemove.Count == 0 && foldersToAdd.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var folder in foldersToAdd)
|
||||
{
|
||||
if (Folders.ContainsKey(folder))
|
||||
{
|
||||
Debug.LogWarningFormat("Skipping folder \"{0}\" because it is already in the list.", folder);
|
||||
}
|
||||
else
|
||||
{
|
||||
Folders.Add(folder, new AssetBundleFolder(folder));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var folder in foldersToRemove)
|
||||
{
|
||||
Folders.Remove(folder);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of error messages that are applicable to AssetBundles marked for packaging/delivery
|
||||
/// along with the number of AssetBundles that are marked for packaging/delivery.
|
||||
/// </summary>
|
||||
public List<string> GetPackagingErrorMessages(out int numAssetPacksToDeliver)
|
||||
{
|
||||
// Check for errors in asset packs
|
||||
numAssetPacksToDeliver = 0;
|
||||
var errors = new List<string>();
|
||||
|
||||
foreach (var assetBundlePack in AssetBundlePacks.Values)
|
||||
{
|
||||
if (assetBundlePack.DeliveryMode == AssetPackDeliveryMode.DoNotPackage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var errorSummary = assetBundlePack.ErrorSummary;
|
||||
if (errorSummary != null)
|
||||
{
|
||||
errors.Add(string.Format(
|
||||
"Unable to package AssetBundle pack \"{0}\" because {1}.",
|
||||
assetBundlePack.Name, errorSummary.ToLower()));
|
||||
}
|
||||
|
||||
numAssetPacksToDeliver++;
|
||||
foreach (var variant in assetBundlePack.Variants.Values)
|
||||
{
|
||||
var variantErrorSummary = variant.ErrorSummary;
|
||||
if (variantErrorSummary != null)
|
||||
{
|
||||
errors.Add(string.Format(
|
||||
"Unable to package AssetBundle \"{0}\" because {1}.",
|
||||
variant.Path, variantErrorSummary.ToLower()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors in the rest of the configuration.
|
||||
if (HasTextureCompressionFormatTargeting() &&
|
||||
!GetAllTextureCompressionFormats().Contains(DefaultTextureCompressionFormat))
|
||||
{
|
||||
errors.Add(string.Format(
|
||||
"The default texture compression format ({0}) is not used by any AssetBundles.",
|
||||
DefaultTextureCompressionFormat.ToString()));
|
||||
}
|
||||
|
||||
// TODO: think about moving to a build check.
|
||||
if (HasTextureCompressionFormatTargeting() &&
|
||||
EditorUserBuildSettings.androidBuildSubtarget != MobileTextureSubtarget.Generic)
|
||||
{
|
||||
errors.Add(string.Format(
|
||||
"Texture Compression in the Build Settings for Android is set to \"{0}\". This is not compatible with asset packs having targeted textures. Set the texture compression in the Build Settings for Android to \"Don't override'\" before continuing.",
|
||||
EditorUserBuildSettings.androidBuildSubtarget));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the list of all the <see cref="AssetBundlePacks"/> represented by the folders.
|
||||
/// </summary>
|
||||
public void RefreshAssetBundlePacks()
|
||||
{
|
||||
var assetBundlePacks = new Dictionary<string, AssetBundlePack>();
|
||||
foreach (var folder in Folders.Values)
|
||||
{
|
||||
var assetPacks = folder.ExtractAssetBundleVariant();
|
||||
|
||||
foreach (var targetedAssetPackPair in assetPacks)
|
||||
{
|
||||
var targetedAssetPackName = targetedAssetPackPair.Key;
|
||||
var targetedAssetPack = targetedAssetPackPair.Value;
|
||||
|
||||
AssetBundlePack assetBundlePack;
|
||||
if (!assetBundlePacks.TryGetValue(targetedAssetPackName, out assetBundlePack))
|
||||
{
|
||||
var deliveryMode = GetMultiTargetingAssetPackDeliveryMode(targetedAssetPackName);
|
||||
assetBundlePack = new AssetBundlePack(targetedAssetPackName, deliveryMode);
|
||||
assetBundlePacks.Add(targetedAssetPackName, assetBundlePack);
|
||||
}
|
||||
|
||||
if (!assetBundlePack.Add(folder.TextureCompressionFormat, targetedAssetPack))
|
||||
{
|
||||
assetBundlePack.Errors.Add(AssetPackError.DuplicateName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AssetBundlePacks = assetBundlePacks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the delivery mode for the AssetBundlePack with the given name, or DoNotPackage if not found.
|
||||
/// </summary>
|
||||
private AssetPackDeliveryMode GetMultiTargetingAssetPackDeliveryMode(string multiTargetingAssetPackName)
|
||||
{
|
||||
AssetBundlePack assetBundlePack;
|
||||
if (AssetBundlePacks.TryGetValue(multiTargetingAssetPackName, out assetBundlePack))
|
||||
{
|
||||
return assetBundlePack.DeliveryMode;
|
||||
}
|
||||
|
||||
return AssetPackDeliveryMode.DoNotPackage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all texture compression formats used by asset packs.
|
||||
/// </summary>
|
||||
public IEnumerable<TextureCompressionFormat> GetAllTextureCompressionFormats()
|
||||
{
|
||||
return AssetBundlePacks.Values.SelectMany(
|
||||
multiTargetingAssetPack => multiTargetingAssetPack.Variants.Keys).Distinct();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if some asset packs are using targeted texture compression format.
|
||||
/// </summary>
|
||||
public bool HasTextureCompressionFormatTargeting()
|
||||
{
|
||||
return GetAllTextureCompressionFormats().Any(textureCompressionFormat =>
|
||||
textureCompressionFormat != TextureCompressionFormat.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the specified bundlePack for build errors using validators registered in AssetPackValidatorRegistry.
|
||||
/// </summary>
|
||||
private void CheckBuildTypeErrors(AssetBundlePack bundlePack)
|
||||
{
|
||||
var buildErrors = new HashSet<AssetPackError>();
|
||||
foreach (var buildValidator in _packValidators)
|
||||
{
|
||||
buildErrors.UnionWith(buildValidator.CheckBuildErrors(bundlePack));
|
||||
}
|
||||
|
||||
foreach (var error in buildErrors)
|
||||
{
|
||||
bundlePack.AddErrorToVariants(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc061489f3c64fd78ed0cefe895b007
|
||||
timeCreated: 1549914493
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for serializing <see cref="AssetDeliveryConfig"/> to a JSON file and for deserializing it.
|
||||
/// </summary>
|
||||
public static class AssetDeliveryConfigSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Save the specified AssetDeliveryConfig config to disk.
|
||||
/// </summary>
|
||||
public static void SaveConfig(AssetDeliveryConfig assetDeliveryConfig)
|
||||
{
|
||||
Debug.LogFormat("Saving {0}", SerializationHelper.ConfigurationFilePath);
|
||||
var config = Serialize(assetDeliveryConfig);
|
||||
var jsonText = JsonUtility.ToJson(config);
|
||||
File.WriteAllText(SerializationHelper.ConfigurationFilePath, jsonText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an AssetDeliveryConfig loaded from disk.
|
||||
/// </summary>
|
||||
public static AssetDeliveryConfig LoadConfig()
|
||||
{
|
||||
Debug.LogFormat("Loading {0}", SerializationHelper.ConfigurationFilePath);
|
||||
SerializableAssetPackConfig config;
|
||||
if (File.Exists(SerializationHelper.ConfigurationFilePath))
|
||||
{
|
||||
var jsonText = File.ReadAllText(SerializationHelper.ConfigurationFilePath);
|
||||
config = JsonUtility.FromJson<SerializableAssetPackConfig>(jsonText);
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new SerializableAssetPackConfig();
|
||||
}
|
||||
|
||||
return Deserialize(config);
|
||||
}
|
||||
|
||||
private static SerializableAssetPackConfig Serialize(AssetDeliveryConfig assetDeliveryConfig)
|
||||
{
|
||||
var config = new SerializableAssetPackConfig
|
||||
{
|
||||
DefaultTextureCompressionFormat = assetDeliveryConfig.DefaultTextureCompressionFormat,
|
||||
splitBaseModuleAssets = assetDeliveryConfig.SplitBaseModuleAssets
|
||||
};
|
||||
|
||||
foreach (var assetPack in assetDeliveryConfig.AssetBundlePacks.Values)
|
||||
{
|
||||
config.assetBundles.Add(new SerializableMultiTargetingAssetBundle
|
||||
{
|
||||
name = assetPack.Name,
|
||||
DeliveryMode = assetPack.DeliveryMode,
|
||||
assetBundles = assetPack.Variants.Select(pack => new SerializableAssetBundle
|
||||
{
|
||||
path = pack.Value.Path,
|
||||
TextureCompressionFormat = pack.Key
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
config.assetPacks = assetDeliveryConfig.rawAssetsPacks;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static AssetDeliveryConfig Deserialize(SerializableAssetPackConfig config)
|
||||
{
|
||||
var assetDeliveryConfig = new AssetDeliveryConfig
|
||||
{
|
||||
DefaultTextureCompressionFormat = config.DefaultTextureCompressionFormat,
|
||||
SplitBaseModuleAssets = config.splitBaseModuleAssets
|
||||
};
|
||||
|
||||
var paths = new HashSet<string>();
|
||||
foreach (var multiTargetingAssetBundle in config.assetBundles)
|
||||
{
|
||||
paths.UnionWith(
|
||||
multiTargetingAssetBundle.assetBundles.Select(item => Path.GetDirectoryName(item.path)));
|
||||
assetDeliveryConfig.AssetBundlePacks.Add(multiTargetingAssetBundle.name,
|
||||
new AssetBundlePack(multiTargetingAssetBundle.name,
|
||||
multiTargetingAssetBundle.DeliveryMode));
|
||||
}
|
||||
|
||||
paths.ToList().ForEach(path => assetDeliveryConfig.Folders.Add(path, new AssetBundleFolder(path)));
|
||||
|
||||
assetDeliveryConfig.rawAssetsPacks = config.assetPacks;
|
||||
|
||||
return assetDeliveryConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f127ef3c767384ec89ad3e3aef7ae1bb
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,399 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
public class AssetDeliveryWindow : EditorWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Label for the split base APK checkbox. Visible for referencing from error messages.
|
||||
/// </summary>
|
||||
public const string SeparateAssetsLabel = "Separate Base APK Assets";
|
||||
|
||||
private const string RefreshButtonText = "Refresh";
|
||||
private const int WindowMinWidth = 610;
|
||||
private const int WindowMinHeight = 300;
|
||||
private const int ButtonWidth = 150;
|
||||
private const int FieldWidth = 100;
|
||||
|
||||
private AssetDeliveryConfig _assetDeliveryConfig;
|
||||
private Vector2 _scrollPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The names of asset packs that are shown collapsed.
|
||||
/// </summary>
|
||||
private HashSet<string> _collapsedAssetPacks;
|
||||
|
||||
/// <summary>
|
||||
/// Displays this window, creating it if necessary.
|
||||
/// </summary>
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow(typeof(AssetDeliveryWindow), false, "Asset Delivery");
|
||||
window.minSize = new Vector2(WindowMinWidth, WindowMinHeight);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (_assetDeliveryConfig == null)
|
||||
{
|
||||
_assetDeliveryConfig = AssetDeliveryConfigSerializer.LoadConfig();
|
||||
_assetDeliveryConfig.Refresh();
|
||||
}
|
||||
|
||||
if (_collapsedAssetPacks == null)
|
||||
{
|
||||
_collapsedAssetPacks = new HashSet<string>();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Base APK Asset Delivery", EditorStyles.boldLabel);
|
||||
RenderDescription(
|
||||
"When building an Android App Bundle (AAB), automatically move assets that would normally be " +
|
||||
"delivered in the base APK into an install-time asset pack. This option reduces base APK size " +
|
||||
"similarly to the \"Split Application Binary\" publishing setting, but it uses Play Asset Delivery " +
|
||||
"instead of APK Expansion (OBB) files, so it's compatible with AABs. This option is recommended for " +
|
||||
"apps with a large base APK, e.g. over 150 MB.");
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(SeparateAssetsLabel, GUILayout.Width(145));
|
||||
var pendingSplitBaseModuleAssets = EditorGUILayout.Toggle(_assetDeliveryConfig.SplitBaseModuleAssets);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Asset Pack Configuration", EditorStyles.boldLabel);
|
||||
RenderDescription(
|
||||
"Add folders that directly contain AssetBundle files, for example the output folder from Unity's " +
|
||||
"AssetBundle Browser. All AssetBundles directly contained in the specified folders will be displayed " +
|
||||
"below. Update the \"Delivery Mode\" to include an AssetBundle in AAB builds.");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// TODO(b/144677274): Remove this #if once TCF support is available in Play, to make TCF targeting discoverable.
|
||||
#if SHOW_TCF_IN_UI
|
||||
if (_assetDeliveryConfig.Folders.Count != 0 && !_assetDeliveryConfig.HasTextureCompressionFormatTargeting())
|
||||
{
|
||||
// TODO(b/144677274): Add link to public documentation when available.
|
||||
EditorGUILayout.HelpBox(
|
||||
"These AssetBundles don't specify texture compression format targeting. Generate additional " +
|
||||
"AssetBundles into folders ending with #tcf_xxx (e.g. AssetBundles#tcf_astc), and the " +
|
||||
"AssetBundles will be delivered to devices supporting that format.", MessageType.Info);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
#endif
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// Store the changes and defer the actual update at the end to avoid modifications during rendering.
|
||||
var refreshAssetDeliveryConfig = false;
|
||||
var foldersToRemove = new List<string>();
|
||||
var foldersToAdd = new List<string>();
|
||||
|
||||
if (GUILayout.Button("Add Folder...", GUILayout.Width(ButtonWidth)))
|
||||
{
|
||||
var folderPath = EditorUtility.OpenFolderPanel("Add Folder Containing AssetBundles", null, null);
|
||||
if (string.IsNullOrEmpty(folderPath))
|
||||
{
|
||||
// Assume cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_assetDeliveryConfig.Folders.ContainsKey(folderPath))
|
||||
{
|
||||
var errorMessage =
|
||||
string.Format(
|
||||
"Cannot add a folder that has already been added. Use \"{0}\" to analyze existing folders.",
|
||||
RefreshButtonText);
|
||||
EditorUtility.DisplayDialog("Add Folder Error", errorMessage, WindowUtils.OkButtonText);
|
||||
}
|
||||
else
|
||||
{
|
||||
foldersToAdd.Add(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(RefreshButtonText, GUILayout.Width(ButtonWidth)))
|
||||
{
|
||||
refreshAssetDeliveryConfig = true;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("App Bundle Summary...", GUILayout.Width(ButtonWidth)))
|
||||
{
|
||||
_assetDeliveryConfig.Refresh();
|
||||
EditorUtility.DisplayDialog("App Bundle Summary", GetPackagingSummary(), WindowUtils.OkButtonText);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Hide this setting for projects targeting SDK 21+ since it only affects install-time asset pack
|
||||
// installation on older devices.
|
||||
if (_assetDeliveryConfig.HasTextureCompressionFormatTargeting() &&
|
||||
TextureTargetingTools.IsSdkVersionPreLollipop(PlayerSettings.Android.minSdkVersion))
|
||||
{
|
||||
EditorGUILayout.LabelField("Texture Compression Configuration", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
bool targetingUpdated;
|
||||
RenderTextureCompressionFormatTargetingConfiguration(_assetDeliveryConfig, out targetingUpdated);
|
||||
refreshAssetDeliveryConfig |= targetingUpdated;
|
||||
}
|
||||
|
||||
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
||||
|
||||
EditorGUILayout.LabelField("AssetBundle Folders", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
foreach (var folderPair in _assetDeliveryConfig.Folders)
|
||||
{
|
||||
var folderPath = folderPair.Key;
|
||||
bool removeFolder;
|
||||
RenderFolder(folderPath, folderPair.Value, out removeFolder);
|
||||
if (removeFolder)
|
||||
{
|
||||
foldersToRemove.Add(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (_assetDeliveryConfig.Folders.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Add folders that directly contain AssetBundles.", MessageType.None);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("AssetBundle Configuration", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
foreach (var assetBundlePack in _assetDeliveryConfig.AssetBundlePacks.Values)
|
||||
{
|
||||
bool refreshFolder;
|
||||
RenderAssetBundlePack(assetBundlePack, out refreshFolder);
|
||||
refreshAssetDeliveryConfig |= refreshFolder;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
// Defer folder refresh, addition and removal to avoid modification of the collection while iterating.
|
||||
var pendingConfigChanges =
|
||||
_assetDeliveryConfig.UpdateAndRefreshFolders(refreshAssetDeliveryConfig, foldersToAdd, foldersToRemove);
|
||||
pendingConfigChanges |= pendingSplitBaseModuleAssets != _assetDeliveryConfig.SplitBaseModuleAssets;
|
||||
if (pendingConfigChanges)
|
||||
{
|
||||
_assetDeliveryConfig.SplitBaseModuleAssets = pendingSplitBaseModuleAssets;
|
||||
AssetDeliveryConfigSerializer.SaveConfig(_assetDeliveryConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderAssetBundlePack(AssetBundlePack assetBundlePack, out bool refreshFolder)
|
||||
{
|
||||
refreshFolder = false;
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
var showGroupExpanded = !_collapsedAssetPacks.Contains(assetBundlePack.Name);
|
||||
var groupExpanded =
|
||||
EditorGUILayout.BeginFoldoutHeaderGroup(showGroupExpanded, assetBundlePack.Name);
|
||||
if (groupExpanded)
|
||||
{
|
||||
_collapsedAssetPacks.Remove(assetBundlePack.Name);
|
||||
RenderAssetBundlePackContent(assetBundlePack, out refreshFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
_collapsedAssetPacks.Add(assetBundlePack.Name);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
#else
|
||||
// Old Unity versions don't support foldable headers. Use a simple label instead.
|
||||
EditorGUILayout.LabelField(assetBundlePack.Name, EditorStyles.boldLabel);
|
||||
RenderAssetBundlePackContent(assetBundlePack, out refreshFolder);
|
||||
EditorGUILayout.Separator();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void RenderAssetBundlePackContent(AssetBundlePack assetBundlePack, out bool refreshFolder)
|
||||
{
|
||||
refreshFolder = false;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newDeliveryMode =
|
||||
(AssetPackDeliveryMode) EditorGUILayout.EnumPopup("Delivery Mode", assetBundlePack.DeliveryMode,
|
||||
GUILayout.Width(300));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
assetBundlePack.DeliveryMode = newDeliveryMode;
|
||||
|
||||
// Don't use a short-circuit evaluation one-liner to ensure EndChangeCheck() is called above.
|
||||
refreshFolder = true;
|
||||
}
|
||||
|
||||
// Display the direct dependencies of all the contained asset packs.
|
||||
var directDependencies = assetBundlePack.DirectDependencies;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel("Dependencies");
|
||||
GUILayout.TextArea(
|
||||
directDependencies.Count > 0 ? string.Join(", ", directDependencies.ToArray()) : "None");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.LabelField("AssetBundles");
|
||||
EditorGUI.indentLevel++;
|
||||
foreach (var variant in assetBundlePack.Variants)
|
||||
{
|
||||
RenderVariant(variant.Value, variant.Key);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private static void RenderVariant(AssetBundleVariant variant, TextureCompressionFormat targeting)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// TODO: revisit TextureCompressionFormat enum extension methods.
|
||||
var targetingDescription = targeting == TextureCompressionFormat.Default
|
||||
? targeting.ToString()
|
||||
: targeting.ToString().ToUpper();
|
||||
EditorGUILayout.PrefixLabel(targetingDescription);
|
||||
|
||||
EditorGUILayout.LabelField(variant.FileSizeText, GUILayout.ExpandWidth(false));
|
||||
|
||||
var errors = variant.Errors;
|
||||
if (errors.Count == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("No errors", GUILayout.ExpandWidth(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button(variant.ErrorSummary + "...", GUILayout.ExpandWidth(true)))
|
||||
{
|
||||
var errorDialogTitle = "AssetBundle Error" + (errors.Count == 1 ? string.Empty : "s");
|
||||
var errorDialogMessage = string.Join("\n\n",
|
||||
errors.Select(e => NameAndDescriptionAttribute.GetAttribute(e).Description).ToArray());
|
||||
EditorUtility.DisplayDialog(errorDialogTitle, errorDialogMessage, WindowUtils.OkButtonText);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private static void RenderFolder(string folderPath, AssetBundleFolder assetBundleFolder, out bool removeFolder)
|
||||
{
|
||||
// Folder path and associated management buttons.
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(folderPath, EditorStyles.wordWrappedLabel);
|
||||
removeFolder = GUILayout.Button("Remove", GUILayout.Width(FieldWidth));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Display the folder state.
|
||||
if (assetBundleFolder.State == AssetPackFolderState.Ok)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
string.Format("Found {0} AssetBundle(s) in this folder", assetBundleFolder.AssetBundleCount),
|
||||
MessageType.None);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = NameAndDescriptionAttribute.GetAttribute(assetBundleFolder.State).Description;
|
||||
EditorGUILayout.HelpBox(errorMessage, MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private static void RenderTextureCompressionFormatTargetingConfiguration(
|
||||
AssetDeliveryConfig assetDeliveryConfig, out bool targetingUpdated)
|
||||
{
|
||||
targetingUpdated = false;
|
||||
const string label = "Format for pre-L devices";
|
||||
var width = GUILayout.Width(300);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
var validTextureCompressionFormats = assetDeliveryConfig.GetAllTextureCompressionFormats();
|
||||
var newDefaultTextureCompressionFormat =
|
||||
(TextureCompressionFormat) EditorGUILayout.EnumPopup(new GUIContent(label),
|
||||
assetDeliveryConfig.DefaultTextureCompressionFormat,
|
||||
textureCompressionFormat =>
|
||||
validTextureCompressionFormats.Contains((TextureCompressionFormat) textureCompressionFormat),
|
||||
false, width);
|
||||
#else
|
||||
var newDefaultTextureCompressionFormat =
|
||||
(TextureCompressionFormat) EditorGUILayout.EnumPopup(label,
|
||||
assetDeliveryConfig.DefaultTextureCompressionFormat, width);
|
||||
#endif
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
assetDeliveryConfig.DefaultTextureCompressionFormat = newDefaultTextureCompressionFormat;
|
||||
targetingUpdated = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"This compression format will be used for devices running Android 4.4.4 (SDK 20) or older. " +
|
||||
"AssetBundles marked as install-time will be included in the APK generated for these devices. " +
|
||||
"Android 5.0 (SDK 21) and newer devices aren't affected by this setting.",
|
||||
MessageType.None);
|
||||
}
|
||||
|
||||
private static void RenderDescription(string label)
|
||||
{
|
||||
var descriptionTextStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontStyle = FontStyle.Italic,
|
||||
wordWrap = true
|
||||
};
|
||||
|
||||
EditorGUILayout.BeginVertical("textfield"); // Adds a light grey background.
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(label, descriptionTextStyle);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private string GetPackagingSummary()
|
||||
{
|
||||
int numAssetPacksToDeliver;
|
||||
var errorMessages = _assetDeliveryConfig.GetPackagingErrorMessages(out numAssetPacksToDeliver);
|
||||
|
||||
if (numAssetPacksToDeliver == 0)
|
||||
{
|
||||
return _assetDeliveryConfig.SplitBaseModuleAssets
|
||||
? "The Base APK's assets will be packaged in an asset pack."
|
||||
: "There are no AssetBundles marked for packaging.";
|
||||
}
|
||||
|
||||
if (errorMessages.Count > 0)
|
||||
{
|
||||
const string separator = "\n\n- ";
|
||||
return "The following error(s) will occur when building an Android App Bundle:" + separator +
|
||||
string.Join(separator, errorMessages.ToArray());
|
||||
}
|
||||
|
||||
var description = numAssetPacksToDeliver == 1
|
||||
? "There is 1 AssetBundle marked for packaging."
|
||||
: string.Format("There are {0} AssetBundles marked for packaging.", numAssetPacksToDeliver);
|
||||
return _assetDeliveryConfig.SplitBaseModuleAssets
|
||||
? description + " Also, the Base APK's assets will be packaged in an asset pack."
|
||||
: description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fe34ebc6742d4f818529bc14c64c974
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes various possible error states of an AssetPack.
|
||||
/// </summary>
|
||||
public enum AssetPackError
|
||||
{
|
||||
[NameAndDescription("Duplicate Name",
|
||||
"AssetBundles with the same name and texture format exist in different folders. An " +
|
||||
"AssetBundle's name and parent folder texture format must be unique.")]
|
||||
DuplicateName,
|
||||
|
||||
[NameAndDescription("Invalid Name",
|
||||
"A Play-delivered AssetBundle's name must start with an English letter and can only contain letters, " +
|
||||
"numbers, and underscores. Please regenerate the AssetBundle with a new name.")]
|
||||
InvalidName,
|
||||
|
||||
[NameAndDescription("Missing File", "An AssetBundle file with this name is missing from the folder.")]
|
||||
FileMissing,
|
||||
|
||||
[NameAndDescription("Dependency Error", "One or more of this AssetBundle's dependencies has an error.")]
|
||||
DependencyError,
|
||||
|
||||
[NameAndDescription("Missing Dependency", "One or more of this AssetBundle's dependencies is missing.")]
|
||||
DependencyMissing,
|
||||
|
||||
[NameAndDescription("Dependency Not Packaged",
|
||||
"This AssetBundle is marked for delivery, but one or more of its dependencies is not.")]
|
||||
DependencyNotPackaged,
|
||||
|
||||
[NameAndDescription("Incompatible Dependency",
|
||||
"This AssetBundle is marked to be delivered earlier than one or more of its dependencies.")]
|
||||
DependencyIncompatibleDelivery,
|
||||
|
||||
[NameAndDescription("Instant Incompatible",
|
||||
"Install-time asset packs aren't supported for instant apps.")]
|
||||
InstallTimeAndInstant,
|
||||
|
||||
[NameAndDescription("Instant Incompatible",
|
||||
"Fast-follow asset packs aren't supported for instant apps.")]
|
||||
FastFollowAndInstant,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cad3b640bc35422c88ba5dd808e4170
|
||||
timeCreated: 1549483271
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes various possible states of a folder containing AssetBundle files.
|
||||
/// </summary>
|
||||
public enum AssetPackFolderState
|
||||
{
|
||||
// Does not need NameAndDescription attribute.
|
||||
Ok,
|
||||
|
||||
[NameAndDescription("Folder Missing", "This folder does not exist.")]
|
||||
FolderMissing,
|
||||
|
||||
[NameAndDescription("Folder Empty", "This folder contains no files.")]
|
||||
FolderEmpty,
|
||||
|
||||
[NameAndDescription("Manifest File Missing",
|
||||
"This folder is missing an AssetBundle file that matches the folder name.")]
|
||||
ManifestFileMissing,
|
||||
|
||||
[NameAndDescription("Manifest File Load Error",
|
||||
"There was an error loading the AssetBundle containing the AssetBundleManifest asset.")]
|
||||
ManifestFileLoadError,
|
||||
|
||||
[NameAndDescription("AssetBundleManifest Missing",
|
||||
"This folder's AssetBundle manifest file is missing the AssetBundleManifest asset.")]
|
||||
ManifestAssetMissing,
|
||||
|
||||
[NameAndDescription("AssetBundleManifest Load Error",
|
||||
"There was an error loading the AssetBundleManifest asset.")]
|
||||
ManifestAssetLoadError,
|
||||
|
||||
[NameAndDescription("AssetBundle Files Missing", "This folder contains no AssetBundle files.")]
|
||||
AssetBundlesMissing,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0110294bad4d544c486274699623a0c2
|
||||
timeCreated: 1548906103
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a <see cref="Registry"/> of <see cref="IAssetPackValidator"/>s.
|
||||
/// </summary>
|
||||
public class AssetPackValidatorRegistry : Registry<IAssetPackValidator>
|
||||
{
|
||||
private static AssetPackValidatorRegistry _instance;
|
||||
|
||||
public static AssetPackValidatorRegistry Registry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new AssetPackValidatorRegistry();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ad448820c8d482996636e3862c1a420
|
||||
timeCreated: 1574899929
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for validating whether an <see cref="AssetBundlePack"/> can be included in an app bundle.
|
||||
/// </summary>
|
||||
public interface IAssetPackValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of errors that would prevent the specified asset pack from being included in an app bundle.
|
||||
/// </summary>
|
||||
IList<AssetPackError> CheckBuildErrors(AssetBundlePack assetBundlePack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56526b5a57974a7f8d23788f47d6a0db
|
||||
timeCreated: 1574729323
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Google.Android.AppBundle.Editor.Internal.BuildTools;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.AssetPacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Tools for adding texture compression format targeting to folders of an Android App Bundle.
|
||||
/// </summary>
|
||||
public static class TextureTargetingTools
|
||||
{
|
||||
/// <summary>
|
||||
/// The key used by bundletool to recognize a folder targeted for a specific texture format
|
||||
/// (for example, "tcf" in "textures#tcf_astc").
|
||||
/// </summary>
|
||||
private const string TextureCompressionFormatTargetingKey = "tcf";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string used to designate a texture compression format in bundletool.
|
||||
/// </summary>
|
||||
public static string GetBundleToolTextureCompressionFormatName(TextureCompressionFormat format)
|
||||
{
|
||||
if (format == TextureCompressionFormat.Default)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var name = Enum.GetName(typeof(TextureCompressionFormat), format);
|
||||
return name == null ? format.ToString() : name.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the targeting suffix for a given texture compression format, to
|
||||
/// be appended to a folder name containing AssetBundles.
|
||||
/// </summary>
|
||||
public static string GetTargetingSuffix(TextureCompressionFormat format)
|
||||
{
|
||||
if (format == TextureCompressionFormat.Default)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return string.Format("#{0}_{1}", TextureCompressionFormatTargetingKey,
|
||||
GetBundleToolTextureCompressionFormatName(format));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the folder name for a texture compression format, if any, and return it as
|
||||
/// well as the name of the folder without the targeting.
|
||||
/// </summary>
|
||||
public static void GetTextureCompressionFormatAndStripSuffix(string folderName,
|
||||
out TextureCompressionFormat targeting,
|
||||
out string tcfStrippedFolderName)
|
||||
{
|
||||
targeting = TextureCompressionFormat.Default;
|
||||
tcfStrippedFolderName = folderName;
|
||||
|
||||
// Parse the texture compression format used, if any, using the same convention
|
||||
// as bundletool for folder suffixes.
|
||||
Regex regex = new Regex(@"(?<base>.+?)#tcf_(?<value>.+)");
|
||||
GroupCollection matchedGroups = regex.Match(folderName).Groups;
|
||||
|
||||
if (!matchedGroups["base"].Success || !matchedGroups["value"].Success)
|
||||
{
|
||||
// No texture compression format targeting found.
|
||||
return;
|
||||
}
|
||||
|
||||
string tcfValue = matchedGroups["value"].Captures[0].Value;
|
||||
try
|
||||
{
|
||||
targeting = (TextureCompressionFormat) Enum.Parse(typeof(TextureCompressionFormat), tcfValue, true);
|
||||
tcfStrippedFolderName = matchedGroups["base"].Captures[0].Value;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
Debug.LogWarningFormat(
|
||||
"Ignoring unrecognized texture format \"{0}\" for folder: {1}",
|
||||
tcfValue, folderName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified SDK version is less than 21 and false if it's greater than or equal to 21.
|
||||
///
|
||||
/// This method can be used (along with the existence of install-time asset packs) to determine whether this
|
||||
/// app requires <see cref="BundletoolConfig.StandaloneConfig"/> to support pre-L devices.
|
||||
/// </summary>
|
||||
public static bool IsSdkVersionPreLollipop(AndroidSdkVersions sdkVersion)
|
||||
{
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
// The minimum API level supported by Unity 2021.2+ is 22.
|
||||
return false;
|
||||
#else
|
||||
return sdkVersion < AndroidSdkVersions.AndroidApiLevel21;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d474e3691efbb4141a8e8f46aeb406ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Google.Android.AppBundle.Editor.AssetPacks;
|
||||
using Google.Android.AppBundle.Editor.Internal.BuildTools;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build and run the app based on the current build configuration.
|
||||
/// </summary>
|
||||
public static class BuildAndRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the app, then runs it on device.
|
||||
/// First checks if any IBuildAndRunnerExtensions opt to override this action.
|
||||
/// If so, it calls their BuildAndRun method.
|
||||
/// Otherwise, it builds an AppBundle or apk based on asset delivery settings.
|
||||
/// </summary>
|
||||
public static void BuildAndRun()
|
||||
{
|
||||
// IBuildAndRunExtensions can opt to override the default Build and Run functionality.
|
||||
var extensions = GetOverridingExtensions();
|
||||
switch (extensions.Count)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
extensions.First().BuildAndRun();
|
||||
return;
|
||||
default:
|
||||
// TODO(b/144588472): Choose one of multiple implementations instead of throwing.
|
||||
throw new InvalidOperationException(
|
||||
"Multiple IBuildExtensions attempting to override Build and Run.");
|
||||
}
|
||||
|
||||
BuildAndRunDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// First builds the current project as an APK or Android App Bundle, and then installs and runs it on a
|
||||
/// connected Android device.
|
||||
/// </summary>
|
||||
private static void BuildAndRunDefault()
|
||||
{
|
||||
var assetPackConfig = AssetPackConfigSerializer.LoadConfig();
|
||||
if (assetPackConfig.HasDeliveredAssetPacks())
|
||||
{
|
||||
AppBundlePublisher.BuildAndRun(assetPackConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmulateUnityBuildAndRun();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emulates Unity's File -> Build And Run menu option.
|
||||
/// </summary>
|
||||
private static void EmulateUnityBuildAndRun()
|
||||
{
|
||||
var androidSdk = new AndroidSdk();
|
||||
var androidSdkPlatform = new AndroidSdkPlatform(androidSdk);
|
||||
var androidBuilder = new AndroidBuilder(androidSdkPlatform);
|
||||
var buildToolLogger = new BuildToolLogger();
|
||||
if (!androidBuilder.Initialize(buildToolLogger))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var artifactName = EditorUserBuildSettings.buildAppBundle ? "temp.aab" : "temp.apk";
|
||||
var artifactPath = Path.Combine(Path.GetTempPath(), artifactName);
|
||||
var buildPlayerOptions = AndroidBuildHelper.CreateBuildPlayerOptions(artifactPath);
|
||||
buildPlayerOptions.options |= BuildOptions.AutoRunPlayer;
|
||||
androidBuilder.Build(buildPlayerOptions);
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
public static List<IBuildAndRunExtension> GetOverridingExtensions()
|
||||
{
|
||||
return GetExtensions().Where(x => x.ShouldOverride()).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<IBuildAndRunExtension> GetExtensions()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes())
|
||||
.Where(type =>
|
||||
typeof(IBuildAndRunExtension).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
|
||||
.Select(Activator.CreateInstance)
|
||||
.Cast<IBuildAndRunExtension>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0928808bcd00049ba8435d9cfe4f410c
|
||||
timeCreated: 1540331104
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A window for managing settings related to building an Android app for distribution on Google Play.
|
||||
/// </summary>
|
||||
public class BuildSettingsWindow : EditorWindow
|
||||
{
|
||||
public const string WindowTitle = "Play Build Settings";
|
||||
private const int WindowMinWidth = 500;
|
||||
private const int WindowMinHeight = 250;
|
||||
private const int FieldWidth = 175;
|
||||
|
||||
private static BuildSettingsWindow _windowInstance;
|
||||
|
||||
private string _assetBundleManifestPath;
|
||||
|
||||
/// <summary>
|
||||
/// Displays this window, creating it if necessary.
|
||||
/// </summary>
|
||||
public static void ShowWindow()
|
||||
{
|
||||
_windowInstance = (BuildSettingsWindow) GetWindow(typeof(BuildSettingsWindow), true, WindowTitle);
|
||||
_windowInstance.minSize = new Vector2(WindowMinWidth, WindowMinHeight);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_windowInstance = null;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ReadFromBuildConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read and update the window with most recent build configuration values.
|
||||
/// </summary>
|
||||
void ReadFromBuildConfiguration()
|
||||
{
|
||||
_assetBundleManifestPath = AndroidBuildConfiguration.AssetBundleManifestPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update window with most recent build configuration values if the window is open.
|
||||
/// </summary>
|
||||
public static void UpdateWindowIfOpen()
|
||||
{
|
||||
if (_windowInstance != null)
|
||||
{
|
||||
_windowInstance.ReadFromBuildConfiguration();
|
||||
_windowInstance.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
// Edge case that takes place when the plugin code gets re-compiled while this window is open.
|
||||
if (_windowInstance == null)
|
||||
{
|
||||
_windowInstance = this;
|
||||
}
|
||||
|
||||
var descriptionTextStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontStyle = FontStyle.Italic,
|
||||
wordWrap = true
|
||||
};
|
||||
|
||||
EditorGUILayout.LabelField("Scenes in Build", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField(
|
||||
string.Format(
|
||||
"Note: the following settings affect APKs and app bundles built using the \"{0}\" menu's " +
|
||||
"build actions, but not apps built using the \"File\" menu's build actions.",
|
||||
GoogleEditorMenu.MainMenuName), descriptionTextStyle);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
"The scenes in the build are selected via Unity's \"Build Settings\" window.", descriptionTextStyle);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
var enabledScenes = string.Join(", ", AndroidBuildHelper.GetEditorBuildEnabledScenes());
|
||||
EditorGUILayout.LabelField(string.Format("Scenes: {0}", enabledScenes), EditorStyles.wordWrappedLabel);
|
||||
if (GUILayout.Button("Update", GUILayout.Width(100)))
|
||||
{
|
||||
GetWindow(Type.GetType("UnityEditor.BuildPlayerWindow,UnityEditor"), true);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
"If you use AssetBundles, provide the path to your AssetBundle Manifest file below to ensure that " +
|
||||
"required engine components are not stripped during the build process.", descriptionTextStyle);
|
||||
EditorGUILayout.Space();
|
||||
_assetBundleManifestPath =
|
||||
GetLabelAndTextField("AssetBundle Manifest (Optional)", _assetBundleManifestPath);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Disable the Save button unless one of the fields has changed.
|
||||
GUI.enabled = IsAnyFieldChanged();
|
||||
|
||||
if (GUILayout.Button("Save"))
|
||||
{
|
||||
SaveConfiguration();
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
}
|
||||
|
||||
private bool IsAnyFieldChanged()
|
||||
{
|
||||
return _assetBundleManifestPath != AndroidBuildConfiguration.AssetBundleManifestPath;
|
||||
}
|
||||
|
||||
private static string GetLabelAndTextField(string label, string text)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(label, GUILayout.Width(FieldWidth));
|
||||
var result = EditorGUILayout.TextField(text);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SaveConfiguration()
|
||||
{
|
||||
_assetBundleManifestPath = _assetBundleManifestPath.Trim();
|
||||
AndroidBuildConfiguration.SaveConfiguration(_assetBundleManifestPath);
|
||||
|
||||
Debug.Log("Saved Android Build Settings");
|
||||
|
||||
// If a TextField is in focus, it won't update to reflect the Trim(). So reassign focus to controlID 0.
|
||||
GUIUtility.keyboardControl = 0;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3494e8040ba74ee78ff64f1018b5969
|
||||
timeCreated: 1540331105
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ee7c5073444e433eb10f83395460280
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Google.Android.AppBundle.Editor.Internal.PlayServices;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Build tool for running <a href="https://developer.android.com/studio/command-line/aapt2">aapt2</a> commands.
|
||||
/// </summary>
|
||||
public class AndroidAssetPackagingTool : IBuildTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum version of Android SDK Build-Tools that supports "aapt2 link --proto-format".
|
||||
/// </summary>
|
||||
private const string BuildToolsMinimumVersion = "28.0.0";
|
||||
|
||||
private const string BuildToolsDisplayName = "Android SDK Build-Tools";
|
||||
|
||||
private readonly AndroidBuildTools _androidBuildTools;
|
||||
private readonly AndroidSdkPlatform _androidSdkPlatform;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AndroidAssetPackagingTool(AndroidBuildTools androidBuildTools, AndroidSdkPlatform androidSdkPlatform)
|
||||
{
|
||||
_androidBuildTools = androidBuildTools;
|
||||
_androidSdkPlatform = androidSdkPlatform;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
if (!_androidBuildTools.Initialize(buildToolLogger) || !_androidSdkPlatform.Initialize(buildToolLogger))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newestBuildToolsVersion = _androidBuildTools.GetNewestBuildToolsVersion();
|
||||
if (newestBuildToolsVersion == null)
|
||||
{
|
||||
buildToolLogger.DisplayErrorDialog(string.Format("Failed to locate {0}", BuildToolsDisplayName));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AndroidBuildTools.IsBuildToolsVersionAtLeast(newestBuildToolsVersion, BuildToolsMinimumVersion))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var message =
|
||||
string.Format(
|
||||
"This build requires {0} version {1} or later.", BuildToolsDisplayName, BuildToolsMinimumVersion);
|
||||
buildToolLogger.DisplayErrorDialog(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the AndroidManifest.xml file path, creates an APK whose
|
||||
/// files are exploded into the specified output directory path.
|
||||
/// </summary>
|
||||
/// <returns>An error message if there was a problem running aapt2, or null if successful.</returns>
|
||||
public virtual string Link(string manifestPath, string outputPath)
|
||||
{
|
||||
return Run(
|
||||
"link -I {0} --manifest {1} --proto-format --output-to-dir -o {2}",
|
||||
CommandLine.QuotePath(GetAndroidJarPath()),
|
||||
CommandLine.QuotePath(manifestPath),
|
||||
CommandLine.QuotePath(outputPath));
|
||||
}
|
||||
|
||||
private string Run(string aaptCommand, params object[] args)
|
||||
{
|
||||
var aaptPath = Path.Combine(_androidBuildTools.GetNewestBuildToolsPath(), "aapt2");
|
||||
var result = CommandLine.Run(aaptPath, string.Format(aaptCommand, args));
|
||||
return result.exitCode == 0 ? null : result.message;
|
||||
}
|
||||
|
||||
private string GetAndroidJarPath()
|
||||
{
|
||||
var newestPlatformPath = _androidSdkPlatform.GetNewestAndroidSdkPlatformPath();
|
||||
if (newestPlatformPath == null)
|
||||
{
|
||||
throw new Exception("Unable to locate the latest version of the Android SDK Platform.");
|
||||
}
|
||||
|
||||
var androidJarPath = Path.Combine(newestPlatformPath, "android.jar");
|
||||
if (!File.Exists(androidJarPath))
|
||||
{
|
||||
throw new Exception("Unable to locate android.jar in path:" + androidJarPath);
|
||||
}
|
||||
|
||||
return androidJarPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c349f457532c645d8817661cbdcb5729
|
||||
timeCreated: 1556911730
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utility methods related to Android SDK build-tools.
|
||||
/// </summary>
|
||||
public class AndroidBuildTools : IBuildTool
|
||||
{
|
||||
private const long VersionComponentMultiplier = 10000L;
|
||||
private const long ReleaseCandidateComponentSubtractor = 5000L;
|
||||
|
||||
private static readonly Regex VersionRegex = RegexHelper.CreateCompiled(@"^(\d+)\.(\d+)\.(\d+)(-rc(\d+))?$");
|
||||
|
||||
private readonly AndroidSdk _androidSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor. Must be called from the main thread.
|
||||
/// </summary>
|
||||
public AndroidBuildTools(AndroidSdk androidSdk)
|
||||
{
|
||||
_androidSdk = androidSdk;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
return _androidSdk.Initialize(buildToolLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the path to the newest version of build-tools path as a string, or null if it wasn't found.
|
||||
/// </summary>
|
||||
public virtual string GetNewestBuildToolsPath()
|
||||
{
|
||||
var newestBuildToolsVersion = GetNewestBuildToolsVersion();
|
||||
return newestBuildToolsVersion == null ? null : Path.Combine(GetBuildToolsPath(), newestBuildToolsVersion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the newest build-tools version as a string, or null if one couldn't be found.
|
||||
/// </summary>
|
||||
public virtual string GetNewestBuildToolsVersion()
|
||||
{
|
||||
var buildToolsPath = GetBuildToolsPath();
|
||||
if (!Directory.Exists(buildToolsPath))
|
||||
{
|
||||
Debug.LogErrorFormat("Failed to locate build-tools path: {0}", buildToolsPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(buildToolsPath);
|
||||
var directoryNames = directoryInfo.GetDirectories().Select(dir => dir.Name);
|
||||
var newestBuildTools = GetNewestVersion(directoryNames);
|
||||
if (newestBuildTools == null)
|
||||
{
|
||||
Debug.LogErrorFormat("Failed to locate newest build-tools: {0}", buildToolsPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newestBuildTools;
|
||||
}
|
||||
|
||||
private string GetBuildToolsPath()
|
||||
{
|
||||
return Path.Combine(_androidSdk.RootPath, "build-tools");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified existing version is the same as or newer than the specified minimum version,
|
||||
/// and false otherwise.
|
||||
/// </summary>
|
||||
public static bool IsBuildToolsVersionAtLeast(string existingVersion, string minimumRequiredVersion)
|
||||
{
|
||||
return ConvertVersionStringToLong(existingVersion) >= ConvertVersionStringToLong(minimumRequiredVersion);
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
public static string GetNewestVersion(IEnumerable<string> versions)
|
||||
{
|
||||
var maxVersionLong = -1L;
|
||||
string maxVersionString = null;
|
||||
foreach (var versionString in versions)
|
||||
{
|
||||
var versionLong = ConvertVersionStringToLong(versionString);
|
||||
if (versionLong > maxVersionLong)
|
||||
{
|
||||
maxVersionLong = versionLong;
|
||||
maxVersionString = versionString;
|
||||
}
|
||||
}
|
||||
|
||||
return maxVersionString;
|
||||
}
|
||||
|
||||
private static long ConvertVersionStringToLong(string versionString)
|
||||
{
|
||||
var match = VersionRegex.Match(versionString);
|
||||
if (!match.Success)
|
||||
{
|
||||
return -1L;
|
||||
}
|
||||
|
||||
var versionLong = 0L;
|
||||
for (var i = 1; i <= 3; i++)
|
||||
{
|
||||
var versionComponent = long.Parse(match.Groups[i].Value);
|
||||
if (versionComponent >= VersionComponentMultiplier)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
string.Format("Component {0} from {1} exceeds the limit.", versionComponent, versionString),
|
||||
"versionString");
|
||||
}
|
||||
|
||||
versionLong += versionComponent;
|
||||
// Multiply by a somewhat arbitrary value since major version outweighs minor version.
|
||||
// This particular arbitrary value supports up to 4 digits per version component.
|
||||
versionLong *= VersionComponentMultiplier;
|
||||
}
|
||||
|
||||
var releaseCandidateVersionGroup = match.Groups[5];
|
||||
if (releaseCandidateVersionGroup.Success)
|
||||
{
|
||||
var releaseCandidateVersion = long.Parse(releaseCandidateVersionGroup.Value);
|
||||
if (releaseCandidateVersion >= ReleaseCandidateComponentSubtractor)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
string.Format("rc{0} from {1} exceeds the limit.", releaseCandidateVersion, versionString),
|
||||
"versionString");
|
||||
}
|
||||
|
||||
// Add the release candidate version, e.g. rc2 is newer than rc1.
|
||||
versionLong += releaseCandidateVersion;
|
||||
// But also subtract a little, since any "rc" is earlier than the equivalent release,
|
||||
// e.g. "28.0.0-rc2" is older than "28.0.0".
|
||||
versionLong -= ReleaseCandidateComponentSubtractor;
|
||||
}
|
||||
|
||||
return versionLong;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46ae92b3dd8eb4e38b66ff3b58205318
|
||||
timeCreated: 1556911730
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,257 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Google.Android.AppBundle.Editor.Internal.AssetPacks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
#endif
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the results of an <see cref="AndroidBuilder"/> build. This provides similar information as
|
||||
/// Task<AndroidBuildReport> without requiring the .NET 4.0 async task API.
|
||||
/// </summary>
|
||||
public class AndroidBuildResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if Android Player build Succeeded, otherwise false.
|
||||
/// </summary>
|
||||
public bool Succeeded
|
||||
{
|
||||
get { return !Cancelled && ErrorMessage == null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Android Player build was cancelled before it finished.
|
||||
/// </summary>
|
||||
public bool Cancelled { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If non-null, the build failed and this message may indicate the cause.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; internal set; }
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
/// <summary>
|
||||
/// Contains the BuildReport generated during the creation of the Android Player.
|
||||
/// </summary>
|
||||
public BuildReport Report { get; internal set; }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for building an Android app for distribution on Google Play.
|
||||
/// </summary>
|
||||
public class AndroidBuilder : IBuildTool
|
||||
{
|
||||
// This string is returned by pre-2018.1's BuildPipeline.BuildPlayer() when a build is cancelled.
|
||||
private const string BuildCancelledMessage = "Building Player was cancelled";
|
||||
|
||||
private readonly AndroidSdkPlatform _androidSdkPlatform;
|
||||
|
||||
private BuildToolLogger _buildToolLogger;
|
||||
|
||||
public AndroidBuilder(AndroidSdkPlatform androidSdkPlatform)
|
||||
{
|
||||
_androidSdkPlatform = androidSdkPlatform;
|
||||
}
|
||||
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
public AndroidBuilder(AndroidSdkPlatform androidSdkPlatform, ApkSigner apkSigner) : this(androidSdkPlatform)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
_buildToolLogger = buildToolLogger;
|
||||
|
||||
if (EditorUserBuildSettings.androidBuildSystem != AndroidBuildSystem.Gradle)
|
||||
{
|
||||
// We require Gradle builds for APK Signature Scheme V2, which is required for Play Instant on Unity 2017+.
|
||||
const string message = "This build requires the Gradle Build System.\n\n" +
|
||||
"Click \"OK\" to change the Android Build System to Gradle.";
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(message))
|
||||
{
|
||||
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/154937088): remove this check if we add support for exporting a Gradle project.
|
||||
if (EditorUserBuildSettings.exportAsGoogleAndroidProject)
|
||||
{
|
||||
const string message = "This build doesn't support exporting to Android Studio.\n\n" +
|
||||
"Click \"OK\" to disable exporting a Gradle project.";
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(message))
|
||||
{
|
||||
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BuiltInPadHelper.EditorSupportsPad() && BuiltInPadHelper.ProjectHasAndroidPacks())
|
||||
{
|
||||
var message =
|
||||
"This build method doesn't support .androidpack folders. Assets within those folders will not be included" +
|
||||
" in the build.";
|
||||
buildToolLogger.DisplayOptOutDialog(message, "androidPackError");
|
||||
}
|
||||
|
||||
if (PlayerSettings.Android.splitApplicationBinary)
|
||||
{
|
||||
string messagePrefix;
|
||||
if (BuiltInPadHelper.EditorSupportsPad())
|
||||
{
|
||||
messagePrefix = "This build method doesn't support Unity's \"Split Application Binary\" option.";
|
||||
}
|
||||
else
|
||||
{
|
||||
messagePrefix = "This build method doesn't support APK Expansion (OBB) files.";
|
||||
}
|
||||
|
||||
var message = string.Format(
|
||||
"{0}\n\nLarge games can instead use the " +
|
||||
"\"{1}\" option available through the \"Google > Android App Bundle > Asset Delivery " +
|
||||
"Settings\" menu or the AssetPackConfig API's SplitBaseModuleAssets field.\n\n" +
|
||||
"Click \"OK\" to disable the \"Split Application Binary\" setting.",
|
||||
messagePrefix,
|
||||
AssetDeliveryWindow.SeparateAssetsLabel);
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(message))
|
||||
{
|
||||
PlayerSettings.Android.splitApplicationBinary = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return _androidSdkPlatform.Initialize(buildToolLogger);
|
||||
}
|
||||
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
public virtual bool BuildAndSign(BuildPlayerOptions buildPlayerOptions)
|
||||
{
|
||||
return Build(buildPlayerOptions).Succeeded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an APK or AAB based on the specified options.
|
||||
/// Displays warning/error dialogs if there are issues during the build.
|
||||
/// </summary>
|
||||
public virtual AndroidBuildResult Build(BuildPlayerOptions buildPlayerOptions)
|
||||
{
|
||||
if (_buildToolLogger == null)
|
||||
{
|
||||
throw new BuildToolNotInitializedException(this);
|
||||
}
|
||||
|
||||
if (buildPlayerOptions.target != BuildTarget.Android)
|
||||
{
|
||||
throw new ArgumentException("The build target must be Android.", "buildPlayerOptions");
|
||||
}
|
||||
|
||||
if (buildPlayerOptions.targetGroup != BuildTargetGroup.Android)
|
||||
{
|
||||
throw new ArgumentException("The build target group must be Android.", "buildPlayerOptions");
|
||||
}
|
||||
|
||||
// Note: the type of the variable below differs by version. On 2018+ it's BuildReport. On pre-2018 it's
|
||||
// string: if the string is null, the build was successful, otherwise it's a build error message.
|
||||
var buildReportOrErrorMessage = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
||||
var androidBuildResult = GetAndroidBuildResult(buildReportOrErrorMessage);
|
||||
if (androidBuildResult.Cancelled)
|
||||
{
|
||||
// Don't display an error message dialog if the user asked to cancel, just log to the Console.
|
||||
Debug.Log(BuildCancelledMessage);
|
||||
return androidBuildResult;
|
||||
}
|
||||
|
||||
if (androidBuildResult.Succeeded && !File.Exists(buildPlayerOptions.locationPathName))
|
||||
{
|
||||
// Sometimes the build "succeeds" but the AAB/APK file is missing.
|
||||
androidBuildResult.ErrorMessage =
|
||||
string.Format(
|
||||
"The Android Player file \"{0}\" is missing, possibly because of a late cancellation.",
|
||||
buildPlayerOptions.locationPathName);
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
androidBuildResult.ErrorMessage += " TotalErrors=" + buildReportOrErrorMessage.summary.totalErrors;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (androidBuildResult.Succeeded)
|
||||
{
|
||||
Debug.Log("Android Player build succeeded");
|
||||
}
|
||||
else
|
||||
{
|
||||
_buildToolLogger.DisplayErrorDialog(androidBuildResult.ErrorMessage);
|
||||
}
|
||||
|
||||
return androidBuildResult;
|
||||
}
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
private static AndroidBuildResult GetAndroidBuildResult(BuildReport buildReport)
|
||||
{
|
||||
var androidBuildResult = new AndroidBuildResult();
|
||||
androidBuildResult.Report = buildReport;
|
||||
switch (buildReport.summary.result)
|
||||
{
|
||||
case BuildResult.Succeeded:
|
||||
// Do nothing.
|
||||
break;
|
||||
case BuildResult.Cancelled:
|
||||
androidBuildResult.Cancelled = true;
|
||||
break;
|
||||
case BuildResult.Failed:
|
||||
androidBuildResult.ErrorMessage =
|
||||
string.Format("Build failed with {0} error(s)", buildReport.summary.totalErrors);
|
||||
break;
|
||||
case BuildResult.Unknown:
|
||||
androidBuildResult.ErrorMessage = "Build failed with unknown result";
|
||||
break;
|
||||
default:
|
||||
androidBuildResult.ErrorMessage =
|
||||
"Build failed with unexpected result: " + buildReport.summary.result;
|
||||
break;
|
||||
}
|
||||
|
||||
return androidBuildResult;
|
||||
}
|
||||
#else
|
||||
private static AndroidBuildResult GetAndroidBuildResult(string errorMessage)
|
||||
{
|
||||
var androidBuildResult = new AndroidBuildResult();
|
||||
if (errorMessage == BuildCancelledMessage)
|
||||
{
|
||||
androidBuildResult.Cancelled = true;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
// Assume that a null or empty error message string indicates success.
|
||||
androidBuildResult.ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
return androidBuildResult;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24e34b4393a4d4a5d8de83ca5e08621c
|
||||
timeCreated: 1556919494
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.IO;
|
||||
using Google.Android.AppBundle.Editor.Internal.PlayServices;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for <a href="https://developer.android.com/studio/command-line/adb">adb</a>.
|
||||
/// </summary>
|
||||
public class AndroidDebugBridge : IBuildTool
|
||||
{
|
||||
private const string AdbName = "adb";
|
||||
private const string AdbDirectory = "platform-tools";
|
||||
private readonly AndroidSdk _androidSdk;
|
||||
private string _adbPath;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AndroidDebugBridge(AndroidSdk androidSdk)
|
||||
{
|
||||
_androidSdk = androidSdk;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
if (!_androidSdk.Initialize(buildToolLogger))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_adbPath = GetAdbPath();
|
||||
return _adbPath != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the default activity in the specified package.
|
||||
/// Assumes that there is exactly one adb connected device.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the package to be launched e.g. "com.package.name".</param>
|
||||
/// <returns>An error message if the command failed or null if successful.</returns>
|
||||
public string LaunchApp(string packageName)
|
||||
{
|
||||
// Run the Monkey tool restricted to packageName and with 1 event, thereby launching the app.
|
||||
// See https://developer.android.com/studio/test/monkey.html
|
||||
return Run("shell monkey -p {0} 1", packageName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the adb executable associated with the androidSdk object passed into Initialize().
|
||||
/// </summary>
|
||||
public string GetAdbPath()
|
||||
{
|
||||
var adbPath = Path.Combine(_androidSdk.RootPath,
|
||||
Path.Combine(AdbDirectory, AdbName + CommandLine.GetExecutableExtension()));
|
||||
if (File.Exists(adbPath))
|
||||
{
|
||||
return adbPath;
|
||||
}
|
||||
|
||||
adbPath = CommandLine.FindExecutable(AdbName);
|
||||
return File.Exists(adbPath) ? adbPath : null;
|
||||
}
|
||||
|
||||
private string Run(string adbCommand, params object[] args)
|
||||
{
|
||||
var adbCommandWithArgs = string.Format(adbCommand, args);
|
||||
var result = CommandLine.Run(_adbPath, adbCommandWithArgs);
|
||||
return result.exitCode == 0 ? null : result.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a5aaac6c5d78491aa3590cbff08a890
|
||||
timeCreated: 1556913938
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Build tool for finding the Android SDK root path.
|
||||
/// </summary>
|
||||
public class AndroidSdk : IBuildTool
|
||||
{
|
||||
/// <summary>
|
||||
/// The ANDROID_HOME environment variable key.
|
||||
/// </summary>
|
||||
public const string AndroidHomeEnvironmentVariableKey = "ANDROID_HOME";
|
||||
|
||||
private const string AndroidNdkHomeEnvironmentVariableKey = "ANDROID_NDK_HOME";
|
||||
|
||||
/// <summary>
|
||||
/// The environment variable key we use when overriding existing Unity editor preferences.
|
||||
/// We use UNITY_JAVA_HOME instead of JAVA_HOME because Unity will override JAVA_HOME to match it's editor preferences.
|
||||
/// </summary>
|
||||
private const string JavaHomeEnvironmentVariableKey = "UNITY_JAVA_HOME";
|
||||
private const string AndroidSdkRootEditorPrefsKey = "AndroidSdkRoot";
|
||||
private const string AndroidNdkRootEditorPrefsKey = "AndroidNdkRoot";
|
||||
private const string JdkPathEditorPrefsKey = "JdkPath";
|
||||
private const string JdkUseEmbeddedEditorPrefsKey = "JdkUseEmbedded";
|
||||
|
||||
private string _androidSdkRoot;
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
return SetSdkRoot(buildToolLogger, EditorPrefs.GetString(AndroidSdkRootEditorPrefsKey));
|
||||
#elif UNITY_ANDROID
|
||||
// Guard with #if UNITY_ANDROID since UnityEditor.Android.Extensions.dll might be unavailable,
|
||||
// e.g. on a build machine without the Android platform installed.
|
||||
return SetSdkRoot(buildToolLogger, UnityEditor.Android.AndroidExternalToolsSettings.sdkRootPath);
|
||||
#else
|
||||
// This would be an unexpected and hopefully transient error.
|
||||
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
|
||||
{
|
||||
buildToolLogger.DisplayErrorDialog("ActiveBuildTarget disagrees with #if UNITY_ANDROID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle the case where the UnityEditor.Android.Extensions.dll file may not be available by requiring that
|
||||
// Android be the selected platform on 2019.3+.
|
||||
const string message = "The Build Settings > Platform must be set to Android." +
|
||||
"\n\nClick \"OK\" to switch the active platform to Android.";
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(message))
|
||||
{
|
||||
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the AndroidSdkRoot from Unity preferences or from the ANDROID_HOME environment variable.
|
||||
/// </summary>
|
||||
public virtual string RootPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_androidSdkRoot == null)
|
||||
{
|
||||
throw new BuildToolNotInitializedException(this);
|
||||
}
|
||||
|
||||
return _androidSdkRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the AndroidSdkRoot/AndroidNdkRoot/JdkPath EditorPrefs with corresponding environment variables.
|
||||
/// This is especially helpful for automated builds where the preferences may not be set.
|
||||
/// </summary>
|
||||
public static void OverrideEditorPreferences()
|
||||
{
|
||||
#if !UNITY_ANDROID
|
||||
return;
|
||||
#elif UNITY_2019_3_OR_NEWER
|
||||
OverrideEditorPreference(AndroidHomeEnvironmentVariableKey,
|
||||
() => UnityEditor.Android.AndroidExternalToolsSettings.sdkRootPath,
|
||||
v => { UnityEditor.Android.AndroidExternalToolsSettings.sdkRootPath = v; });
|
||||
OverrideEditorPreference(AndroidNdkHomeEnvironmentVariableKey,
|
||||
() => UnityEditor.Android.AndroidExternalToolsSettings.ndkRootPath,
|
||||
v => { UnityEditor.Android.AndroidExternalToolsSettings.ndkRootPath = v; });
|
||||
OverrideEditorPreference(JavaHomeEnvironmentVariableKey,
|
||||
() => UnityEditor.Android.AndroidExternalToolsSettings.jdkRootPath,
|
||||
v => { UnityEditor.Android.AndroidExternalToolsSettings.jdkRootPath = v; });
|
||||
#else
|
||||
OverrideEditorPreference(AndroidHomeEnvironmentVariableKey,
|
||||
() => EditorPrefs.GetString(AndroidSdkRootEditorPrefsKey),
|
||||
v => { EditorPrefs.SetString(AndroidSdkRootEditorPrefsKey, v); });
|
||||
OverrideEditorPreference(AndroidNdkHomeEnvironmentVariableKey,
|
||||
() => EditorPrefs.GetString(AndroidNdkRootEditorPrefsKey),
|
||||
v => { EditorPrefs.SetString(AndroidNdkRootEditorPrefsKey, v); });
|
||||
|
||||
OverrideEditorPreference(JavaHomeEnvironmentVariableKey,
|
||||
() => EditorPrefs.GetString(JdkPathEditorPrefsKey),
|
||||
v => { EditorPrefs.SetString(JdkPathEditorPrefsKey, v); });
|
||||
|
||||
// Older versions of Unity have an additional boolean preference that must be disabled.
|
||||
// Otherwise, Unity will use the JDK installed with the editor instead of the path we set in EditorPrefs.
|
||||
EditorPrefs.SetInt(JdkUseEmbeddedEditorPrefsKey, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO(b/189958664): Move this to a helper class and move the JDK portion of OverrideEditorPreferences into JavaUtils.
|
||||
private static void OverrideEditorPreference(
|
||||
string environmentVariableKey, Func<string> getPreference, Action<string> setPreference)
|
||||
{
|
||||
var environmentVariableValue = Environment.GetEnvironmentVariable(environmentVariableKey);
|
||||
var existingPreferenceValue = getPreference();
|
||||
Debug.LogFormat("Existing preference for {0} is \"{1}\"", environmentVariableKey, existingPreferenceValue);
|
||||
if (environmentVariableValue == existingPreferenceValue)
|
||||
{
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(environmentVariableValue))
|
||||
{
|
||||
Debug.LogWarningFormat("Skipping empty environment variable: {0}", environmentVariableKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogFormat("Setting preference for {0} to {1}", environmentVariableKey, environmentVariableValue);
|
||||
setPreference(environmentVariableValue);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetSdkRoot(BuildToolLogger buildToolLogger, string sdkPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sdkPath))
|
||||
{
|
||||
sdkPath = Environment.GetEnvironmentVariable(AndroidHomeEnvironmentVariableKey);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(sdkPath))
|
||||
{
|
||||
buildToolLogger.DisplayErrorDialog(
|
||||
"Failed to locate the Android SDK. Check Preferences -> External Tools to set the path.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_androidSdkRoot = sdkPath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 408798f1df959420d8fb95911a935f2d
|
||||
timeCreated: 1556899571
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Build tool for discovering Android SDK Platform versions.
|
||||
/// </summary>
|
||||
public class AndroidSdkPlatform : IBuildTool
|
||||
{
|
||||
// The minimum Android target SDK version supported by Google Play is described here:
|
||||
// https://support.google.com/googleplay/android-developer/answer/113469#targetsdk
|
||||
private const int MinimumVersion = 30;
|
||||
private const int LatestVersion = 31;
|
||||
|
||||
private static readonly Regex PlatformVersionRegex = RegexHelper.CreateCompiled(@"^android-(\d+)$");
|
||||
|
||||
private readonly AndroidSdk _androidSdk;
|
||||
|
||||
public AndroidSdkPlatform(AndroidSdk androidSdk)
|
||||
{
|
||||
_androidSdk = androidSdk;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
if (!_androidSdk.Initialize(buildToolLogger))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If targetSdkVersion is higher than the MinimumVersion, we skip verifying the newest version,
|
||||
// and instead trust that Unity will install it at build time if it isn't already available.
|
||||
var targetSdkVersion = PlayerSettings.Android.targetSdkVersion;
|
||||
if ((int)targetSdkVersion >= MinimumVersion)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string ignoredPath;
|
||||
var newestVersion = GetNewestVersionAndPath(out ignoredPath);
|
||||
if (newestVersion == null)
|
||||
{
|
||||
// Being unable to find any existing SDK may indicate a config issue, so don't try to install a new SDK.
|
||||
buildToolLogger.DisplayErrorDialog(
|
||||
"Failed to locate any Android SDK Platform version. Check that the Android SDK specified "
|
||||
+ "through Preferences -> External Tools has at least one Android SDK Platform installed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newestVersion < MinimumVersion)
|
||||
{
|
||||
var installedVersionMessage = string.Format(
|
||||
"The highest installed Android API Level is {0}, however version {1} is the minimum "
|
||||
+ "required to build for Google Play.\n\nClick \"OK\" to install Android API Level {2}.",
|
||||
newestVersion, MinimumVersion, LatestVersion);
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(installedVersionMessage))
|
||||
{
|
||||
// Note: this install can be slow, but it's not clear that it's any slower through Unity.
|
||||
AndroidSdkPackageInstaller.InstallPackage(
|
||||
string.Format("platforms;android-{0}", LatestVersion),
|
||||
string.Format("Android SDK Platform {0}", LatestVersion),
|
||||
_androidSdk.RootPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetSdkVersion == AndroidSdkVersions.AndroidApiLevelAuto)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var selectedVersionMessage = string.Format(
|
||||
"The currently selected Android Target API Level is {0}, however version {1} is the minimum "
|
||||
+ "required to build for Google Play.\n\nClick \"OK\" to change the Target API Level to "
|
||||
+ "\"Automatic (highest installed)\", which is currently {2}.",
|
||||
(int)targetSdkVersion, MinimumVersion, newestVersion);
|
||||
if (buildToolLogger.DisplayActionableErrorDialog(selectedVersionMessage))
|
||||
{
|
||||
PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the path to the newest installed version of the Android SDK Platform, or null if it isn't found.
|
||||
/// </summary>
|
||||
public virtual string GetNewestAndroidSdkPlatformPath()
|
||||
{
|
||||
string newestPlatformPath;
|
||||
GetNewestVersionAndPath(out newestPlatformPath);
|
||||
return newestPlatformPath;
|
||||
}
|
||||
|
||||
private int? GetNewestVersionAndPath(out string newestPlatformPath)
|
||||
{
|
||||
newestPlatformPath = null;
|
||||
var platformsPath = Path.Combine(_androidSdk.RootPath, "platforms");
|
||||
var platformsDirectoryInfo = new DirectoryInfo(platformsPath);
|
||||
if (!platformsDirectoryInfo.Exists)
|
||||
{
|
||||
Debug.LogErrorFormat("Unable to locate Android SDK platforms directory: {0}", platformsPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
int? newestPlatformVersion = null;
|
||||
DirectoryInfo newestPlatformDirectory = null;
|
||||
foreach (var platformDirectory in platformsDirectoryInfo.GetDirectories())
|
||||
{
|
||||
var match = PlatformVersionRegex.Match(platformDirectory.Name);
|
||||
if (!match.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var platformVersionString = match.Groups[1].Value;
|
||||
var platformVersion = int.Parse(platformVersionString);
|
||||
newestPlatformVersion = Math.Max(platformVersion, newestPlatformVersion ?? -1);
|
||||
if (platformVersion == newestPlatformVersion)
|
||||
{
|
||||
newestPlatformDirectory = platformDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
if (newestPlatformDirectory == null)
|
||||
{
|
||||
Debug.LogErrorFormat("Unable to locate newest Android SDK platform in directory: {0}", platformsPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
newestPlatformPath = newestPlatformDirectory.FullName;
|
||||
return newestPlatformVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36e42f83ed7a745ea9e4f4166d786141
|
||||
timeCreated: 1556910452
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Provided methods on 2017.4 and earlier that call the Android SDK build tool "apksigner" to verify whether an APK
|
||||
/// complies with <a href="https://source.android.com/security/apksigning/v2">APK Signature Scheme V2</a>.
|
||||
/// </summary>
|
||||
// TODO(b/189958664): Needed for 1.x API compatibility. Should be removed with 2.x.
|
||||
public class ApkSigner : IBuildTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public ApkSigner(AndroidBuildTools androidBuildTools, JavaUtils javaUtils)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 960e36d9911aa4011958de1e7407757d
|
||||
timeCreated: 1556916021
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,992 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Google.Android.AppBundle.Editor.Internal.AndroidManifest;
|
||||
using Google.Android.AppBundle.Editor.Internal.AssetPacks;
|
||||
using Google.Android.AppBundle.Editor.Internal.Config;
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build an Android App Bundle file on Unity.
|
||||
/// </summary>
|
||||
public class AppBundleBuilder : IBuildTool
|
||||
{
|
||||
public delegate void PostBuildCallback(string finishedAabFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Status for coordinating between the background thread and main thread on async builds.
|
||||
/// </summary>
|
||||
private enum BuildStatus
|
||||
{
|
||||
Running,
|
||||
Failing,
|
||||
Succeeding,
|
||||
Halted
|
||||
}
|
||||
|
||||
private const string AndroidManifestFileName = "AndroidManifest.xml";
|
||||
private const string AssetsDirectoryName = "assets";
|
||||
private const string BundleMetadataDirectoryName = "BUNDLE-METADATA";
|
||||
private const string ManifestDirectoryName = "manifest";
|
||||
private const string RequiredDirectoryName = "assets/bin/Data/Managed";
|
||||
private const string ResourceTableFileName = "resources.pb";
|
||||
private const float ProgressCreateBaseModule = 0.3f;
|
||||
private const float ProgressProcessModules = 0.5f;
|
||||
private const float ProgressRunBundletool = 0.7f;
|
||||
private const int ProgressBarWaitHandleTimeoutMs = 100;
|
||||
|
||||
// For simplicity we use the same filename when building an APK or an AAB.
|
||||
// Note: AAB builds fail if we use a .apk file extension, but APK builds can use any extension.
|
||||
private const string AndroidPlayerFileName = "tmp.aab";
|
||||
private const string AndroidPlayerFilePrefix = "tmp";
|
||||
|
||||
/// <summary>
|
||||
/// The folder where to store the asset pack, inside the "assets" folder of an Android App Bundle module.
|
||||
/// This intermediate folder name can be suffixed with a texture compression format targeting (e.g: #tcf_astc),
|
||||
/// which will be stripped out by bundletool.
|
||||
/// </summary>
|
||||
private const string AssetPackFolder = "assetpack";
|
||||
|
||||
private readonly AndroidAssetPackagingTool _androidAssetPackagingTool;
|
||||
private readonly AndroidBuilder _androidBuilder;
|
||||
private readonly BundletoolHelper _bundletool;
|
||||
private readonly JarSigner _jarSigner;
|
||||
private readonly string _workingDirectoryPath;
|
||||
private readonly ZipUtils _zipUtils;
|
||||
|
||||
private volatile BuildStatus _buildStatus = BuildStatus.Running;
|
||||
private volatile string _progressBarMessage = "";
|
||||
private volatile float _progressBarProgress;
|
||||
private volatile string _buildErrorMessage;
|
||||
private volatile string _finishedAabFilePath;
|
||||
private volatile bool _canceled;
|
||||
private Thread _backgroundThread;
|
||||
private EventWaitHandle _progressBarWaitHandle;
|
||||
private AndroidSdkVersions _minSdkVersion;
|
||||
private string _packageName;
|
||||
private int _versionCode;
|
||||
private string _versionName;
|
||||
private PostBuildCallback _createBundleAsyncOnSuccess = delegate { };
|
||||
private IEnumerable<IAssetPackManifestTransformer> _assetPackManifestTransformers;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AppBundleBuilder(
|
||||
AndroidAssetPackagingTool androidAssetPackagingTool,
|
||||
AndroidBuilder androidBuilder,
|
||||
BundletoolHelper bundletool,
|
||||
JarSigner jarSigner,
|
||||
string workingDirectoryPath,
|
||||
ZipUtils zipUtils)
|
||||
{
|
||||
_androidAssetPackagingTool = androidAssetPackagingTool;
|
||||
_androidBuilder = androidBuilder;
|
||||
_bundletool = bundletool;
|
||||
_jarSigner = jarSigner;
|
||||
_workingDirectoryPath = workingDirectoryPath;
|
||||
_zipUtils = zipUtils;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
// Cache information that is only accessible from the main thread.
|
||||
_minSdkVersion = PlayerSettings.Android.minSdkVersion;
|
||||
_packageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
|
||||
_versionCode = PlayerSettings.Android.bundleVersionCode;
|
||||
_versionName = PlayerSettings.bundleVersion;
|
||||
|
||||
_assetPackManifestTransformers = AssetPackManifestTransformerRegistry.Registry.ConstructInstances();
|
||||
var initializedManifestTransformers = true;
|
||||
foreach (var transformer in _assetPackManifestTransformers)
|
||||
{
|
||||
initializedManifestTransformers &= transformer.Initialize(buildToolLogger);
|
||||
}
|
||||
|
||||
return CheckUnityVersion(buildToolLogger)
|
||||
&& initializedManifestTransformers
|
||||
&& _androidAssetPackagingTool.Initialize(buildToolLogger)
|
||||
&& _androidBuilder.Initialize(buildToolLogger)
|
||||
&& _jarSigner.Initialize(buildToolLogger)
|
||||
&& _bundletool.Initialize(buildToolLogger)
|
||||
&& _zipUtils.Initialize(buildToolLogger);
|
||||
}
|
||||
|
||||
public string WorkingDirectoryPath
|
||||
{
|
||||
get { return _workingDirectoryPath; }
|
||||
}
|
||||
|
||||
private string AndroidPlayerFilePath
|
||||
{
|
||||
get { return Path.Combine(_workingDirectoryPath, AndroidPlayerFileName); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android Player with the specified options.
|
||||
/// Note: the specified <see cref="BuildPlayerOptions.locationPathName"/> field is ignored and the
|
||||
/// Android Player is written to a temporary file.
|
||||
/// </summary>
|
||||
public virtual AndroidBuildResult BuildAndroidPlayer(BuildPlayerOptions buildPlayerOptions)
|
||||
{
|
||||
var workingDirectory = new DirectoryInfo(_workingDirectoryPath);
|
||||
if (workingDirectory.Exists)
|
||||
{
|
||||
workingDirectory.Delete(true);
|
||||
}
|
||||
|
||||
workingDirectory.Create();
|
||||
|
||||
EditorUserBuildSettings.buildAppBundle = true;
|
||||
|
||||
Debug.LogFormat("Building Android Player: {0}", AndroidPlayerFilePath);
|
||||
// This Android Player is an intermediate build artifact, so use a temporary path for the output file path.
|
||||
var updatedBuildPlayerOptions = new BuildPlayerOptions
|
||||
{
|
||||
assetBundleManifestPath = buildPlayerOptions.assetBundleManifestPath,
|
||||
locationPathName = AndroidPlayerFilePath,
|
||||
options = buildPlayerOptions.options,
|
||||
scenes = buildPlayerOptions.scenes,
|
||||
target = buildPlayerOptions.target,
|
||||
targetGroup = buildPlayerOptions.targetGroup
|
||||
};
|
||||
|
||||
// Do not use BuildAndSign since this signature won't be used.
|
||||
return _androidBuilder.Build(updatedBuildPlayerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously builds an AAB given the specified options and existing Android Player on disk.
|
||||
/// </summary>
|
||||
/// <returns>An error message if there was an error, or null if successful.</returns>
|
||||
public string CreateBundle(CreateBundleOptions options)
|
||||
{
|
||||
if (_buildStatus != BuildStatus.Running)
|
||||
{
|
||||
throw new Exception("Unexpected call to CreateBundle() with status: " + _buildStatus);
|
||||
}
|
||||
|
||||
var moduleDirectoryList = new List<DirectoryInfo>();
|
||||
var workingDirectory = new DirectoryInfo(_workingDirectoryPath);
|
||||
|
||||
var error = CreateAssetModules(moduleDirectoryList, options.AssetPackConfig, workingDirectory);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
IList<string> bundleMetadata;
|
||||
error = CreateBaseModules(moduleDirectoryList, options, workingDirectory, out bundleMetadata);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
error = CreateBundle(moduleDirectoryList, options, bundleMetadata);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
Debug.LogFormat("Finished building app bundle: {0}", options.AabFilePath);
|
||||
_finishedAabFilePath = options.AabFilePath;
|
||||
_buildStatus = BuildStatus.Succeeding;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously builds an AAB at the specified path.
|
||||
/// </summary>
|
||||
/// <param name="aabFilePath">The AAB output file path.</param>
|
||||
/// <param name="assetPackConfig">Asset packs to include in the AAB.</param>
|
||||
/// <param name="onSuccess">
|
||||
/// Callback that fires with the final aab file location, when the bundle creation succeeds.
|
||||
/// </param>
|
||||
public void CreateBundleAsync(CreateBundleOptions options, PostBuildCallback onSuccess)
|
||||
{
|
||||
// Copy the AssetPackConfig before leaving the main thread in case the original is modified later.
|
||||
options.AssetPackConfig = SerializationHelper.DeepCopy(options.AssetPackConfig);
|
||||
_createBundleAsyncOnSuccess = onSuccess;
|
||||
StartCreateBundleAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateBundle(options);
|
||||
}
|
||||
catch (ThreadAbortException ex)
|
||||
{
|
||||
if (!_canceled)
|
||||
{
|
||||
// Unexpected ThreadAbortException.
|
||||
DisplayBuildError("Exception", ex.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Catch and display exceptions since they may otherwise be undetected on a background thread.
|
||||
DisplayBuildError("Exception", ex.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#if UNITY_2018_4_OR_NEWER && !NET_LEGACY
|
||||
/// <summary>
|
||||
/// Synchronously builds an Android Player and then produces a final AAB synchronously or asynchronously,
|
||||
/// as specified.
|
||||
/// </summary>
|
||||
/// <param name="androidBuildOptions">Options indicating how to build the AAB, including asset packs.</param>
|
||||
/// <returns>An async task that provides an AndroidBuildReport.</returns>
|
||||
public async Task<AndroidBuildReport> CreateBundleWithTask(AndroidBuildOptions androidBuildOptions)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<AndroidBuildReport>();
|
||||
var androidBuildResult = BuildAndroidPlayer(androidBuildOptions.BuildPlayerOptions);
|
||||
if (androidBuildResult.Cancelled)
|
||||
{
|
||||
taskCompletionSource.SetCanceled();
|
||||
return await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
var androidBuildReport = new AndroidBuildReport(androidBuildResult.Report);
|
||||
if (androidBuildResult.ErrorMessage != null)
|
||||
{
|
||||
taskCompletionSource.SetException(
|
||||
new AndroidBuildException(androidBuildResult.ErrorMessage, androidBuildReport));
|
||||
return await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
var createBundleOptions = new CreateBundleOptions
|
||||
{
|
||||
AabFilePath = androidBuildOptions.BuildPlayerOptions.locationPathName,
|
||||
AssetPackConfig = androidBuildOptions.AssetPackConfig ?? new AssetPackConfig(),
|
||||
CompressionOptions = androidBuildOptions.CompressionOptions
|
||||
};
|
||||
if (androidBuildOptions.ForceSingleThreadedBuild || Application.isBatchMode)
|
||||
{
|
||||
CreateBundleInternal(
|
||||
taskCompletionSource,
|
||||
() => CreateBundle(createBundleOptions),
|
||||
androidBuildReport,
|
||||
androidBuildReport);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the AssetPackConfig while still on the main thread in case the original is modified later.
|
||||
createBundleOptions.AssetPackConfig = SerializationHelper.DeepCopy(createBundleOptions.AssetPackConfig);
|
||||
StartCreateBundleAsync(() =>
|
||||
{
|
||||
CreateBundleInternal(
|
||||
taskCompletionSource,
|
||||
() => CreateBundle(createBundleOptions),
|
||||
androidBuildReport,
|
||||
androidBuildReport);
|
||||
});
|
||||
}
|
||||
|
||||
return await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an Android App Bundle containing only asset packs.
|
||||
/// </summary>
|
||||
public async Task CreateAssetOnlyBundle(AssetOnlyBuildOptions assetOnlyBuildOptions)
|
||||
{
|
||||
var assetOnlyOptions = new AssetOnlyOptions
|
||||
{
|
||||
AppVersions = new List<long>(assetOnlyBuildOptions.AppVersions),
|
||||
AssetVersionTag = assetOnlyBuildOptions.AssetVersionTag
|
||||
};
|
||||
|
||||
var createBundleOptions = new CreateBundleOptions
|
||||
{
|
||||
AabFilePath = assetOnlyBuildOptions.LocationPathName,
|
||||
AssetPackConfig = SerializationHelper.DeepCopy(assetOnlyBuildOptions.AssetPackConfig),
|
||||
AssetOnlyOptions = assetOnlyOptions
|
||||
};
|
||||
|
||||
var completionSource = new TaskCompletionSource<bool>();
|
||||
if (assetOnlyBuildOptions.ForceSingleThreadedBuild || Application.isBatchMode)
|
||||
{
|
||||
CreateBundleInternal(
|
||||
completionSource,
|
||||
() => CreateAssetOnlyBundle(createBundleOptions),
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCreateBundleAsync(() =>
|
||||
{
|
||||
CreateBundleInternal(
|
||||
completionSource,
|
||||
() => CreateAssetOnlyBundle(createBundleOptions),
|
||||
true);
|
||||
});
|
||||
}
|
||||
|
||||
await completionSource.Task;
|
||||
}
|
||||
|
||||
private string CreateAssetOnlyBundle(CreateBundleOptions options)
|
||||
{
|
||||
if (_buildStatus != BuildStatus.Running)
|
||||
{
|
||||
throw new Exception("Unexpected call to CreateAssetOnlyBundle() with status: " + _buildStatus);
|
||||
}
|
||||
|
||||
var moduleDirectoryList = new List<DirectoryInfo>();
|
||||
var workingDirectory = new DirectoryInfo(_workingDirectoryPath);
|
||||
|
||||
var error = CreateAssetModules(moduleDirectoryList, options.AssetPackConfig, workingDirectory);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
error = CreateBundle(moduleDirectoryList, options);
|
||||
if (error != null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
Debug.LogFormat("Finished building asset-only app bundle: {0}", options.AabFilePath);
|
||||
_finishedAabFilePath = options.AabFilePath;
|
||||
_buildStatus = BuildStatus.Succeeding;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CreateBundleInternal<T>(
|
||||
TaskCompletionSource<T> taskCompletionSource,
|
||||
Func<string> createBundleFunc,
|
||||
T successResult,
|
||||
AndroidBuildReport androidBuildReport = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorMessage = createBundleFunc.Invoke();
|
||||
if (errorMessage == null)
|
||||
{
|
||||
taskCompletionSource.SetResult(successResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already logged.
|
||||
taskCompletionSource.SetException(new AndroidBuildException(errorMessage, androidBuildReport));
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException ex)
|
||||
{
|
||||
if (_canceled)
|
||||
{
|
||||
taskCompletionSource.SetCanceled();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unexpected ThreadAbortException.
|
||||
taskCompletionSource.SetException(new AndroidBuildException(ex, androidBuildReport));
|
||||
DisplayBuildError("Exception", ex.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.SetException(new AndroidBuildException(ex, androidBuildReport));
|
||||
DisplayBuildError("Exception", ex.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private string CreateBaseModules(IList<DirectoryInfo> moduleDirectoryList, CreateBundleOptions options,
|
||||
DirectoryInfo workingDirectory, out IList<string> bundleMetadata)
|
||||
{
|
||||
// Create base module directory.
|
||||
bundleMetadata = new List<string>();
|
||||
var baseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseModuleName);
|
||||
var baseErrorMessage = CreateBaseModule(baseDirectory, out bundleMetadata);
|
||||
if (baseErrorMessage != null)
|
||||
{
|
||||
// Already displayed the error.
|
||||
return baseErrorMessage;
|
||||
}
|
||||
|
||||
moduleDirectoryList.Add(baseDirectory);
|
||||
|
||||
if (options.AssetPackConfig.SplitBaseModuleAssets)
|
||||
{
|
||||
// Move assets from base module directory to the separate module's directory.
|
||||
var splitBaseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseAssetsModuleName);
|
||||
var splitBaseErrorMessage = CreateSplitBaseModule(baseDirectory, splitBaseDirectory);
|
||||
if (splitBaseErrorMessage != null)
|
||||
{
|
||||
// Already displayed the error.
|
||||
return splitBaseErrorMessage;
|
||||
}
|
||||
|
||||
moduleDirectoryList.Add(splitBaseDirectory);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string CreateAssetModules(IList<DirectoryInfo> moduleDirectoryList, AssetPackConfig assetPackConfig,
|
||||
DirectoryInfo workingDirectory)
|
||||
{
|
||||
// Create asset pack module directories.
|
||||
var index = 0;
|
||||
var assetPacks = assetPackConfig.DeliveredAssetPacks;
|
||||
foreach (var entry in assetPacks)
|
||||
{
|
||||
DisplayProgress(
|
||||
string.Format("Processing asset pack {0} of {1}", index + 1, assetPacks.Count),
|
||||
Mathf.Lerp(0.1f, ProgressCreateBaseModule, (float)index / assetPacks.Count));
|
||||
index++;
|
||||
|
||||
var assetPackName = entry.Key;
|
||||
var assetPack = entry.Value;
|
||||
var assetPackDirectoryInfo = workingDirectory.CreateSubdirectory(assetPackName);
|
||||
var assetPackErrorMessage = CreateAssetPackModule(assetPackName, assetPack, assetPackDirectoryInfo);
|
||||
if (assetPackErrorMessage != null)
|
||||
{
|
||||
// Already displayed the error.
|
||||
return assetPackErrorMessage;
|
||||
}
|
||||
|
||||
moduleDirectoryList.Add(assetPackDirectoryInfo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string CreateBundle(List<DirectoryInfo> moduleDirectoryList, CreateBundleOptions options,
|
||||
IList<string> bundleMetadata = null)
|
||||
{
|
||||
// Create a ZIP file for each module directory.
|
||||
var moduleFiles = new List<string>();
|
||||
var numModules = moduleDirectoryList.Count;
|
||||
for (var i = 0; i < numModules; i++)
|
||||
{
|
||||
if (numModules == 1)
|
||||
{
|
||||
DisplayProgress("Processing base module", ProgressProcessModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayProgress(
|
||||
string.Format("Processing module {0} of {1}", i + 1, numModules),
|
||||
Mathf.Lerp(ProgressProcessModules, ProgressRunBundletool, (float)i / numModules));
|
||||
}
|
||||
|
||||
var moduleDirectoryInfo = moduleDirectoryList[i];
|
||||
var destinationDirectoryInfo = GetDestinationSubdirectory(moduleDirectoryInfo);
|
||||
|
||||
// Create ZIP file path, for example /path/to/files/base becomes /path/to/files/base/base.zip
|
||||
var zipFilePath = Path.Combine(moduleDirectoryInfo.FullName, moduleDirectoryInfo.Name + ".zip");
|
||||
var zipErrorMessage = _zipUtils.CreateZipFile(zipFilePath, destinationDirectoryInfo.FullName, ".");
|
||||
if (zipErrorMessage != null)
|
||||
{
|
||||
return DisplayBuildError("Zip creation", zipErrorMessage);
|
||||
}
|
||||
|
||||
moduleFiles.Add(zipFilePath);
|
||||
}
|
||||
|
||||
if (bundleMetadata == null)
|
||||
{
|
||||
bundleMetadata = new List<string>();
|
||||
}
|
||||
|
||||
DisplayProgress("Running bundletool", ProgressRunBundletool);
|
||||
var configParams = CreateBuildBundleConfigParams(options);
|
||||
var buildBundleErrorMessage =
|
||||
_bundletool.BuildBundle(options.AabFilePath, moduleFiles, bundleMetadata, configParams);
|
||||
if (buildBundleErrorMessage != null)
|
||||
{
|
||||
return DisplayBuildError("Bundletool", buildBundleErrorMessage);
|
||||
}
|
||||
|
||||
// Only sign the .aab if a custom keystore is configured.
|
||||
if (_jarSigner.UseCustomKeystore)
|
||||
{
|
||||
DisplayProgress("Signing bundle", 0.9f);
|
||||
var signingErrorMessage = _jarSigner.Sign(options.AabFilePath);
|
||||
if (signingErrorMessage != null)
|
||||
{
|
||||
Debug.LogError("Failed to sign");
|
||||
return DisplayBuildError("Signing", signingErrorMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogFormat("Skipped signing since a Custom Keystore isn't configured in Android Player Settings");
|
||||
}
|
||||
|
||||
MoveSymbolsZipFile(options.AabFilePath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private BundletoolHelper.BuildBundleConfigParams CreateBuildBundleConfigParams(CreateBundleOptions options)
|
||||
{
|
||||
var configParams = new BundletoolHelper.BuildBundleConfigParams
|
||||
{
|
||||
defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName(
|
||||
options.AssetPackConfig.DefaultTextureCompressionFormat),
|
||||
defaultDeviceTier =
|
||||
DeviceTierTargetingTools.GetBundleToolDeviceTierFormatName(
|
||||
options.AssetPackConfig.DefaultDeviceTier),
|
||||
minSdkVersion = _minSdkVersion,
|
||||
compressionOptions = options.CompressionOptions ?? new CompressionOptions(),
|
||||
containsInstallTimeAssetPack = options.AssetPackConfig.SplitBaseModuleAssets
|
||||
};
|
||||
|
||||
var assetPacks = options.AssetPackConfig.DeliveredAssetPacks;
|
||||
foreach (var entry in assetPacks)
|
||||
{
|
||||
var assetPackName = entry.Key;
|
||||
var assetPack = entry.Value;
|
||||
|
||||
configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetBundleFilePath != null;
|
||||
configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetPackDirectoryPath != null;
|
||||
configParams.enableDeviceTierTargeting |= assetPack.DeviceTierToAssetBundleFilePath != null;
|
||||
configParams.enableDeviceTierTargeting |= assetPack.DeviceTierToAssetPackDirectoryPath != null;
|
||||
configParams.containsInstallTimeAssetPack |=
|
||||
assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime;
|
||||
}
|
||||
|
||||
configParams.containsInstallTimeAssetPack |= options.AssetPackConfig.SplitBaseModuleAssets;
|
||||
configParams.assetOnlyOptions = options.AssetOnlyOptions;
|
||||
return configParams;
|
||||
}
|
||||
|
||||
private void StartCreateBundleAsync(ThreadStart threadStart)
|
||||
{
|
||||
_progressBarWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
EditorApplication.update += HandleUpdate;
|
||||
_backgroundThread = new Thread(threadStart);
|
||||
_backgroundThread.Name = "AppBundle";
|
||||
_backgroundThread.Start();
|
||||
}
|
||||
|
||||
// Handler for EditorApplication.update callbacks on the main thread.
|
||||
private void HandleUpdate()
|
||||
{
|
||||
switch (_buildStatus)
|
||||
{
|
||||
case BuildStatus.Running:
|
||||
// Checking for task cancellation during EditorApplication.update is much more responsive
|
||||
// than only calling DisplayCancelableProgressBar() when the message changes.
|
||||
if (EditorUtility.DisplayCancelableProgressBar(
|
||||
"Building App Bundle", _progressBarMessage, _progressBarProgress))
|
||||
{
|
||||
Debug.Log("Cancelling app bundle build...");
|
||||
EditorUtility.ClearProgressBar();
|
||||
_canceled = true;
|
||||
_backgroundThread.Abort();
|
||||
_buildStatus = BuildStatus.Halted;
|
||||
}
|
||||
|
||||
// Signal the background thread that we've handled an update since DisplayProgress() was called.
|
||||
_progressBarWaitHandle.Set();
|
||||
break;
|
||||
case BuildStatus.Failing:
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.DisplayDialog(
|
||||
BuildToolLogger.BuildErrorTitle, _buildErrorMessage, WindowUtils.OkButtonText);
|
||||
_buildStatus = BuildStatus.Halted;
|
||||
break;
|
||||
case BuildStatus.Succeeding:
|
||||
EditorUtility.ClearProgressBar();
|
||||
_buildStatus = BuildStatus.Halted;
|
||||
_createBundleAsyncOnSuccess(_finishedAabFilePath);
|
||||
break;
|
||||
case BuildStatus.Halted:
|
||||
_progressBarWaitHandle.Close();
|
||||
EditorApplication.update -= HandleUpdate;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unexpected BuildStatus: " + _buildStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private string CreateAssetPackModule(
|
||||
string assetPackName, AssetPack assetPack, DirectoryInfo assetPackDirectoryInfo)
|
||||
{
|
||||
var aaptErrorMessage =
|
||||
CreateAssetPackWithManifest(assetPackDirectoryInfo, assetPackName, assetPack.DeliveryMode);
|
||||
if (aaptErrorMessage != null)
|
||||
{
|
||||
// Already displayed the error.
|
||||
return aaptErrorMessage;
|
||||
}
|
||||
|
||||
// Copy all assets to an "assets" subdirectory in the destination folder.
|
||||
var destinationAssetsDirectory =
|
||||
GetDestinationSubdirectory(assetPackDirectoryInfo).CreateSubdirectory(AssetsDirectoryName);
|
||||
if (assetPack.AssetBundleFilePath != null)
|
||||
{
|
||||
// Copy AssetBundle files into the module's "assets" folder, inside an "assetpack" folder.
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(AssetPackFolder).FullName;
|
||||
File.Copy(assetPack.AssetBundleFilePath, Path.Combine(outputDirectory, assetPackName));
|
||||
}
|
||||
else if (assetPack.CompressionFormatToAssetBundleFilePath != null)
|
||||
{
|
||||
// Copy the AssetBundle files into the module's "assets" folder, inside an "assetpack#tcf_xxx" folder.
|
||||
foreach (var compressionAndFilePath in assetPack.CompressionFormatToAssetBundleFilePath)
|
||||
{
|
||||
var targetedAssetsFolderName =
|
||||
AssetPackFolder + TextureTargetingTools.GetTargetingSuffix(compressionAndFilePath.Key);
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(targetedAssetsFolderName);
|
||||
File.Copy(compressionAndFilePath.Value, Path.Combine(outputDirectory.FullName, assetPackName));
|
||||
}
|
||||
}
|
||||
else if (assetPack.DeviceTierToAssetBundleFilePath != null)
|
||||
{
|
||||
// Copy the AssetBundle files into the module's "assets" folder, inside an "assetpack#tier_xxx" folder.
|
||||
foreach (var deviceTierAndFilePath in assetPack.DeviceTierToAssetBundleFilePath)
|
||||
{
|
||||
var targetedAssetsFolderName =
|
||||
AssetPackFolder + DeviceTierTargetingTools.GetTargetingSuffix(deviceTierAndFilePath.Key);
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(targetedAssetsFolderName);
|
||||
File.Copy(deviceTierAndFilePath.Value, Path.Combine(outputDirectory.FullName, assetPackName));
|
||||
}
|
||||
}
|
||||
else if (assetPack.AssetPackDirectoryPath != null)
|
||||
{
|
||||
var sourceAssetsDirectory = new DirectoryInfo(assetPack.AssetPackDirectoryPath);
|
||||
if (!sourceAssetsDirectory.Exists)
|
||||
{
|
||||
// TODO: check this earlier.
|
||||
return DisplayBuildError("Missing directory for " + assetPackName, sourceAssetsDirectory.FullName);
|
||||
}
|
||||
|
||||
// Copy asset pack files into the module's "assets" folder, inside an "assetpack" folder.
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(AssetPackFolder);
|
||||
CopyFilesRecursively(sourceAssetsDirectory, outputDirectory);
|
||||
}
|
||||
else if (assetPack.CompressionFormatToAssetPackDirectoryPath != null)
|
||||
{
|
||||
// Copy asset pack files into the module's "assets" folder, inside an "assetpack#tcf_xxx" folder.
|
||||
foreach (var compressionAndDirectoryPath in assetPack.CompressionFormatToAssetPackDirectoryPath)
|
||||
{
|
||||
var sourceAssetsDirectory = new DirectoryInfo(compressionAndDirectoryPath.Value);
|
||||
if (!sourceAssetsDirectory.Exists)
|
||||
{
|
||||
// TODO: check this earlier.
|
||||
return DisplayBuildError(
|
||||
"Missing directory for " + assetPackName, sourceAssetsDirectory.FullName);
|
||||
}
|
||||
|
||||
var targetedAssetsFolderName =
|
||||
AssetPackFolder + TextureTargetingTools.GetTargetingSuffix(compressionAndDirectoryPath.Key);
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(targetedAssetsFolderName);
|
||||
CopyFilesRecursively(sourceAssetsDirectory, outputDirectory);
|
||||
}
|
||||
}
|
||||
else if (assetPack.DeviceTierToAssetPackDirectoryPath != null)
|
||||
{
|
||||
// Copy asset pack files into the module's "assets" folder, inside an "assetpack#tier_xxx" folder.
|
||||
foreach (var deviceTierAndDirectoryPath in assetPack.DeviceTierToAssetPackDirectoryPath)
|
||||
{
|
||||
var sourceAssetsDirectory = new DirectoryInfo(deviceTierAndDirectoryPath.Value);
|
||||
if (!sourceAssetsDirectory.Exists)
|
||||
{
|
||||
// TODO: check this earlier.
|
||||
return DisplayBuildError("Missing directory for " + assetPackName,
|
||||
sourceAssetsDirectory.FullName);
|
||||
}
|
||||
|
||||
var targetedAssetsFolderName =
|
||||
AssetPackFolder + DeviceTierTargetingTools.GetTargetingSuffix(deviceTierAndDirectoryPath.Key);
|
||||
var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(targetedAssetsFolderName);
|
||||
CopyFilesRecursively(sourceAssetsDirectory, outputDirectory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Missing asset pack files: " + assetPackName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void MoveSymbolsZipFile(string aabFilePath)
|
||||
{
|
||||
var outputDirectoryPath = Path.GetDirectoryName(aabFilePath);
|
||||
if (_workingDirectoryPath == outputDirectoryPath)
|
||||
{
|
||||
// If the temporary player file and final output file are in the same directory, don't move the symbols.
|
||||
// (This is likely a Build & Run.)
|
||||
return;
|
||||
}
|
||||
|
||||
var symbolsFilePath = Path.Combine(_workingDirectoryPath, GetSymbolsFileName(AndroidPlayerFilePrefix));
|
||||
if (!File.Exists(symbolsFilePath))
|
||||
{
|
||||
// The file won't exist for Mono builds or if EditorUserBuildSettings.androidCreateSymbolsZip is false.
|
||||
return;
|
||||
}
|
||||
|
||||
var outputSymbolsFileName = GetSymbolsFileName(Path.GetFileNameWithoutExtension(aabFilePath));
|
||||
var outputSymbolsFilePath = Path.Combine(outputDirectoryPath, outputSymbolsFileName);
|
||||
if (File.Exists(outputSymbolsFilePath))
|
||||
{
|
||||
// If the symbols file already exists, we need to delete it first.
|
||||
File.Delete(outputSymbolsFilePath);
|
||||
}
|
||||
|
||||
File.Move(symbolsFilePath, outputSymbolsFilePath);
|
||||
}
|
||||
|
||||
private string GetSymbolsFileName(string prefix)
|
||||
{
|
||||
return string.Format("{0}-{1}-v{2}.symbols.zip", prefix, _versionName, _versionCode);
|
||||
}
|
||||
|
||||
private static void CopyFilesRecursively(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory)
|
||||
{
|
||||
foreach (var sourceSubdirectory in sourceDirectory.GetDirectories())
|
||||
{
|
||||
var destinationSubdirectory = destinationDirectory.CreateSubdirectory(sourceSubdirectory.Name);
|
||||
CopyFilesRecursively(sourceSubdirectory, destinationSubdirectory);
|
||||
}
|
||||
|
||||
foreach (var file in sourceDirectory.GetFiles())
|
||||
{
|
||||
file.CopyTo(Path.Combine(destinationDirectory.FullName, file.Name), false);
|
||||
}
|
||||
}
|
||||
|
||||
private XDocument CreateAssetPackManifestXDocument(string packName, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
if (_assetPackManifestTransformers == null)
|
||||
{
|
||||
throw new BuildToolNotInitializedException(this);
|
||||
}
|
||||
|
||||
var doc = AssetPackManifestHelper.CreateAssetPackManifestXDocument(
|
||||
_packageName,
|
||||
packName,
|
||||
deliveryMode);
|
||||
foreach (var transformer in _assetPackManifestTransformers)
|
||||
{
|
||||
var error = transformer.Transform(doc);
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
DisplayBuildError("AndroidManifest configuration", error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private string CreateBaseModule(DirectoryInfo baseWorkingDirectory, out IList<string> bundleMetadata)
|
||||
{
|
||||
DisplayProgress("Creating base module", ProgressCreateBaseModule);
|
||||
|
||||
var sourceDirectoryInfo = baseWorkingDirectory.CreateSubdirectory("source");
|
||||
var unzipErrorMessage = _zipUtils.UnzipFile(AndroidPlayerFilePath, sourceDirectoryInfo.FullName);
|
||||
if (unzipErrorMessage != null)
|
||||
{
|
||||
bundleMetadata = null;
|
||||
return DisplayBuildError("Unzip", unzipErrorMessage);
|
||||
}
|
||||
|
||||
bundleMetadata = GetExistingBundleMetadata(sourceDirectoryInfo);
|
||||
|
||||
var destinationDirectoryInfo = GetDestinationSubdirectory(baseWorkingDirectory);
|
||||
var baseModuleDirectories = sourceDirectoryInfo.GetDirectories(AndroidAppBundle.BaseModuleName);
|
||||
if (baseModuleDirectories.Length != 1)
|
||||
{
|
||||
return DisplayBuildError("Find base directory", sourceDirectoryInfo.FullName);
|
||||
}
|
||||
|
||||
ArrangeFilesForExistingModule(baseModuleDirectories[0], destinationDirectoryInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
private string CreateSplitBaseModule(DirectoryInfo baseDirectory, DirectoryInfo splitBaseDirectory)
|
||||
{
|
||||
var aaptErrorMessage =
|
||||
CreateAssetPackWithManifest(
|
||||
splitBaseDirectory, AndroidAppBundle.BaseAssetsModuleName, AssetPackDeliveryMode.InstallTime);
|
||||
if (aaptErrorMessage != null)
|
||||
{
|
||||
// Already displayed the error.
|
||||
return aaptErrorMessage;
|
||||
}
|
||||
|
||||
var baseDestination = GetDestinationSubdirectory(baseDirectory);
|
||||
var baseAssetsDirectories = baseDestination.GetDirectories(AssetsDirectoryName);
|
||||
if (baseAssetsDirectories.Length != 1)
|
||||
{
|
||||
return DisplayBuildError("Find base assets directory",
|
||||
string.Format("Expected 1 directory but found {0}", baseAssetsDirectories.Length));
|
||||
}
|
||||
|
||||
var splitBaseDestination = GetDestinationSubdirectory(splitBaseDirectory);
|
||||
var splitBaseAssetsPath = Path.Combine(splitBaseDestination.FullName, AssetsDirectoryName);
|
||||
baseAssetsDirectories[0].MoveTo(splitBaseAssetsPath);
|
||||
|
||||
// IL2CPP build crash unless the assets/bin/Data/Managed folder exists in the base apk.
|
||||
// Create an empty file in that folder to ensure that folder is present.
|
||||
var requiredDirPath = Path.Combine(baseDestination.FullName, RequiredDirectoryName);
|
||||
var requiredDirInfo = Directory.CreateDirectory(requiredDirPath);
|
||||
File.Create(Path.Combine(requiredDirInfo.FullName, ".keep_folder")).Close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't support certain versions of Unity due to the "Failed to load 'libmain.so'" crash.
|
||||
// See https://github.com/google/play-unity-plugins/issues/80 and
|
||||
// https://issuetracker.unity3d.com/issues/android-app-installed-using-apk-from-app-bundle-option-in-android-studio-fails-to-run
|
||||
private static bool CheckUnityVersion(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
#if UNITY_2020_2 || UNITY_2020_3_0 || UNITY_2020_3_1 || UNITY_2020_3_2 || UNITY_2021_1_0 || UNITY_2021_1_1
|
||||
buildToolLogger.DisplayErrorDialog(
|
||||
"Apps built as AABs with this version of Unity may crash at runtime. Upgrade to 2020.3.3f1, 2021.1.2f1, or later to avoid this issue.");
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
private string CreateAssetPackWithManifest(
|
||||
DirectoryInfo rootDirectory, string assetPackName, AssetPackDeliveryMode deliveryMode)
|
||||
{
|
||||
var androidManifestFilePath = Path.Combine(rootDirectory.FullName, AndroidManifestFileName);
|
||||
var assetPackManifestXDocument = CreateAssetPackManifestXDocument(assetPackName, deliveryMode);
|
||||
assetPackManifestXDocument.Save(androidManifestFilePath);
|
||||
|
||||
var source = rootDirectory.CreateSubdirectory("manifest");
|
||||
var aaptErrorMessage = _androidAssetPackagingTool.Link(androidManifestFilePath, source.FullName);
|
||||
if (aaptErrorMessage != null)
|
||||
{
|
||||
return DisplayBuildError("AAPT2 link " + assetPackName, aaptErrorMessage);
|
||||
}
|
||||
|
||||
// aapt2 link creates an empty resource table even though asset packs have no resources.
|
||||
// Bundletool fails if the asset pack has a resources.pb. Only retain the AndroidManifest.xml file.
|
||||
var destination = GetDestinationSubdirectory(rootDirectory);
|
||||
foreach (var sourceFileInfo in source.GetFiles())
|
||||
{
|
||||
if (sourceFileInfo.Name == AndroidManifestFileName)
|
||||
{
|
||||
var destinationSubdirectory = destination.CreateSubdirectory(ManifestDirectoryName);
|
||||
sourceFileInfo.MoveTo(Path.Combine(destinationSubdirectory.FullName, sourceFileInfo.Name));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ArrangeFilesForExistingModule(DirectoryInfo source, DirectoryInfo destination)
|
||||
{
|
||||
// Only include the resources.pb file located directly in the source directory.
|
||||
foreach (var sourceFileInfo in source.GetFiles())
|
||||
{
|
||||
if (sourceFileInfo.Name == ResourceTableFileName)
|
||||
{
|
||||
sourceFileInfo.MoveTo(Path.Combine(destination.FullName, ResourceTableFileName));
|
||||
}
|
||||
}
|
||||
|
||||
// Include all directories located in the source directory.
|
||||
foreach (var sourceDirectoryInfo in source.GetDirectories())
|
||||
{
|
||||
sourceDirectoryInfo.MoveTo(Path.Combine(destination.FullName, sourceDirectoryInfo.Name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the root directory of an existing AAB, return a list of all bundle metadata files in the specific
|
||||
/// format expected by bundletool.
|
||||
/// </summary>
|
||||
private static IList<string> GetExistingBundleMetadata(DirectoryInfo rootDirectoryInfo)
|
||||
{
|
||||
var bundleMetadata = new List<string>();
|
||||
var bundleMetadataDirectories = rootDirectoryInfo.GetDirectories(BundleMetadataDirectoryName);
|
||||
if (bundleMetadataDirectories.Length != 1)
|
||||
{
|
||||
return bundleMetadata;
|
||||
}
|
||||
|
||||
// The metadata files are usually one directory deep, but use Directory.GetFiles() since this isn't a given.
|
||||
var bundleMetadataPath = bundleMetadataDirectories[0].FullName;
|
||||
var metadataFiles = Directory.GetFiles(bundleMetadataPath, "*", SearchOption.AllDirectories);
|
||||
foreach (var physicalFile in metadataFiles)
|
||||
{
|
||||
if (physicalFile.Substring(0, bundleMetadataPath.Length) != bundleMetadataPath)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A bundle metadata file doesn't match the expected parent path: " + physicalFile);
|
||||
}
|
||||
|
||||
var startIndex = bundleMetadataPath.Length + 1;
|
||||
var relativePath = physicalFile.Substring(startIndex, physicalFile.Length - startIndex);
|
||||
// Expected format is "<bundle-path>:<physical-file>". "<bundle-path>" uses "/" directory separators.
|
||||
var bundlePath = relativePath.Replace("\\", "/");
|
||||
bundleMetadata.Add(string.Format("{0}:{1}", bundlePath, physicalFile));
|
||||
}
|
||||
|
||||
return bundleMetadata;
|
||||
}
|
||||
|
||||
private static DirectoryInfo GetDestinationSubdirectory(DirectoryInfo directoryInfo)
|
||||
{
|
||||
return directoryInfo.CreateSubdirectory("destination");
|
||||
}
|
||||
|
||||
private void DisplayProgress(string info, float progress)
|
||||
{
|
||||
Debug.LogFormat("{0}...", info);
|
||||
if (_backgroundThread == null)
|
||||
{
|
||||
// Running synchronously in batch mode.
|
||||
return;
|
||||
}
|
||||
|
||||
_progressBarMessage = info;
|
||||
_progressBarProgress = progress;
|
||||
|
||||
// Wait for the main thread to display the message in at least one callback of the update handler.
|
||||
// Give up after a short timeout since this is best effort, and we don't want to block indefinitely.
|
||||
_progressBarWaitHandle.WaitOne(ProgressBarWaitHandleTimeoutMs);
|
||||
}
|
||||
|
||||
private string DisplayBuildError(string errorType, string errorMessage)
|
||||
{
|
||||
if (_buildStatus == BuildStatus.Halted)
|
||||
{
|
||||
// Ignore any errors after we've halted, e.g. if the thread abort causes an exception to be thrown.
|
||||
return null;
|
||||
}
|
||||
|
||||
_buildStatus = BuildStatus.Failing;
|
||||
_buildErrorMessage = string.Format("{0} failed: {1}", errorType, errorMessage);
|
||||
Debug.LogError(_buildErrorMessage);
|
||||
return _buildErrorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4ee87517b6d8405f9af0cd4e6d2029e
|
||||
timeCreated: 1556919555
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to build an Android App Bundle file on Unity.
|
||||
/// </summary>
|
||||
public class AppBundleRunner : IBuildTool
|
||||
{
|
||||
private readonly AndroidDebugBridge _adb;
|
||||
private readonly BundletoolHelper _bundletool;
|
||||
private string _packageName;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AppBundleRunner(AndroidDebugBridge adb, BundletoolHelper bundletool)
|
||||
{
|
||||
_adb = adb;
|
||||
_bundletool = bundletool;
|
||||
}
|
||||
|
||||
public virtual bool Initialize(BuildToolLogger buildToolLogger)
|
||||
{
|
||||
_packageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
|
||||
return _adb.Initialize(buildToolLogger) && _bundletool.Initialize(buildToolLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified app bundle into an apk set file,
|
||||
/// installs the proper apks,
|
||||
/// then runs the app on device.
|
||||
/// Note: This is designed to run in the main thread.
|
||||
/// TODO(b/139089705): Explore running this in a background thread.
|
||||
/// </summary>
|
||||
public void RunBundle(string aabFilePath, BundletoolBuildMode buildMode)
|
||||
{
|
||||
string apkSetFilePath;
|
||||
var errorMessage = ConvertAabToApkSet(aabFilePath, buildMode, out apkSetFilePath);
|
||||
if (errorMessage != null)
|
||||
{
|
||||
DisplayRunError("Creating apk set", errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
errorMessage = _bundletool.InstallApkSet(apkSetFilePath, _adb.GetAdbPath());
|
||||
if (errorMessage != null)
|
||||
{
|
||||
DisplayRunError("Installing app bundle", errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Installing app bundle");
|
||||
|
||||
// TODO(b/138958246): Check the number of devices before launching to display a nicer error message.
|
||||
errorMessage = _adb.LaunchApp(_packageName);
|
||||
if (errorMessage != null)
|
||||
{
|
||||
DisplayRunError("Launching app bundle", errorMessage);
|
||||
}
|
||||
|
||||
Debug.Log("Launching app bundle");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified app bundle into an apk set file.
|
||||
/// </summary>
|
||||
/// <returns>An error message if the operation failed and null otherwise.</returns>
|
||||
public string ConvertAabToApkSet(string aabFilePath, BundletoolBuildMode buildMode, out string apkSetFilePath)
|
||||
{
|
||||
var aabFileDirectory = Path.GetDirectoryName(aabFilePath);
|
||||
if (aabFileDirectory == null)
|
||||
{
|
||||
apkSetFilePath = null;
|
||||
return "App Bundle does not exist at path " + aabFilePath;
|
||||
}
|
||||
|
||||
var apkSetFileName = Path.GetFileNameWithoutExtension(aabFilePath);
|
||||
apkSetFilePath = Path.Combine(aabFileDirectory, apkSetFileName + ".apks");
|
||||
File.Delete(apkSetFilePath);
|
||||
|
||||
// TODO(b/149439143): Set this value to be true regardless of BuildMode once local testing works on instant.
|
||||
var enableLocalTesting = buildMode == BundletoolBuildMode.Persistent;
|
||||
|
||||
return _bundletool.BuildApkSet(aabFilePath, apkSetFilePath, buildMode, enableLocalTesting);
|
||||
}
|
||||
|
||||
// TODO(b/138958246): Display a dialog prompt and/or progress bar.
|
||||
private void DisplayRunError(string errorType, string errorMessage)
|
||||
{
|
||||
var fullErrorMessage = string.Format("{0} failed: {1}", errorType, errorMessage);
|
||||
Debug.LogError(fullErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8103f73350aca4529af9e65605055a8b
|
||||
timeCreated: 1540331104
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Google.Android.AppBundle.Editor.Internal.Utils;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Google.Android.AppBundle.Editor.Internal.BuildTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for logging errors and/or displaying dialogs if there is a build issue.
|
||||
/// </summary>
|
||||
public class BuildToolLogger
|
||||
{
|
||||
public const string BuildErrorTitle = "Build Error";
|
||||
public const string BuildWarningTitle = "Build Warning";
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the running instance of Unity is headless (e.g. a command line build),
|
||||
/// and false if it's a normal instance of Unity.
|
||||
/// </summary>
|
||||
public virtual bool IsHeadlessMode()
|
||||
{
|
||||
return WindowUtils.IsHeadlessMode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the specified message indicating that a build error occurred. Displays an "OK" button that can
|
||||
/// be used to indicate that the user wants to perform a followup action, e.g. fixing a build setting.
|
||||
/// </summary>
|
||||
/// <returns>True if the user clicks "OK", otherwise false.</returns>
|
||||
public virtual bool DisplayActionableErrorDialog(string message)
|
||||
{
|
||||
Debug.LogErrorFormat("Actionable build error: {0}", message);
|
||||
|
||||
if (IsHeadlessMode())
|
||||
{
|
||||
// During a headless build it isn't possible to prompt to fix the issue, so always return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
return EditorUtility.DisplayDialog(
|
||||
BuildErrorTitle, message, WindowUtils.OkButtonText, WindowUtils.CancelButtonText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays dialog with an additional option to "Opt out for this session". If the user selects that setting,
|
||||
/// they won't be shown the dialog again until they restart Unity. Instead, the specified message will be logged
|
||||
/// as a warning.
|
||||
///
|
||||
/// On Unity versions 2019.2 and below, this method displays a normal dialog without the opt out option.
|
||||
/// </summary>
|
||||
/// <param name="message">The message included in the dialog.</param>
|
||||
/// <param name="optOutPreferenceKey">The unique key used to determine if a user has opted out of this dialog.</param>
|
||||
public virtual void DisplayOptOutDialog(string message, string optOutPreferenceKey)
|
||||
{
|
||||
Debug.LogWarningFormat("Build warning: {0}", message);
|
||||
if (IsHeadlessMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
var didOptOut =
|
||||
EditorUtility.GetDialogOptOutDecision(DialogOptOutDecisionType.ForThisSession, optOutPreferenceKey);
|
||||
if (didOptOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
EditorUtility.DisplayDialog(BuildWarningTitle, message, WindowUtils.OkButtonText,
|
||||
DialogOptOutDecisionType.ForThisSession, optOutPreferenceKey);
|
||||
#else
|
||||
EditorUtility.DisplayDialog(BuildWarningTitle, message, WindowUtils.OkButtonText);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the specified message indicating that a build error occurred.
|
||||
/// </summary>
|
||||
public virtual void DisplayErrorDialog(string message)
|
||||
{
|
||||
Debug.LogErrorFormat("Build error: {0}", message);
|
||||
|
||||
if (!IsHeadlessMode())
|
||||
{
|
||||
EditorUtility.DisplayDialog(BuildErrorTitle, message, WindowUtils.OkButtonText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user