From 103b7b89b6e3fe3bbcf0742c23032e6ffde95619 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Fri, 18 Oct 2024 14:23:45 +0000 Subject: [PATCH 01/10] Hello world Kotlin Jetpack Compose Example; Fixes:#3550 --- .../app/AndroidManifest.xml | 12 ++++ .../kotlin/com/helloworld/app/MainActivity.kt | 21 ++++++ .../2-jetpack-compose-hello-world/build.mill | 45 +++++++++++++ .../android/AndroidAppKotlinModule.scala | 67 ++++++++++++++++++- 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml new file mode 100644 index 00000000000..6cc47e94dc4 --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt new file mode 100644 index 00000000000..29b6c95f35a --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt @@ -0,0 +1,21 @@ +package com.helloworld.app + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Greeting("Hello, World!") + } + } +} + +@Composable +fun Greeting(name: String) { + Text(text = name) +} diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill new file mode 100644 index 00000000000..578588ded94 --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill @@ -0,0 +1,45 @@ +//// SNIPPET:BUILD +package build + +import mill._ +import kotlinlib._ +import coursier.maven.MavenRepository +import mill.kotlinlib.android.AndroidAppKotlinModule +import mill.javalib.android.AndroidSdkModule + +val maven_google = Seq( + MavenRepository("https://maven.google.com/"), + MavenRepository("https://repo1.maven.org/maven2") +) + +object androidSdkModule0 extends AndroidSdkModule { + def buildToolsVersion = "35.0.0" +} + +object app extends AndroidAppKotlinModule { + def kotlinVersion = "2.0.20" + def androidSdkModule = mill.define.ModuleRef(androidSdkModule0) + + override def mandatoryIvyDeps: T[Agg[Dep]] = Task { + super.mandatoryIvyDeps() ++ Agg( + // Jetpack Compose dependencies + ivy"androidx.compose.compiler:compiler:1.5.15", + ivy"androidx.activity:activity:1.8.2", + ivy"androidx.activity:activity-compose:1.8.2", + ivy"androidx.compose.runtime:runtime:1.3.1", + ivy"androidx.compose.material3:material3:1.0.1" + ) + } + + def repositoriesTask = T.task { super.repositoriesTask() ++ maven_google } + +} + +////SNIPPET:END + +/** Usage + +> ./mill show app.androidApk +".../out/app/androidApk.dest/app.apk" + +*/ diff --git a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala index ddfaf985b9c..8a32695a703 100644 --- a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala +++ b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala @@ -2,6 +2,9 @@ package mill.kotlinlib.android import mill.kotlinlib.KotlinModule import mill.javalib.android.AndroidAppModule +import mill._ +import coursier.Type +import upickle.default.ReadWriter /** * Trait for building Android applications using the Mill build tool. @@ -18,4 +21,66 @@ import mill.javalib.android.AndroidAppModule * [[https://developer.android.com/studio Android Studio Documentation]] */ @mill.api.experimental -trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule {} +trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { + + /** + * Implicit `ReadWriter` for serializing and deserializing `coursier.Type` values. + * Converts a `coursier.Type` to a `String` and vice versa. + */ + implicit val coursierTypeRW: ReadWriter[Type] = upickle.default.readwriter[String].bimap( + _.value, // Serialize coursier.Type to String + Type(_) // Deserialize String to coursier.Type + ) + + /** + * Implicit `ReadWriter` for handling `Set[coursier.Type]`, allowing conversion + * between a set of `coursier.Type` and a set of `String`. + */ + implicit val coursierTypeSetRW: ReadWriter[Set[Type]] = + upickle.default.readwriter[Set[String]].bimap( + _.map(_.value), // Serialize Set[coursier.Type] to Set[String] + _.map(Type(_)) // Deserialize Set[String] to Set[coursier.Type] + ) + + /** + * Adds the "aar" type to the set of artifact types handled by this module. + * + * @return A task that yields an updated set of artifact types including "aar". + */ + override def artifactTypes: T[Set[Type]] = + T { super.artifactTypes() + coursier.Type("aar") } + + /** + * Task to extract `classes.jar` files from AAR files in the classpath. + * + * @return A sequence of `PathRef` pointing to the extracted JAR files. + */ + def recompileAARs: T[Seq[PathRef]] = Task { + val aarFiles = super.compileClasspath().map(_.path).filter(_.ext == "aar").toSeq + + aarFiles.map { aarFile => + val extractDir = T.dest / aarFile.baseName + os.call(Seq("unzip", aarFile.toString, "-d", extractDir.toString)) + PathRef(extractDir / "classes.jar") + } + } + + /** + * Updates the compile classpath by removing `.aar` files and including the + * extracted `.jar` files from the AARs. + * + * @return A task yielding the updated classpath with `.jar` files. + */ + def updatedCompileClasspath: T[Agg[PathRef]] = Task { + super.compileClasspath().filter(_.path.ext == "jar") ++ Agg.from(recompileAARs()) + } + + /** + * Overrides the compile classpath to replace `.aar` files with the extracted + * `.jar` files. + * + * @return The updated classpath with `.jar` files only. + */ + override def compileClasspath: T[Agg[PathRef]] = updatedCompileClasspath() + +} From a73defdf59512ddfcc05758beb885c849fe3df74 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Sat, 19 Oct 2024 17:53:05 +0000 Subject: [PATCH 02/10] Reviews Resolved ; Fixes: #3550 --- .../android/AndroidAppKotlinModule.scala | 67 +------------------ .../javalib/android/AndroidAppModule.scala | 34 +++++++++- .../src/mill/scalalib/JsonFormatters.scala | 11 ++- 3 files changed, 44 insertions(+), 68 deletions(-) diff --git a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala index 8a32695a703..ddfaf985b9c 100644 --- a/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala +++ b/kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala @@ -2,9 +2,6 @@ package mill.kotlinlib.android import mill.kotlinlib.KotlinModule import mill.javalib.android.AndroidAppModule -import mill._ -import coursier.Type -import upickle.default.ReadWriter /** * Trait for building Android applications using the Mill build tool. @@ -21,66 +18,4 @@ import upickle.default.ReadWriter * [[https://developer.android.com/studio Android Studio Documentation]] */ @mill.api.experimental -trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule { - - /** - * Implicit `ReadWriter` for serializing and deserializing `coursier.Type` values. - * Converts a `coursier.Type` to a `String` and vice versa. - */ - implicit val coursierTypeRW: ReadWriter[Type] = upickle.default.readwriter[String].bimap( - _.value, // Serialize coursier.Type to String - Type(_) // Deserialize String to coursier.Type - ) - - /** - * Implicit `ReadWriter` for handling `Set[coursier.Type]`, allowing conversion - * between a set of `coursier.Type` and a set of `String`. - */ - implicit val coursierTypeSetRW: ReadWriter[Set[Type]] = - upickle.default.readwriter[Set[String]].bimap( - _.map(_.value), // Serialize Set[coursier.Type] to Set[String] - _.map(Type(_)) // Deserialize Set[String] to Set[coursier.Type] - ) - - /** - * Adds the "aar" type to the set of artifact types handled by this module. - * - * @return A task that yields an updated set of artifact types including "aar". - */ - override def artifactTypes: T[Set[Type]] = - T { super.artifactTypes() + coursier.Type("aar") } - - /** - * Task to extract `classes.jar` files from AAR files in the classpath. - * - * @return A sequence of `PathRef` pointing to the extracted JAR files. - */ - def recompileAARs: T[Seq[PathRef]] = Task { - val aarFiles = super.compileClasspath().map(_.path).filter(_.ext == "aar").toSeq - - aarFiles.map { aarFile => - val extractDir = T.dest / aarFile.baseName - os.call(Seq("unzip", aarFile.toString, "-d", extractDir.toString)) - PathRef(extractDir / "classes.jar") - } - } - - /** - * Updates the compile classpath by removing `.aar` files and including the - * extracted `.jar` files from the AARs. - * - * @return A task yielding the updated classpath with `.jar` files. - */ - def updatedCompileClasspath: T[Agg[PathRef]] = Task { - super.compileClasspath().filter(_.path.ext == "jar") ++ Agg.from(recompileAARs()) - } - - /** - * Overrides the compile classpath to replace `.aar` files with the extracted - * `.jar` files. - * - * @return The updated classpath with `.jar` files only. - */ - override def compileClasspath: T[Agg[PathRef]] = updatedCompileClasspath() - -} +trait AndroidAppKotlinModule extends AndroidAppModule with KotlinModule {} diff --git a/scalalib/src/mill/javalib/android/AndroidAppModule.scala b/scalalib/src/mill/javalib/android/AndroidAppModule.scala index 64195f83d2d..15baa18d216 100644 --- a/scalalib/src/mill/javalib/android/AndroidAppModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidAppModule.scala @@ -1,9 +1,9 @@ package mill.javalib.android import mill._ +import mill.scalalib._ import mill.api.PathRef import mill.define.ModuleRef -import mill.scalalib.JavaModule /** * Trait for building Android applications using the Mill build tool. @@ -35,6 +35,38 @@ trait AndroidAppModule extends JavaModule { */ def androidManifest: Task[PathRef] = Task.Source(millSourcePath / "AndroidManifest.xml") + /** + * Adds the "aar" type to the set of artifact types handled by this module. + * + * @return A task that yields an updated set of artifact types including "aar". + */ + def artifactTypes: T[Set[coursier.Type]] = Task { super.artifactTypes() + coursier.Type("aar") } + + /** + * Task to extract `classes.jar` files from AAR files in the classpath. + * + * @return A sequence of `PathRef` pointing to the extracted JAR files. + */ + def androidUnpackArchives: T[Seq[PathRef]] = Task { + val aarFiles = super.compileClasspath().map(_.path).filter(_.ext == "aar").toSeq + + aarFiles.map { aarFile => + val extractDir = T.dest / aarFile.baseName + os.unzip(aarFile, extractDir) + PathRef(extractDir / "classes.jar") + } + } + + /** + * Overrides the compile classpath to replace `.aar` files with the extracted + * `.jar` files. + * + * @return The updated classpath with `.jar` files only. + */ + override def compileClasspath: T[Agg[PathRef]] = Task { + super.compileClasspath().filter(_.path.ext == "jar") ++ Agg.from(androidUnpackArchives()) + } + /** * Generates the Android resources (such as layouts, strings, and other assets) needed * for the application. diff --git a/scalalib/src/mill/scalalib/JsonFormatters.scala b/scalalib/src/mill/scalalib/JsonFormatters.scala index 751ebcc60b3..255237e6222 100644 --- a/scalalib/src/mill/scalalib/JsonFormatters.scala +++ b/scalalib/src/mill/scalalib/JsonFormatters.scala @@ -20,8 +20,17 @@ trait JsonFormatters { implicit lazy val orgFormat: RW[coursier.Organization] = upickle.default.macroRW implicit lazy val modNameFormat: RW[coursier.ModuleName] = upickle.default.macroRW implicit lazy val configurationFormat: RW[coursier.core.Configuration] = upickle.default.macroRW - implicit lazy val typeFormat: RW[coursier.core.Type] = upickle.default.macroRW implicit lazy val classifierFormat: RW[coursier.core.Classifier] = upickle.default.macroRW + implicit lazy val coursierTypeRW: RW[coursier.core.Type] = + upickle.default.readwriter[String].bimap( + _.value, + coursier.core.Type(_) + ) + implicit lazy val coursierTypeSetRW: RW[Set[coursier.core.Type]] = + upickle.default.readwriter[Set[String]].bimap( + _.map(_.value), + _.map(coursier.core.Type(_)) + ) } object JsonFormatters extends JsonFormatters From cd320447c518b6bbd6a2c640388c1ec72cc20408 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Sat, 19 Oct 2024 18:18:54 +0000 Subject: [PATCH 03/10] Fixed JsonForamtters.scala ; Fixes: #3550 --- scalalib/src/mill/scalalib/JsonFormatters.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scalalib/src/mill/scalalib/JsonFormatters.scala b/scalalib/src/mill/scalalib/JsonFormatters.scala index 255237e6222..66340708def 100644 --- a/scalalib/src/mill/scalalib/JsonFormatters.scala +++ b/scalalib/src/mill/scalalib/JsonFormatters.scala @@ -20,12 +20,8 @@ trait JsonFormatters { implicit lazy val orgFormat: RW[coursier.Organization] = upickle.default.macroRW implicit lazy val modNameFormat: RW[coursier.ModuleName] = upickle.default.macroRW implicit lazy val configurationFormat: RW[coursier.core.Configuration] = upickle.default.macroRW + implicit lazy val typeFormat: RW[coursier.core.Type] = upickle.default.macroRW implicit lazy val classifierFormat: RW[coursier.core.Classifier] = upickle.default.macroRW - implicit lazy val coursierTypeRW: RW[coursier.core.Type] = - upickle.default.readwriter[String].bimap( - _.value, - coursier.core.Type(_) - ) implicit lazy val coursierTypeSetRW: RW[Set[coursier.core.Type]] = upickle.default.readwriter[Set[String]].bimap( _.map(_.value), From bfa6b1d02e30fa193328eb9ae2f1006a2ac213a7 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Sat, 19 Oct 2024 18:38:23 +0000 Subject: [PATCH 04/10] Revert JsonForamtters.scala ; Fixes: #3550 --- scalalib/src/mill/scalalib/JsonFormatters.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scalalib/src/mill/scalalib/JsonFormatters.scala b/scalalib/src/mill/scalalib/JsonFormatters.scala index 66340708def..751ebcc60b3 100644 --- a/scalalib/src/mill/scalalib/JsonFormatters.scala +++ b/scalalib/src/mill/scalalib/JsonFormatters.scala @@ -22,11 +22,6 @@ trait JsonFormatters { implicit lazy val configurationFormat: RW[coursier.core.Configuration] = upickle.default.macroRW implicit lazy val typeFormat: RW[coursier.core.Type] = upickle.default.macroRW implicit lazy val classifierFormat: RW[coursier.core.Classifier] = upickle.default.macroRW - implicit lazy val coursierTypeSetRW: RW[Set[coursier.core.Type]] = - upickle.default.readwriter[Set[String]].bimap( - _.map(_.value), - _.map(coursier.core.Type(_)) - ) } object JsonFormatters extends JsonFormatters From c81b9059a3b968d73e0111ad99cd039617bd2da9 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Mon, 21 Oct 2024 16:17:14 +0000 Subject: [PATCH 05/10] Final Code Updated and Docs Generated, Ready to Merge After Review; Fixes: #3550 --- .../pages/kotlinlib/android-examples.adoc | 8 ++ .../java/com/helloworld/app/MainActivity.kt | 34 ++++++ .../java/com/helloworld/app/MainActivity.kt | 41 +++++++ .../kotlin/com/helloworld/app/MainActivity.kt | 21 ---- .../2-jetpack-compose-hello-world/build.mill | 46 +++++++ .../javalib/android/AndroidAppModule.scala | 113 +++++++++++++----- .../javalib/android/AndroidSdkModule.scala | 7 ++ 7 files changed, 220 insertions(+), 50 deletions(-) create mode 100644 example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt delete mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt diff --git a/docs/modules/ROOT/pages/kotlinlib/android-examples.adoc b/docs/modules/ROOT/pages/kotlinlib/android-examples.adoc index e45ec649f12..34ec25a9a23 100644 --- a/docs/modules/ROOT/pages/kotlinlib/android-examples.adoc +++ b/docs/modules/ROOT/pages/kotlinlib/android-examples.adoc @@ -27,6 +27,14 @@ This example demonstrates how to create a basic "Hello World" Android applicatio using the Mill build tool. It outlines the minimum setup required to compile Kotlin code, package it into an APK, and run the app on an Android device. +== Kotlin Android Jetpack Compose Application + +include::partial$example/kotlinlib/android/2-jetpack-compose-hello-world.adoc[] + +This example demonstrates how to create a basic "Hello World" Jetpack Compose Android application +using the Mill build tool. It outlines the minimum setup required to compile Kotlin code, +Compiling Jetpack Compose Libraries, package it into an APK, and run the app on an Android device. + == Understanding `AndroidSdkModule` and `AndroidAppKotlinModule` The two main modules you need to understand when building Android apps with Mill diff --git a/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt new file mode 100644 index 00000000000..f7e04a63de9 --- /dev/null +++ b/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt @@ -0,0 +1,34 @@ +package com.helloworld.app + +import android.app.Activity +import android.os.Bundle +import android.widget.TextView +import android.view.Gravity +import android.view.ViewGroup.LayoutParams + +class MainActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Create a new TextView + val textView = TextView(this) + + // Set the text to "Hello, World!" + textView.text = "Hello, World Kotlin!" + + // Set text size + textView.textSize = 32f + + // Center the text within the view + textView.gravity = Gravity.CENTER + + // Set layout parameters (width and height) + textView.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + + // Set the content view to display the TextView + setContentView(textView) + } +} diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt new file mode 100644 index 00000000000..e69cc22931a --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt @@ -0,0 +1,41 @@ +package com.helloworld.app + +import android.app.Activity +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.ViewGroup.LayoutParams +import android.widget.LinearLayout +import android.widget.TextView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + +class MainActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val linearLayout = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + setBackgroundColor(Color.parseColor("#FFFFFF")) + layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + } + + val textView = TextView(this).apply { + text = "Hello, World!\nJetpack Compose!" + textSize = 32f + setTextColor(Color.parseColor("#34A853")) + gravity = Gravity.CENTER + setPadding(16, 16, 16, 16) + } + + linearLayout.addView(textView) + + setContentView(linearLayout) + } +} \ No newline at end of file diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt deleted file mode 100644 index 29b6c95f35a..00000000000 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.helloworld.app - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - Greeting("Hello, World!") - } - } -} - -@Composable -fun Greeting(name: String) { - Text(text = name) -} diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill index 578588ded94..013be54841e 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill @@ -1,3 +1,12 @@ +// This build configuration file defines an Android application using Mill, +// Kotlin, and Jetpack Compose. It manages dependencies through Maven repositories +// and specifies the necessary configurations for the Android SDK and Kotlin versions. + +// The main components include: +// - Maven repositories for dependency resolution +// - Android SDK configuration, including the build tools version +// - Android application module with required dependencies for Jetpack Compose + //// SNIPPET:BUILD package build @@ -43,3 +52,40 @@ object app extends AndroidAppKotlinModule { ".../out/app/androidApk.dest/app.apk" */ + +// To build the Android APK, you can use this command +// This command will output the path to the generated APK file +// This path indicates where the built APK can be found after executing the build process. +// +// +// Detailed Code Explanation +// 1. **Maven Repositories**: +// - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved. +// - Google Maven repository is essential for Android libraries. +// - Maven Central repository provides additional libraries. +// +// 2. **Android SDK Module**: +// - `object androidSdkModule0`: Defines an Android SDK module. +// - `def buildToolsVersion`: Specifies the version of the Android build tools to be used during the build process. +// +// 3. **Android Application Module**: +// - `object app`: Defines the main Android application module. +// - `def kotlinVersion`: Specifies the version of Kotlin used in the application. +// - `def androidSdkModule`: Links to the previously defined Android SDK module. +// - `override def mandatoryIvyDeps`: Overrides the default dependencies to include necessary Jetpack Compose libraries. +// - `def repositoriesTask`: Combines the default repositories with custom Maven repositories for dependency resolution. +// +// Directory Structure: +// The project structure is as follows: +// +// example/kotlinlib/android/2-jetpack-compose-hello-world +// ├── app +// │ ├── AndroidManifest.xml // Manifest file for the application +// │ └── src +// │ └── main +// │ └── java +// │ └── com +// │ └── helloworld +// │ └── app +// │ └── MainActivity.kt // Main activity of the application +// └── build.mill // Mill build configuration file \ No newline at end of file diff --git a/scalalib/src/mill/javalib/android/AndroidAppModule.scala b/scalalib/src/mill/javalib/android/AndroidAppModule.scala index 15baa18d216..0fe9e690f9f 100644 --- a/scalalib/src/mill/javalib/android/AndroidAppModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidAppModule.scala @@ -43,18 +43,48 @@ trait AndroidAppModule extends JavaModule { def artifactTypes: T[Set[coursier.Type]] = Task { super.artifactTypes() + coursier.Type("aar") } /** - * Task to extract `classes.jar` files from AAR files in the classpath. + * Task to extract files from AAR files in the classpath. * * @return A sequence of `PathRef` pointing to the extracted JAR files. */ - def androidUnpackArchives: T[Seq[PathRef]] = Task { + def androidUnpackArchives: T[(Seq[PathRef], Seq[PathRef])] = Task { + // Get all AAR files from the compile classpath val aarFiles = super.compileClasspath().map(_.path).filter(_.ext == "aar").toSeq - aarFiles.map { aarFile => + // Initialize sequences for jar files and resource folders + var jarFiles: Seq[PathRef] = Seq() + var resFolders: Seq[PathRef] = Seq() + + // Process each AAR file + aarFiles.foreach { aarFile => val extractDir = T.dest / aarFile.baseName os.unzip(aarFile, extractDir) - PathRef(extractDir / "classes.jar") + + // Collect all .jar files in the AAR directory + jarFiles ++= os.walk(extractDir).filter(_.ext == "jar").map(PathRef(_)) + + // If the res folder exists, add it to the resource folders + val resFolder = extractDir / "res" + if (os.exists(resFolder)) { + resFolders :+= PathRef(resFolder) + } } + + // Return both jar files and resource folders + (jarFiles, resFolders) + } + + /** + * Overrides the `resources` task to include resources from unpacked AAR files + * + * @return Combined sequence of original and filtered AAR resources. + */ + override def resources: T[Seq[PathRef]] = Task { + // Call the function to unpack AARs and get the jar and resource paths + val (_, resFolders) = androidUnpackArchives() + + // Combine and return all resources + super.resources() ++ resFolders } /** @@ -64,38 +94,63 @@ trait AndroidAppModule extends JavaModule { * @return The updated classpath with `.jar` files only. */ override def compileClasspath: T[Agg[PathRef]] = Task { - super.compileClasspath().filter(_.path.ext == "jar") ++ Agg.from(androidUnpackArchives()) + // Call the function to get jar files and resource paths + val (jarFiles, _) = androidUnpackArchives() + super.compileClasspath().filter(_.path.ext == "jar") ++ jarFiles } /** - * Generates the Android resources (such as layouts, strings, and other assets) needed - * for the application. + * Compiles and links Android resources using `aapt2`, generating the `R.java` file. * - * This method uses the Android `aapt` tool to compile resources specified in the - * project's `AndroidManifest.xml` and any additional resource directories. It creates - * the necessary R.java files and other compiled resources for Android. These generated - * resources are crucial for the app to function correctly on Android devices. + * @return A `PathRef` to the directory containing the generated `R.java`. * - * For more details on the aapt tool, refer to: + * For more details on the aapt2 tool, refer to: * [[https://developer.android.com/tools/aapt2 aapt Documentation]] */ - def androidResources: T[PathRef] = Task { - val genDir: os.Path = T.dest // Directory to store generated resources. - - os.call(Seq( - androidSdkModule().aaptPath().path.toString, // Call aapt tool - "package", - "-f", - "-m", - "-J", - genDir.toString, // Generate R.java files - "-M", - androidManifest().path.toString, // Use AndroidManifest.xml - "-I", - androidSdkModule().androidJarPath().path.toString // Include Android SDK JAR - )) - - PathRef(genDir) + def androidResources: T[PathRef] = T.task { + val genDir: os.Path = T.dest // Directory to store generated R.java + val compiledResDir: os.Path = T.dest / "compiled" // Directory for compiled resources + val resourceDirs = resources().map(_.path).filter(os.exists) // Merge all resource directories + os.makeDir.all(compiledResDir) + + try { + // Step 1: Compile resources using `aapt2 compile` + resourceDirs.foreach { resDir => + os.call(Seq( + androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool + "compile", + "-o", + compiledResDir.toString, // Output directory for compiled resources + "--dir", + resDir.toString // Compile each resource directory + )) + } + + // Collect all .flat files explicitly + val flatFiles = os.walk(compiledResDir).filter(_.ext == "flat").map(_.toString) + + // Step 2: Link resources using `aapt2 link` + os.call(Seq( + androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool + "link", + "-o", + (genDir / "resources.apk").toString, // Output APK or intermediate result + "-I", + androidSdkModule().androidJarPath().path.toString, // Include Android SDK JAR + "--manifest", + androidManifest().path.toString, // Use AndroidManifest.xml + "--auto-add-overlay", // Automatically add resources from overlays + "--java", + genDir.toString // Generate R.java in the genDir + ) ++ flatFiles.flatMap(flatFile => Seq("-R", flatFile))) + + PathRef(genDir) // Return the generated directory if successful + + } catch { + case e: Throwable => + T.log.info(s"Using Only Correct Resources") + PathRef(T.dest) // Return PathRef for only correct resources + } } /** diff --git a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala index 20b7ae4afa4..5874da43dfe 100644 --- a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala @@ -61,6 +61,13 @@ trait AndroidSdkModule extends Module { PathRef(buildToolsPath().path / "aapt") } + /** + * Provides the path to AAPT2, used for resource handling and APK packaging. + */ + def aapt2Path: T[PathRef] = Task { + PathRef(buildToolsPath().path / "aapt2") + } + /** * Provides the path to the Zipalign tool, which optimizes APK files by aligning their data. */ From 84875da2ea6730169c1468258b529d2e2c5b7184 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Mon, 21 Oct 2024 16:36:39 +0000 Subject: [PATCH 06/10] Final Code Updated and Docs Generated, Ready to Merge After Review; Fixes: #3550 --- .../2-jetpack-compose-hello-world/build.mill | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill index 013be54841e..b0a8e61f99a 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill @@ -3,9 +3,10 @@ // and specifies the necessary configurations for the Android SDK and Kotlin versions. // The main components include: -// - Maven repositories for dependency resolution -// - Android SDK configuration, including the build tools version -// - Android application module with required dependencies for Jetpack Compose +// +// - Maven repositories for dependency resolution +// - Android SDK configuration, including the build tools version +// - Android application module with required dependencies for Jetpack Compose //// SNIPPET:BUILD package build @@ -53,31 +54,34 @@ object app extends AndroidAppKotlinModule { */ -// To build the Android APK, you can use this command -// This command will output the path to the generated APK file +// To build the Android APK, you can use this command. +// This command will output the path to the generated APK file. // This path indicates where the built APK can be found after executing the build process. // // // Detailed Code Explanation +// // 1. **Maven Repositories**: -// - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved. +// - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved. // - Google Maven repository is essential for Android libraries. // - Maven Central repository provides additional libraries. // // 2. **Android SDK Module**: -// - `object androidSdkModule0`: Defines an Android SDK module. +// - `object androidSdkModule0`: Defines an Android SDK module. // - `def buildToolsVersion`: Specifies the version of the Android build tools to be used during the build process. // // 3. **Android Application Module**: -// - `object app`: Defines the main Android application module. +// - `object app`: Defines the main Android application module. // - `def kotlinVersion`: Specifies the version of Kotlin used in the application. // - `def androidSdkModule`: Links to the previously defined Android SDK module. // - `override def mandatoryIvyDeps`: Overrides the default dependencies to include necessary Jetpack Compose libraries. // - `def repositoriesTask`: Combines the default repositories with custom Maven repositories for dependency resolution. // -// Directory Structure: +// ### Project Structure: +// // The project structure is as follows: // +// ---- // example/kotlinlib/android/2-jetpack-compose-hello-world // ├── app // │ ├── AndroidManifest.xml // Manifest file for the application @@ -88,4 +92,6 @@ object app extends AndroidAppKotlinModule { // │ └── helloworld // │ └── app // │ └── MainActivity.kt // Main activity of the application -// └── build.mill // Mill build configuration file \ No newline at end of file +// └── build.mill // Mill build configuration file +//---- +// \ No newline at end of file From 5bb8991c98778262dfdf3e9714beef827121df56 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Mon, 21 Oct 2024 17:13:50 +0000 Subject: [PATCH 07/10] Fixed Silly Mistake (MainActivity.kt) ; Fixes: #3550 --- .../kotlin/com/helloworld/app/MainActivity.kt | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 example/kotlinlib/android/1-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt diff --git a/example/kotlinlib/android/1-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/1-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt deleted file mode 100644 index f7e04a63de9..00000000000 --- a/example/kotlinlib/android/1-hello-world/app/src/main/kotlin/com/helloworld/app/MainActivity.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.helloworld.app - -import android.app.Activity -import android.os.Bundle -import android.widget.TextView -import android.view.Gravity -import android.view.ViewGroup.LayoutParams - -class MainActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Create a new TextView - val textView = TextView(this) - - // Set the text to "Hello, World!" - textView.text = "Hello, World Kotlin!" - - // Set text size - textView.textSize = 32f - - // Center the text within the view - textView.gravity = Gravity.CENTER - - // Set layout parameters (width and height) - textView.layoutParams = LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT - ) - - // Set the content view to display the TextView - setContentView(textView) - } -} From e410fdd00050e99f1ed46e57f59a8f94543fa316 Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Mon, 21 Oct 2024 17:41:30 +0000 Subject: [PATCH 08/10] Final Fixture --- .../android/2-jetpack-compose-hello-world/build.mill | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill index b0a8e61f99a..12ce65f7569 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill @@ -59,7 +59,7 @@ object app extends AndroidAppKotlinModule { // This path indicates where the built APK can be found after executing the build process. // // -// Detailed Code Explanation +// **Detailed Code Explanation** // // 1. **Maven Repositories**: // - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved. @@ -82,7 +82,7 @@ object app extends AndroidAppKotlinModule { // The project structure is as follows: // // ---- -// example/kotlinlib/android/2-jetpack-compose-hello-world +// 2-jetpack-compose-hello-world // ├── app // │ ├── AndroidManifest.xml // Manifest file for the application // │ └── src From 971ea8fedc8ca9939bcb81c13c6f14a5c2fbecef Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Thu, 24 Oct 2024 19:42:17 +0000 Subject: [PATCH 09/10] Whole Android Updated; Fixes:#3550 --- .../1-hello-world/app/AndroidManifest.xml | 2 +- .../app/resources/values/colors.xml | 4 + .../app/resources/values/strings.xml | 4 + .../java/com/helloworld/app/MainActivity.java | 17 ++- .../1-hello-world/app/AndroidManifest.xml | 2 +- .../app/resources/values/colors.xml | 4 + .../app/resources/values/strings.xml | 4 + .../java/com/helloworld/app/MainActivity.kt | 13 ++- .../android/1-hello-world/build.mill | 2 +- .../app/AndroidManifest.xml | 16 +-- .../app/resources/values/colors.xml | 4 + .../app/resources/values/strings.xml | 4 + .../java/com/helloworld/app/MainActivity.kt | 8 +- .../2-jetpack-compose-hello-world/build.mill | 59 +--------- .../javalib/android/AndroidAppModule.scala | 91 +++++++-------- .../javalib/android/AndroidSdkModule.scala | 108 ++++++++++++------ 16 files changed, 179 insertions(+), 163 deletions(-) create mode 100644 example/javalib/android/1-hello-world/app/resources/values/colors.xml create mode 100644 example/javalib/android/1-hello-world/app/resources/values/strings.xml create mode 100644 example/kotlinlib/android/1-hello-world/app/resources/values/colors.xml create mode 100644 example/kotlinlib/android/1-hello-world/app/resources/values/strings.xml create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/colors.xml create mode 100644 example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/strings.xml rename example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/{main => }/java/com/helloworld/app/MainActivity.kt (83%) diff --git a/example/javalib/android/1-hello-world/app/AndroidManifest.xml b/example/javalib/android/1-hello-world/app/AndroidManifest.xml index b33d6eb4174..c9b02d82f21 100644 --- a/example/javalib/android/1-hello-world/app/AndroidManifest.xml +++ b/example/javalib/android/1-hello-world/app/AndroidManifest.xml @@ -1,7 +1,7 @@ - + diff --git a/example/javalib/android/1-hello-world/app/resources/values/colors.xml b/example/javalib/android/1-hello-world/app/resources/values/colors.xml new file mode 100644 index 00000000000..fd314b015cb --- /dev/null +++ b/example/javalib/android/1-hello-world/app/resources/values/colors.xml @@ -0,0 +1,4 @@ + + #FFFFFF + #34A853 + diff --git a/example/javalib/android/1-hello-world/app/resources/values/strings.xml b/example/javalib/android/1-hello-world/app/resources/values/strings.xml new file mode 100644 index 00000000000..0144e656903 --- /dev/null +++ b/example/javalib/android/1-hello-world/app/resources/values/strings.xml @@ -0,0 +1,4 @@ + + HelloWorldApp + Hello, World Java! + diff --git a/example/javalib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.java b/example/javalib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.java index 1883d567555..55f7ead2ffb 100644 --- a/example/javalib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.java +++ b/example/javalib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.java @@ -2,11 +2,10 @@ import android.app.Activity; import android.os.Bundle; -import android.view.View; +import android.view.Gravity; import android.widget.TextView; import android.view.ViewGroup.LayoutParams; -import android.view.Gravity; - +import android.graphics.Color; public class MainActivity extends Activity { @Override @@ -16,8 +15,8 @@ protected void onCreate(Bundle savedInstanceState) { // Create a new TextView TextView textView = new TextView(this); - // Set the text to "Hello, World!" - textView.setText("Hello, World!"); + // Set the text to the string resource + textView.setText(getString(R.string.hello_world)); // Set text size textView.setTextSize(32); @@ -25,11 +24,17 @@ protected void onCreate(Bundle savedInstanceState) { // Center the text within the view textView.setGravity(Gravity.CENTER); - // Set layout parameters (width and height) + // Set the layout parameters (width and height) textView.setLayoutParams(new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + // Set the text color using a resource + textView.setTextColor(getResources().getColor(R.color.text_green)); + + // Set the background color using a resource + textView.setBackgroundColor(getResources().getColor(R.color.white)); + // Set the content view to display the TextView setContentView(textView); } diff --git a/example/kotlinlib/android/1-hello-world/app/AndroidManifest.xml b/example/kotlinlib/android/1-hello-world/app/AndroidManifest.xml index b33d6eb4174..c9b02d82f21 100644 --- a/example/kotlinlib/android/1-hello-world/app/AndroidManifest.xml +++ b/example/kotlinlib/android/1-hello-world/app/AndroidManifest.xml @@ -1,7 +1,7 @@ - + diff --git a/example/kotlinlib/android/1-hello-world/app/resources/values/colors.xml b/example/kotlinlib/android/1-hello-world/app/resources/values/colors.xml new file mode 100644 index 00000000000..fd314b015cb --- /dev/null +++ b/example/kotlinlib/android/1-hello-world/app/resources/values/colors.xml @@ -0,0 +1,4 @@ + + #FFFFFF + #34A853 + diff --git a/example/kotlinlib/android/1-hello-world/app/resources/values/strings.xml b/example/kotlinlib/android/1-hello-world/app/resources/values/strings.xml new file mode 100644 index 00000000000..1615ee97f66 --- /dev/null +++ b/example/kotlinlib/android/1-hello-world/app/resources/values/strings.xml @@ -0,0 +1,4 @@ + + HelloWorldApp + Hello, World Kotlin! + diff --git a/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt index f7e04a63de9..b7e23c0c846 100644 --- a/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt +++ b/example/kotlinlib/android/1-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt @@ -2,9 +2,10 @@ package com.helloworld.app import android.app.Activity import android.os.Bundle -import android.widget.TextView import android.view.Gravity +import android.widget.TextView import android.view.ViewGroup.LayoutParams +import android.graphics.Color class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -13,8 +14,8 @@ class MainActivity : Activity() { // Create a new TextView val textView = TextView(this) - // Set the text to "Hello, World!" - textView.text = "Hello, World Kotlin!" + // Set the text to the string resource + textView.text = getString(R.string.hello_world) // Set text size textView.textSize = 32f @@ -28,6 +29,12 @@ class MainActivity : Activity() { LayoutParams.MATCH_PARENT ) + // Set the text color using a resource + textView.setTextColor(getColor(R.color.text_green)) // Using hex color code directly + + // Set the background color using a resource + textView.setBackgroundColor(getColor(R.color.white)) // Using hex color code directly + // Set the content view to display the TextView setContentView(textView) } diff --git a/example/kotlinlib/android/1-hello-world/build.mill b/example/kotlinlib/android/1-hello-world/build.mill index 49df51f1e50..3a80a21c23d 100644 --- a/example/kotlinlib/android/1-hello-world/build.mill +++ b/example/kotlinlib/android/1-hello-world/build.mill @@ -22,7 +22,7 @@ object androidSdkModule0 extends AndroidSdkModule{ // Actual android application object app extends AndroidAppKotlinModule { - def kotlinVersion = "2.0.0" + def kotlinVersion = "2.0.20" def androidSdkModule = mill.define.ModuleRef(androidSdkModule0) } diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml index 6cc47e94dc4..ea86cf8de37 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/AndroidManifest.xml @@ -1,11 +1,13 @@ - - - - + + + + + + - - + + diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/colors.xml b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/colors.xml new file mode 100644 index 00000000000..fd314b015cb --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/colors.xml @@ -0,0 +1,4 @@ + + #FFFFFF + #34A853 + diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/strings.xml b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/strings.xml new file mode 100644 index 00000000000..64af6cb4c60 --- /dev/null +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/resources/values/strings.xml @@ -0,0 +1,4 @@ + + HelloWorldApp + Hello, World!\nJetpack Compose! + diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/java/com/helloworld/app/MainActivity.kt similarity index 83% rename from example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt rename to example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/java/com/helloworld/app/MainActivity.kt index e69cc22931a..90004059dfe 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/main/java/com/helloworld/app/MainActivity.kt +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/app/src/java/com/helloworld/app/MainActivity.kt @@ -19,7 +19,7 @@ class MainActivity : Activity() { val linearLayout = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL gravity = Gravity.CENTER - setBackgroundColor(Color.parseColor("#FFFFFF")) + setBackgroundColor(getColor(R.color.white)) // Use color from resources layoutParams = LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT @@ -27,9 +27,9 @@ class MainActivity : Activity() { } val textView = TextView(this).apply { - text = "Hello, World!\nJetpack Compose!" + text = getString(R.string.hello_world) textSize = 32f - setTextColor(Color.parseColor("#34A853")) + setTextColor(getColor(R.color.text_green)) // Use color from resources gravity = Gravity.CENTER setPadding(16, 16, 16, 16) } @@ -38,4 +38,4 @@ class MainActivity : Activity() { setContentView(linearLayout) } -} \ No newline at end of file +} diff --git a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill index 12ce65f7569..fb51afee396 100644 --- a/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill +++ b/example/kotlinlib/android/2-jetpack-compose-hello-world/build.mill @@ -1,14 +1,3 @@ -// This build configuration file defines an Android application using Mill, -// Kotlin, and Jetpack Compose. It manages dependencies through Maven repositories -// and specifies the necessary configurations for the Android SDK and Kotlin versions. - -// The main components include: -// -// - Maven repositories for dependency resolution -// - Android SDK configuration, including the build tools version -// - Android application module with required dependencies for Jetpack Compose - -//// SNIPPET:BUILD package build import mill._ @@ -30,6 +19,7 @@ object app extends AndroidAppKotlinModule { def kotlinVersion = "2.0.20" def androidSdkModule = mill.define.ModuleRef(androidSdkModule0) + // If Dependencies are not provided but used in MainActivity then error will occur override def mandatoryIvyDeps: T[Agg[Dep]] = Task { super.mandatoryIvyDeps() ++ Agg( // Jetpack Compose dependencies @@ -42,56 +32,11 @@ object app extends AndroidAppKotlinModule { } def repositoriesTask = T.task { super.repositoriesTask() ++ maven_google } - } -////SNIPPET:END - /** Usage > ./mill show app.androidApk ".../out/app/androidApk.dest/app.apk" -*/ - -// To build the Android APK, you can use this command. -// This command will output the path to the generated APK file. -// This path indicates where the built APK can be found after executing the build process. -// -// -// **Detailed Code Explanation** -// -// 1. **Maven Repositories**: -// - `val maven_google`: This sequence defines the Maven repositories from which dependencies will be resolved. -// - Google Maven repository is essential for Android libraries. -// - Maven Central repository provides additional libraries. -// -// 2. **Android SDK Module**: -// - `object androidSdkModule0`: Defines an Android SDK module. -// - `def buildToolsVersion`: Specifies the version of the Android build tools to be used during the build process. -// -// 3. **Android Application Module**: -// - `object app`: Defines the main Android application module. -// - `def kotlinVersion`: Specifies the version of Kotlin used in the application. -// - `def androidSdkModule`: Links to the previously defined Android SDK module. -// - `override def mandatoryIvyDeps`: Overrides the default dependencies to include necessary Jetpack Compose libraries. -// - `def repositoriesTask`: Combines the default repositories with custom Maven repositories for dependency resolution. -// -// ### Project Structure: -// -// The project structure is as follows: -// -// ---- -// 2-jetpack-compose-hello-world -// ├── app -// │ ├── AndroidManifest.xml // Manifest file for the application -// │ └── src -// │ └── main -// │ └── java -// │ └── com -// │ └── helloworld -// │ └── app -// │ └── MainActivity.kt // Main activity of the application -// └── build.mill // Mill build configuration file -//---- -// \ No newline at end of file +*/ \ No newline at end of file diff --git a/scalalib/src/mill/javalib/android/AndroidAppModule.scala b/scalalib/src/mill/javalib/android/AndroidAppModule.scala index 0fe9e690f9f..26f4d67b666 100644 --- a/scalalib/src/mill/javalib/android/AndroidAppModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidAppModule.scala @@ -79,7 +79,7 @@ trait AndroidAppModule extends JavaModule { * * @return Combined sequence of original and filtered AAR resources. */ - override def resources: T[Seq[PathRef]] = Task { + def resources: T[Seq[PathRef]] = Task { // Call the function to unpack AARs and get the jar and resource paths val (_, resFolders) = androidUnpackArchives() @@ -107,50 +107,51 @@ trait AndroidAppModule extends JavaModule { * For more details on the aapt2 tool, refer to: * [[https://developer.android.com/tools/aapt2 aapt Documentation]] */ - def androidResources: T[PathRef] = T.task { + def androidResources: T[PathRef] = Task { val genDir: os.Path = T.dest // Directory to store generated R.java val compiledResDir: os.Path = T.dest / "compiled" // Directory for compiled resources val resourceDirs = resources().map(_.path).filter(os.exists) // Merge all resource directories os.makeDir.all(compiledResDir) + var count = 0 + val compiledZips = resourceDirs.map { resDir => + val outputZip = compiledResDir / s"${resDir.last}-${count}.zip" + count = count + 1 + os.call(Seq( + androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool + "compile", + "--dir", + resDir.toString, // Compile each resource directory + "-o", + outputZip.toString // Output directory for compiled resources + )) + outputZip + } - try { - // Step 1: Compile resources using `aapt2 compile` - resourceDirs.foreach { resDir => - os.call(Seq( - androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool - "compile", - "-o", - compiledResDir.toString, // Output directory for compiled resources - "--dir", - resDir.toString // Compile each resource directory - )) - } + // Filter to find the single "resources" zip and exclude it from the regular zips + val ResourceZip = compiledZips.find(_.toString.contains("resources")) + val libzips = compiledZips.filterNot(_.toString.contains("resources")) - // Collect all .flat files explicitly - val flatFiles = os.walk(compiledResDir).filter(_.ext == "flat").map(_.toString) + val compiledlibs = libzips.flatMap(zip => Seq("-R", zip.toString)) - // Step 2: Link resources using `aapt2 link` - os.call(Seq( - androidSdkModule().aapt2Path().path.toString, // Call aapt2 tool + os.call( + Seq( + androidSdkModule().aapt2Path().path.toString, // AAPT2 tool path "link", - "-o", - (genDir / "resources.apk").toString, // Output APK or intermediate result "-I", androidSdkModule().androidJarPath().path.toString, // Include Android SDK JAR + "--auto-add-overlay" // Automatically add resources from overlays + ) ++ compiledlibs ++ Seq( "--manifest", androidManifest().path.toString, // Use AndroidManifest.xml - "--auto-add-overlay", // Automatically add resources from overlays "--java", - genDir.toString // Generate R.java in the genDir - ) ++ flatFiles.flatMap(flatFile => Seq("-R", flatFile))) - - PathRef(genDir) // Return the generated directory if successful + genDir.toString, // Generate R.java in the genDir + "-o", + s"${genDir / "res.apk"}", + ResourceZip.map(_.toString).getOrElse("") + ) + ) - } catch { - case e: Throwable => - T.log.info(s"Using Only Correct Resources") - PathRef(T.dest) // Return PathRef for only correct resources - } + PathRef(genDir) } /** @@ -211,33 +212,23 @@ trait AndroidAppModule extends JavaModule { androidSdkModule().androidJarPath().path.toString // Include Android framework classes ) ) - PathRef(dexOutputDir) } /** - * Packages the DEX files and Android resources into an unsigned APK using the `aapt` tool. + * Packages DEX files and Android resources into an unsigned APK. * - * The `aapt` tool takes the DEX files (compiled code) and resources (such as layouts and assets), - * and packages them into an APK (Android Package) file. This APK file is unsigned and requires - * further processing to be distributed. + * @return A `PathRef` to the generated unsigned APK file (`app.unsigned.apk`). */ def androidUnsignedApk: T[PathRef] = Task { val unsignedApk: os.Path = T.dest / "app.unsigned.apk" - - os.call( - Seq( - androidSdkModule().aaptPath().path.toString, - "package", - "-f", - "-M", - androidManifest().path.toString, // Path to AndroidManifest.xml - "-I", - androidSdkModule().androidJarPath().path.toString, // Include Android JAR - "-F", - unsignedApk.toString // Output APK - ) ++ Seq(androidDex().path.toString) // Include DEX files - ) + os.copy.over((T.dest / os.up / "androidResources.dest" / "res.apk"), unsignedApk) + os.call(Seq( + "zip", + "-j", + unsignedApk.toString, + s"${androidDex().path / "classes.dex"}" + )) PathRef(unsignedApk) } diff --git a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala index 5874da43dfe..1db02b1d5c4 100644 --- a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala @@ -1,6 +1,7 @@ package mill.javalib.android import mill._ +import scala.util.Try /** * Trait for managing the Android SDK in a Mill build system. @@ -83,46 +84,87 @@ trait AndroidSdkModule extends Module { } /** - * Installs the Android SDK by performing the following actions: + * Installs the Android SDK by performing the following steps: * - * - Downloads the SDK command-line tools from the specified URL. + * 1. Downloads the SDK command-line tools if not already cached. * - * - Extracts the downloaded zip file into the SDK directory. + * 2. Extracts the downloaded tools into the SDK directory. * - * - Accepts the required SDK licenses. + * 3. Accepts SDK licenses automatically. * - * - Installs essential SDK components such as platform-tools, build-tools, and Android platforms. + * 4. Installs the following components if not already installed: + * - platform-tools + * - build-tools (version specified by `buildToolsVersion`) + * - platforms (version specified by `platformsVersion`) * - * For more details on the sdkmanager tool, refer to: - * [[https://developer.android.com/tools/sdkmanager sdkmanager Documentation]] + * 5. Removes the downloaded zip file after extraction. * - * @return A task containing a `PathRef` pointing to the installed SDK directory. + * @return A task returning a `PathRef` pointing to the installed SDK directory. + * + * @see [[https://developer.android.com/tools/sdkmanager sdkmanager Documentation]] */ def installAndroidSdk: T[PathRef] = Task { - val zipFilePath: os.Path = T.dest / "commandlinetools.zip" - val sdkManagerPath: os.Path = T.dest / "cmdline-tools/bin/sdkmanager" - - // Download SDK command-line tools - os.write(zipFilePath, requests.get(sdkUrl().toString).bytes) - - // Extract the downloaded SDK tools into the destination directory - os.call(Seq("unzip", zipFilePath.toString, "-d", T.dest.toString)) - - // Automatically accept the SDK licenses - os.call(Seq( - "bash", - "-c", - s"yes | $sdkManagerPath --licenses --sdk_root=${T.dest}" - )) - - // Install platform-tools, build-tools, and the Android platform - os.call(Seq( - sdkManagerPath.toString, - s"--sdk_root=${T.dest}", - "platform-tools", - s"build-tools;${buildToolsVersion().toString}", - s"platforms;${platformsVersion().toString}" - )) - PathRef(T.dest) + val sdkCache: os.Path = os.home / ".android-sdk-cache" + val zipFilePath: os.Path = sdkCache / "commandlinetools.zip" + val sdkManagerPath: os.Path = sdkCache / "cmdline-tools/bin/sdkmanager" + + // Helper method to check if a tool is installed + def isToolInstalled(toolPath: os.Path): Boolean = os.exists(toolPath) + + // Paths for the tools + val platformToolsPath: os.Path = sdkCache / "platform-tools" + val buildToolsPath: os.Path = sdkCache / "build-tools" / buildToolsVersion() + val platformsPath: os.Path = sdkCache / "platforms" / platformsVersion() + + // Check if the SDK manager is already cached + if (!os.exists(sdkManagerPath)) { + // Ensure cache directory exists + os.makeDir.all(sdkCache) + // Download SDK command-line tools + os.write(zipFilePath, requests.get(sdkUrl().toString).bytes) + + // Extract the downloaded SDK tools into the destination directory + os.call(Seq("unzip", zipFilePath.toString, "-d", sdkCache.toString)) + + // Automatically accept the SDK licenses + os.call(Seq( + "bash", + "-c", + s"yes | $sdkManagerPath --licenses --sdk_root=${sdkCache}" + )) + + // Clean up the downloaded zip file + Try(os.remove(zipFilePath)) + } + + // Install platform-tools if not already installed + if (!isToolInstalled(platformToolsPath)) { + os.call(Seq( + sdkManagerPath.toString, + s"--sdk_root=${sdkCache}", + "platform-tools" + )) + } + + // Install build-tools if not already installed + if (!isToolInstalled(buildToolsPath)) { + os.call(Seq( + sdkManagerPath.toString, + s"--sdk_root=${sdkCache}", + s"build-tools;${buildToolsVersion().toString}" + )) + } + + // Install platforms if not already installed + if (!isToolInstalled(platformsPath)) { + os.call(Seq( + sdkManagerPath.toString, + s"--sdk_root=${sdkCache}", + s"platforms;${platformsVersion().toString}" + )) + } + + PathRef(sdkCache) } + } From 8d153b7677d833bece5dd26c7cdd45455f82a33c Mon Sep 17 00:00:00 2001 From: himanshumahajan138 Date: Fri, 8 Nov 2024 16:28:43 +0530 Subject: [PATCH 10/10] Fixture Clean --- .../javalib/android/AndroidSdkModule.scala | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala index 56f34ebf2cb..413aff21c3f 100644 --- a/scalalib/src/mill/javalib/android/AndroidSdkModule.scala +++ b/scalalib/src/mill/javalib/android/AndroidSdkModule.scala @@ -89,89 +89,6 @@ trait AndroidSdkModule extends Module { } /** -<<<<<<< HEAD - * Installs the Android SDK by performing the following steps: - * - * 1. Downloads the SDK command-line tools if not already cached. - * - * 2. Extracts the downloaded tools into the SDK directory. - * - * 3. Accepts SDK licenses automatically. - * - * 4. Installs the following components if not already installed: - * - platform-tools - * - build-tools (version specified by `buildToolsVersion`) - * - platforms (version specified by `platformsVersion`) - * - * 5. Removes the downloaded zip file after extraction. - * - * @return A task returning a `PathRef` pointing to the installed SDK directory. - * - * @see [[https://developer.android.com/tools/sdkmanager sdkmanager Documentation]] - */ - def installAndroidSdk: T[PathRef] = Task { - val sdkCache: os.Path = os.home / ".android-sdk-cache" - val zipFilePath: os.Path = sdkCache / "commandlinetools.zip" - val sdkManagerPath: os.Path = sdkCache / "cmdline-tools/bin/sdkmanager" - - // Helper method to check if a tool is installed - def isToolInstalled(toolPath: os.Path): Boolean = os.exists(toolPath) - - // Paths for the tools - val platformToolsPath: os.Path = sdkCache / "platform-tools" - val buildToolsPath: os.Path = sdkCache / "build-tools" / buildToolsVersion() - val platformsPath: os.Path = sdkCache / "platforms" / platformsVersion() - - // Check if the SDK manager is already cached - if (!os.exists(sdkManagerPath)) { - // Ensure cache directory exists - os.makeDir.all(sdkCache) - // Download SDK command-line tools - os.write(zipFilePath, requests.get(sdkUrl().toString).bytes) - - // Extract the downloaded SDK tools into the destination directory - os.call(Seq("unzip", zipFilePath.toString, "-d", sdkCache.toString)) - - // Automatically accept the SDK licenses - os.call(Seq( - "bash", - "-c", - s"yes | $sdkManagerPath --licenses --sdk_root=${sdkCache}" - )) - - // Clean up the downloaded zip file - Try(os.remove(zipFilePath)) - } - - // Install platform-tools if not already installed - if (!isToolInstalled(platformToolsPath)) { - os.call(Seq( - sdkManagerPath.toString, - s"--sdk_root=${sdkCache}", - "platform-tools" - )) - } - - // Install build-tools if not already installed - if (!isToolInstalled(buildToolsPath)) { - os.call(Seq( - sdkManagerPath.toString, - s"--sdk_root=${sdkCache}", - s"build-tools;${buildToolsVersion().toString}" - )) - } - - // Install platforms if not already installed - if (!isToolInstalled(platformsPath)) { - os.call(Seq( - sdkManagerPath.toString, - s"--sdk_root=${sdkCache}", - s"platforms;${platformsVersion().toString}" - )) - } - - PathRef(sdkCache) -======= * Installs the necessary Android SDK components such as platform-tools, build-tools, and Android platforms. * * For more details on the `sdkmanager` tool, refer to: @@ -300,7 +217,6 @@ trait AndroidSdkModule extends Module { } } Some(sdkManagerPath).filter(os.exists) ->>>>>>> 4f70fd6c392a4acae12f43a292d8f88d84e912c0 } }