Add generateBuildSubstitutions task to bom projects. (#3653)

* Add generateBuildSubstitutions task to bom projects.

This is a convenience task that can be used to
generate a code snippet that can be copy-pasted
for use in composite builds.

* Fix typo in filter.

* Add convenience task to root project.

* Add documentation for how to use composite builds.

* Add one more link to gradle docs.

* Wrong variable name.

* Update CONTRIBUTING.md

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Christian Neumüller 2021-09-26 22:38:09 +02:00 committed by GitHub
parent adaf239605
commit a10672bbad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 47 deletions

View File

@ -141,7 +141,7 @@ It does not support all required rules, so you still have to run `spotlessApply`
* Unit tests target Java 8, so language features such as lambda and streams can be used in tests.
## Common tasks
## Specific tasks
### Updating OTLP proto dependency version
@ -151,3 +151,47 @@ The OTLP proto dependency version is defined [here](proto/build.gradle). To bump
2. Download the zip source code archive
3. Run `shasum -a 256 ~/path/to/downloaded.zip` to compute its checksum
4. Update `protoVersion` and `protoChecksum` in the build file with the new version and checksum
### Composing builds
Beware that this section is only meant for developers of opentelemetry-java, or closely related projects.
The steps described here could change at any time and what you do for one version (commit) may break
with the next one already.
Gradle provides a feature called ["composite builds"](https://docs.gradle.org/current/userguide/composite_builds.html)
that allows to replace some normally externally provided dependencies with a project that is built
(included) in the same Gradle invocation. This can be useful to quickly test a new feature or bug fix you are
developing in opentelemetry-java with the examples or the app or instrumentation library where you
need the feature or run into the bug. Unfortunately, opentelemetry-java does not work out of the box
with this feature because Gradle is unable to map the project names to the customized artifact
coordinates (see e.g. [gradle/gradle#18291](https://github.com/gradle/gradle/issues/18291)
and related issues. However, gradle supports manually declaring the mapping between ("substitution of")
artifact coordinates and project names. To ease this tedious task, opentelemetry-java provides a
gradle task `:generateBuildSubstitutions` that generates a code snippet with these substitutions in
kts (Kotlin Script) format.
Example usage could be as follows:
1. Run `./gradlew generateBuildSubstitutions`
2. Two files named `build/substitutions.gradle.kts` are generated in the bom and bom-alpha project's
directory, containing substitutions for the stable and alpha projects respectively.
3. Copy & paste the content of these files to a new `settings.gradle.kts` or the one where you want
to include the opentelemetry build into, so that it contains something like the following:
```kotlin
includeBuild("PATH/TO/OPENTELEMETRY-JAVA/ROOT/DIRECTORY") {
// Copy & paste following block from the generated substitutions.gradle.kts, *not* from here!
dependencySubstitution {
substitute(module("io.opentelemetry:opentelemetry-api")).using(project(":api:all"))
substitute(module("io.opentelemetry:opentelemetry-sdk")).using(project(":sdk:all"))
// ...
}
}
```
See [the Gradle documentation](https://docs.gradle.org/current/userguide/composite_builds.html#included_build_declaring_substitutions)
for more information.
4. If you now build your project, it will use the included build to supply the opentelemetry-java artifacts,
ignoring any version declarations. Use the prefix `:DIRECTORY:` to refer to tasks/projects within
the included build, where DIRECTORY is the name of the directory in the included build (only the part after the last `/`).

View File

@ -171,6 +171,12 @@ Libraries will usually only need `opentelemetry-api`, while applications
will want to use the `opentelemetry-sdk` module which contains our standard implementation
of the APIs.
## Gradle composite builds
For opentelemetry-java developers that need to test the latest source code with another
project, composite builds can be used as an alternative to `publishToMavenLocal`. This
requires some setup which is explained [here](CONTRIBUTING.md#composing-builds).
## Releases
See the [VERSIONING.md](VERSIONING.md) document for our policies for releases and compatibility

View File

@ -1,30 +1,9 @@
plugins {
id("java-platform")
id("otel.publish-conventions")
id("otel.bom-conventions")
}
description = "OpenTelemetry Bill of Materials (Alpha)"
group = "io.opentelemetry"
base.archivesBaseName = "opentelemetry-bom-alpha"
rootProject.subprojects.forEach { subproject ->
if (!project.name.startsWith("bom")) {
evaluationDependsOn(subproject.path)
}
}
afterEvaluate {
dependencies {
constraints {
rootProject.subprojects
.sortedBy { it.findProperty("archivesBaseName") as String? }
.filter { !it.name.startsWith("bom") }
.filter { it.findProperty("otel.release") == "alpha" }
.forEach { project ->
project.plugins.withId("maven-publish") {
api(project)
}
}
}
}
}
otelBom.projectFilter.set { it.findProperty("otel.release") == "alpha" }

View File

@ -1,30 +1,9 @@
plugins {
id("java-platform")
id("otel.publish-conventions")
id("otel.bom-conventions")
}
description = "OpenTelemetry Bill of Materials"
group = "io.opentelemetry"
base.archivesBaseName = "opentelemetry-bom"
rootProject.subprojects.forEach { subproject ->
if (project != subproject) {
evaluationDependsOn(subproject.path)
}
}
afterEvaluate {
dependencies {
constraints {
rootProject.subprojects
.sortedBy { it.findProperty("archivesBaseName") as String? }
.filter { !it.name.startsWith("bom") }
.filter { !it.hasProperty("otel.release") }
.forEach { project ->
project.plugins.withId("maven-publish") {
api(project)
}
}
}
}
}
otelBom.projectFilter.set { !it.hasProperty("otel.release") }

View File

@ -59,6 +59,12 @@ nexusPublishing {
}
}
// The BOM projects register dependent tasks that actually do the generating.
tasks.register("generateBuildSubstitutions") {
group = "publishing"
description = "Generate a code snippet that can be copy-pasted for use in composite builds."
}
subprojects {
group = "io.opentelemetry"
}

View File

@ -0,0 +1,14 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.gradle
import org.gradle.api.Project
import org.gradle.api.provider.Property
import java.util.function.Predicate
abstract class OtelBomExtension {
abstract val projectFilter: Property<Predicate<Project>>
}

View File

@ -0,0 +1,64 @@
import io.opentelemetry.gradle.OtelBomExtension
import org.gradle.kotlin.dsl.create
plugins {
id("otel.publish-conventions")
id("java-platform")
}
if (!project.name.startsWith("bom")) {
throw IllegalStateException("Name of BOM projects must start with 'bom'.")
}
rootProject.subprojects.forEach { subproject ->
if (!subproject.name.startsWith("bom")) {
evaluationDependsOn(subproject.path)
}
}
val otelBom = extensions.create<OtelBomExtension>("otelBom")
val generateBuildSubstitutions by tasks.registering {
group = "publishing"
description = "Generate a code snippet that can be copy-pasted for use in composite builds."
}
tasks.named("publish") {
dependsOn(generateBuildSubstitutions)
}
rootProject.tasks.named(generateBuildSubstitutions.name) {
dependsOn(generateBuildSubstitutions)
}
afterEvaluate {
otelBom.projectFilter.finalizeValue()
val bomProjects = rootProject.subprojects
.sortedBy { it.findProperty("archivesBaseName") as String? }
.filter { !it.name.startsWith("bom") }
.filter(otelBom.projectFilter.get()::test)
.filter { it.plugins.hasPlugin("maven-publish") }
generateBuildSubstitutions {
val outputFile = File(buildDir, "substitutions.gradle.kts")
outputs.file(outputFile)
val substitutionSnippet = bomProjects.joinToString(
separator = "\n",
prefix = "dependencySubstitution {\n",
postfix = "\n}\n"
) { project ->
val publication = project.publishing.publications.getByName("mavenPublication") as MavenPublication
" substitute(module(\"${publication.groupId}:${publication.artifactId}\")).using(project(\"${project.path}\"))"
}
inputs.property("projectPathsAndArtifactCoordinates", substitutionSnippet)
doFirst {
outputFile.writeText(substitutionSnippet)
}
}
bomProjects.forEach { project ->
dependencies {
constraints {
api(project)
}
}
}
}