Skip to content

Commit

Permalink
Generate the projectversions.txt file.
Browse files Browse the repository at this point in the history
  • Loading branch information
baron1405 committed Dec 1, 2024
1 parent 643ba13 commit c9be757
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 17 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

### Added

- The plugin now generates the text file `build/projectversion.txt` containing the
complete semantic version number of the plugin.

## [2.0.0] - 2024-10-26

- The plugin has been migrated from JSR 305 to [JSpecify](https://jspecify.dev/) for `null` checking
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ enforces the following:
* The project version is an instance of the ProjectVersion class
* Release builds do not depend on snapshot versions of C Thing Software artifacts

When applied to the root project, the plugin generates the file `build/projectversion.txt`
containing the complete semantic version of the project.

The plugin provides the `version` task, which displays the complete semantic version of the
project on the standard output.

## Usage

The plugin is available from the
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ buildscript {
}
}

version = ProjectVersion("2.0.1", BuildType.snapshot)
version = ProjectVersion("3.0.0", BuildType.snapshot)
group = "org.cthing"
description = "A Gradle plugin that establishes the versioning scheme for C Thing Software projects."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

package org.cthing.gradle.plugins.versioning;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

Expand All @@ -14,6 +20,8 @@
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.tasks.TaskExecutionException;


/**
Expand All @@ -22,6 +30,7 @@
* <pre>
* version = ProjectVersion("1.2.3", BuildType.snapshot)
* </pre>
*
* <p>
* The plugin also enforces that release builds only depend on release versions of C Thing Software
* artifacts.
Expand All @@ -31,13 +40,20 @@ public class VersioningPlugin implements Plugin<Project> {

public static final String VERSION_TASK_NAME = "version";

private static final String VERSION_FILE_TASK_NAME = "projectVersionFile";
private static final String VERSION_FILE_TASK_PATH = ":" + VERSION_FILE_TASK_NAME;
private static final Pattern SNAPHOT_VERSION_PATTERN = Pattern.compile(".*(?:\\+|-SNAPSHOT|-\\d+)$");
private static final String PROJECT_VERSION_FILENAME = "projectversion.txt";
private static final Set<String> CTHING_GROUPS = Set.of("com.cthing", "org.cthing");
private static final Set<String> BUILD_CONFIGS = Set.of("api",
"compileOnly",
"compileOnlyApi",
"implementation",
"runtimeOnly");
private static final List<String> BUILD_RELATED_FILES = List.of("gradle.properties",
"settings.gradle",
"settings.gradle.kts",
"gradle/libs.versions.toml");

@Override
public void apply(final Project project) {
Expand All @@ -52,11 +68,11 @@ public void apply(final Project project) {
// Validate that a release build only depends on release build internal artifacts.
validateReleaseDependencies(project);

project.getTasks().register(VERSION_TASK_NAME, task -> {
task.setGroup("Help");
task.setDescription("Display project version number");
task.doFirst(t -> System.out.println(project.getVersion()));
});
// Create the task that writes the version file.
createVersionFileTask(project);

// Create the task that displays the version.
createVersionTask(project);
}
}

Expand All @@ -76,9 +92,8 @@ private void validateReleaseDependencies(final Project project) {
final String group = dep.getGroup();
if (group != null && CTHING_GROUPS.contains(group)
&& (version == null || SNAPHOT_VERSION_PATTERN.matcher(version).matches())) {
proj.getLogger().error(
"Release build depends on snapshot artifact {}:{}:{} ({})",
group, dep.getName(), version, config.getName());
proj.getLogger().error("Release build depends on snapshot artifact {}:{}:{} ({})",
group, dep.getName(), version, config.getName());
throw new GradleException("Release build depends on snapshot artifacts");
}
}
Expand All @@ -88,4 +103,77 @@ private void validateReleaseDependencies(final Project project) {
}
});
}

/**
* Create the task that writes the version file.
*
* @param project Project whose version is to be written
*/
private void createVersionFileTask(final Project project) {
// Only write the file if this is the root project and "clean" is not the only task.
if (project.equals(project.getRootProject()) && isNotCleanOnly(project)) {
project.getTasks().register(VERSION_FILE_TASK_NAME, task -> {
final File buildDir = project.getLayout().getBuildDirectory().get().getAsFile();
final File projectVersionFile = new File(buildDir, PROJECT_VERSION_FILENAME);

// The project version file is the output
task.getOutputs().file(projectVersionFile);

// Build related files are the inputs.
project.getAllprojects().forEach(proj -> task.getInputs().file(proj.getBuildFile()));
BUILD_RELATED_FILES.forEach(filename -> {
final File file = project.file(filename);
if (file.exists()) {
task.getInputs().file(file);
}
});

// If "clean" is one of the tasks, generate the project version file after it has run
if (project.getTasks().findByName(BasePlugin.CLEAN_TASK_NAME) != null) {
task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME);
}

task.doLast(t -> {
try {
Files.writeString(projectVersionFile.toPath(),
project.getRootProject().getVersion().toString(),
StandardCharsets.UTF_8);
} catch (final IOException ex) {
throw new TaskExecutionException(task, ex);
}
});
});

// Ensure that the task is always considered for execution.
final List<String> taskNames = new ArrayList<>(project.getGradle().getStartParameter().getTaskNames());
taskNames.add(VERSION_FILE_TASK_PATH);
project.getGradle().getStartParameter().setTaskNames(taskNames);
}
}

/**
* Creates the task to display the project version.
*
* @param project Project whose version is to be displayed
*/
private void createVersionTask(final Project project) {
project.getTasks().register(VERSION_TASK_NAME, task -> {
task.setGroup("Help");
task.setDescription("Display project version number");
task.doFirst(t -> System.out.println(project.getVersion()));
});
}

/**
* Indicates whether the build will run more than just the 'clean' task.
*
* @param project Root project to test for tasks
* @return {@code true} if the there are no tasks specified (i.e. use default tasks) or the tasks include more
* than just "clean".
*/
private static boolean isNotCleanOnly(final Project project) {
final List<String> taskNames = project.getGradle().getStartParameter().getTaskNames();
return taskNames.isEmpty()
|| taskNames.stream().anyMatch(name -> !BasePlugin.CLEAN_TASK_NAME.equals(name));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public void testApply(@TempDir final File projectDir) {
final Project project = ProjectBuilder.builder().withName("testProject").withProjectDir(projectDir).build();
project.getPluginManager().apply("org.cthing.cthing-versioning");

final Task versionFileTask = project.getTasks().findByName("projectVersionFile");
assertThat(versionFileTask).isNotNull();

final Task versionTask = project.getTasks().findByName("version");
assertThat(versionTask).isNotNull();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class PluginIntegTest {
public static Stream<Arguments> gradleVersionProvider() {
return Stream.of(
arguments("8.0"),
arguments("8.10.2")
arguments("8.11.1")
);
}

Expand Down Expand Up @@ -72,11 +72,12 @@ public void testGoodVersion(final String gradleVersion) throws IOException {
version = ProjectVersion("1.2.3", BuildType.snapshot)
""");

final BuildResult result = createGradleRunner(gradleVersion).build();
final BuildResult result = createGradleRunner(gradleVersion, "version").build();
final BuildTask versionTask = result.task(":version");
assertThat(versionTask).isNotNull();
assertThat(versionTask.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("1.2.3");
assertThat(this.projectDir.resolve("build/projectversion.txt")).exists();
}

@ParameterizedTest
Expand All @@ -91,8 +92,9 @@ public void testStringVersion(final String gradleVersion) throws IOException {
version = "1.2.3"
""");

final UnexpectedBuildFailure exception = catchThrowableOfType(UnexpectedBuildFailure.class,
() -> createGradleRunner(gradleVersion).build());
final UnexpectedBuildFailure exception =
catchThrowableOfType(UnexpectedBuildFailure.class,
() -> createGradleRunner(gradleVersion, "version").build());
final BuildResult result = exception.getBuildResult();
assertThat(result.getOutput()).contains("Version is not an instance of org.cthing.projectversion.ProjectVersion");
}
Expand Down Expand Up @@ -130,16 +132,88 @@ public void testRelaseWithSnapshots(final String gradleVersion) throws IOExcepti
}
""");

final UnexpectedBuildFailure exception = catchThrowableOfType(UnexpectedBuildFailure.class,
() -> createGradleRunner(gradleVersion).build());
final UnexpectedBuildFailure exception =
catchThrowableOfType(UnexpectedBuildFailure.class,
() -> createGradleRunner(gradleVersion, "version").build());
final BuildResult result = exception.getBuildResult();
assertThat(result.getOutput()).contains("Release build depends on snapshot artifact org.cthing:versionparser:4.+ (implementation)");
}

private GradleRunner createGradleRunner(final String gradleVersion) {
@ParameterizedTest
@MethodSource("gradleVersionProvider")
public void testVersionFile(final String gradleVersion) throws IOException {
Files.writeString(this.projectDir.resolve("settings.gradle.kts"), "rootProject.name=\"test\"");
Files.writeString(this.projectDir.resolve("build.gradle.kts"), """
import org.cthing.projectversion.BuildType
import org.cthing.projectversion.ProjectVersion
repositories {
mavenCentral()
}
plugins {
id("org.cthing.cthing-versioning")
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.cthing:cthing-projectversion:1.0.0")
}
}
version = ProjectVersion("1.2.3", BuildType.snapshot)
""");

final BuildResult result = createGradleRunner(gradleVersion, "projectVersionFile").build();
final BuildTask versionFileTask = result.task(":projectVersionFile");
assertThat(versionFileTask).isNotNull();
assertThat(versionFileTask.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.projectDir.resolve("build/projectversion.txt")).exists();
}

@ParameterizedTest
@MethodSource("gradleVersionProvider")
public void testVersionFileCleanOnly(final String gradleVersion) throws IOException {
Files.writeString(this.projectDir.resolve("settings.gradle.kts"), "rootProject.name=\"test\"");
Files.writeString(this.projectDir.resolve("build.gradle.kts"), """
import org.cthing.projectversion.BuildType
import org.cthing.projectversion.ProjectVersion
repositories {
mavenCentral()
}
plugins {
id("java")
id("org.cthing.cthing-versioning")
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.cthing:cthing-projectversion:1.0.0")
}
}
version = ProjectVersion("1.2.3", BuildType.snapshot)
""");

final BuildResult result = createGradleRunner(gradleVersion, "clean").build();
final BuildTask cleanTask = result.task(":clean");
assertThat(cleanTask).isNotNull();
assertThat(cleanTask.getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
assertThat(this.projectDir.resolve("build/projectversion.txt")).doesNotExist();
}

private GradleRunner createGradleRunner(final String gradleVersion, final String... arguments) {
return GradleRunner.create()
.withProjectDir(this.projectDir.toFile())
.withArguments("version")
.withArguments(arguments)
.withPluginClasspath()
.withEnvironment(Map.of("CTHING_CI", "true"))
.withGradleVersion(gradleVersion);
Expand Down

0 comments on commit c9be757

Please sign in to comment.