Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper Android support #24

Merged
merged 1 commit into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.