Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Change Log
==========

## Unreleased

* Fix bug #264
* Clean up internal utils

## Version 3.1.0 (2026-01-18)

* Fix bug #255 by ensuring that "stemProvider" dependencies are not transitive.
Expand Down
4 changes: 3 additions & 1 deletion demo-app/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin)
id("com.likethesalad.stem")
}

Expand All @@ -14,6 +13,9 @@ android {
targetSdk = 34
versionCode = 1
versionName = "1.0"
buildFeatures {
resValues = true
}
resValue("string", "generated_string", "My generated string")
}

Expand Down
5 changes: 2 additions & 3 deletions demo-app/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[versions]
project-utilities = "3.1.0"
android = "8.13.2"
android = "9.0.0"

[libraries]
robolectric = "org.robolectric:robolectric:4.16"
unitTesting = { module = "com.likethesalad.tools:unit-testing", version.ref = "project-utilities" }

[plugins]
android-application = { id = "com.android.application", version.ref = "android" }
android-library = { id = "com.android.library", version.ref = "android" }
kotlin = { id = "org.jetbrains.kotlin.android", version = "2.3.0" }
android-library = { id = "com.android.library", version.ref = "android" }
1 change: 0 additions & 1 deletion demo-app/my-library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin)
}

android {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Sun Jan 18 14:49:20 UTC 2026
kotlin.code.style=official
version=3.2.0
version=3.1.1
group=com.likethesalad.android
4 changes: 1 addition & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[versions]
project-utilities = "3.1.0"
android = "8.13.2"
android = "9.0.0"

[libraries]
android-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" }
Expand All @@ -20,7 +19,6 @@ junit = ["junit-jupiter", "junit-launcher", "junit-engine"]

[plugins]
artifactPublisher = { id = "com.likethesalad.artifact-publisher", version = "3.5.0" }
androidTestTools = { id = "com.likethesalad.tools.android-compilation-testing", version.ref = "project-utilities" }
android-application = { id = "com.android.application", version.ref = "android" }
wire = { id = "com.squareup.wire", version = "5.5.0" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.3.0" }
7 changes: 1 addition & 6 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
google()
Expand All @@ -10,12 +11,6 @@ pluginManagement {
if (pluginId == "com.likethesalad.artifact-publisher") {
useModule("com.likethesalad.tools:artifact-publisher:${requested.version}")
}
if (pluginId == "plugin-metadata-producer") {
useModule("com.likethesalad.tools:plugin-metadata-producer:1.0.0")
}
if (requested.id.namespace == "com.likethesalad.tools") {
useModule("com.likethesalad.tools:plugin-tools:${requested.version}")
}
}
}
}
Expand Down
13 changes: 11 additions & 2 deletions stem-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
alias(libs.plugins.kotlin)
id("java-gradle-plugin")
alias(libs.plugins.androidTestTools)
alias(libs.plugins.wire)
}

val testPluginDependency: Configuration by configurations.creating
val testPluginClasspath: Configuration = configurations.create("testPluginClasspath") {
extendsFrom(testPluginDependency)
isCanBeConsumed = false
isCanBeResolved = true
}
tasks.withType<PluginUnderTestMetadata> {
pluginClasspath.from(testPluginClasspath)
}

dependencies {
implementation(libs.gson)
compileOnly(libs.android.plugin)
Expand All @@ -16,7 +25,7 @@ dependencies {
testImplementation(libs.android.plugin)
testImplementation(libs.assertj)
testImplementation(libs.mockk)
testPluginDependency("com.android.tools.build:gradle:8.13.2")
testPluginDependency("com.android.tools.build:gradle:9.0.0")
}

kotlin {
Expand Down
8 changes: 5 additions & 3 deletions stem-plugin/src/main/java/com/likethesalad/stem/StemPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.likethesalad.stem

import com.android.build.api.AndroidPluginVersion
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.likethesalad.stem.configuration.StemConfiguration
import com.likethesalad.stem.modules.collector.task.RawStringCollectorTask
Expand Down Expand Up @@ -28,6 +29,7 @@ class StemPlugin : Plugin<Project> {
companion object {
private const val EXTENSION_NAME = "androidStem"
private val artifactTypeAttr = Attribute.of("artifactType", String::class.java)
private val AGP_MIN_SUPPORTED_VERSION = AndroidPluginVersion(8, 4, 0)
}

override fun apply(project: Project) {
Expand Down Expand Up @@ -102,11 +104,11 @@ class StemPlugin : Plugin<Project> {
val components = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
val pluginVersion = components.pluginVersion

if (pluginVersion.major >= 8 && pluginVersion.minor >= 4) {
return components
if (pluginVersion < AGP_MIN_SUPPORTED_VERSION) {
throw IllegalStateException("Android Stem requires a minimum Android Gradle Plugin version of 8.4.0. The current version is: $pluginVersion")
}

throw IllegalStateException("Android Stem requires a minimum Android Gradle Plugin version of 8.4.0. The current version is: $pluginVersion")
return components
}

private fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, into: AttributeContainer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import com.likethesalad.stem.functionaltest.testtools.BasePluginTest
import com.likethesalad.stem.functionaltest.testtools.PlaceholderBlock
import com.likethesalad.stem.functionaltest.testtools.StemConfigBlock
import com.likethesalad.stem.testutils.upperFirst
import com.likethesalad.tools.functional.testing.AndroidTestProject
import com.likethesalad.tools.functional.testing.android.blocks.AndroidBlockItem
import com.likethesalad.tools.functional.testing.android.blocks.DefaultConfigAndroidBlockItem
import com.likethesalad.tools.functional.testing.android.blocks.FlavorAndroidBlockItem
import com.likethesalad.tools.functional.testing.android.descriptor.AndroidAppProjectDescriptor
import com.likethesalad.tools.functional.testing.android.descriptor.AndroidLibProjectDescriptor
import com.likethesalad.tools.functional.testing.blocks.GradleBlockItem
import com.likethesalad.tools.functional.testing.blocks.impl.plugins.GradlePluginDeclaration
import com.likethesalad.tools.functional.testing.utils.TestAssetsProvider
import com.likethesalad.tools.AndroidTestProject
import com.likethesalad.tools.android.blocks.AndroidBlockItem
import com.likethesalad.tools.android.blocks.DefaultConfigAndroidBlockItem
import com.likethesalad.tools.android.blocks.FlavorAndroidBlockItem
import com.likethesalad.tools.android.descriptor.AndroidAppProjectDescriptor
import com.likethesalad.tools.android.descriptor.AndroidLibProjectDescriptor
import com.likethesalad.tools.blocks.GradleBlockItem
import com.likethesalad.tools.blocks.impl.plugins.GradlePluginDeclaration
import com.likethesalad.tools.utils.TestAssetsProvider
import java.io.File
import junit.framework.TestCase.fail
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.fail
import org.xmlunit.builder.DiffBuilder.compare
import org.xmlunit.builder.Input

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.likethesalad.stem.functionaltest.testtools

import com.likethesalad.tools.functional.testing.AndroidTestProject
import com.likethesalad.tools.functional.testing.descriptor.ProjectDescriptor
import com.likethesalad.tools.AndroidTestProject
import com.likethesalad.tools.descriptor.ProjectDescriptor
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.BuildResult
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.likethesalad.stem.functionaltest.testtools

import com.likethesalad.tools.functional.testing.blocks.GradleBlockItem
import com.likethesalad.tools.blocks.GradleBlockItem

class PlaceholderBlock(private val start: String?=null, private val end: String?=null) : GradleBlockItem {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.likethesalad.stem.functionaltest.testtools

import com.likethesalad.tools.functional.testing.blocks.GradleBlockItem
import com.likethesalad.tools.blocks.GradleBlockItem

class StemConfigBlock(
private val includeLocalizedOnlyTemplates: Boolean? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.likethesalad.stem.configuration.StemConfiguration
import com.likethesalad.stem.testutils.createForTest
import com.likethesalad.stem.testutils.named
import com.likethesalad.stem.tools.extensions.name
import junit.framework.TestCase.fail
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.Test

class RecursiveLevelDetectorTest {
Expand Down
138 changes: 138 additions & 0 deletions stem-plugin/src/test/java/com/likethesalad/tools/AndroidTestProject.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.likethesalad.tools

import com.likethesalad.tools.descriptor.ProjectDescriptor
import java.io.File
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner

class AndroidTestProject(val rootDir: File) {
private lateinit var rootGradleFile: File
private lateinit var settingsFile: File

companion object {
private const val BUILD_GRADLE_FILE_NAME = "build.gradle"
private const val ANDROID_MANIFEST_FILE_NAME = "AndroidManifest.xml"
private const val SETTINGS_GRADLE_FILE_NAME = "settings.gradle"
}

init {
setUpRootProject()
}

fun runGradle(
forProjectName: String,
command: String,
withInfo: Boolean = false
): BuildResult {
return runGradle(forProjectName, listOf(command), withInfo)
}

fun runGradleAndFail(
forProjectName: String,
command: String,
withInfo: Boolean = false
): BuildResult {
return runGradleAndFail(forProjectName, listOf(command), withInfo)
}

fun runGradle(
forProjectName: String,
commands: List<String>,
withInfo: Boolean = false
): BuildResult {
val extraArgs = mutableListOf("--stacktrace")
if (withInfo) {
extraArgs.add("--info")
}
return createGradleRunner()
.withArguments(commands.map { ":$forProjectName:$it" }.plus(extraArgs))
.build()
}

fun runGradleAndFail(
forProjectName: String,
commands: List<String>,
withInfo: Boolean = false
): BuildResult {
val extraArgs = mutableListOf("--stacktrace")
if (withInfo) {
extraArgs.add("--info")
}
return createGradleRunner()
.withArguments(commands.map { ":$forProjectName:$it" }.plus(extraArgs))
.buildAndFail()
}

fun addSubproject(descriptor: ProjectDescriptor) {
val name = descriptor.projectName
val projectDir = File(rootDir, name)

projectDir.mkdirs()

createProjectBuildGradleFile(descriptor.getBuildGradleContents(), projectDir)
createProjectManifestFile(projectDir)
descriptor.projectDirBuilder.buildDirectory(projectDir)

settingsFile.appendText(
"""

include '$name'
""".trimIndent()
)
}

private fun createGradleRunner(): GradleRunner {
return GradleRunner.create()
.withProjectDir(rootDir)
.withPluginClasspath()
}

private fun createProjectBuildGradleFile(buildGradleContent: String, parentDir: File) {
val buildGradle = File(parentDir, BUILD_GRADLE_FILE_NAME)
buildGradle.writeText(buildGradleContent)
}

private fun createProjectManifestFile(parentDir: File) {
val mainDir = File(parentDir, "src/main")
if (!mainDir.exists()) {
mainDir.mkdirs()
}
val androidManifest = File(mainDir, ANDROID_MANIFEST_FILE_NAME)
androidManifest.writeText(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
""".trimIndent()
)
}

private fun setUpRootProject() {
rootGradleFile = File(rootDir, BUILD_GRADLE_FILE_NAME)
settingsFile = File(rootDir, SETTINGS_GRADLE_FILE_NAME)

rootGradleFile.writeText(
"""
""".trimIndent()
)

settingsFile.writeText(
"""
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
gradlePluginPortal()
}
}
rootProject.name = "sample-project"
""".trimIndent()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.likethesalad.tools.android.blocks

import com.likethesalad.tools.blocks.GradleBlockItem

interface AndroidBlockItem : GradleBlockItem
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.likethesalad.tools.android.blocks

class DefaultConfigAndroidBlockItem(private val strings: Map<String, String>) : AndroidBlockItem {

override fun getItemText(): String {
return """
defaultConfig {
buildFeatures {
resValues = true
}
${getGradleStrings()}
}
""".trimIndent()
}

private fun getGradleStrings(): String {
val stringLines = mutableListOf<String>()
strings.forEach { (name, value) ->
stringLines.add(getGradleStringGenDeclaration(name, value))
}

return stringLines.fold("") { accumulated, current ->
"$accumulated\n$current"
}
}

private fun getGradleStringGenDeclaration(stringName: String, stringValue: String): String {
return "resValue \"string\", \"$stringName\", \"\\\"$stringValue\\\"\""
}
}
Loading