Skip to content

Commit

Permalink
Proper Android support
Browse files Browse the repository at this point in the history
  • Loading branch information
natario1 committed Oct 24, 2024
1 parent 6a15a3b commit 8060e04
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 87 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ to different kinds of repositories. It supports publishing to:

> For Maven Central builds, the plugin takes care of releasing the artifacts using Sonatype REST APIs so you don't have to use their web UI.

It supports automatic configuration for a certain set of projects:

- [Android Projects](https://opensource.deepmedia.io/deployer/artifacts#android-projects)
- [Kotlin Projects](https://opensource.deepmedia.io/deployer/artifacts#kotlin-regular-projects)
- [Kotlin Multiplatform Projects](https://opensource.deepmedia.io/deployer/artifacts#kotlin-multiplatform-projects)
- [Gradle Plugin Projects](https://opensource.deepmedia.io/deployer/artifacts#gradle-plugin-projects)

In addition, you may configure deployments manually based on some existing `SoftwareComponent`, `MavenPublication` or simple file artifacts.

```kotlin
// settings.gradle.kts
pluginManagement {
Expand All @@ -27,7 +37,7 @@ pluginManagement {

// build.gradle.kts of deployable modules
plugins {
id("io.deepmedia.tools.deployer") version "0.14.0"
id("io.deepmedia.tools.deployer") version "0.15.0-alpha1"
}
```

Expand Down
6 changes: 3 additions & 3 deletions deployer/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
plugins {
`kotlin-dsl`
`java-gradle-plugin`
id("io.deepmedia.tools.deployer") version "0.14.0-alpha1"
id("io.deepmedia.tools.deployer") version "0.14.0"
kotlin("plugin.serialization") version "1.9.23"
id("org.jetbrains.dokka") version "1.9.20"
}

dependencies {
compileOnly("com.android.tools.build:gradle:8.0.2")
compileOnly("com.android.tools.build:gradle:8.1.1")
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")

// api("org.jetbrains.dokka:dokka-gradle-plugin:1.8.20")
Expand All @@ -33,7 +33,7 @@ gradlePlugin {
}

group = "io.deepmedia.tools.deployer"
version = "0.14.0" // on change, update README
version = "0.15.0-alpha1" // on change, update README

val javadocs = tasks.register<Jar>("dokkaJavadocJar") {
dependsOn(tasks.dokkaJavadoc)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.deepmedia.tools.deployer.central.portal

import com.android.tools.r8.internal.Bo
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.deepmedia.tools.deployer.inference

import io.deepmedia.tools.deployer.isKotlinProject
import io.deepmedia.tools.deployer.model.Component
import io.deepmedia.tools.deployer.model.DeploySpec
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType

/**
* Android targets can be configured for publishing in two different ways:
Expand All @@ -11,7 +15,7 @@ import org.gradle.api.Project
* with a custom name. The name of the single variant, or the custom name of a multi-variant publiation, will be
* the name of the software component.
* https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-main/build-system/gradle-api/src/main/java/com/android/build/api/dsl/LibraryPublishing.kt
* TODO: there doesn't seem to be any way of reading variants from AGP
* NOTE: there doesn't seem to be any way of reading variants from AGP
*
* 2. Via KGP's configuration block.
* In this case, the software components can be retrieved using [KotlinTarget.components] as we do in fromKotlinTarget.
Expand All @@ -20,11 +24,27 @@ import org.gradle.api.Project
* - there may be more than one component, if user chose to do so
* - [KotlinTarget.components] *must* be called in a afterEvaluate block, after AGP's afterEvaluate.
*
* We can't support 1. until a proper AGP API exist.
* We do already support 2. through KotlinInference, so this is useless for now.
*/
internal class AndroidLibraryInference : Inference {
internal class AndroidInference(private val componentNames: List<String>) : Inference {

override fun inferComponents(project: Project, spec: DeploySpec, create: (Component.() -> Unit) -> Component) {
TODO("Not yet implemented")
project.plugins.withId("com.android.library") {
val kotlin = if (project.isKotlinProject) project.kotlinExtension else null
componentNames.forEach { componentName ->
create {
fromSoftwareComponent(componentName)

packaging.set("aar")

if (kotlin is KotlinMultiplatformExtension) {
val androidTargets = kotlin.targets.matching { it.platformType == KotlinPlatformType.androidJvm }
artifactId.set {
val target = androidTargets.firstOrNull { it.components.any { it.name == componentName } } ?: androidTargets.first()
"$it-${target.name.lowercase()}"
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.deepmedia.tools.deployer.inference

import io.deepmedia.tools.deployer.model.Component
import io.deepmedia.tools.deployer.model.DeploySpec
import io.deepmedia.tools.deployer.whenEvaluated
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
Expand All @@ -16,35 +15,21 @@ internal class KotlinInference : Inference {
private val pluginIds = listOf(
"org.jetbrains.kotlin.jvm",
"org.jetbrains.kotlin.js",
"org.jetbrains.kotlin.android",
"org.jetbrains.kotlin.multiplatform"
)

private fun inferComponent(target: KotlinTarget, multiplatform: Boolean, create: (Component.() -> Unit) -> Component) {
if (target is KotlinAndroidTarget) {
// Should use AndroidInference!
return
}
if (target is KotlinOnlyTarget<*>) {
create {
fromKotlinTarget(target)
if (multiplatform && target.platformType != KotlinPlatformType.common) {
artifactId.set { "$it-${target.name.lowercase()}" }
}
}
} else if (target is KotlinAndroidTarget) {
// Android's KotlinTarget.components must be called in afterEvaluate,
// possibly nested so we jump after AGP's afterEvaluate blocks.
// There may be 0, 1 or more components depending on how KotlinAndroidTarget was configured.
// NOTE: if multiple components are present, they will have the same artifactId.
target.project.whenEvaluated {
target.project.whenEvaluated {
(target as KotlinTarget).components.forEach { component ->
create {
fromSoftwareComponent(component, tag = target)
if (multiplatform) {
artifactId.set { "$it-${target.name.lowercase()}" }
}
}
}
}
}
} else {
error("Unexpected kotlin target: $target")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.deepmedia.tools.deployer.model

import io.deepmedia.tools.deployer.inference.AndroidInference
import io.deepmedia.tools.deployer.inference.GradlePluginInference
import io.deepmedia.tools.deployer.inference.Inference
import io.deepmedia.tools.deployer.inference.KotlinInference
Expand All @@ -25,62 +26,38 @@ open class Content @Inject constructor(private val objects: ObjectFactory) : Com
return component
}

private val inferenceAction = objects.property<Action<Component>>()
private val inference = objects.property<Inference>()
private data class InferenceData(val inference: Inference, val action: Action<Component>)
private val inferenceData = objects.listProperty<InferenceData>()

fun kotlinComponents(action: Action<Component> = Action { }) {
inference = KotlinInference()
inferenceAction = action
inferenceData.add(InferenceData(KotlinInference(), action))
}

fun gradlePluginComponents(action: Action<Component> = Action { }) {
inference = GradlePluginInference()
inferenceAction = action
inferenceData.add(InferenceData(GradlePluginInference(), action))
}

// private val sourcesInjection = objects.property<String>()
// private val docsInjection = objects.property<String>()
fun androidComponents(componentName: String, vararg otherComponentNames: String, action: Action<Component> = Action { }) {
inferenceData.add(InferenceData(AndroidInference(listOf(componentName, *otherComponentNames)), action))
}

internal fun fallback(to: Content) {
// inherit.fallback(to.inherit) // doesn't seem right
// sourcesInjection.fallback(to.sourcesInjection)
// docsInjection.fallback(to.docsInjection)
to.inheritedComponents.all { inheritedComponents.add(this) }
to.inheritedComponents.whenObjectRemoved { inheritedComponents.remove(this) }
to.declaredComponents.all { inheritedComponents.add(this) }
to.declaredComponents.whenObjectRemoved { inheritedComponents.remove(this) }
}

/* fun emptySources() { sourcesInjection.set("empty") }
fun emtpyDocs() { docsInjection.set("empty") }
@Deprecated("autoDocs is deprecated.", level = DeprecationLevel.WARNING)
fun autoDocs() { docsInjection.set("auto") }
@Deprecated("autoSources is deprecated.", level = DeprecationLevel.WARNING)
fun autoSources() { sourcesInjection.set("auto") } */

internal fun resolve(project: Project, spec: DeploySpec) {
inherit.finalizeValue() // fixes 'components'
inference.finalizeValue()
inferenceAction.finalizeValue()
inference.orNull?.inferComponents(project, spec) { configure ->
component {
configure()
inferenceAction.orNull?.execute(this)
inferenceData.finalizeValue()
inferenceData.get().forEach {
it.inference.inferComponents(project, spec) { configure ->
component {
configure()
it.action.execute(this)
}
}
}
/* val commonSources = sourcesInjection.orNull
val commonDocs = docsInjection.orNull
allComponents.configureEach {
when {
sources.isPresent -> { /* already provided */ }
commonSources == "empty" -> sources(project.makeSourcesJar(spec, this, true))
commonSources == "auto" -> sources(project.makeSourcesJar(spec, this, false))
}
when {
docs.isPresent -> { /* already provided */ }
commonDocs == "empty" -> docs(project.makeDocsJar(spec, this, true))
commonDocs == "auto" -> docs(project.makeDocsJar(spec, this, false))
}
} */
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.deepmedia.tools.deployer.model

import com.android.build.gradle.BaseExtension
import io.deepmedia.tools.deployer.isAndroidLibraryProject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
Expand Down Expand Up @@ -32,16 +30,7 @@ open class Release @Inject constructor(objects: ObjectFactory) {
// Tag defaults to v$version.
// Description defaults to target name + tag
// Packaging can be set to aar for AARs.
resolvedVersion = version.orElse(target.provider {
when {
target.isAndroidLibraryProject -> {
val android = target.extensions.getByName("android") as BaseExtension
android.defaultConfig.versionName + (android.defaultConfig.versionNameSuffix ?: "")
}
else -> target.version.toString()
}
})

resolvedVersion = version.orElse(target.provider { target.version.toString() })
resolvedTag = tag.orElse(resolvedVersion.map { "v$it" })
resolvedDescription = description.orElse(tag.map { "${target.name} $it" })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ internal fun Artifacts.Entry.Resolved.wrapped(project: Project, logger: Logger,
else -> when (val data = unwrappedArtifact) {
is PublishArtifact -> data.classifier to data.extension
is AbstractArchiveTask -> data.archiveClassifier.orNull to data.archiveExtension.get()
// NOTE: logic here is very weak, could at least check if last() seems to be a version number and if so,
// we can infer that this file has no classifier (that is, it's the main POM artifact)
else -> resolvedFile.nameWithoutExtension.split("-").last() to resolvedFile.extension
}
}
Expand Down
39 changes: 31 additions & 8 deletions docs/artifacts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ Each spec can support multiple components, each one corresponding to a maven pub

## Built-in components

Inside the `content {}`, a few utilities are available so that:
- You give the deployer some hints about the kind of project that you are working on
- The deployer infers a list of components that you will want to export
Inside the `content {}` block, a few utilities are available so that:
- You give us some hints about the kind of project that you are working on
- We infer a list of components that you will want to export

##### Kotlin Multiplatform projects

Use `content.kotlinComponents()`. The spec will export:
- One component for Kotlin Metadata (the `common` target)
- One component per declared target
- For Android targets, one component per exported variant
- One component per declared kotlin target

> If your Kotlin project includes Android, you should also use [`androidComponents()`](#android-projects).
```kotlin
content {
Expand All @@ -30,9 +31,9 @@ content {

##### Kotlin regular projects

Again, use `content.kotlinComponents()`. The spec will export:
- if target is Android, one component per configured variant
- in all other cases (JS, JVM, ...), one component only
Again, use `content.kotlinComponents()`. The spec will export a single component based for the desired platform.

> If your Kotlin project targets Android, you should use [`androidComponents()`](#android-projects) instead.
```kotlin
content {
Expand All @@ -42,6 +43,28 @@ content {
}
```

##### Android projects

Use `content.androidComponents("softwareComponentName")` for Android projects. This function accepts a vararg
list of software component names, which you must declare in the regular `android {}` configuration.

Unfortunately, Android Gradle Plugin offers no API to read the variants that you configured for publishing, so
the exact names must be passed to the deployer, too.

```kotlin
android {
publishing {
singleVariant("release")
multipleVariants("merged") { includeBuildTypeValues("debug", "release") }
}
}
content {
androidComponents("release", "merged") {
// Optional configuration, invoked on each component.
}
}
```

##### Gradle Plugin projects

For projects providing gradle plugins, it is expected that the `java-gradle-plugin` is applied
Expand Down
9 changes: 9 additions & 0 deletions docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ to different kinds of repositories. It supports publishing to:

> For Maven Central builds, the plugin takes care of releasing the artifacts using Sonatype REST APIs so you don't have to use their web UI.
It supports automatic configuration for a certain set of projects:

- [Android Projects](artifacts#android-projects)
- [Kotlin Projects](artifacts#kotlin-regular-projects)
- [Kotlin Multiplatform Projects](artifacts#kotlin-multiplatform-projects)
- [Gradle Plugin Projects](artifacts#gradle-plugin-projects)

In addition, you may configure deployments manually based on some existing `SoftwareComponent`, `MavenPublication` or simple file artifacts.

0 comments on commit 8060e04

Please sign in to comment.