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);
        }
 /// <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());
         }
     });
 }
        /// <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>
        /// 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);
        }
        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 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);
        }