mill icon indicating copy to clipboard operation
mill copied to clipboard

Android: Variant and Release Submodules

Open souvlakias opened this issue 1 month ago • 9 comments

This implements https://github.com/com-lihaoyi/mill/issues/6047#issuecomment-3463884935.

Changes

  • AndroidModules now have inner Variant traits that share common tasks.
  • AppModules also have Release traits which, for now, only override androidIsDebug to false.

Having a dedicated Variant for each module type (Android, AndroidKotlin, AndroidR8, AndroidNative) seems necessary to avoid missing configurations.

Users now need to create a release object that extends the corresponding Release trait, and then run: ./mill app.release.compile, etc

What's Next

Remove the androidIsDebug flag and instead let the Release traits override the relevant tasks directly. Release configurations (keystore details) should be in the release variant too

Concerns

This approach utilizes shared resources and may provide a better user experience with the APIs. However in my opinion, manually overriding tasks to reference outer implementations may be error-prone, both in terms of development and potential unexpected behavior if users override unshared tasks.

Maybe the shared base trait approach is something safer and more scalable, but with the cost of not sharing resources. Perhaps we could use the outer approach —just as an exception— to share some resource-heavy tasks.

souvlakias avatar Nov 05 '25 11:11 souvlakias

Hi! I'm autofix logoautofix.ci, a bot that automatically fixes trivial issues such as code formatting in pull requests.

I would like to apply some automated changes to this pull request, but it looks like I don't have the necessary permissions to do so. To get this pull request into a mergeable state, please do one of the following two things:

  1. Allow edits by maintainers for your pull request, and then re-trigger CI (for example by pushing a new commit).
  2. Manually fix the issues identified for your pull request (see the GitHub Actions output for details on what I would like to change).

autofix-ci[bot] avatar Nov 05 '25 14:11 autofix-ci[bot]

@lefou what do you think about this approach? Looking at the build variants in general, I think the cross module might be a better fit and perhaps we give a trait to easily achieve the release targets.

Just thinking out loud: For the variants in general the users are free to create a cross module themselves, but we should have something for release imo, but in this form a lot of the weight falls on us maintaining it while perhaps a different cross module approach than our first attempt would be better, for example having a AndroidCrossModule extends CrossModule[Android]("debug", "release") and then an AndroidReleaseModule extends AndroidCrossModule("release") or something in those lines (I haven't tried it yet)

vaslabs avatar Nov 12 '25 09:11 vaslabs

Now that I got to play with spring boot under a similar concept, I'll give this another go

vaslabs avatar Nov 20 '25 07:11 vaslabs

From a quick glance, this approach looks good to me.

I'm not sure what you refer to with "shared base trait approach". In general, I think the variant-sub-modules should be fully capable android modules that can produce results on their own. The outer-references just provide the configuration (sources, resources, etc). This is, what we also do in other sub-traits, e.g. ScalaModule and ScalaTests.

lefou avatar Nov 20 '25 14:11 lefou

I added to the docs in kotlin 1-hello-kotlin example, and from the user experience perspective, it feels nice.

Let's go with this I think

vaslabs avatar Nov 21 '25 13:11 vaslabs

well, feels less nice when the build is more complex with hilt, R8 + kotlin

something like that is needed

object release extends AndroidHiltCompose, AndroidKotlinReleaseModule, AndroidR8ReleaseModule {
    def androidReleaseKeyName: Option[String] = Some("releaseKey.jks")

    def androidReleaseKeyAlias: T[Option[String]] = Task {
      Some("releaseKey")
    }

    def androidReleaseKeyPass: T[Option[String]] = Task {
      Some("MillBuildTool")
    }

    def androidReleaseKeyStorePass: T[Option[String]] = Task {
      Some("MillBuildTool")
    }

    override def androidEmulatorPort = "5654"
  }
  
trait AndroidHiltCompose extends AndroidHiltLib with AndroidCompose {

  override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq(
      mvn"androidx.hilt:hilt-navigation-compose:1.2.0",
  )
}

trait AndroidCompose extends AndroidKotlinLib {
  override def androidEnableCompose: T[Boolean] = Task { true }
}

vaslabs avatar Nov 21 '25 13:11 vaslabs

this is a working release configuration with a complex setup

https://github.com/vaslabs/Pokedex_Compose_Multi_Module/pull/1/files

vaslabs avatar Nov 21 '25 13:11 vaslabs

on the other hand, cross module is fairly straight forward to be plugged in and I don't think we need to do anything there to support it (https://github.com/vaslabs/Pokedex_Compose_Multi_Module/pull/2)

apart from maybe some minor indirect UX improvements like making the emulator port a task so it can be derived from androidIsDebug

vaslabs avatar Nov 21 '25 14:11 vaslabs

the question is, this approach has a lot of complexity, do we take the hit? I think we don't do anything and close this PR, the cross module should be more than enough for the android variant to work out of the box by purely using Mill's cross module features

we can add some examples to aid any users and that's it imo

vaslabs avatar Nov 21 '25 14:11 vaslabs