diff --git a/build.gradle b/build.gradle index 0c9b4e7..9c47342 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ plugins { id 'idea' id 'eclipse' id 'maven-publish' - id 'net.minecraftforge.licenser' version '1.1.1' - id 'net.minecraftforge.gradleutils' + id 'net.minecraftforge.licenser' version '1.2.0' + id 'net.minecraftforge.gradleutils' version '2.5.1' id 'com.gradle.plugin-publish' version '1.3.1' id 'com.gradleup.shadow' version '8.3.6' } @@ -19,7 +19,41 @@ version = gitversion.tagOffset println "Version: $version" -apply from: 'build_shared.gradle' +java { + // GitVersion requires Java 17 + toolchain.languageVersion = JavaLanguageVersion.of(17) + withSourcesJar() +} + +tasks.withType(GroovyCompile).configureEach { + groovyOptions.optimizationOptions.indy = true +} + +repositories { + maven gradleutils.forgeMaven + maven { url = 'https://repo.eclipse.org/content/groups/releases/' } + mavenCentral() + mavenLocal() +} + +configurations { + implementation { + exclude group: 'org.slf4j', module: 'slf4j-api' + } +} + +dependencies { + compileOnly libs.groovy + + // GitHub Actions Workflows + implementation libs.yaml + + // Git Version + implementation libs.gitver + + // TODO - Deprecated git utilities, remove in 3.0 + implementation libs.jgit +} license { header = file('LICENSE-header.txt') @@ -76,9 +110,10 @@ gradlePlugin { publishing { publications.register('pluginMaven', MavenPublication) { - changelog.publish(it) + artifactId = project.name + changelog.publish it + pom { pom -> - artifactId = project.name name = projectDisplayName description = project.description diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index 34fe153..0000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id 'java-gradle-plugin' -} - -apply from: '../build_shared.gradle' - -sourceSets.main.groovy.srcDirs = ['../src/main/groovy'] - -gradlePlugin { - plugins { - gradleutils { - id = 'net.minecraftforge.gradleutils' - implementationClass = 'net.minecraftforge.gradleutils.GradleUtilsPlugin' - } - } -} diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle deleted file mode 100644 index 7a9066a..0000000 --- a/buildSrc/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -apply from: '../settings_shared.gradle' diff --git a/build_shared.gradle b/build_shared.gradle deleted file mode 100644 index 8316737..0000000 --- a/build_shared.gradle +++ /dev/null @@ -1,33 +0,0 @@ -java { - // GitVersion requires Java 17 - toolchain.languageVersion = JavaLanguageVersion.of(17) - withSourcesJar() -} - -tasks.withType(GroovyCompile).configureEach { - groovyOptions.optimizationOptions.indy = true -} - -repositories { - maven { url = 'https://maven.minecraftforge.net' } - maven { url = 'https://repo.eclipse.org/content/groups/releases/' } - mavenCentral() - mavenLocal() -} - -configurations { - implementation { - exclude group: 'org.slf4j', module: 'slf4j-api' - } -} - -dependencies { - // GitHub Actions Workflows - implementation libs.yaml - - // Git Version - implementation libs.gitver - - // TODO - Deprecated git utilities, remove in 3.0 - implementation libs.jgit -} diff --git a/gradle.properties b/gradle.properties index 324bf4a..48374b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,5 +5,5 @@ org.gradle.configureondemand=true # TODO [GradleUtils][Gradle9] Re-enable config cache in Gradle 9 # Configuration Cache comes with too many bugs to be worth the trouble. # Do continue to make our Gradle plugins (GU, FG7, etc.) support it though. -org.gradle.configuration-cache=false -org.gradle.configuration-cache.parallel=false +#org.gradle.configuration-cache=true +#org.gradle.configuration-cache.parallel=true diff --git a/settings.gradle b/settings.gradle index 0de5df6..aa25800 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,20 @@ plugins { rootProject.name = 'gradleutils' -apply from: 'settings_shared.gradle' +dependencyResolutionManagement { + versionCatalogs { + libs { + // Included with Gradle API, but manually declared to view sources and JavaDoc links + library 'groovy', 'org.codehaus.groovy', 'groovy-all' version '3.0.22' + + // GitHub Actions Workflows + library('yaml', 'org.yaml', 'snakeyaml').version('2.4') + + // Git Version + library('gitver', 'net.minecraftforge', 'git-version').version('0.4.2') + + // TODO - Deprecated git utilities, remove in 3.0 + library('jgit', 'org.eclipse.jgit', 'org.eclipse.jgit').version('7.2.0.202503040940-r') + } + } +} diff --git a/settings_shared.gradle b/settings_shared.gradle deleted file mode 100644 index e2e50e0..0000000 --- a/settings_shared.gradle +++ /dev/null @@ -1,14 +0,0 @@ -dependencyResolutionManagement { - versionCatalogs { - libs { - // GitHub Actions Workflows - library('yaml', 'org.yaml', 'snakeyaml').version('2.4') - - // Git Version - library('gitver', 'net.minecraftforge', 'git-version').version('0.4.2') - - // TODO - Deprecated git utilities, remove in 3.0 - library('jgit', 'org.eclipse.jgit', 'org.eclipse.jgit').version('7.2.0.202503040940-r') - } - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/ConfigureTeamCity.groovy b/src/main/groovy/net/minecraftforge/gradleutils/ConfigureTeamCity.groovy index 9a5cce2..b710d56 100644 --- a/src/main/groovy/net/minecraftforge/gradleutils/ConfigureTeamCity.groovy +++ b/src/main/groovy/net/minecraftforge/gradleutils/ConfigureTeamCity.groovy @@ -5,6 +5,8 @@ package net.minecraftforge.gradleutils import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import groovy.transform.PackageScopeTarget import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.provider.Property @@ -15,31 +17,18 @@ import org.gradle.api.tasks.TaskProvider import javax.inject.Inject -/** - * This task prints the marker lines into the log which configure the pipeline. - * - * @deprecated Will be removed once Forge moves off of TeamCity. - */ +// TODO [GradleUtils][TeamCity] Delete this when off of TeamCity @CompileStatic -@Deprecated(forRemoval = true) +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.FIELDS]) abstract class ConfigureTeamCity extends DefaultTask { - public static final String NAME = 'configureTeamCity' - - static TaskProvider register(Project project) { - register(project, NAME) - } - - static TaskProvider register(Project project, String name) { - project.tasks.register(name, ConfigureTeamCity) - } - - @Inject abstract ProviderFactory getProviders() + static final String NAME = 'configureTeamCity' - ConfigureTeamCity() { + @Inject + ConfigureTeamCity(ProviderFactory providers) { this.description = 'Prints the marker lines into the log which configure the pipeline. [deprecated]' - this.onlyIf('Only runs on TeamCity, so the TEAMCITY_VERSION environment variable must be set.') { GradleUtils.hasEnvVar('TEAMCITY_VERSION', this.providers).get() } + this.onlyIf('Only runs on TeamCity, so the TEAMCITY_VERSION environment variable must be set.') { providers.environmentVariable('TEAMCITY_VERSION').present } - this.buildNumber.convention this.providers.provider { this.project.version?.toString() } + this.buildNumber.convention providers.provider { this.project.version?.toString() } } /** The build number to print, usually the project version. */ diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GenerateActionsWorkflow.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GenerateActionsWorkflow.groovy index ea588b2..853b970 100644 --- a/src/main/groovy/net/minecraftforge/gradleutils/GenerateActionsWorkflow.groovy +++ b/src/main/groovy/net/minecraftforge/gradleutils/GenerateActionsWorkflow.groovy @@ -6,9 +6,9 @@ package net.minecraftforge.gradleutils import groovy.transform.CompileStatic import groovy.transform.PackageScope +import groovy.transform.PackageScopeTarget import net.minecraftforge.gradleutils.gitversion.GitVersionExtension import org.gradle.api.DefaultTask -import org.gradle.api.Project import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -17,52 +17,50 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.TaskProvider import org.yaml.snakeyaml.DumperOptions import org.yaml.snakeyaml.Yaml import javax.inject.Inject +import java.nio.charset.StandardCharsets +import java.nio.file.Files /** * This task generates the GitHub Actions workflow file for the project, respecting declared subprojects in Git Version. *

This can be very useful when creating new projects or subprojects.

*/ @CompileStatic +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.FIELDS]) abstract class GenerateActionsWorkflow extends DefaultTask { - public static final String NAME = 'generateActionsWorkflow' + static final String NAME = 'generateActionsWorkflow' - @PackageScope static TaskProvider register(Project project) { - project.tasks.register(NAME, GenerateActionsWorkflow) - } - - @Inject abstract ProviderFactory getProviders() - - GenerateActionsWorkflow() { + @Inject + GenerateActionsWorkflow(ProviderFactory providers) { + this.group = 'Build Setup' this.description = 'Generates the GitHub Actions workflow file for the project, respecting declared subprojects in Git Version.' - this.outputFile.convention this.project.rootProject.layout.projectDirectory.file(this.providers.provider { "build_${this.project.name}.yaml" }) - - this.projectName.convention this.providers.provider { this.project.name } - this.branch.convention this.providers.provider { this.project.extensions.getByType(GitVersionExtension).info.branch } + this.projectName.convention providers.provider { this.project.name } + this.branch.convention providers.provider { this.project.extensions.getByType(GitVersionExtension).info.branch } this.localPath.convention this.project.extensions.getByType(GitVersionExtension).projectPath - this.paths.convention this.providers.provider { this.project.extensions.getByType(GitVersionExtension).subprojectPaths.get().collect { "!${it}/**".toString() } } + this.paths.convention providers.provider { this.project.extensions.getByType(GitVersionExtension).subprojectPaths.get().collect { "!${it}/**".toString() } } this.gradleJavaVersion.convention 21 this.sharedActionsBranch.convention 'v0' + + this.outputFile.convention this.project.rootProject.layout.projectDirectory.dir('.github/workflows').file(providers.provider { "build_${this.projectName.get()}.yaml" }) } abstract @OutputFile RegularFileProperty getOutputFile() abstract @Input Property getProjectName() - abstract @Input @Optional Property getBranch() - abstract @Input @Optional Property getLocalPath() - abstract @Input @Optional ListProperty getPaths() + abstract @Optional @Input Property getBranch() + abstract @Optional @Input Property getLocalPath() + abstract @Optional @Input ListProperty getPaths() abstract @Input Property getGradleJavaVersion() abstract @Input Property getSharedActionsBranch() @TaskAction void exec() throws IOException { var localPath = this.localPath.orNull - var paths = this.paths.getOrElse(Collections.emptyList()) + var paths = this.paths.getOrElse Collections.emptyList() var push = [ 'branches': this.branch.getOrElse('master'), @@ -105,9 +103,13 @@ abstract class GenerateActionsWorkflow extends DefaultTask { ).dump(yaml).replace("'on':", 'on:') var file = outputFile.asFile.get() - if (!file.parentFile.exists()) - file.parentFile.mkdirs() - - file.setText(workflow, 'UTF8') + if (!file.parentFile.exists() && !file.parentFile.mkdirs()) + throw new IllegalStateException() + + Files.writeString( + file.toPath(), + workflow, + StandardCharsets.UTF_8 + ) } } diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtils.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtils.groovy deleted file mode 100644 index 5dbc48c..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtils.groovy +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils - -import groovy.transform.CompileDynamic -import groovy.transform.CompileStatic -import net.minecraftforge.gitver.api.GitVersion -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.api.errors.GitAPIException -import org.eclipse.jgit.lib.Config -import org.eclipse.jgit.storage.file.FileBasedConfig -import org.eclipse.jgit.transport.RemoteConfig -import org.eclipse.jgit.util.FS -import org.eclipse.jgit.util.SystemReader -import org.gradle.api.Action -import org.gradle.api.Project -import org.gradle.api.artifacts.repositories.MavenArtifactRepository -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.authentication.http.BasicAuthentication -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.Nullable - -/** - * Utility methods, usually for GradleUtils itself and is often delegated to from the - * {@linkplain GradleUtilsExtension extension}. - * - * @deprecated In GradleUtils 3.0, this class will be hidden as {@linkplain groovy.transform.PackageScope package-private}. - * @see GradleUtilsExtension - */ -@CompileStatic -@ApiStatus.Internal -class GradleUtils { - static void ensureAfterEvaluate(Project project, Action action) { - if (project.state.executed) - action.execute(project) - else - project.afterEvaluate(action) - } - - static Provider getEnvVar(String name, ProviderFactory providers) { - providers.of(GradleUtilsSources.EnvVar) { it.parameters.variableName.set name } - } - - static Provider hasEnvVar(String name, ProviderFactory providers) { - providers.of(GradleUtilsSources.HasEnvVar) { it.parameters.variableName.set name } - } - - //@formatter:off - @CompileDynamic - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - private static void initDynamic() { String.metaClass.rsplit = GradleUtils.&rsplit } - static { initDynamic() } - //@formatter:on - - private static boolean rsplitDeprecationLogged - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static @Nullable List rsplit(@Nullable String input, String del, int limit = -1) { - if (!rsplitDeprecationLogged) { - println 'WARNING: Usage of GradleUtils.rsplit is DEPRECATED and will be removed in GradleUtils 3.0!' - rsplitDeprecationLogged = true - } - - rsplitInternal(input, del, limit) - } - - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - private static @Nullable List rsplitInternal(@Nullable String input, String del, int limit = -1) { - if (input === null) return null - List lst = [] - int x = 0 - int idx - String tmp = input - while ((idx = tmp.lastIndexOf(del)) !== -1 && (limit === -1 || x++ < limit)) { - lst.add(0, tmp.substring(idx + del.length(), tmp.length())) - tmp = tmp.substring(0, idx) - } - lst.add(0, tmp) - return lst - } - - /** @deprecated Use {@link GitVersion#disableSystemConfig() */ - @Deprecated(forRemoval = true, since = '2.4') - @CompileStatic - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static class DisableSystemConfig extends SystemReader.Delegate { - final SystemReader parent - - DisableSystemConfig(SystemReader parent) { - super(parent) - this.parent = parent - - println 'WARNING: Usage of GradleUtils.DisableSystemConfig is DEPRECATED and will be removed in GradleUtils 3.0! Consider using GitVersion.disableSystemConfig() instead.' - } - - @Override - FileBasedConfig openSystemConfig(Config parent, FS fs) { - new FileBasedConfig(parent, null, fs) { - @Override void load() {} - - @Override boolean isOutdated() { false } - } - } - } - - private static boolean gitInfoDeprecationLogged - /** @deprecated Use {@link GitVersion#getInfo()} via {@link net.minecraftforge.gradleutils.gitversion.GitVersionExtension#getVersion() GitVersionExtension.getVersion()} */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static Map gitInfo(File dir, String... globFilters) { - if (!gitInfoDeprecationLogged) { - println 'WARNING: Usage of GradleUtils.gitInfo(File, String...) is DEPRECATED and will be removed in GradleUtils 3.0! Consider using GitVersion.disableSystemConfig() instead.' - gitInfoDeprecationLogged = true - } - - try (var version = GitVersion.builder().project(dir).strict(false).build()) { - [ - dir : version.gitDir.absolutePath, - tag : version.info.tag, - offset : version.info.offset, - hash : version.info.hash, - branch : version.info.branch, - commit : version.info.commit, - abbreviatedId: version.info.abbreviatedId, - url : version.info.url - ].tap { it.removeAll { it.value == null } } - } - } - - /** - * Get a configuring action to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * publishing block. - * - * Important: The following environment variables must be set for this to work: - *
    - *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • - *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • - *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • - *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • - *
- * - * @param project The project to setup publishing for - * @param defaultFolder The default folder if the required maven information is not set - * @return The action - */ - static Action getPublishingForgeMaven(Project project, File defaultFolder = project.rootProject.file('repo')) { - setupSnapshotCompatiblePublishing(project, 'https://maven.minecraftforge.net/', defaultFolder) - } - - /** - * Get a configuring action to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * publishing block. This action respects the current project's version, with regards to publishing to a release or - * snapshot repository. - *

- * Important: The following environment variables must be set for this to work: - *

    - *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • - *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • - *
- *

- * The following environment variables are optional: - *

    - *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • - *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • - *
- *

- * If the {@code MAVEN_URL_RELEASE} variable is not set, the passed in fallback URL will be used for the release - * repository (by default, this is {@code https://maven.minecraftforge.net/}). This is done to preserve backwards - * compatibility with the old {@link #getPublishingForgeMaven(Project, File)} method. - * - * @param project The project to setup publishing for - * @param defaultFolder The default folder if the required maven information is not set - * @return The action - */ - static Action setupSnapshotCompatiblePublishing(Project project, String fallbackPublishingEndpoint = 'https://maven.minecraftforge.net/', File defaultFolder = project.rootProject.file('repo'), File defaultSnapshotFolder = project.rootProject.file('snapshots')) { - { MavenArtifactRepository repo -> - repo.name = 'forge' - - if (System.getenv('MAVEN_USER') && System.getenv('MAVEN_PASSWORD')) { - String publishingEndpoint = fallbackPublishingEndpoint - if (System.getenv('MAVEN_URL_RELEASE')) { - publishingEndpoint = System.getenv('MAVEN_URL_RELEASE') - } - - if (project.version.toString().endsWith('-SNAPSHOT') && System.getenv('MAVEN_URL_SNAPSHOTS')) { - repo.url = System.getenv('MAVEN_URL_SNAPSHOTS') - } else { - repo.url = publishingEndpoint - } - repo.authentication { auth -> - auth.create('basic', BasicAuthentication) - } - repo.credentials { creds -> - creds.username = System.getenv('MAVEN_USER') - creds.password = System.getenv('MAVEN_PASSWORD') - } - } else { - if (project.version.toString().endsWith('-SNAPSHOT')) { - repo.url = 'file://' + defaultSnapshotFolder.absolutePath - } else { - repo.url = 'file://' + defaultFolder.absolutePath - } - } - } - } - - /** - * Get a configuring action for the Forge maven to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * repositories block. - * - * @return The action - */ - static Closure getForgeMaven() { - { MavenArtifactRepository repo -> - repo.name = 'MinecraftForge' - repo.url = 'https://maven.minecraftforge.net/' - } - } - - /** - * Get a configuring action for the Forge releases maven to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * repositories block. - * - * @return The action - */ - static Closure getForgeReleaseMaven() { - { MavenArtifactRepository repo -> - repo.name = 'MinecraftForge releases' - repo.url = 'https://maven.minecraftforge.net/releases' - } - } - - /** - * Get a configuring action for the Forge snapshots maven to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * repositories block. - * - * @return The action - */ - static Closure getForgeSnapshotMaven() { - { MavenArtifactRepository repo -> - repo.name = 'MinecraftForge snapshots' - repo.url = 'https://maven.minecraftforge.net/snapshots' - } - } - - /** - * Get a configuring action for the Minecraft libraries maven to be passed into - * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Action) RepositoryHandler.maven(Action)} in a - * repositories block. - * - * @return The action - */ - static Closure getMinecraftLibsMaven() { - { MavenArtifactRepository repo -> - repo.name = 'Minecraft libraries' - repo.url = 'https://libraries.minecraft.net/' - } - } - - /** - * Returns a version in the form {@code $tag.$offset}, e.g. 1.0.5 - * - * @param info A git info object generated from {@code #gitInfo} - * @return a version in the form {@code $tag.$offset}, e.g. 1.0.5 - * @deprecated Use {@link GitVersion#getTagOffset()} instead - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getTagOffsetVersion(Map info) { - "${info.tag}.${info.offset}" - } - - /** @deprecated Filters can no longer be defined at configuration. Use the Git Version config. */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getFilteredTagOffsetVersion(Map info, boolean prefix = false, String filter) { - getTagOffsetVersion(info) - } - - /** - * Returns a version in the form {@code $tag.$offset}, optionally with the branch appended if it is not in the - * defined list of allowed branches - * - * @param info A git info object generated from {@link #gitInfo(File, String ...)} - * @param allowedBranches A list of allowed branches; the current branch is appended if not in this list - * @return a version in the form {@code $tag.$offset} or {@code $tag.$offset-$branch} - * @deprecated Use {@link GitVersion#getTagOffsetBranch(String ...)} via {@link net.minecraftforge.gradleutils.gitversion.GitVersionExtension#getVersion() GitVersionExtension.getVersion()} - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getTagOffsetBranchVersion(Map info, String... allowedBranches) { - if (!allowedBranches || allowedBranches.length === 0) - allowedBranches = [null, 'master', 'main', 'HEAD'] - final version = getTagOffsetVersion(info) - String branch = info.branch - if (branch?.startsWith('pulls/')) - branch = 'pr' + rsplitInternal(branch, '/', 1)[1] - branch = branch?.replaceAll(/[\\\/]/, '-') - return branch in allowedBranches ? version : "$version-${branch}" - } - - /** @deprecated Filters can no longer be defined at configuration. Use the Git Version config. */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getFilteredTagOffsetBranchVersion(Map info, boolean prefix = false, String filter, String... allowedBranches) { - getTagOffsetBranchVersion(info, allowedBranches) - } - - /** - * Returns a version in the form {@code $mcVersion-$tag.$offset}, optionally with - * the branch appended if it is not in the defined list of allowed branches - * - * @param info A git info object generated from {@code #gitInfo} - * @param mcVersion The current minecraft version - * @param allowedBranches A list of allowed branches; the current branch is appended if not in this list - * @return a version in the form {@code $mcVersion-$tag.$offset} or {@code $mcVersion-$tag.$offset-$branch} - * @deprecated Use {@link GitVersion#getMCTagOffsetBranch(String, String ...)} instead - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getMCTagOffsetBranchVersion(Map info, String mcVersion, String... allowedBranches) { - if (!allowedBranches || allowedBranches.length === 0) - allowedBranches = [null, 'master', 'main', 'HEAD', mcVersion, mcVersion + '.0', mcVersion + '.x', rsplitInternal(mcVersion, '.', 1)[0] + '.x'] - - "$mcVersion-${getTagOffsetBranchVersion(info, allowedBranches)}" - } - - /** @deprecated Filters for GitVersion should be set early, using one of the methods in {@link GradleUtilsExtension} */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String getFilteredMCTagOffsetBranchVersion(Map info, boolean prefix = false, String filter, String mcVersion, String... allowedBranches) { - getMCTagOffsetBranchVersion(info, mcVersion, allowedBranches) - } - - /** @see net.minecraftforge.gitver.internal.GitUtils#buildProjectUrl(String) GitUtils.buildProjectUrl(String) */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String buildProjectUrl(String project) { - buildProjectUrl("MinecraftForge", project); - } - - /** @see net.minecraftforge.gitver.internal.GitUtils#buildProjectUrl(String, String) GitUtils.buildProjectUrl(String, String) */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String buildProjectUrl(String organization, String project) { - buildProjectUrlLogDeprecation() - "https://github.com/${organization}/${project}" - } - - /** - * Identical to - * {@link net.minecraftforge.gitver.internal.GitUtils#buildProjectUrl(Git) GitUtils.buildProjectUrl(Git)}. The only - * difference is that this does not return {@code null} to preserve GradleUtils 2.x behavior. - * - * @deprecated Replaced by GitVersion, use {@link GitVersion.Info#getUrl()} via {@link net.minecraftforge.gradleutils.gitversion.GitVersionExtension#getVersion() GitVersionExtension.getVersion()} - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - static String buildProjectUrl(Git git) { - buildProjectUrlLogDeprecation() - - List remotes - try { - remotes = git.remoteList().call() - if (remotes.isEmpty()) return '' - } catch (GitAPIException ignored) { - return '' - } - - //Get the origin remote. - var originRemote = - remotes.find { // First try finding the remote that has MinecraftForge - remote -> remote.URIs.find { it.toString().contains('MinecraftForge/') } - } ?: remotes.find { // Ok, just get the origin then - remote -> remote.name == 'origin' - } ?: remotes.first() // No origin? Get whatever we can get our hands on - - var originUrls = originRemote.getURIs() - if (originUrls.empty) return '' - - //Grab its string representation and process. - var originUrlString = originUrls.first().toString() - //Determine the protocol - if (originUrlString.lastIndexOf(':') > 'https://'.length()) { - //If ssh then check for authentication data. - if (originUrlString.contains('@')) { - //We have authentication data: Strip it. - return 'https://' + originUrlString.substring(originUrlString.indexOf('@') + 1).replace('.git', '').replace(':', '/') - } else { - //No authentication data: Switch to https. - return 'https://' + originUrlString.replace('ssh://', '').replace('.git', '').replace(':', '/') - } - } else if (originUrlString.startsWith('http')) { - //Standard http protocol: Strip the '.git' ending only. - return originUrlString.replace('.git', '') - } - - //What other case exists? Just to be sure lets return this. - return originUrlString - } - - private static boolean buildProjectUrlDeprecationLogged - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - private static void buildProjectUrlLogDeprecation() { - if (!buildProjectUrlDeprecationLogged) { - println 'WARNING: Usage of GradleUtils.buildProjectUrl is DEPRECATED and will be removed in GradleUtils 3.0! Use gitversion.url instead.' - buildProjectUrlDeprecationLogged = true - } - } - - /** - * Configures CI related tasks for TeamCity. - * - * @param project The project to configure TeamCity tasks for - * @deprecated Once Forge has completely moved off of TeamCity, this will be deleted. New tasks added to GradleUtils should handle registration themselves. - */ - @Deprecated(forRemoval = true) - static void setupCITasks(Project project) { - ConfigureTeamCity.register(project) - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.groovy deleted file mode 100644 index 797a20c..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.groovy +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils - -import groovy.transform.CompileStatic -import net.minecraftforge.gradleutils.gitversion.GitVersionExtension -import org.gradle.api.Action -import org.gradle.api.Project -import org.gradle.api.artifacts.repositories.MavenArtifactRepository -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ProviderFactory -import org.jetbrains.annotations.ApiStatus - -import javax.inject.Inject - -/** - * The heart of the GradleUtils library. This class can be directly accessed from buildscripts that have the - * {@linkplain GradleUtilsPlugin plugin} applied using {@code gradleutils}. - */ -@CompileStatic -class GradleUtilsExtension { - public static final String NAME = 'gradleutils' - - private final Project project - private final ObjectFactory objects - private final ProviderFactory providers - - private final GitVersionExtension gitversion - - /** Holds a project-aware Pom utilities class, useful for configuring repositories and publishing. */ - public final PomUtils pom - - /** @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getRoot() GitVersion.getRoot()} via {@link GitVersionExtension#getVersion()} instead. */ - @Deprecated(forRemoval = true, since = '2.4') @Lazy DirectoryProperty gitRoot = { - this.project.logger.warn "WARNING: This project is still using 'gradleutils.gitRoot'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'gitversion.rootDir' instead." - - this.gitversion.rootDir - }() - /** @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getInfo() GitVersion.getInfo()} via {@link GitVersionExtension#getVersion()} instead. */ - @Deprecated(forRemoval = true, since = '2.4') @Lazy Map gitInfo = { - this.project.logger.warn "WARNING: This project is still using 'gradleutils.gitInfo'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'gitversion.info' instead." - - var gitversion = this.project.extensions.getByType(GitVersionExtension) - var info = gitversion.info - [ - dir : gitversion.gitDir.get().asFile.absolutePath, - tag : info.tag, - offset : info.offset, - hash : info.hash, - branch : info.branch, - commit : info.commit, - abbreviatedId: info.abbreviatedId, - url : gitversion.url - ].tap { it.removeAll { it.value == null } } - }() - - /** @deprecated This constructor will be made package-private in GradleUtils 3.0 */ - @Inject - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - GradleUtilsExtension(Project project, ObjectFactory objects, ProviderFactory providers) { - this.project = project - this.objects = objects - this.providers = providers - - // Git Version - this.gitversion = project.extensions.getByType(GitVersionExtension) - - // Pom Utils - this.pom = new PomUtils(project, providers, this.gitversion) - - // Tasks - GenerateActionsWorkflow.register(this.project) - GradleUtils.setupCITasks(this.project) - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *


-     *     // Before:
-     *     version = gradleutils.tagOffsetVersion
-     *
-     *     // After:
-     *     version = gitversion.tagOffset
-     * 
- * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getTagOffset() GitVersion.tagOffset} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getTagOffsetVersion() { - this.logDeprecation('tagOffsetVersion', 'tagOffsetVersion') - this.project.extensions.getByType(GitVersionExtension).tagOffset - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *

-     *     // Before:
-     *     version = gradleutils.tagOffsetVersion
-     *
-     *     // After:
-     *     version = gitversion.tagOffset
-     * 
- * You must declare your filters in the Git Version config file!. - * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getTagOffset() GitVersion.tagOffset} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getFilteredTagOffsetVersion(boolean prefix = false, String filter) { - this.updateInfo(prefix, filter) - this.tagOffsetVersion - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *

-     *     // Before:
-     *     version = gradleutils.getTagOffsetBranchVersion()
-     *
-     *     // After:
-     *     version = gitversion.tagOffsetBranch
-     * 
- * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getTagOffsetBranch(String ...) GitVersion.getTagOffsetBranch(String...)} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getTagOffsetBranchVersion(String... allowedBranches) { - this.logDeprecation('tagOffsetBranchVersion', 'getTagOffsetBranchVersion(String...)') - var gitversion = this.project.extensions.getByType(GitVersionExtension) - allowedBranches ? gitversion.getTagOffsetBranch(allowedBranches) : gitversion.tagOffsetBranch - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *

-     *     // Before:
-     *     version = gradleutils.tagOffsetBranchVersion
-     *
-     *     // After:
-     *     version = gitversion.tagOffsetBranch
-     * 
- *

- * You must declare your filters in the Git Version config file!. - * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getTagOffset() GitVersion.tagOffset} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getFilteredTagOffsetBranchVersion(boolean prefix = false, String filter, String... allowedBranches) { - this.updateInfo(prefix, filter) - this.getTagOffsetBranchVersion(allowedBranches) - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *


-     *     // Before:
-     *     version = gradleutils.getMCTagOffsetBranchVersion('1.21.4')
-     *
-     *     // After:
-     *     version = gitversion.getMCTagOffsetBranch('1.21.4')
-     * 
- * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getMCTagOffsetBranch(String, String ...) GitVersion.getMCTagOffsetBranch(String, String...)} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getMCTagOffsetBranchVersion(String mcVersion, String... allowedBranches) { - this.logDeprecation('MCTagOffsetBranchVersion', 'getMCTagOffsetBranchVersion(String, String...)') - var gitVersion = this.project.extensions.getByType(GitVersionExtension) - allowedBranches ? gitVersion.getMCTagOffsetBranch(mcVersion, allowedBranches) : gitVersion.getMCTagOffsetBranch(mcVersion) - } - - /** - * This method has been deprecated in favor of usage of GitVersion. - *

-     *     // Before:
-     *     version = gradleutils.getMCTagOffsetBranchVersion('1.21.4')
-     *
-     *     // After:
-     *     version = gitversion.getMCTagOffsetBranch('1.21.4')
-     * 
- *

- * You must declare your filters in the Git Version config file!. - * - * @deprecated Use {@link net.minecraftforge.gitver.api.GitVersion#getMCTagOffsetBranch(String, String ...) GitVersion.getMCTagOffsetBranch(String, String...)} instead. - */ - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - String getFilteredMCTagOffsetBranchVersion(boolean prefix = false, String filter, String mcVersion, String... allowedBranches) { - this.updateInfo(prefix, filter) - this.getMCTagOffsetBranchVersion(mcVersion, allowedBranches) - } - - @Deprecated(forRemoval = true, since = '2.4') - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - private void updateInfo(boolean prefix, String filter) { - this.gitversion.versionInternal.tap { - if (prefix) it.tagPrefix = filter - else it.filters = new String[] {filter} - } - } - - private void logDeprecation(String name, String fullName) { - this.project.logger.warn "WARNING: This project is still using 'gradleutils.$name'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'gitversion.$fullName' instead." - } - - /** @see GradleUtils#getPublishingForgeMaven(Project, File) */ - Action getPublishingForgeMaven(File defaultFolder = this.project.rootProject.file('repo')) { - GradleUtils.getPublishingForgeMaven(this.project, defaultFolder) - } - - /** @see GradleUtils#getForgeMaven() */ - static Closure getForgeMaven() { - GradleUtils.forgeMaven - } - - /** @see GradleUtils#getForgeReleaseMaven() */ - static Closure getForgeReleaseMaven() { - GradleUtils.forgeReleaseMaven - } - - /** @see GradleUtils#getForgeSnapshotMaven() */ - static Closure getForgeSnapshotMaven() { - GradleUtils.forgeSnapshotMaven - } - - /** @see GradleUtils#getMinecraftLibsMaven() */ - static Closure getMinecraftLibsMaven() { - GradleUtils.minecraftLibsMaven - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.java b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.java new file mode 100644 index 0000000..bd8ab0d --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtension.java @@ -0,0 +1,215 @@ +package net.minecraftforge.gradleutils; + +import groovy.lang.Closure; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; + +import java.io.File; +import java.util.function.Consumer; + +/** + * Contains various utilities for working with Gradle scripts. + *

Projects that apply GradleUtils are given {@link GradleUtilsExtension.ForProject}

+ */ +@SuppressWarnings("rawtypes") // public-facing Closures +public sealed interface GradleUtilsExtension permits GradleUtilsExtensionImpl, GradleUtilsExtension.ForProject { + /** The name for this extension. */ + String NAME = "gradleutils"; + + /** + * A closure for the Forge maven to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)}. + *

+     * repositories {
+     *     maven fg.forgeMaven
+     * }
+     * 
+ * + * @see #forgeMaven + */ + Closure forgeMaven = closure((MavenArtifactRepository repo) -> { + repo.setName("MinecraftForge"); + repo.setUrl("https://maven.minecraftforge.net/"); + }); + + /** + * A closure for the Forge releases maven to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)}. + *

+     * repositories {
+     *     maven fg.forgeReleaseMaven
+     * }
+     * 
+ * + * @see #forgeMaven + */ + Closure forgeReleaseMaven = closure((MavenArtifactRepository repo) -> { + repo.setName("'MinecraftForge releases'"); + repo.setUrl("https://maven.minecraftforge.net/releases"); + }); + + /** + * A closure for the Forge snapshot maven to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)}. + *

+     * repositories {
+     *     maven fg.forgeSnapshotMaven
+     * }
+     * 
+ * + * @see #forgeMaven + */ + Closure getForgeSnapshotMaven = closure((MavenArtifactRepository repo) -> { + repo.setName("MinecraftForge snapshots"); + repo.setUrl("https://maven.minecraftforge.net/snapshots"); + }); + + /** + * A closure for the Minecraft libraries maven to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)}. + *

+     * repositories {
+     *     maven fg.minecraftLibsMaven
+     * }
+     * 
+ */ + Closure getMinecraftLibsMaven = closure((MavenArtifactRepository repo) -> { + repo.setName("Minecraft libraries"); + repo.setUrl("https://libraries.minecraft.net/"); + }); + + /** + * The GradleUtils extension for {@linkplain org.gradle.api.Project projects}, which include additional utilities + * that are only available for them. + *

When applied, GradleUtils will

+ *
    + *
  • Create a referenceable {@link PomUtils} instance in {@link #getPom()}.
  • + *
  • Register the {@code generateActionsWorkflow} task to the project for generating a default template GitHub + * Actions workflow.
  • + *
  • Register the {@code configureTeamCity} task to the project for working with TeamCity CI pipelines.
  • + *
+ * + * @see GradleUtilsExtension + */ + sealed interface ForProject extends GradleUtilsExtension permits GradleUtilsExtensionImpl.ForProject { + /** + * Utilities for working with a {@link org.gradle.api.publish.maven.MavenPom} for publishing artifacts. + * + * @return The POM utilities + * @see PomUtils + */ + PomUtils getPom(); + + /** + * Get a configuring closure to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)} in a publishing block. + *

This closure respects the current project's version in regard to publishing to a release or snapshot + * repository.

+ *

Important: The following environment variables must be set for this to work:

+ *
    + *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • + *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • + *
+ *

The following environment variables are optional:

+ *
    + *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • + *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • + *
+ *

If the required environment variables are not present, the output Maven will be a local folder named + * {@code repo} on the root of the + * {@linkplain org.gradle.api.file.ProjectLayout#getProjectDirectory() project directory}.

+ *

If the {@code MAVEN_URL_RELEASE} variable is not set, the Forge Maven will be used + * ({@code https://maven.minecraftforge.net/}).

+ * + * @return The closure + */ + default Closure getPublishingForgeMaven() { + return getPublishingForgeMaven("https://maven.minecraftforge.net/"); + } + + /** + * Get a configuring closure to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)} in a publishing block. + *

This closure respects the current project's version in regard to publishing to a release or snapshot + * repository.

+ *

Important: The following environment variables must be set for this to work:

+ *
    + *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • + *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • + *
+ *

The following environment variables are optional:

+ *
    + *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • + *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • + *
+ *

If the required environment variables are not present, the output Maven will be a local folder named + * {@code repo} on the root of the + * {@linkplain org.gradle.api.file.ProjectLayout#getProjectDirectory() project directory}.

+ *

If the {@code MAVEN_URL_RELEASE} variable is not set, the passed in fallback URL will be used for the + * release repository.

+ * + * @param fallbackPublishingEndpoint The fallback URL for the release repository + * @return The closure + */ + Closure getPublishingForgeMaven(String fallbackPublishingEndpoint); + + /** + * Get a configuring closure to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)} in a publishing block. + *

This closure respects the current project's version in regard to publishing to a release or snapshot + * repository.

+ *

Important: The following environment variables must be set for this to work:

+ *
    + *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • + *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • + *
+ *

The following environment variables are optional:

+ *
    + *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • + *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • + *
+ *

If the required environment variables are not present, the output Maven will be set to the given default + * folder.

+ *

If the {@code MAVEN_URL_RELEASE} variable is not set, the passed in fallback URL will be used for the + * release repository.

+ * + * @param fallbackPublishingEndpoint The fallback URL for the release repository + * @param defaultFolder The default folder if the required maven information is not set + * @return The closure + */ + default Closure getPublishingForgeMaven(String fallbackPublishingEndpoint, File defaultFolder) { + return getPublishingForgeMaven(fallbackPublishingEndpoint, defaultFolder, new File(defaultFolder.getAbsoluteFile().getParentFile(), "snapshots")); + } + + /** + * Get a configuring closure to be passed into + * {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(Closure)} in a publishing block. + *

This closure respects the current project's version in regard to publishing to a release or snapshot + * repository.

+ *

Important: The following environment variables must be set for this to work:

+ *
    + *
  • {@code MAVEN_USER}: Containing the username to use for authentication
  • + *
  • {@code MAVEN_PASSWORD}: Containing the password to use for authentication
  • + *
+ *

The following environment variables are optional:

+ *
    + *
  • {@code MAVEN_URL_RELEASE}: Containing the URL to use for the release repository
  • + *
  • {@code MAVEN_URL_SNAPSHOT}: Containing the URL to use for the snapshot repository
  • + *
+ *

If the required environment variables are not present, the output Maven will be set to the given default + * folder.

+ *

If the {@code MAVEN_URL_RELEASE} variable is not set, the passed in fallback URL will be used for the + * release repository.

+ * + * @param fallbackPublishingEndpoint The fallback URL for the release repository + * @param defaultFolder The default folder if the required maven information is not set + * @param defaultSnapshotFolder The default folder for the snapshot repository if the required maven + * information is not set + * @return The closure + */ + Closure getPublishingForgeMaven(String fallbackPublishingEndpoint, File defaultFolder, File defaultSnapshotFolder); + } + + private static Closure closure(Consumer consumer) { + return Util.closure(GradleUtilsExtension.class, consumer); + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtensionImpl.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtensionImpl.groovy new file mode 100644 index 0000000..2702fcf --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsExtensionImpl.groovy @@ -0,0 +1,93 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import org.gradle.api.Project +import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.provider.ProviderFactory +import org.gradle.authentication.http.BasicAuthentication + +@CompileStatic +final class GradleUtilsExtensionImpl implements GradleUtilsExtension { + private final ObjectFactory objects + private final ProviderFactory providers + + @PackageScope static GradleUtilsExtension create(ExtensionAware target, ObjectFactory objects, ProviderFactory providers) { + final base = new GradleUtilsExtensionImpl(objects, providers) + target instanceof Project ? new ForProject(base, target) : base + } + + private GradleUtilsExtensionImpl(ObjectFactory objects, ProviderFactory providers) { + this.objects = objects + this.providers = providers + } + + @CompileStatic + static final class ForProject implements GradleUtilsExtension.ForProject { + // Groovy 3 doesn't have non-sealed, so we are using the base as a delegate for now + @Delegate GradleUtilsExtensionImpl base + private final Project project + + final PomUtils pom + + private ForProject(GradleUtilsExtensionImpl base, Project project) { + this.base = base + this.project = project + + this.pom = new PomUtilsImpl(this.project, base.providers) + + this.project.tasks.register GenerateActionsWorkflow.NAME, GenerateActionsWorkflow + this.project.tasks.register ConfigureTeamCity.NAME, ConfigureTeamCity + } + + @Override + Closure getPublishingForgeMaven(String fallbackPublishingEndpoint) { + this.getPublishingForgeMaven(fallbackPublishingEndpoint, this.project.rootProject.file('repo')) + } + + @Override + Closure getPublishingForgeMaven(String fallbackPublishingEndpoint, File defaultFolder, File defaultSnapshotFolder) { + // make properties of what we use so gradle's cache is aware + final snapshot = this.base.objects.property(Boolean).value this.base.providers.provider { + this.project.version?.toString()?.endsWith('-SNAPSHOT') + } + + // collecting all of our environment variables here so gradle's cache is aware + final mavenUser = this.base.providers.environmentVariable 'MAVEN_USER' + final mavenPassword = this.base.providers.environmentVariable 'MAVEN_PASSWORD' + final mavenUrlRelease = this.base.providers.environmentVariable 'MAVEN_URL_RELEASE' + final mavenUrlSnapshots = this.base.providers.environmentVariable 'MAVEN_URL_SNAPSHOTS' + + { MavenArtifactRepository repo -> + repo.name = 'forge' + + if (mavenUser.present && mavenPassword.present) { + var publishingEndpoint = mavenUrlRelease.present ? mavenUrlRelease.get() : fallbackPublishingEndpoint + + repo.url = snapshot.getOrElse(false) && mavenUrlSnapshots.present + ? mavenUrlSnapshots.get() + : publishingEndpoint + + repo.authentication { authentication -> + authentication.create('basic', BasicAuthentication) + } + + repo.credentials { credentials -> + credentials.username = mavenUser.get() + credentials.password = mavenPassword.get() + } + } else { + repo.url = snapshot.getOrElse(false) + ? defaultSnapshotFolder.absoluteFile.toURI() + : defaultFolder.absoluteFile.toURI() + } + } + } + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.groovy deleted file mode 100644 index 876da22..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.groovy +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils - -import groovy.transform.CompileStatic -import net.minecraftforge.gradleutils.changelog.ChangelogPlugin -import net.minecraftforge.gradleutils.gitversion.GitVersionPlugin -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ProviderFactory - -import javax.inject.Inject - -/** The entry point for the Gradle Utils plugin. Exists to create the {@linkplain GradleUtilsExtension extension}. */ -@CompileStatic -abstract class GradleUtilsPlugin implements Plugin { - /** @see ObjectFactory Service Injection */ - @Inject abstract ObjectFactory getObjects() - /** @see ProviderFactory Service Injection */ - @Inject abstract ProviderFactory getProviders() - - @Override - void apply(Project project) { - project.plugins.apply(GitVersionPlugin) - project.plugins.apply(ChangelogPlugin) - // TODO [GradleUtils][3.0] Use direct constructor - project.extensions.create(GradleUtilsExtension.NAME, GradleUtilsExtension, project, this.objects, this.providers) - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.java b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.java new file mode 100644 index 0000000..02683ef --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsPlugin.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils; + +import org.gradle.api.Plugin; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.PluginAware; +import org.gradle.api.problems.Problems; +import org.gradle.api.provider.ProviderFactory; + +import javax.inject.Inject; + +/** The entry point for the Gradle Utils plugin. Exists to create the {@linkplain GradleUtilsExtension extension}. */ +abstract class GradleUtilsPlugin implements Plugin { + @Override + public void apply(T target) { + target.getExtensions().add(GradleUtilsExtension.class, GradleUtilsExtension.NAME, GradleUtilsExtensionImpl.create( + target, + this.getObjects(), + this.getProviders() + )); + } + + @Inject + public GradleUtilsPlugin() { } + + protected @Inject Problems getProblems() { + return injectFailed(); + } + + protected @Inject ObjectFactory getObjects() { + return injectFailed(); + } + + protected @Inject ProviderFactory getProviders() { + return injectFailed(); + } + + private static T injectFailed() { + throw new IllegalStateException("Cannot use in current context"); + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsSources.groovy b/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsSources.groovy deleted file mode 100644 index 0c85bbf..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/GradleUtilsSources.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import org.gradle.api.Describable -import org.gradle.api.provider.Property -import org.gradle.api.provider.ValueSource -import org.gradle.api.provider.ValueSourceParameters -import org.jetbrains.annotations.Nullable - -@CompileStatic -@PackageScope -final class GradleUtilsSources { - @CompileStatic - static abstract class EnvVar implements ValueSource<@Nullable String, Parameters>, Describable { - interface Parameters extends ValueSourceParameters { - Property getVariableName(); - } - - @Override - @Nullable String obtain() { - System.getenv(this.parameters.variableName.get()) - } - - @Override - String getDisplayName() { - "Environment Variable value: ${this.parameters.variableName.get()}" - } - } - - @CompileStatic - static abstract class HasEnvVar implements ValueSource, Describable { - interface Parameters extends ValueSourceParameters { - Property getVariableName(); - } - - @Override - Boolean obtain() { - final env = System.getenv(this.parameters.variableName.get()) - env !== null && !env.isBlank() && !"false".equalsIgnoreCase(env) - } - - @Override - String getDisplayName() { - "Environment Variable presense: ${this.parameters.variableName.get()}" - } - } - - private GradleUtilsSources() {} -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.groovy b/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.groovy deleted file mode 100644 index e7695c0..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.groovy +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import net.minecraftforge.gradleutils.gitversion.GitVersionExtension -import org.gradle.api.Action -import org.gradle.api.Project -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.publish.maven.MavenPom -import org.gradle.api.publish.maven.MavenPomDeveloper -import org.gradle.api.publish.maven.MavenPomLicense -import org.gradle.api.publish.maven.MavenPublication -import org.jetbrains.annotations.ApiStatus - -/** - * Utilities for making configuring a {@code MavenPom} more ergonomic. - * - * @see MavenPom - */ -@CompileStatic -@SuppressWarnings('unused') -final class PomUtils { - private final Project project - private final ProviderFactory providers - - private final GitVersionExtension gitversion - - @PackageScope PomUtils(Project project, ProviderFactory providers, GitVersionExtension gitversion) { - this.project = project - this.providers = providers - this.gitversion = gitversion - } - - /** Allows accessing licenses from buildscripts using {@code gradleutils.pom.licenses}. */ - public static final Licenses licenses = new Licenses() - @CompileStatic - static final class Licenses { - public static final Closure Apache2_0 = makeLicense('Apache-2.0', 'https://www.apache.org/licenses/LICENSE-2.0') - public static final Closure LGPLv2_1 = makeLicense('LGPL-2.1-only', 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html') - public static final Closure LGPLv3 = makeLicense('LGPL-3.0-only', 'https://www.gnu.org/licenses/lgpl-3.0-standalone.html') - public static final Closure MIT = makeLicense('MIT', 'https://opensource.org/license/mit/') - - private static Closure makeLicense(String name, String url) { - return { MavenPomLicense license -> - license.name.set name - license.url.set url - license.distribution.set 'repo' - } - } - - private Licenses() {} - } - - /** Common developers in the Minecraft Forge organization. */ - public static final Map> developers = [ - LexManos : makeDev('LexManos', 'Lex Manos'), - Paint_Ninja : makeDev('Paint_Ninja'), - SizableShrimp: makeDev('SizableShrimp'), - cpw : makeDev('cpw'), - Jonathing : makeDev('Jonathing', 'me@jonathing.me', 'https://jonathing.me', 'America/New_York') // i'm overkill - ].withDefault(this.&makeDev) as Map> - - /** - * @deprecated Casing changed. - * @see #developers - */ - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - public static final Map> Developers = developers - - private static Action makeDev(String id, String name = id) { - return { MavenPomDeveloper developer -> - developer.id.set(id) - developer.name.set(name) - } - } - - private static Action makeDev(String id, String name = id, String email, String url, String timezone) { - return { MavenPomDeveloper developer -> - developer.id.set(id) - developer.name.set(name) - developer.email.set(email) - developer.url.set(url) - developer.timezone.set(timezone) - } - } - - @SuppressWarnings('GrDeprecatedAPIUsage') - void promote(MavenPublication publication, String promotionType = 'latest') { - PromoteArtifact.Type type - try { - type = PromoteArtifact.Type.valueOf(promotionType.toUpperCase()) - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid promotion type: $promotionType. Known types: ${PromoteArtifact.Type.values()*.toString()}", e) - } - - PromoteArtifact.register(this.project, publication, type) - } - - /** - * Reduces boilerplate when setting up GitHub details in a {@link MavenPom}. - * - * @param pom The pom to configure - */ - void setGitHubDetails(MavenPom pom) { - this.setGitHubDetails(pom, '') - } - - /** - * Reduces boilerplate when setting up GitHub details in a {@link MavenPom}. The organization is assumed to be - * {@literal 'MinecraftForge'}. - * - * @param pom The pom to configure - * @param repo The name of the repository on GitHub - */ - void setGitHubDetails(MavenPom pom, String repo) { - this.setGitHubDetails(pom, 'MinecraftForge', repo) - } - - /** - * Reduces boilerplate when setting up GitHub details in a {@link MavenPom}. - * - * @param pom The pom to configure - * @param organization The organization or username the GitHub project is under - * @param repo The name of the repository on GitHub - */ - void setGitHubDetails(MavenPom pom, String organization, String repo) { - // always add this - pom.organization { org -> - org.name.set 'Forge Development LLC' - org.url.set 'https://minecraftforge.net' - } - - var inCI = GradleUtils.hasEnvVar('GITHUB_ACTIONS', this.providers).get().booleanValue() - - var remoteUrl = stripProtocol(this.gitversion.url) - var url = remoteUrl - if (organization && repo) { - url = "github.com/${organization}/${repo}".toString() - - if (url && url == remoteUrl) { - this.project.logger.warn "WARNING: The repository name was specified in the 'setGitHubDetails' method, but it was already present in the Git remote URL. This is redundant and may cause issues if the remote repository URL changes in the future." - } - } - - if (!url) { - this.project.logger.warn 'WARNING: The GitHub URL for this repo could not be automatically determined by Git Version. This is likely due to the repository not having any remotes, not having one set, or some other issue with Git Version.' - if (inCI) - throw new IllegalStateException('GitHub URL could not be determined, which is required in CI') - - return - } - - if (!url.contains('github.com')) { - this.project.logger.warn "WARNING: The repository URL found or created in 'setGitHubDetails' does not include 'github.com' This is problematic since all Minecraft Forge projects are hosted on GitHub. Found url: $url" - } - - if (!url.contains('github.com/MinecraftForge')) { - this.project.logger.warn "WARNING: The repository URL found or created in 'setGitHubDetails' does not include 'github.com/MinecraftForge' This is problematic if you are attempting to publish this project, especially from GitHub Actions. Found url: $url" - } - - var fullURL = "https://${url}".toString() - pom.url.set fullURL - pom.scm { scm -> - scm.url.set fullURL - scm.connection.set "scm:git:git://${url}.git".toString() - scm.developerConnection.set "scm:git:git@${url}.git".toString() - } - pom.issueManagement { issues -> - issues.system.set url.split('\\.', 2)[0] - issues.url.set "https://${url}/issues".toString() - } - pom.ciManagement { ci -> - ci.system.set 'github' - ci.url.set "https://${url}/actions".toString() - } - } - - private static String stripProtocol(String url) { - if (!url) return url - - final s = '://' - int index = url.indexOf(s) - return index == -1 ? url : url.substring(index + s.length()) - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.java b/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.java new file mode 100644 index 0000000..b2bc300 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/PomUtils.java @@ -0,0 +1,191 @@ +package net.minecraftforge.gradleutils; + +import org.gradle.api.Action; +import org.gradle.api.publish.maven.MavenPom; +import org.gradle.api.publish.maven.MavenPomDeveloper; +import org.gradle.api.publish.maven.MavenPomLicense; +import org.gradle.api.publish.maven.MavenPublication; +import org.jetbrains.annotations.ApiStatus; + +import java.util.HashMap; +import java.util.Map; + +/** + * Contains utilities to make working with {@link MavenPom POMs} more ergonomic. + *

This can be accessed by {@linkplain org.gradle.api.Project projects} using the + * {@link GradleUtilsExtension.ForProject gradleutils} extension.

+ */ +public sealed interface PomUtils permits PomUtilsImpl { + /** + * Allows accessing licenses from buildscripts using {@code gradleutils.pom.licenses}. + * + * @see Licenses + */ + Licenses licenses = new PomUtilsImpl.Licenses(); + + /** + * Contains several licenses used by MinecraftForge to reduce needing to manually write them out in each project + * that uses one. + * + * @see #licenses + */ + sealed interface Licenses permits PomUtilsImpl.Licenses { + Action + Apache2_0 = makeLicense("Apache-2.0", "https://www.apache.org/licenses/LICENSE-2.0"), + LGPLv2_1 = makeLicense("LGPL-2.1-only", "https://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html"), + LGPLv3 = makeLicense("LGPL-3.0-only", "https://www.gnu.org/licenses/lgpl-3.0-standalone.html"), + MIT = makeLicense("MIT", "https://opensource.org/license/mit/"); + } + + /** + * Contains several developers within the MinecraftForge organization to reduce needing to manually write them out + * in each project they contribute to. + *

If a queried developer does not exist, it is automatically created with the input which is set to the + * {@linkplain MavenPomDeveloper#getId() ID} and {@linkplain MavenPomDeveloper#getName() name}.

+ */ + Map> developers = makeDevelopers(Map.of( + "LexManos", makeDev("LexManos", "Lex Manos"), + "Paint_Ninja", makeDev("Paint_Ninja"), + "SizableShrimp", makeDev("SizableShrimp"), + "cpw", makeDev("cpw"), + "Jonathing", makeDev("Jonathing", "me@jonathing.me", "https://jonathing.me", "America/New_York") + )); + + /** + * Promotes the given publication's artifact as the latest to the + * Forge Files site's project index. + * + * @param publication The publication to promote + * @apiNote Should only be used by MinecraftForge projects. + */ + @ApiStatus.Internal + default void promote(MavenPublication publication) { + this.promote(publication, "latest"); + } + + /** + * Promotes the given publication's artifact, using the given promotion type, to the + * Forge Files site's project index. + * + * @param publication The publication to promote + * @apiNote Should only be used by MinecraftForge projects. + */ + @ApiStatus.Internal + void promote(MavenPublication publication, String promotionType); + + /** + * Adds MinecraftForge-specific details to the given POM. + * + * @param pom The POM to add details to + */ + static void addForgeDetails(MavenPom pom) { + pom.organization(organization -> { + organization.getName().set("Forge Development LLC"); + organization.getUrl().set("https://minecraftforge.net"); + }); + } + + /** + * Adds details from the project's remote URL to the given POM. + * + * @param pom The POM to add details to + * @apiNote If the project does not have the + * {@link net.minecraftforge.gradleutils.gitversion.GitVersionExtension net.minecraftforge.gitversion} plugin + * applied, this method will fail. If you are not using Git Version, manually specify your project's URL using + * {@link #addRemoteDetails(MavenPom, String)}. + */ + void addRemoteDetails(MavenPom pom); + + /** + * Adds details from the given remote URL to the given POM. + * + * @param pom The pom to add details to + * @param url The URL of the repository + * @apiNote If you are using the + * {@link net.minecraftforge.gradleutils.gitversion.GitVersionExtension net.minecraftforge.gitversion} plugin, you + * can use {@link #addRemoteDetails(MavenPom)} to use the URL discovered by Git Version instead of specifying it + * manually. + */ + static void addRemoteDetails(MavenPom pom, String url) { + if (url == null || url.isBlank()) + throw new IllegalArgumentException(); + + var strippedUrl = stripProtocol(url); + var fullURL = "https://" + url; + pom.getUrl().set(fullURL); + pom.scm(scm -> { + scm.getUrl().set(fullURL); + scm.getConnection().set("scm:git:git://%s.git".formatted(strippedUrl)); + scm.getDeveloperConnection().set("scm:git:git@%s.git".formatted(strippedUrl)); + }); + + // the rest is GitHub-exclusive information + if (!strippedUrl.contains("github.com")) return; + + pom.issueManagement(issues -> { + issues.getSystem().set(url.split("\\.", 2)[0]); + issues.getUrl().set(fullURL + "/issues"); + }); + pom.ciManagement(ci -> { + ci.getSystem().set("github"); + ci.getUrl().set(fullURL + "/actions"); + }); + } + + + /* IMPLEMENTATIONS */ + + private static Action makeLicense(String name, String url) { + return license -> { + license.getName().set(name); + license.getUrl().set(url); + license.getDistribution().set("repo"); + }; + } + + private static Map> makeDevelopers(Map> defaults) { + return new HashMap<>(defaults) { + @Override + public Action get(Object key) { + this.ensure((String) key); + return super.get(key); + } + + private void ensure(String key) { + if (!this.containsKey(key)) + this.put(key, makeDev(key)); + } + }; + } + + private static Action makeDev(String id) { + return makeDev(id, id); + } + + private static Action makeDev(String id, String name) { + return developer -> { + developer.getId().set(id); + developer.getName().set(name); + }; + } + + private static Action makeDev(String id, String email, String url, String timezone) { + return makeDev(id, id, email, url, timezone); + } + + private static Action makeDev(String id, String name, String email, String url, String timezone) { + return developer -> { + developer.getId().set(id); + developer.getName().set(name); + developer.getEmail().set(email); + developer.getUrl().set(url); + developer.getTimezone().set(timezone); + }; + } + + private static String stripProtocol(String url) { + int protocolIdx = url.indexOf("://"); + var ret = protocolIdx == -1 ? url : url.substring(protocolIdx + "://".length()); + return ret.endsWith("/") ? ret.substring(0, ret.length() - 1) : ret; + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/PomUtilsImpl.groovy b/src/main/groovy/net/minecraftforge/gradleutils/PomUtilsImpl.groovy new file mode 100644 index 0000000..6f87dfe --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/PomUtilsImpl.groovy @@ -0,0 +1,51 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import groovy.transform.PackageScopeTarget +import net.minecraftforge.gradleutils.gitversion.GitVersionExtension +import org.gradle.api.Project +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.publish.maven.MavenPom +import org.gradle.api.publish.maven.MavenPublication + +@CompileStatic +@SuppressWarnings('unused') +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.CONSTRUCTORS]) +final class PomUtilsImpl implements PomUtils { + private final Project project + private final ProviderFactory providers + + PomUtilsImpl(Project project, ProviderFactory providers) { + this.project = project + this.providers = providers + } + + @CompileStatic + @PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.CONSTRUCTORS]) + static final class Licenses implements PomUtils.Licenses { } + + @Override + void promote(MavenPublication publication, String promotionType) { + PromoteArtifact.register this.project, publication, PromoteArtifact.Type.of(promotionType) + } + + @Override + void addRemoteDetails(MavenPom pom) { + // check if we have git version + final gitversion = this.project.extensions.findByType(GitVersionExtension) + if (gitversion === null) + throw new IllegalArgumentException() + + // try to get the url git version found + final url = gitversion.url + if (!url) + throw new IllegalArgumentException() + + addRemoteDetails pom, url + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/PromoteArtifact.groovy b/src/main/groovy/net/minecraftforge/gradleutils/PromoteArtifact.groovy index 6820d25..ea0b981 100644 --- a/src/main/groovy/net/minecraftforge/gradleutils/PromoteArtifact.groovy +++ b/src/main/groovy/net/minecraftforge/gradleutils/PromoteArtifact.groovy @@ -6,6 +6,7 @@ package net.minecraftforge.gradleutils import groovy.json.JsonBuilder import groovy.transform.CompileStatic +import groovy.transform.PackageScope import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.provider.Property @@ -14,7 +15,6 @@ import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskProvider -import org.jetbrains.annotations.ApiStatus import javax.inject.Inject import javax.net.ssl.SSLContext @@ -23,34 +23,25 @@ import java.net.http.HttpRequest import java.net.http.HttpResponse import java.time.Duration -/** - * This task promotes an artifact on the Forge maven. - * - * @deprecated Aside from the fact that this is internal to Forge only due to the way the Files site works, this is planned to be superseded at some point in the future by a solution within the Files/Maven site itself, rather than through this task. While not scheduled for removal quite yet, don't count on it sticking around. - */ @CompileStatic -@ApiStatus.Internal -@ApiStatus.Experimental -@Deprecated -@SuppressWarnings('GrDeprecatedAPIUsage') -abstract class PromoteArtifact extends DefaultTask { - /** Registers a maven publication to be promoted to the Forge maven. */ - static TaskProvider register(Project project, MavenPublication publication, Type type) { - project.tasks.register('promoteArtifact', PromoteArtifact) { task -> - task.artifactGroup.set publication.groupId - task.artifactName.set publication.artifactId - task.artifactVersion.set publication.version - } +@PackageScope abstract class PromoteArtifact extends DefaultTask { + @PackageScope static TaskProvider register(Project project, MavenPublication publication, Type type) { + project.tasks.register("promote${publication.name.capitalize()}", PromoteArtifact, publication, type) } - @Inject abstract ProviderFactory getProviders(); + protected abstract @Inject ProviderFactory getProviders(); + + @Inject + PromoteArtifact(MavenPublication publication, Type type) { + this.webhookURL.convention this.providers.environmentVariable('PROMOTE_ARTIFACT_WEBHOOK') + this.username.convention this.providers.environmentVariable('PROMOTE_ARTIFACT_USERNAME') + this.password.convention this.providers.environmentVariable('PROMOTE_ARTIFACT_PASSWORD') - PromoteArtifact() { - this.promotionType.convention Type.LATEST + this.artifactGroup.set publication.groupId + this.artifactName.set publication.artifactId + this.artifactVersion.set publication.version - this.webhookURL.convention GradleUtils.getEnvVar('PROMOTE_ARTIFACT_WEBHOOK', this.providers) - this.username.convention GradleUtils.getEnvVar('PROMOTE_ARTIFACT_USERNAME', this.providers) - this.password.convention GradleUtils.getEnvVar('PROMOTE_ARTIFACT_PASSWORD', this.providers) + this.promotionType.convention(Type.LATEST).set type this.onlyIf { this.webhookURL.present && this.username.present && this.password.present } } @@ -63,27 +54,35 @@ abstract class PromoteArtifact extends DefaultTask { abstract @Input Property getUsername() abstract @Input Property getPassword() - static enum Type { + @PackageScope static enum Type { LATEST, RECOMMENDED; @Override String toString() { return super.toString().toLowerCase() } + + static Type of(String type) { + try { + return PromoteArtifact.Type.valueOf(type.toUpperCase()) + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid promotion type: $type. Known types: ${PromoteArtifact.Type.values()*.toString()}", e) + } + } } @TaskAction void exec() { var client = HttpClient .newBuilder() - .sslParameters(SSLContext.default.defaultSSLParameters.tap { protocols = ["TLSv1.3"] }) + .sslParameters(SSLContext.default.defaultSSLParameters.tap { protocols = ['TLSv1.3'] }) .build() var request = HttpRequest .newBuilder() .uri(URI.create(this.webhookURL.get())) - .setHeader("Content-Type", 'application/json') - .setHeader("Authorization", "Basic ${Base64.getEncoder().encodeToString("${this.username.get()}:${this.password.get()}".getBytes())}") + .setHeader('Content-Type', 'application/json') + .setHeader('Authorization', "Basic ${Base64.encoder.encodeToString "${this.username.get()}:${this.password.get()}".bytes}") .timeout(Duration.ofSeconds(10)) .POST(HttpRequest.BodyPublishers.ofString(new JsonBuilder( group: this.artifactGroup.get(), diff --git a/src/main/groovy/net/minecraftforge/gradleutils/Util.java b/src/main/groovy/net/minecraftforge/gradleutils/Util.java new file mode 100644 index 0000000..259d442 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/Util.java @@ -0,0 +1,46 @@ +package net.minecraftforge.gradleutils; + +import groovy.lang.Closure; + +import java.util.function.Consumer; + +interface Util { + /** + * Creates a closure backed by the given consumer. + * + * @param owner The owner of the closure + * @param consumer The consumer to execute + * @param The type of the action + * @return The closure + */ + static Closure closure(Object owner, Consumer consumer) { + return new Closures.Consuming<>(owner, consumer); + } + + @SuppressWarnings("unused") // doCall() is consumed by Groovy + final class Closures { + private static final class Consuming extends Closure { + private final Consumer consumer; + + private Consuming(Object owner, Consumer consumer) { + super(owner, owner); + this.consumer = consumer; + } + + public Void doCall(T object) { + this.consumer.accept(object); + return null; + } + } + + private static final class Empty extends Closure { + private Empty(Object owner) { + super(owner, owner); + } + + public Void doCall(Object object) { + return null; + } + } + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.groovy deleted file mode 100644 index 21169e3..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.groovy +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils.changelog - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import org.gradle.api.Project -import org.gradle.api.file.Directory -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.TaskProvider -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.Nullable - -import javax.inject.Inject - -/** The heart of the Changelog plugin. This extension is used to enable and partially configure the changelog generation task. */ -@CompileStatic -class ChangelogExtension { - public static final String NAME = 'changelog' - - @PackageScope final Project project - - /** @deprecated The Git root is automatically discovered by Git Version on Changelog generation. */ - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - @Nullable Directory gitRoot - - boolean publishAll = true - @PackageScope boolean isGenerating - private @Lazy TaskProvider task = { - this.isGenerating = true - - ChangelogUtils.setupChangelogTask(this.project) { task -> - this.project.afterEvaluate { project -> - if (this.gitRoot) { - task.configure { - it.gitDirectory.set gitRoot - } - } - - if (this.publishAll) - ChangelogUtils.setupChangelogGenerationOnAllPublishTasks project - } - } - }() - - /** @deprecated This constructor will be made package-private in GradleUtils 3.0 */ - @Inject - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - ChangelogExtension(Project project) { - this.project = project - } - - void fromBase() { - from null - } - - void from(String marker) { - this.task.configure { it.start.set marker } - } - - private static boolean fromTagDeprecationWarning - @Deprecated(forRemoval = true, since = '2.4') - void fromTag(String tag) { - if (!fromTagDeprecationWarning) { - this.project.logger.warn "WARNING: This project is still using 'changelog.fromTag'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'changelog.from' instead." - fromTagDeprecationWarning = true - } - - this.from tag - } - - private static boolean fromCommitDeprecationWarning - @Deprecated(forRemoval = true, since = '2.4') - void fromCommit(String commit) { - if (!fromCommitDeprecationWarning) { - this.project.logger.warn "WARNING: This project is still using 'changelog.fromCommit'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'changelog.from' instead." - fromCommitDeprecationWarning = true - } - - this.from commit - } - - private static boolean disableAutomaticPublicationRegistrationDeprecationWarning - @Deprecated(forRemoval = true, since = '2.4') - void disableAutomaticPublicationRegistration() { - if (!disableAutomaticPublicationRegistrationDeprecationWarning) { - this.project.logger.warn "WARNING: This project is still using 'changelog.disableAutomaticPublicationRegistration'. It has been deprecated and will be removed in GradleUtils 3.0. Consider using 'changelog.publishAll = false' instead." - disableAutomaticPublicationRegistrationDeprecationWarning = true - } - - this.publishAll = false - } - - void publish(MavenPublication publication) { - ChangelogUtils.setupChangelogGenerationForPublishing this.project, publication - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.java b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.java new file mode 100644 index 0000000..a3def52 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtension.java @@ -0,0 +1,22 @@ +package net.minecraftforge.gradleutils.changelog; + +import org.gradle.api.publish.maven.MavenPublication; +import org.jetbrains.annotations.Nullable; + +public sealed interface ChangelogExtension permits ChangelogExtensionImpl { + String NAME = "changelog"; + + default void fromBase() { + this.from(null); + } + + void from(@Nullable String marker); + + boolean isGenerating(); + + void publish(MavenPublication publication); + + boolean isPublishAll(); + + void setPublishAll(boolean publishAll); +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtensionImpl.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtensionImpl.groovy new file mode 100644 index 0000000..30e8aea --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogExtensionImpl.groovy @@ -0,0 +1,69 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils.changelog + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import groovy.transform.PackageScopeTarget +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.annotations.Nullable + +/** The heart of the Changelog plugin. This extension is used to enable and partially configure the changelog generation task. */ +@CompileStatic +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.CONSTRUCTORS, PackageScopeTarget.FIELDS]) +final class ChangelogExtensionImpl implements ChangelogExtension { + public static final String NAME = 'changelog' + + final Project project + + private final Property publishingAll + private final Property isGenerating + + private @Lazy TaskProvider task = { + this.isGenerating.set true + this.project.afterEvaluate { project -> + if (this.publishAll) + ChangelogUtils.setupChangelogGenerationOnAllPublishTasks project + } + + ChangelogUtils.setupChangelogTask this.project + }() + + ChangelogExtensionImpl(Project project, ObjectFactory objects) { + this.project = project + + this.publishingAll = objects.property(Boolean).convention false + this.isGenerating = objects.property(Boolean).convention false + } + + @Override + void from(@Nullable String marker) { + this.task.configure { it.start.set marker } + } + + @Override + boolean isGenerating() { + this.isGenerating.getOrElse false + } + + @Override + void publish(MavenPublication publication) { + ChangelogUtils.setupChangelogGenerationForPublishing this.project, publication + } + + @Override + boolean isPublishAll() { + this.publishingAll.getOrElse false + } + + @Override + void setPublishAll(boolean publishAll) { + this.publishingAll.set publishAll + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.groovy deleted file mode 100644 index 2d7e386..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.groovy +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils.changelog - -import groovy.transform.CompileStatic -import org.gradle.api.Plugin -import org.gradle.api.Project - -/** The entry point for the Changelog plugin. Exists to create the {@linkplain ChangelogExtension extension}. */ -@CompileStatic -abstract class ChangelogPlugin implements Plugin { - @Override - void apply(Project project) { - // TODO [GradleUtils][3.0][Changelog] Use direct constructor - project.extensions.create ChangelogExtension.NAME, ChangelogExtension, project - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.java b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.java new file mode 100644 index 0000000..f71acbf --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogPlugin.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils.changelog; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.model.ObjectFactory; + +import javax.inject.Inject; + +abstract class ChangelogPlugin implements Plugin { + @Inject + public ChangelogPlugin() { } + + @Override + public void apply(Project project) { + project.getExtensions().add(ChangelogExtension.class, ChangelogExtension.NAME, new ChangelogExtensionImpl( + project, + this.getObjects() + )); + } + + protected abstract @Inject ObjectFactory getObjects(); +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogUtils.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogUtils.groovy index d4df55b..c28fb52 100644 --- a/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogUtils.groovy +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/ChangelogUtils.groovy @@ -7,8 +7,6 @@ package net.minecraftforge.gradleutils.changelog import groovy.transform.CompileStatic import groovy.transform.PackageScope import groovy.transform.PackageScopeTarget -import net.minecraftforge.gradleutils.GradleUtils -import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.PublishingExtension @@ -19,7 +17,7 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin /** Utility methods for configuring and working with the changelog tasks. */ @CompileStatic -@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.METHODS]) +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.CONSTRUCTORS, PackageScopeTarget.METHODS]) class ChangelogUtils { /** * Adds the createChangelog task to the target project. Also exposes it as a artifact of the 'createChangelog' @@ -32,12 +30,11 @@ class ChangelogUtils { * @param project Project to add the task to * @return The task responsible for generating the changelog */ - static TaskProvider setupChangelogTask(Project project, Action> action) { + static TaskProvider setupChangelogTask(Project project) { project.tasks.register(GenerateChangelog.NAME, GenerateChangelog).tap { task -> project.configurations.register(GenerateChangelog.NAME) { it.canBeResolved = false } project.artifacts.add(GenerateChangelog.NAME, task) - project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure { it.dependsOn(task) } - action.execute task + project.tasks.named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).configure { it.dependsOn task } } } @@ -78,9 +75,9 @@ class ChangelogUtils { } } - private static ChangelogExtension findParent(Project project) { + private static ChangelogExtensionImpl findParent(Project project) { var ext = project.extensions.findByType(ChangelogExtension) - if (ext?.isGenerating) return ext + if (ext?.generating) return ext as ChangelogExtensionImpl var parent = project.parent == project ? null : project.parent return parent == null ? null : findParent(parent) @@ -106,7 +103,8 @@ class ChangelogUtils { project.tasks.register(CopyChangelog.NAME, CopyChangelog) { task -> var dependency = project.dependencies.project('path': parent.project.path, 'configuration': GenerateChangelog.NAME) - task.configuration.set project.configurations.detachedConfiguration(dependency).tap { it.canBeConsumed = false } + var configuration = project.configurations.detachedConfiguration(dependency).tap { it.canBeConsumed = false } + task.inputFile.fileProvider project.providers.provider { configuration.singleFile } } } @@ -117,25 +115,23 @@ class ChangelogUtils { * @param publication The publication in question */ static void setupChangelogGenerationForPublishing(Project project, MavenPublication publication) { - GradleUtils.ensureAfterEvaluate(project) { - setupChangelogGenerationForPublishingAfterEvaluation(it, publication) + Util.ensureAfterEvaluate(project) { p -> + setupChangelogGenerationForPublishingAfterEvaluation p, publication } } private static void setupChangelogGenerationForPublishingAfterEvaluation(Project project, MavenPublication publication) { - boolean existing = !publication.artifacts.findAll { MavenArtifact it -> it.classifier == 'changelog' && it.extension == 'txt' }.isEmpty() + boolean existing = !publication.artifacts.findAll { MavenArtifact a -> a.classifier == 'changelog' && a.extension == 'txt' }.isEmpty() if (existing) return // Grab the task - var task = findChangelogTask(project) + var task = findChangelogTask project // Add a new changelog artifact and publish it - publication.artifact(task.get().outputs.files.singleFile) { - it.builtBy(task) - it.classifier = 'changelog' - it.extension = 'txt' + publication.artifact(task.get().outputs.files.singleFile) { artifact -> + artifact.builtBy task + artifact.classifier = 'changelog' + artifact.extension = 'txt' } } - - private ChangelogUtils() {} } diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.groovy deleted file mode 100644 index abb9693..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils.changelog - -import groovy.transform.CompileStatic -import org.gradle.api.DefaultTask -import org.gradle.api.file.FileCollection -import org.gradle.api.file.ProjectLayout -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -import javax.inject.Inject - -/** This task copies a changelog file to this project's build directory.*/ -@CompileStatic -abstract class CopyChangelog extends DefaultTask { - public static final String NAME = 'copyChangelog' - - @Inject abstract ProjectLayout getLayout() - - CopyChangelog() { - this.description = "Copies a changelog file to this project's build directory." - - this.outputFile.convention this.layout.buildDirectory.file('changelog.txt') - } - - /** The output file for the copied changelog. */ - abstract @OutputFile RegularFileProperty getOutputFile() - /** The configuration (or file collection) containing the changelog to copy. It must be a single file. */ - abstract @InputFiles Property getConfiguration() - - @TaskAction - void exec() { - var input = this.configuration.get().singleFile - var output = this.outputFile.get().asFile - if (!output.parentFile.exists()) - output.parentFile.mkdirs() - output.bytes = input.bytes - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.java b/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.java new file mode 100644 index 0000000..f9757fd --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/CopyChangelog.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils.changelog; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +// This task class is internal. Do NOT attempt to use it directly. +// If you need the ouput, use `project.tasks.named('copyChangelog').outputs.files` instead +abstract class CopyChangelog extends DefaultTask { + static final String NAME = "copyChangelog"; + + private final ProviderFactory providers; + + @Inject + public CopyChangelog(ProjectLayout layout, ProviderFactory providers) { + this.providers = providers; + + this.setDescription("Copies a changelog file to this project's build directory."); + + this.getOutputFile().convention(layout.getBuildDirectory().file("changelog.txt")); + } + + public abstract @OutputFile RegularFileProperty getOutputFile(); + + public abstract @InputFile RegularFileProperty getInputFile(); + + @TaskAction + public void exec() { + byte[] input; + try { + // ProviderFactory#fileContents so Gradle is aware of our usage of the input + input = this.providers.fileContents(this.getInputFile()).getAsBytes().get(); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } + + File output = this.getOutputFile().get().getAsFile(); + + if (!output.getParentFile().exists() && !output.getParentFile().mkdirs()) + throw new IllegalStateException(); + + try { + Files.write( + output.toPath(), + input + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.groovy b/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.groovy deleted file mode 100644 index 180df10..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.groovy +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils.changelog - -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import net.minecraftforge.gitver.api.GitVersion -import net.minecraftforge.gitver.api.GitVersionException -import org.gradle.api.DefaultTask -import org.gradle.api.configuration.BuildFeatures -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.ProjectLayout -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Property -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskAction - -import javax.inject.Inject - -/** This task generates a changelog for the project based on the Git history using Git Version. */ -@CompileStatic -abstract class GenerateChangelog extends DefaultTask { - @PackageScope static final String NAME = 'createChangelog' - - @Inject abstract ObjectFactory getObjects() - @Inject abstract ProjectLayout getLayout() - @Inject abstract ProviderFactory getProviders() - @Inject abstract BuildFeatures getBuildFeatures() - - GenerateChangelog() { - this.description = 'Generates a changelog for the project based on the Git history using Git Version.' - - //Setup defaults: Using merge-base based text changelog generation of the local project into build/changelog.txt - this.outputFile.convention this.layout.buildDirectory.file('changelog/changelog.txt') - - this.gitDirectory.convention this.objects.directoryProperty().fileProvider(this.providers.provider { GitVersion.findGitRoot(this.layout.projectDirectory.asFile) }).dir('.git') - this.projectPath.convention this.providers.provider { GitVersion.findRelativePath(this.layout.projectDirectory.asFile) } - this.buildMarkdown.convention false - } - - /** The output file for the changelog. */ - abstract @OutputFile RegularFileProperty getOutputFile() - /** The {@code .git} directory to base the Git Version off of. */ - abstract @InputDirectory @PathSensitive(PathSensitivity.NONE) DirectoryProperty getGitDirectory() - /** The path string of the project from the root. Used to configure Git Version without needing to specify the directory itself. */ - abstract @Input Property getProjectPath() - /** The tag (or object ID) to start the changelog from. */ - abstract @Input @Optional Property getStart() - /** The project URL to use in the changelog. Will attempt to find a URL from Git Version if unspecified. */ - abstract @Input @Optional Property getProjectUrl() - /** Whether to build the changelog in markdown format. */ - abstract @Input Property getBuildMarkdown() - - @TaskAction - void exec() throws IOException { - // If we are using the configuration cache, disable the system config since it calls the git command line tool - if (this.buildFeatures.configurationCache.active.getOrElse(false)) - GitVersion.disableSystemConfig() - - var gitDir = this.gitDirectory.asFile.get() - try (var version = GitVersion.builder().gitDir(gitDir).project(new File(gitDir.absoluteFile.parentFile, this.projectPath.get())).build()) { - var changelog = version.generateChangelog(this.start.orNull, this.projectUrl.orNull, !this.buildMarkdown.get()) - - var file = outputFile.asFile.get() - if (!file.parentFile.exists()) - file.parentFile.mkdirs() - - file.setText(changelog, 'UTF8') - } catch (GitVersionException e) { - this.logger.error 'ERROR: Failed to generate the changelog for this project, likely due to a misconfiguration. GitVersion has caught the exception, the details of which are attached to this error. Check that the correct tags are being used, or updating the tag prefix accordingly.' - throw e - } catch (IOException e) { - this.logger.error 'ERROR: Changelog was generated successfully, but could not be written to the disk. Ensure that you have write permissions to the output directory.' - throw new RuntimeException(e) - } - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.java b/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.java new file mode 100644 index 0000000..3ffd9f6 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/GenerateChangelog.java @@ -0,0 +1,144 @@ +package net.minecraftforge.gradleutils.changelog; + +import net.minecraftforge.gitver.api.GitVersion; +import net.minecraftforge.gitver.api.GitVersionException; +import org.gradle.api.DefaultTask; +import org.gradle.api.configuration.BuildFeatures; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +/** + * Generates a changelog for the project based on the Git history using + * Git Version. + */ +public abstract class GenerateChangelog extends DefaultTask { + /** The name for the task, used by the {@linkplain ChangelogExtension extension} when registering it. */ + public static final String NAME = "createChangelog"; + + private final BuildFeatures buildFeatures; + + /** + * Constructs a new task instance. + *

This constructor is invoked by Gradle when used with + * {@link org.gradle.api.tasks.TaskContainer#register(String, Class)} to {@linkplain Inject inject} the required + * services used by this taks.

+ * + * @param objects The object factory used to create properties + * @param layout The project layout used to resolve project path from the Git root for Git Version + * @param providers The provider factory used to assist in creating properties + * @param buildFeatures The build features for configuration cache awareness + */ + @Inject + GenerateChangelog(ObjectFactory objects, ProjectLayout layout, ProviderFactory providers, BuildFeatures buildFeatures) { + this.buildFeatures = buildFeatures; + + this.setDescription("Generates a changelog for the project based on the Git history using Git Version."); + + //Setup defaults: Using merge-base based text changelog generation of the local project into build/changelog.txt + this.getOutputFile().convention(layout.getBuildDirectory().file("changelog.txt")); + + this.getGitDirectory().convention(objects.directoryProperty().fileProvider(providers.provider( + () -> GitVersion.findGitRoot(layout.getProjectDirectory().getAsFile())) + ).dir(".git")); + this.getProjectPath().convention(providers.provider(() -> { + var root = this.getGitDirectory().get().getAsFile().toPath(); + var project = layout.getProjectDirectory().getAsFile().toPath(); + return root.relativize(project).toString().replace(root.getFileSystem().getSeparator(), "/"); + })); + this.getBuildMarkdown().convention(false); + } + + /** + * The output file for the changelog. + * + * @return A property for the output file + */ + public abstract @OutputFile RegularFileProperty getOutputFile(); + + /** + * The {@code .git} directory to base the Git Version off of. + *

Git Version will automatically attempt to locate this using {@link GitVersion#findGitRoot(File)} if left + * unspecified.

+ * + * @return A property for the Git directory + */ + public abstract @InputDirectory @PathSensitive(PathSensitivity.ABSOLUTE) DirectoryProperty getGitDirectory(); + + /** + * The path string of the project from the root. + *

Used to configure Git Version without needing to specify the directory itself, since using the directory + * itself can cause implicit dependencies on other tasks that use actually it.

+ * + * @return A property for the project path + */ + public abstract @Input Property getProjectPath(); + + /** + * The tag (or object ID) to start the changelog from. + * + * @return A property for the start tag + */ + public abstract @Input @Optional Property getStart(); + + /** + * The project URL to use in the changelog. + *

Git Version will automatically attempt to find a URL from the repository's remote details if left + * unspecified.

+ * + * @return A property for the project URL + */ + public abstract @Input @Optional Property getProjectUrl(); + + /** + * Whether to build the changelog in Markdown format. + * + * @return A property for Markdown formatting + */ + public abstract @Input Property getBuildMarkdown(); + + /** Executes the task to generate the changelog. */ + @TaskAction + public void exec() { + // If we are using the configuration cache, disable the system config since it calls the git command line tool + if (this.buildFeatures.getConfigurationCache().getActive().getOrElse(false)) + GitVersion.disableSystemConfig(); + + var gitDir = this.getGitDirectory().getAsFile().get(); + try (var version = GitVersion.builder().gitDir(gitDir).project(new File(gitDir.getAbsoluteFile().getParentFile(), this.getProjectPath().get())).build()) { + var changelog = version.generateChangelog(this.getStart().getOrNull(), this.getProjectUrl().getOrNull(), !this.getBuildMarkdown().get()); + + var file = this.getOutputFile().get().getAsFile(); + if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) + throw new IllegalStateException(); + + Files.writeString( + file.toPath(), + changelog, + StandardCharsets.UTF_8 + ); + } catch (GitVersionException e) { + this.getLogger().error("ERROR: Failed to generate the changelog for this project, likely due to a misconfiguration. GitVersion has caught the exception, the details of which are attached to this error. Check that the correct tags are being used, or updating the tag prefix accordingly."); + throw e; + } catch (IOException e) { + this.getLogger().error("ERROR: Changelog was generated successfully, but could not be written to the disk. Ensure that you have write permissions to the output directory."); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/changelog/Util.java b/src/main/groovy/net/minecraftforge/gradleutils/changelog/Util.java new file mode 100644 index 0000000..06d9095 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/changelog/Util.java @@ -0,0 +1,13 @@ +package net.minecraftforge.gradleutils.changelog; + +import org.gradle.api.Action; +import org.gradle.api.Project; + +interface Util { + static void ensureAfterEvaluate(Project project, Action action) { + if (project.getState().getExecuted()) + action.execute(project); + else + project.afterEvaluate(action); + } +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.java b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.java new file mode 100644 index 0000000..b6b0dd2 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.java @@ -0,0 +1,69 @@ +package net.minecraftforge.gradleutils.gitversion; + +import net.minecraftforge.gitver.api.GitVersion; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * The extension for the Git Version plugin. This is the main interface for interacting with Git Version in your script. + *

By design, this is a mirror of the {@link net.minecraftforge.gitver.api.GitVersion} interface. The actual Git + * Version object is hidden from public consumption, and is delegated to by this interface.

+ */ +public interface GitVersionExtension { + String NAME = "gitversion"; + + + /* VERSION NUMBER */ + + String getTagOffset(); + + String getTagOffsetBranch(); + + String getTagOffsetBranch(String... allowedBranches); + + String getTagOffsetBranch(Collection allowedBranches); + + String getMCTagOffsetBranch(String mcVersion); + + String getMCTagOffsetBranch(String mcVersion, String... allowedBranches); + + String getMCTagOffsetBranch(String mcVersion, Collection allowedBranches); + + + /* INFO */ + + GitVersion.Info getInfo(); + + @Nullable String getUrl(); + + + /* FILE SYSTEM */ + + DirectoryProperty getGitDir(); + + DirectoryProperty getRootDir(); + + DirectoryProperty getProjectDir(); + + Property getProjectPath(); + + Provider getRelativePath(FileSystemLocation file); + + Provider getRelativePath(Provider file); + + + /* SUBPROJECTS */ + + List getSubprojects(); + + ListProperty getSubprojectPaths(); + + ListProperty getSubprojectPaths(boolean fromRoot); +} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.groovy b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtensionImpl.groovy similarity index 50% rename from src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.groovy rename to src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtensionImpl.groovy index 81aecbd..83c22a8 100644 --- a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtension.groovy +++ b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionExtensionImpl.groovy @@ -6,48 +6,35 @@ package net.minecraftforge.gradleutils.gitversion import groovy.transform.CompileStatic import groovy.transform.PackageScope +import groovy.transform.PackageScopeTarget import net.minecraftforge.gitver.api.GitVersion import net.minecraftforge.gitver.api.GitVersionException -import org.gradle.api.Project import org.gradle.api.configuration.BuildFeatures import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemLocation -import org.gradle.api.file.ProjectLayout +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory -import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nullable -import javax.inject.Inject - -/** - * The heart of the Git Version Gradle plugin. This extension is responsible for creating the GitVersion object and - * allowing access to it from Gradle buildscripts. - *

When using Gradle's Configuration Cache, the system Git config is disabled.

- */ @CompileStatic -@SuppressWarnings('GrDeprecatedAPIUsage') -class GitVersionExtension { - public static final String NAME = 'gitversion' +@PackageScope([PackageScopeTarget.CLASS, PackageScopeTarget.CONSTRUCTORS]) +class GitVersionExtensionImpl implements GitVersionExtension { + private static final Logger LOGGER = Logging.getLogger GitVersionExtension - private final Project project + private final Directory projectDirectory private final ObjectFactory objects - private final ProjectLayout layout private final ProviderFactory providers private final BuildFeatures buildFeatures - /** @deprecated This constructor will be made package-private in GradleUtils 3.0 */ - @Inject - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - GitVersionExtension(Project project, ObjectFactory objects, ProjectLayout layout, ProviderFactory providers, BuildFeatures buildFeatures) { - this.project = project + GitVersionExtensionImpl(Directory projectDirectory, ObjectFactory objects, ProviderFactory providers, BuildFeatures buildFeatures) { + this.projectDirectory = projectDirectory this.objects = objects - this.layout = layout this.providers = providers this.buildFeatures = buildFeatures } @@ -55,77 +42,63 @@ class GitVersionExtension { /* GIT VERSION */ - @Deprecated(forRemoval = true) - @ApiStatus.ScheduledForRemoval(inVersion = '3.0') - @PackageScope @Lazy GitVersion versionInternal = { + private @Lazy GitVersion version = { // If we are using the configuration cache, disable the system config since it calls the git command line tool if (this.buildFeatures.configurationCache.active.getOrElse(false)) GitVersion.disableSystemConfig() - var builder = GitVersion.builder().project(this.layout.projectDirectory.asFile) + var builder = GitVersion.builder().project this.projectDirectory.asFile try { return builder.build().tap { it.info } } catch (GitVersionException ignored) { - this.project.logger.warn 'WARNING: Git Version failed to get version numbers! Attempting to use default version 0.0.0. Check your GitVersion config file and make sure the correct tag prefix and filters are in use. Ensure that the tags you are attempting to use exist in the repository.' + LOGGER.warn 'WARNING: Git Version failed to get version numbers! Attempting to use default version 0.0.0. Check your GitVersion config file and make sure the correct tag prefix and filters are in use. Ensure that the tags you are attempting to use exist in the repository.' return builder.strict(false).build() } catch (IllegalArgumentException e) { - this.project.logger.error 'ERROR: Git Version is misconfigured and cannot be used, likely due to incorrect paths being set. This is an unrecoverable problem and needs to be addressed in the config file. Ensure that the correct subprojects and paths are declared in the config file' + LOGGER.error 'ERROR: Git Version is misconfigured and cannot be used, likely due to incorrect paths being set. This is an unrecoverable problem and needs to be addressed in the config file. Ensure that the correct subprojects and paths are declared in the config file' throw e } }() - // TODO [GradleUtils][3.0] Make private - private static boolean deprecationWarning - @Deprecated(forRemoval = true) - GitVersion getVersion() { - if (!deprecationWarning) { - this.project.logger.warn "WARNING: The usage of 'gitversion.version' has been deprecated and will be removed in GradleUtils 3.0. Please remove the 'version' call (i.e. 'gitversion.version.tagOffset' -> 'gitversion.tagOffset')." - deprecationWarning = true - } - - this.versionInternal - } - /* VERSION NUMBER */ String getTagOffset() { - this.versionInternal.tagOffset + this.version.tagOffset } String getTagOffsetBranch() { - this.versionInternal.tagOffsetBranch + this.version.tagOffsetBranch } String getTagOffsetBranch(String... allowedBranches) { - this.versionInternal.getTagOffsetBranch allowedBranches + this.version.getTagOffsetBranch allowedBranches } String getTagOffsetBranch(Collection allowedBranches) { - this.versionInternal.getTagOffsetBranch allowedBranches + this.version.getTagOffsetBranch allowedBranches } String getMCTagOffsetBranch(String mcVersion) { - this.versionInternal.getMCTagOffsetBranch mcVersion + this.version.getMCTagOffsetBranch mcVersion } String getMCTagOffsetBranch(String mcVersion, String... allowedBranches) { - this.versionInternal.getMCTagOffsetBranch mcVersion, allowedBranches + this.version.getMCTagOffsetBranch mcVersion, allowedBranches } String getMCTagOffsetBranch(String mcVersion, Collection allowedBranches) { - this.versionInternal.getMCTagOffsetBranch mcVersion, allowedBranches + this.version.getMCTagOffsetBranch mcVersion, allowedBranches } /* INFO */ GitVersion.Info getInfo() { - this.versionInternal.info + this.version.info } @Nullable String getUrl() { - this.versionInternal.url + this.version.url } @@ -133,25 +106,25 @@ class GitVersionExtension { @Lazy DirectoryProperty gitDir = { this.objects.directoryProperty().fileProvider(this.providers.provider { - this.versionInternal.gitDir + this.version.gitDir }) }() @Lazy DirectoryProperty rootDir = { this.objects.directoryProperty().fileProvider(this.providers.provider { - this.versionInternal.root + this.version.root }) }() @Lazy DirectoryProperty projectDir = { this.objects.directoryProperty().fileProvider(this.providers.provider { - this.versionInternal.project + this.version.project }) }() @Lazy Property projectPath = { this.objects.property(String).value(this.providers.provider { - this.versionInternal.projectPath + this.version.projectPath }) }() @@ -161,30 +134,28 @@ class GitVersionExtension { Provider getRelativePath(Provider file) { this.providers.provider { - this.versionInternal.getRelativePath file.get().asFile + this.version.getRelativePath file.get().asFile } } /* SUBPROJECTS */ - @Lazy ListProperty subprojects = { - this.objects.listProperty(Directory).value(this.providers.provider { - this.versionInternal.subprojects.collect { - dir -> this.layout.dir(this.providers.provider { dir }).get() - } - }) + @Lazy List subprojects = { + this.version.subprojects.collect { + dir -> this.objects.directoryProperty().fileProvider(this.providers.provider { dir }) + } }() private @Lazy ListProperty subprojectPathsFromRoot = { this.objects.listProperty(String).value(this.providers.provider { - this.versionInternal.getSubprojectPaths true + this.version.getSubprojectPaths true }) }() @Lazy ListProperty subprojectPaths = { this.objects.listProperty(String).value(this.providers.provider { - this.versionInternal.subprojectPaths + this.version.subprojectPaths }) }() diff --git a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.groovy b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.groovy deleted file mode 100644 index 6d068a2..0000000 --- a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.groovy +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.gradleutils.gitversion - -import groovy.transform.CompileStatic -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.configuration.BuildFeatures -import org.gradle.api.file.ProjectLayout -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ProviderFactory - -import javax.inject.Inject - -/** The entry point for the Git Version Gradle plugin. Exists to create the {@linkplain GitVersionExtension extension}. */ -@CompileStatic -abstract class GitVersionPlugin implements Plugin { - /** @see ObjectFactory Service Injection */ - @Inject abstract ObjectFactory getObjects() - /** @see ProjectLayout Service Injection */ - @Inject abstract ProjectLayout getLayout() - /** @see ProviderFactory Service Injection */ - @Inject abstract ProviderFactory getProviders() - /** @see Reacting to build features */ - @Inject abstract BuildFeatures getBuildFeatures() - - @Override - void apply(Project project) { - // TODO [GradleUtils][3.0][GitVersion] Use direct constructor - project.extensions.create(GitVersionExtension.NAME, GitVersionExtension, project, this.objects, this.layout, this.providers, this.buildFeatures) - } -} diff --git a/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.java b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.java new file mode 100644 index 0000000..84abac5 --- /dev/null +++ b/src/main/groovy/net/minecraftforge/gradleutils/gitversion/GitVersionPlugin.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradleutils.gitversion; + +import groovy.transform.CompileStatic; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.configuration.BuildFeatures; +import org.gradle.api.file.BuildLayout; +import org.gradle.api.file.Directory; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.invocation.Gradle; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.PluginAware; +import org.gradle.api.problems.Problems; +import org.gradle.api.provider.ProviderFactory; + +import javax.inject.Inject; + +abstract class GitVersionPlugin implements Plugin { + @Inject + public GitVersionPlugin() { } + + @Override + public void apply(T target) { + Directory projectDirectory; + if (target instanceof Project) + projectDirectory = this.getProjectLayout().getProjectDirectory(); + else if (target instanceof Gradle) + projectDirectory = this.getBuildLayout().getRootDirectory(); + else + throw new IllegalStateException("Cannot determine project directory"); + + target.getExtensions().add(GitVersionExtension.class, GitVersionExtension.NAME, new GitVersionExtensionImpl( + projectDirectory, + this.getObjects(), + this.getProviders(), + this.getBuildFeatures() + )); + } + + protected @Inject Problems getProblems() { + return injectFailed(); + } + + protected @Inject ObjectFactory getObjects() { + return injectFailed(); + } + + protected @Inject ProjectLayout getProjectLayout() { + return injectFailed(); + } + + protected @Inject BuildLayout getBuildLayout() { + return injectFailed(); + } + + protected @Inject ProviderFactory getProviders() { + return injectFailed(); + } + + protected @Inject BuildFeatures getBuildFeatures() { + return injectFailed(); + } + + private static T injectFailed() { + throw new IllegalStateException("Cannot use in current context"); + } +}