private BundletoolHelper.BuildBundleConfigParams CreateBuildBundleConfigParams(CreateBundleOptions options) { var configParams = new BundletoolHelper.BuildBundleConfigParams { defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName( options.AssetPackConfig.DefaultTextureCompressionFormat), 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.containsInstallTimeAssetPack |= assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime; } configParams.containsInstallTimeAssetPack |= options.AssetPackConfig.SplitBaseModuleAssets; return(configParams); }
private static string GetCompressionFormatSuffix(MobileTextureSubtarget subtarget) { var format = GetCompressionFormat(subtarget); return(TextureTargetingTools.GetTargetingSuffix(format)); }
private bool CreateAssetPackModule( string assetPackName, AssetPack assetPack, DirectoryInfo assetPackDirectoryInfo) { var androidManifestXmlPath = Path.Combine(assetPackDirectoryInfo.FullName, AndroidManifestFileName); var assetPackManifestXDocument = CreateAssetPackManifestXDocument(assetPackName, assetPack.DeliveryMode); assetPackManifestXDocument.Save(androidManifestXmlPath); // Use aapt2 link to make a bundletool compatible module var sourceDirectoryInfo = assetPackDirectoryInfo.CreateSubdirectory("source"); var aaptErrorMessage = _androidAssetPackagingTool.Link(androidManifestXmlPath, sourceDirectoryInfo.FullName); if (aaptErrorMessage != null) { DisplayBuildError("aapt2 link " + assetPackName, aaptErrorMessage); return(false); } var destinationDirectoryInfo = GetDestinationSubdirectory(assetPackDirectoryInfo); ArrangeFilesForAssetPack(sourceDirectoryInfo, destinationDirectoryInfo); // Copy all assets to an "assets" subdirectory in the destination folder. var destinationAssetsDirectory = destinationDirectoryInfo.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.AssetPackDirectoryPath != null) { var sourceAssetsDirectory = new DirectoryInfo(assetPack.AssetPackDirectoryPath); if (!sourceAssetsDirectory.Exists) { // TODO: check this earlier. DisplayBuildError("Missing directory for " + assetPackName, sourceAssetsDirectory.FullName); return(false); } // Copy asset pack files into the module's "assets" folder, inside an "assetpack" folder. var outputDirectory = destinationAssetsDirectory.CreateSubdirectory(AssetPackFolder); CopyFilesRecursively(sourceAssetsDirectory, outputDirectory); } else { throw new InvalidOperationException("Missing asset pack files: " + assetPackName); } return(true); }
/// <summary> /// Synchronously builds an Android App Bundle at the specified path using the specified Android Player. /// </summary> /// <returns>True if the build succeeded, false if it failed or was cancelled.</returns> public bool CreateBundle(string aabFilePath, string androidPlayerFilePath, AssetPackConfig assetPackConfig) { 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 configParams = new BundletoolHelper.BuildBundleConfigParams { defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName( assetPackConfig.DefaultTextureCompressionFormat), minSdkVersion = _minSdkVersion }; // 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; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetBundleFilePath != null; configParams.containsInstallTimeAssetPack |= assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime; var assetPackDirectoryInfo = workingDirectory.CreateSubdirectory(assetPackName); if (!CreateAssetPackModule(assetPackName, assetPack, assetPackDirectoryInfo)) { return(false); } moduleDirectoryList.Add(assetPackDirectoryInfo); } // Create base module directory. var baseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseModuleName); IList <string> bundleMetadata; if (!CreateBaseModule(baseDirectory, androidPlayerFilePath, out bundleMetadata)) { return(false); } moduleDirectoryList.Add(baseDirectory); // 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) { DisplayBuildError("Zip creation", zipErrorMessage); return(false); } moduleFiles.Add(zipFilePath); } // If the .aab file exists, EditorUtility.SaveFilePanel() has already prompted for whether to overwrite. // Therefore, prevent Bundletool from throwing an IllegalArgumentException that "File already exists." File.Delete(aabFilePath); DisplayProgress("Running bundletool", ProgressRunBundletool); var buildBundleErrorMessage = _bundletool.BuildBundle(aabFilePath, moduleFiles, bundleMetadata, configParams); if (buildBundleErrorMessage != null) { DisplayBuildError("bundletool", buildBundleErrorMessage); return(false); } DisplayProgress("Signing bundle", 0.9f); var signingErrorMessage = _apkSigner.SignZip(aabFilePath); if (signingErrorMessage != null) { DisplayBuildError("Signing", signingErrorMessage); return(false); } Debug.LogFormat("Finished building app bundle: {0}", aabFilePath); _finishedAabFilePath = aabFilePath; _buildStatus = BuildStatus.Succeeding; return(true); }
/// <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(string aabFilePath, AssetPackConfig assetPackConfig) { 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 configParams = new BundletoolHelper.BuildBundleConfigParams { defaultTcfSuffix = TextureTargetingTools.GetBundleToolTextureCompressionFormatName( assetPackConfig.DefaultTextureCompressionFormat), minSdkVersion = _minSdkVersion }; // 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; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetBundleFilePath != null; configParams.enableTcfTargeting |= assetPack.CompressionFormatToAssetPackDirectoryPath != null; configParams.containsInstallTimeAssetPack |= assetPack.DeliveryMode == AssetPackDeliveryMode.InstallTime; var assetPackDirectoryInfo = workingDirectory.CreateSubdirectory(assetPackName); var assetPackErrorMessage = CreateAssetPackModule(assetPackName, assetPack, assetPackDirectoryInfo); if (assetPackErrorMessage != null) { // Already displayed the error. return(assetPackErrorMessage); } moduleDirectoryList.Add(assetPackDirectoryInfo); } // Create base module directory. var baseDirectory = workingDirectory.CreateSubdirectory(AndroidAppBundle.BaseModuleName); IList <string> bundleMetadata; var baseErrorMessage = CreateBaseModule(baseDirectory, out bundleMetadata); if (baseErrorMessage != null) { // Already displayed the error. return(baseErrorMessage); } moduleDirectoryList.Add(baseDirectory); // 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); } DisplayProgress("Running bundletool", ProgressRunBundletool); var buildBundleErrorMessage = _bundletool.BuildBundle(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(aabFilePath); if (signingErrorMessage != null) { return(DisplayBuildError("Signing", signingErrorMessage)); } } else { Debug.LogFormat("Skipped signing since a Custom Keystore isn't configured in Android Player Settings"); } MoveSymbolsZipFile(aabFilePath); Debug.LogFormat("Finished building app bundle: {0}", aabFilePath); _finishedAabFilePath = aabFilePath; _buildStatus = BuildStatus.Succeeding; return(null); }
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.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 { throw new InvalidOperationException("Missing asset pack files: " + assetPackName); } return(null); }
/// <summary> /// BundleTool config optimized for Unity-based apps. /// </summary> public static BundletoolConfig.Config MakeConfig( BuildBundleConfigParams configParams, string streamingAssetsPath) { var config = new BundletoolConfig.Config(); // APK download size is smaller when native libraries are uncompressed. uncompressNativeLibraries sets // android:extractNativeLibs="false" in the manifest, which also reduces on-disk for Android 6.0+ devices. config.optimizations.uncompressNativeLibraries.enabled = true; config.compression.uncompressedGlob.AddRange(UnityUncompressedGlob); var compressionOptions = configParams.compressionOptions; if (compressionOptions.UncompressedGlobs != null) { config.compression.uncompressedGlob.AddRange(compressionOptions.UncompressedGlobs); } if (!compressionOptions.CompressStreamingAssets) { config.compression.uncompressedGlob.AddRange(GetStreamingAssetsFileGlobs(streamingAssetsPath)); } if (compressionOptions.CompressInstallTimeAssetPacks) { config.compression.installTimeAssetModuleDefaultCompression = BundletoolConfig.Compressed; } var dimensions = config.optimizations.splitsConfig.splitDimension; // Split on ABI so only one set of native libraries (armeabi-v7a, arm64-v8a, or x86) is sent to a device. dimensions.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.Abi, negate = false }); // Do not split on LANGUAGE since Unity games don't store localized strings in the typical Android manner. dimensions.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.Language, negate = true }); // Do not split on SCREEN_DENSITY since Unity games don't have per-density resources other than app icons. dimensions.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.ScreenDensity, negate = true }); if (configParams.enableTcfTargeting) { dimensions.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.TextureCompressionFormat, negate = false, suffixStripping = { enabled = true, defaultSuffix = configParams.defaultTcfSuffix } }); } // Bundletool requires the below standaloneConfig when supporting install-time asset packs for pre-Lollipop. if (configParams.containsInstallTimeAssetPack && TextureTargetingTools.IsSdkVersionPreLollipop(configParams.minSdkVersion)) { config.optimizations.standaloneConfig.splitDimension.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.Abi, negate = true }); config.optimizations.standaloneConfig.splitDimension.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.Language, negate = true }); config.optimizations.standaloneConfig.splitDimension.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.ScreenDensity, negate = true }); config.optimizations.standaloneConfig.splitDimension.Add(new BundletoolConfig.SplitDimension { value = BundletoolConfig.TextureCompressionFormat, negate = true }); config.optimizations.standaloneConfig.strip64BitLibraries = true; } return(config); }