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

fix idea sync failing on composite builds, add support for composite builds #367

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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 .github/workflows/check-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ jobs:
wrapper-directory: sample-multi-modules
build-root-directory: sample-multi-modules
arguments: check --stacktrace
- uses: eskatos/gradle-command-action@v1
with:
wrapper-directory: sample-include-build
build-root-directory: sample-include-build
arguments: check --stacktrace
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- In Android projects, if you used the version placeholder (`_`) directly in `build.gradle(.kts)` files, Android lint would trigger an unwanted warning, or error in the case of the Android build tools (aka. AGP, the Android Gradle Plugin). To avoid this inconvenience, running the refreshVersions task will now automatically check if it's running on an Android project, and in such cases, will edit (safely) the `lint.xml` file, creating it if needed, and add the needed rules to have these specific warnings and errors ignored.

### Changes

- `versionFor` was moved to `versions.versionFor`, proper deprecation notices should make migration easy

### Fixes

- Fix a bug that prevented from using the correct version of `org.jetbrains.kotlinx.benchmark` and any other Gradle plugin with an id starting with `org.jetbrains.kotlinx` because it matched over `org.jetbrains.kotlin` as well. We are now matching on `org.jetbrains.kotlin.` to avoid this issue.
Expand Down
16 changes: 4 additions & 12 deletions docs/add-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,24 @@ As you see, the convention is pretty simple. The key is the id of the plugin, pr
## Get the version from anywhere

In some cases, you might need to get the version defined in the `versions.properties` file in a Gradle script.
For these cases, there's the `versionFor` function that takes either a version key, or a full dependency notation.
For these cases, there's the `versions.versionFor` function that takes either a version key, or a full dependency notation.

Here's a usage example with Jetpack Compose in an Android project:

=== "build.gradle.kts"
```kotlin
import de.fayard.refreshVersions.core.versionFor

...

composeOptions {
kotlinCompilerExtensionVersion = versionFor(AndroidX.compose.ui)
kotlinCompilerExtensionVersion = versions.versionFor(AndroidX.compose.ui)
}
```
=== "build.gradle"
```groovy
import static de.fayard.refreshVersions.core.Versions.versionFor

...

composeOptions {
kotlinCompilerExtensionVersion = versionFor(AndroidX.compose.ui)
kotlinCompilerExtensionVersion = versions.versionFor(AndroidX.compose.ui)
}
```

Using `versionFor("version.androidx.compose.ui")` would also work, so long as `version.androidx.compose.ui` is defined in the `versions.properties` file.
Using `versions.versionFor("version.androidx.compose.ui")` would also work, so long as `version.androidx.compose.ui` is defined in the `versions.properties` file.

## Non-built-in dependency notations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.maven
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,18 @@ open class BuildSrcLibsTask : DefaultTask() {
@TaskAction
fun initVersionsProperties() {
require(project == project.rootProject) { "Expected a rootProject but got $project" }
val config = RefreshVersionsConfigHolder.getConfigForProject(project)
val configurationsWithHardcodedDependencies = project.findHardcodedDependencies()

val versionsMap = RefreshVersionsConfigHolder.readVersionsMap()
val versionKeyReader = RefreshVersionsConfigHolder.versionKeyReader
val versionsMap = config.readVersionsMap()
val versionKeyReader = config.versionKeyReader
val newEntries: Map<String, ExternalDependency> = findMissingEntries(
configurations = configurationsWithHardcodedDependencies,
versionsMap = versionsMap,
versionKeyReader = versionKeyReader
)

writeMissingEntriesInVersionProperties(newEntries)
writeMissingEntriesInVersionProperties(config, newEntries)
OutputFile.VERSIONS_PROPERTIES.logFileWasModified()
Thread.sleep(1000)
}
Expand Down Expand Up @@ -106,7 +107,8 @@ open class BuildSrcLibsTask : DefaultTask() {
}

internal fun Project.findHardcodedDependencies(): List<Configuration> {
val versionsMap = RefreshVersionsConfigHolder.readVersionsMap()
val config = RefreshVersionsConfigHolder.getConfigForProject(this)
val versionsMap = config.readVersionsMap()
val projectsWithHardcodedDependenciesVersions: List<Project> = rootProject.allprojects.filter {
it.countDependenciesWithHardcodedVersions(versionsMap) > 0
}
Expand All @@ -115,7 +117,7 @@ internal fun Project.findHardcodedDependencies(): List<Configuration> {
project.configurations.filterNot { configuration ->
configuration.shouldBeIgnored() || 0 == configuration.countDependenciesWithHardcodedVersions(
versionsMap = versionsMap,
versionKeyReader = RefreshVersionsConfigHolder.versionKeyReader
versionKeyReader = config.versionKeyReader
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ open class RefreshVersionsCorePlugin : Plugin<Project> {
override fun apply(project: Project) {
check(project.isRootProject) { "ERROR: de.fayard.refreshVersions.core should not be applied manually" }
if (project.isBuildSrc.not()) {
val config = RefreshVersionsConfigHolder.getConfigForProject(project)
val versionsFileName = config.versionsPropertiesFile.name
project.tasks.register<RefreshVersionsTask>(name = "refreshVersions") {
group = "Help"
val versionsFileName = RefreshVersionsConfigHolder.versionsPropertiesFile.name
description = "Search for new dependencies versions and update $versionsFileName"
}
project.extensions.add("versions", VersionExtension(config))
}
cleanFilesFromPreviousVersions(project)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,20 @@ import java.io.File
fun Settings.bootstrapRefreshVersionsCore(
artifactVersionKeyRules: List<String> = emptyList(),
versionsPropertiesFile: File = rootDir.resolve("versions.properties")
) {
): RefreshVersionsConfig {
require(settings.isBuildSrc.not()) {
"This bootstrap is only for the root project. For buildSrc, please call " +
"bootstrapRefreshVersionsCoreForBuildSrc() instead (Kotlin DSL)," +
"or RefreshVersionsCoreSetup.bootstrapForBuildSrc() if you're using Groovy DSL."
}
RefreshVersionsConfigHolder.initialize(
//TODO: use versionsPropertiesFile as key
val config = RefreshVersionsConfigHolder.initialize(
settings = settings,
artifactVersionKeyRules = artifactVersionKeyRules,
versionsPropertiesFile = versionsPropertiesFile
)
setupRefreshVersions(settings = settings)
setupRefreshVersions(config = config, settings = settings)
return config
}

/**
Expand Down Expand Up @@ -87,8 +89,8 @@ fun Settings.bootstrapRefreshVersionsCore(
*/
@JvmName("bootstrapForBuildSrc")
fun Settings.bootstrapRefreshVersionsCoreForBuildSrc() {
RefreshVersionsConfigHolder.initializeBuildSrc(this)
setupRefreshVersions(settings = settings)
val config = RefreshVersionsConfigHolder.initializeBuildSrc(this)
setupRefreshVersions(settings = settings, config = config)
}

/**
Expand All @@ -104,7 +106,7 @@ fun Settings.bootstrapRefreshVersionsCoreForBuildSrc() {
* This function also sets up the module for the Android and Fabric (Crashlytics) Gradle plugins, so you can avoid the
* buildscript classpath configuration boilerplate.
*/
private fun setupRefreshVersions(settings: Settings) {
private fun setupRefreshVersions(config: RefreshVersionsConfig, settings: Settings) {
val supportedGradleVersion = "6.3" // 6.2 fail with this error: https://gradle.com/s/shp7hbtd3i3ii
if (GradleVersion.current() < GradleVersion.version(supportedGradleVersion)) {
throw UnsupportedVersionException("""
Expand All @@ -113,22 +115,26 @@ private fun setupRefreshVersions(settings: Settings) {
""".trimIndent())
}


val versionsMap = RefreshVersionsConfigHolder.readVersionsMap()
val versionsMap = config.readVersionsMap()
@Suppress("unchecked_cast")
setupPluginsVersionsResolution(
config = config,
settings = settings,
properties = versionsMap
)

settings.gradle.setupVersionPlaceholdersResolving(versionsMap = versionsMap)
settings.gradle.setupVersionPlaceholdersResolving(
config = config,
versionsMap = versionsMap
)

settings.gradle.rootProject {
apply<RefreshVersionsCorePlugin>()
}
}

private fun setupPluginsVersionsResolution(
config: RefreshVersionsConfig,
settings: Settings,
properties: Map<String, String>
) {
Expand All @@ -152,14 +158,14 @@ private fun setupPluginsVersionsResolution(
when {
pluginNamespace.startsWith("com.android") -> {
val dependencyNotation = "com.android.tools.build:gradle:$version"
UsedPluginsHolder.noteUsedPluginDependency(
config.usedPlugins.noteUsedPluginDependency(
dependencyNotation = dependencyNotation,
repositories = repositories
)
useModule(dependencyNotation)
}
else -> {
UsedPluginsHolder.noteUsedPluginDependency(
config.usedPlugins.noteUsedPluginDependency(
dependencyNotation = "$pluginId:$pluginId.gradle.plugin:$version",
repositories = repositories
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package de.fayard.refreshVersions.core

import de.fayard.refreshVersions.core.internal.RefreshVersionsConfigHolder
import de.fayard.refreshVersions.core.internal.RefreshVersionsConfigHolder.settings
import de.fayard.refreshVersions.core.internal.*
import de.fayard.refreshVersions.core.internal.SettingsPluginsUpdater
import de.fayard.refreshVersions.core.internal.configureLintIfRunningOnAnAndroidProject
import de.fayard.refreshVersions.core.internal.legacy.LegacyBootstrapUpdater
Expand Down Expand Up @@ -56,16 +55,18 @@ open class RefreshVersionsTask : DefaultTask() {
// will reduce the number of repositories lookups, improving performance a little more.

runBlocking {
logger.lifecycle("${project.rootDir.name} task action")
val config = RefreshVersionsConfigHolder.getConfigForProject(project)
val lintUpdatingProblemsAsync = async {
configureLintIfRunningOnAnAndroidProject(settings, RefreshVersionsConfigHolder.readVersionsMap())
configureLintIfRunningOnAnAndroidProject(config.settings, config.readVersionsMap())
}
val result = lookupVersionCandidates(
httpClient = RefreshVersionsConfigHolder.httpClient,
httpClient = config.httpClient,
project = project,
versionMap = RefreshVersionsConfigHolder.readVersionsMap(),
versionKeyReader = RefreshVersionsConfigHolder.versionKeyReader
versionMap = config.readVersionsMap(),
versionKeyReader = config.versionKeyReader
)
VersionsPropertiesModel.writeWithNewVersions(result.dependenciesUpdates)
VersionsPropertiesModel.writeWithNewVersions(config, result.dependenciesUpdates)
SettingsPluginsUpdater.updateGradleSettingsWithAvailablePluginsUpdates(
rootProject = project,
settingsPluginsUpdates = result.settingsPluginsUpdates,
Expand All @@ -78,7 +79,7 @@ open class RefreshVersionsTask : DefaultTask() {
)
}

warnAboutHardcodedVersionsIfAny(result.dependenciesWithHardcodedVersions)
warnAboutHardcodedVersionsIfAny(config, result.dependenciesWithHardcodedVersions)
warnAboutDynamicVersionsIfAny(result.dependenciesWithDynamicVersions)
warnAboutGradleUpdateAvailableIfAny(result.gradleUpdates)
lintUpdatingProblemsAsync.await().forEach { problem ->
Expand Down Expand Up @@ -126,13 +127,13 @@ open class RefreshVersionsTask : DefaultTask() {
}
}

private fun warnAboutHardcodedVersionsIfAny(dependenciesWithHardcodedVersions: List<Dependency>) {
private fun warnAboutHardcodedVersionsIfAny(config: RefreshVersionsConfig, dependenciesWithHardcodedVersions: List<Dependency>) {
if (dependenciesWithHardcodedVersions.isNotEmpty()) {
//TODO: Suggest running a diagnosis task to list the hardcoded versions.
val warnFor = (dependenciesWithHardcodedVersions).take(3).map {
"${it.group}:${it.name}:${it.version}"
}
val versionsFileName = RefreshVersionsConfigHolder.versionsPropertiesFile.name
val versionsFileName = config.versionsPropertiesFile.name
logger.warn(
"""Found ${dependenciesWithHardcodedVersions.count()} hardcoded dependencies versions.
|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.fayard.refreshVersions.core

import de.fayard.refreshVersions.core.internal.RefreshVersionsConfig

open class VersionExtension(private val config: RefreshVersionsConfig) {
fun versionFor(versionKey: String): String {
// This function is overloaded to allow named parameter usage in Kotlin.
// However, no check is performed here because we cannot detect if
// the function wasn't called with named argument.
return retrieveVersionFor(config = config, dependencyNotationOrVersionKey = versionKey)
}

fun versionFor(dependencyNotation: CharSequence): String {
// This function is overloaded to allow named parameter usage in Kotlin.
// However, no check is performed here because we cannot detect if
// the function wasn't called with named argument.
return retrieveVersionFor(config = config, dependencyNotationOrVersionKey = dependencyNotation)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@

package de.fayard.refreshVersions.core

import de.fayard.refreshVersions.core.internal.RefreshVersionsConfig
import de.fayard.refreshVersions.core.internal.RefreshVersionsConfigHolder
import de.fayard.refreshVersions.core.internal.getVersionPropertyName
import de.fayard.refreshVersions.core.internal.resolveVersion
import org.gradle.api.Project

@Deprecated("use versions extension", ReplaceWith("versions.versionFor(versionKey)"), level = DeprecationLevel.ERROR)
fun versionFor(versionKey: String): String {
throw NotImplementedError("use versions.versionFor")
}

@Deprecated("use versions extension", ReplaceWith("versions.versionFor(dependencyNotation)"), level = DeprecationLevel.ERROR)
fun versionFor(dependencyNotation: CharSequence): String {
throw NotImplementedError("use versions.versionFor")
}

@Deprecated("use versions extension", ReplaceWith("versions.versionFor(versionKey)"))
fun Project.versionFor(versionKey: String): String {
// This function is overloaded to allow named parameter usage in Kotlin.
// However, no check is performed here because we cannot detect if
// the function wasn't called with named argument.
return retrieveVersionFor(dependencyNotationOrVersionKey = versionKey)
val config = RefreshVersionsConfigHolder.getConfigForProject(this)
return retrieveVersionFor(config = config, dependencyNotationOrVersionKey = versionKey)
}

fun versionFor(dependencyNotation: CharSequence): String {
@Deprecated("use versions extension", ReplaceWith("versions.versionFor(dependencyNotation)"))
fun Project.versionFor(dependencyNotation: CharSequence): String {
// This function is overloaded to allow named parameter usage in Kotlin.
// However, no check is performed here because we cannot detect if
// the function wasn't called with named argument.
return retrieveVersionFor(dependencyNotationOrVersionKey = dependencyNotation)
val config = RefreshVersionsConfigHolder.getConfigForProject(this)
return retrieveVersionFor(config = config, dependencyNotationOrVersionKey = dependencyNotation)
}

private fun retrieveVersionFor(dependencyNotationOrVersionKey: CharSequence): String {
internal fun retrieveVersionFor(config: RefreshVersionsConfig, dependencyNotationOrVersionKey: CharSequence): String {
val isDependencyNotation = ':' in dependencyNotationOrVersionKey
val versionKey = when {
isDependencyNotation -> {
Expand All @@ -36,7 +52,7 @@ private fun retrieveVersionFor(dependencyNotationOrVersionKey: CharSequence): St
group = it.substringBefore(':'),
name = it.substringBeforeLast(':').substringAfter(':')
),
versionKeyReader = RefreshVersionsConfigHolder.versionKeyReader
versionKeyReader = config.versionKeyReader
)
}
}
Expand All @@ -47,12 +63,12 @@ private fun retrieveVersionFor(dependencyNotationOrVersionKey: CharSequence): St
}
}
return resolveVersion(
properties = RefreshVersionsConfigHolder.lastlyReadVersionsMap,
properties = config.lastlyReadVersionsMap,
key = versionKey
) ?: resolveVersion(
properties = RefreshVersionsConfigHolder.readVersionsMap(),
properties = config.readVersionsMap(),
key = versionKey
) ?: RefreshVersionsConfigHolder.versionsPropertiesFile.name.let { versionsFileName ->
) ?: config.versionsPropertiesFile.name.let { versionsFileName ->
val errorMessage = when {
isDependencyNotation -> "The version of the artifact $dependencyNotationOrVersionKey requested in " +
"versionFor call wasn't found in the $versionsFileName file.\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ import org.gradle.api.initialization.Settings

@InternalRefreshVersionsApi
val Settings.isBuildSrc: Boolean get() = rootProject.name == "buildSrc"

@InternalRefreshVersionsApi
val Settings.isIncluded: Boolean get() = startParameter.projectDir == null
Loading