All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mill.javalib.android.AndroidAppBundle.scala Maven / Gradle / Ivy

The newest version!
package mill.javalib.android

import mill._
import mill.scalalib._
import mill.api.PathRef
import os.zip.ZipSource

/**
 * A Trait for Android App Bundle Creation
 */
@mill.api.experimental
trait AndroidAppBundle extends AndroidAppModule with JavaModule {

  /**
   * Adds `--proto-format` option to AAPT commands.
   */
  override def androidAaptOptions: T[Seq[String]] = Task {
    super.androidAaptOptions() ++ Seq[String]("--proto-format")
  }

  /**
   * Creates a zip with compiled resources and DEX files for the app bundle.
   *
   * @return PathRef to the bundle zip.
   *
   * For Bundle Zip Format please See the official
   * [[https://developer.android.com/guide/app-bundle/app-bundle-format Documentation]]
   */
  def androidBundleZip: T[PathRef] = Task {
    val dexFile = androidDex().path
    val resFile = androidResources()._1.path / "res.apk"
    val baseDir = Task.dest / "base"
    val appDir = Task.dest / "app"

    os.copy(dexFile, Task.dest / dexFile.last)
    os.unzip(resFile, appDir)
    os.copy(
      appDir / "AndroidManifest.xml",
      baseDir / "manifest/AndroidManifest.xml",
      createFolders = true
    )

    for (file <- os.walk(appDir).filter(_.ext == "pb")) {
      os.copy(file, baseDir / file.last)
    }

    for ((file, _) <- os.walk(Task.dest).filter(_.ext == "dex").zipWithIndex) {
      os.copy(file, baseDir / "dex" / file.last, createFolders = true)
    }

    for (folder <- Seq("res", "lib", "assets")) {
      val folderPath = appDir / folder
      if (os.exists(folderPath)) {
        os.copy.over(folderPath, baseDir / folder)
      }
    }

    os.zip(Task.dest / "bundle.zip", os.list(baseDir).map(ZipSource.fromPath))

    PathRef(Task.dest / "bundle.zip")
  }

  /**
   * Builds an unsigned Android App Bundle (AAB) from the bundle zip.
   *
   * @return PathRef to the unsigned AAB.
   *
   * For Process Please Read the Official
   * [[https://developer.android.com/build/building-cmdline Documentation]]
   */
  def androidUnsignedBundle: T[PathRef] = Task {
    val bundleFile = Task.dest / "bundle.aab"
    val zipPath = androidBundleZip().path

    os.call((
      "java",
      "-jar",
      androidSdkModule().bundleToolPath().path,
      "build-bundle",
      s"--modules=$zipPath",
      s"--output=$bundleFile"
    ))

    PathRef(bundleFile)
  }

  /**
   * Signs the AAB using the specified keystore.
   *
   * @return PathRef to the signed AAB.
   *
   * For More Please see JarSigner
   * [[https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html Documentation]]
   */
  def androidBundle: T[PathRef] = Task {
    val signedBundle = Task.dest / "signedBundle.aab"
    val keyPath = androidKeystore()

    // TODO this is duplicated with the parent module. Can we do it better without leaking sensitive credentials
    //  in plain to disk/console? put behind Task.Anon?
    val keystorePass = {
      if (androidIsDebug()) debugKeyStorePass else androidReleaseKeyStorePass().get
    }
    val keyAlias = {
      if (androidIsDebug()) debugKeyAlias else androidReleaseKeyAlias().get
    }
    val keyPass = {
      if (androidIsDebug()) debugKeyPass else androidReleaseKeyPass().get
    }

    os.call((
      "jarsigner",
      "-sigalg",
      "SHA256withRSA",
      "-digestalg",
      "SHA-256",
      "-keypass",
      keyPass,
      "-storepass",
      keystorePass,
      "-keystore",
      keyPath,
      "-signedjar",
      signedBundle,
      androidUnsignedBundle().path,
      keyAlias
    ))

    PathRef(signedBundle)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy