Simplify build plugin license handling (#77009)

- Use file property and conventions to avoid afterEvaluate hook
- Simplify root build script
- One little step closer to configuration cache compliance
This commit is contained in:
Rene Groeschke 2021-10-07 13:01:24 +02:00 committed by GitHub
parent 9e271001bc
commit dd4e4c3ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 115 additions and 117 deletions

View File

@ -15,7 +15,6 @@ import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.initialization.layout.BuildLayout;
import javax.inject.Inject;
import java.io.File;
@ -29,7 +28,7 @@ class GitInfoPlugin implements Plugin<Project> {
private Property<GitInfo> gitInfo;
@Inject
public GitInfoPlugin(ProviderFactory factory, ObjectFactory objectFactory) {
GitInfoPlugin(ProviderFactory factory, ObjectFactory objectFactory) {
this.factory = factory;
this.objectFactory = objectFactory;
}

View File

@ -11,17 +11,15 @@ package org.elasticsearch.gradle.internal.conventions;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.Callable;
public class LicensingPlugin implements Plugin<Project> {
final static String ELASTIC_LICENSE_URL_PREFIX = "https://raw.githubusercontent.com/elastic/elasticsearch/";
final static String ELASTIC_LICENSE_URL_POSTFIX = "/licenses/ELASTIC-LICENSE-2.0.txt";
static final String ELASTIC_LICENSE_URL_PREFIX = "https://raw.githubusercontent.com/elastic/elasticsearch/";
static final String ELASTIC_LICENSE_URL_POSTFIX = "/licenses/ELASTIC-LICENSE-2.0.txt";
private ProviderFactory providerFactory;
@ -34,24 +32,23 @@ public class LicensingPlugin implements Plugin<Project> {
public void apply(Project project) {
Provider<String> revision = project.getRootProject().getPlugins().apply(GitInfoPlugin.class).getRevision();
Provider<String> licenseCommitProvider = providerFactory.provider(() ->
isSnapshotVersion(project) ? revision.get() : "v" + project.getVersion().toString()
isSnapshotVersion(project) ? revision.get() : "v" + project.getVersion()
);
MapProperty<String, String> licensesProperty = project.getObjects().mapProperty(String.class, String.class);
Provider<String> projectLicenseURL = licenseCommitProvider.map(licenseCommit -> ELASTIC_LICENSE_URL_PREFIX +
licenseCommit + ELASTIC_LICENSE_URL_POSTFIX);
// But stick the Elastic license url in project.ext so we can get it if we need to switch to it
project.getExtensions().getExtraProperties().set("elasticLicenseUrl", projectLicenseURL);
MapProperty<String, String> convention = licensesProperty.convention(
providerFactory.provider((Callable<Map<? extends String, ? extends String>>) () -> Map.of(
MapProperty<String, String> licensesProperty = project.getObjects().mapProperty(String.class, String.class).convention(
providerFactory.provider(() -> Map.of(
"Server Side Public License, v 1", "https://www.mongodb.com/licensing/server-side-public-license",
"Elastic License 2.0", projectLicenseURL.get())
)
);
// Default to the SSPL+Elastic dual license
project.getExtensions().getExtraProperties().set("licenseCommit", licenseCommitProvider);
project.getExtensions().getExtraProperties().set("projectLicenses", convention);
project.getExtensions().getExtraProperties().set("projectLicenses", licensesProperty);
}
private boolean isSnapshotVersion(Project project) {

View File

@ -277,8 +277,8 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest {
// requires elasticsearch artifact available
tasks.named('bundlePlugin').configure { enabled = false }
licenseFile = file('license.txt')
noticeFile = file('notice.txt')
licenseFile.set(file('license.txt'))
noticeFile.set(file('notice.txt'))
version = "1.0"
group = 'org.acme'
"""
@ -352,8 +352,8 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest {
// requires elasticsearch artifact available
tasks.named('bundlePlugin').configure { enabled = false }
licenseFile = file('license.txt')
noticeFile = file('notice.txt')
licenseFile.set(file('license.txt'))
noticeFile.set(file('notice.txt'))
version = "2.0"
group = 'org.acme'
"""

View File

@ -8,6 +8,7 @@
package org.elasticsearch.gradle.internal;
import org.apache.commons.io.IOUtils;
import org.elasticsearch.gradle.VersionProperties;
import org.elasticsearch.gradle.internal.test.GradleIntegrationTestCase;
import org.gradle.testkit.runner.BuildResult;
import org.junit.Rule;
@ -47,8 +48,9 @@ public class BuildPluginIT extends GradleIntegrationTestCase {
public void testLicenseAndNotice() throws IOException {
BuildResult result = getGradleRunner().withArguments("clean", "assemble").build();
assertTaskSuccessful(result, ":assemble");
assertBuildFileExists(result, projectName(), "distributions/elasticsearch.build.jar");
try (ZipFile zipFile = new ZipFile(new File(getBuildDir(projectName()), "distributions/elasticsearch.build.jar"))) {
String expectedFilePath = "distributions/elasticsearch.build.jar";
assertBuildFileExists(result, projectName(), expectedFilePath);
try (ZipFile zipFile = new ZipFile(new File(getBuildDir(projectName()), expectedFilePath))) {
ZipEntry licenseEntry = zipFile.getEntry("META-INF/LICENSE.txt");
ZipEntry noticeEntry = zipFile.getEntry("META-INF/NOTICE.txt");
assertNotNull("Jar does not have META-INF/LICENSE.txt", licenseEntry);

View File

@ -10,7 +10,6 @@ package org.elasticsearch.gradle.internal.test;
import java.io.File;
import java.io.IOException;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;

View File

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import org.elasticsearch.gradle.VersionProperties
project.setDescription("Elasticsearch subproject " + project.getPath());
// common maven publishing configuration
project.setGroup("org.elasticsearch");
project.setVersion(VersionProperties.getElasticsearch())

View File

@ -8,29 +8,46 @@
package org.elasticsearch.gradle.internal;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.elasticsearch.gradle.internal.info.GlobalBuildInfoPlugin;
import org.elasticsearch.gradle.internal.precommit.InternalPrecommitTasks;
import org.elasticsearch.gradle.internal.precommit.JarHellPrecommitPlugin;
import org.elasticsearch.gradle.internal.precommit.SplitPackagesAuditPrecommitPlugin;
import org.gradle.api.GradleException;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.initialization.layout.BuildLayout;
import javax.inject.Inject;
import java.io.File;
/**
* Encapsulates build configuration for elasticsearch projects.
*/
public class BuildPlugin implements Plugin<Project> {
public static final String SSPL_LICENSE_PATH = "licenses/SSPL-1.0+ELASTIC-LICENSE-2.0.txt";
private final BuildLayout buildLayout;
private final ObjectFactory objectFactory;
private final ProviderFactory providerFactory;
private final ProjectLayout projectLayout;
@Inject
BuildPlugin(BuildLayout buildLayout, ObjectFactory objectFactory, ProviderFactory providerFactory, ProjectLayout projectLayout){
this.buildLayout = buildLayout;
this.objectFactory = objectFactory;
this.providerFactory = providerFactory;
this.projectLayout = projectLayout;
}
@Override
public void apply(final Project project) {
// make sure the global build info plugin is applied to the root project
project.getRootProject().getPluginManager().apply(GlobalBuildInfoPlugin.class);
if (project.getPluginManager().hasPlugin("elasticsearch.standalone-rest-test")) {
throw new InvalidUserDataException(
"elasticsearch.standalone-test, " + "elasticsearch.standalone-rest-test, and elasticsearch.build are mutually exclusive"
@ -38,28 +55,34 @@ public class BuildPlugin implements Plugin<Project> {
}
project.getPluginManager().apply("elasticsearch.java");
configureLicenseAndNotice(project);
project.getPluginManager().apply("elasticsearch.publish");
project.getPluginManager().apply(DependenciesInfoPlugin.class);
project.getPluginManager().apply(DependenciesGraphPlugin.class);
InternalPrecommitTasks.create(project, true);
configureLicenseAndNotice(project);
}
public static void configureLicenseAndNotice(final Project project) {
public void configureLicenseAndNotice(final Project project) {
final ExtraPropertiesExtension ext = project.getExtensions().getByType(ExtraPropertiesExtension.class);
ext.set("licenseFile", null);
ext.set("noticeFile", null);
// add license/notice files
project.afterEvaluate(p -> p.getTasks().withType(Jar.class).configureEach(jar -> {
if (ext.has("licenseFile") == false
|| ext.get("licenseFile") == null
|| ext.has("noticeFile") == false
|| ext.get("noticeFile") == null) {
throw new GradleException("Must specify license and notice file for project " + p.getPath());
RegularFileProperty licenseFileProperty = objectFactory.fileProperty();
RegularFileProperty noticeFileProperty = objectFactory.fileProperty();
ext.set("licenseFile", licenseFileProperty);
ext.set("noticeFile", noticeFileProperty);
configureLicenseDefaultConvention(licenseFileProperty);
configureNoticeDefaultConvention(noticeFileProperty);
updateJarTasksMetaInf(project);
}
final File licenseFile = DefaultGroovyMethods.asType(ext.get("licenseFile"), File.class);
final File noticeFile = DefaultGroovyMethods.asType(ext.get("noticeFile"), File.class);
private void updateJarTasksMetaInf(Project project) {
final ExtraPropertiesExtension ext = project.getExtensions().getByType(ExtraPropertiesExtension.class);
project.getTasks().withType(Jar.class).configureEach(jar -> {
final RegularFileProperty licenseFileExtProperty = (RegularFileProperty) ext.get("licenseFile");
final RegularFileProperty noticeFileExtProperty = (RegularFileProperty) ext.get("noticeFile");
File licenseFile = licenseFileExtProperty.getAsFile().get();
File noticeFile = noticeFileExtProperty.getAsFile().get();
jar.metaInf(spec -> {
spec.from(licenseFile.getParent(), from -> {
from.include(licenseFile.getName());
@ -70,6 +93,16 @@ public class BuildPlugin implements Plugin<Project> {
from.rename(s -> "NOTICE.txt");
});
});
}));
});
}
private void configureLicenseDefaultConvention(RegularFileProperty licenseFileProperty) {
File licenseFileDefault = new File(buildLayout.getRootDirectory(), SSPL_LICENSE_PATH);
licenseFileProperty.convention(projectLayout.file(providerFactory.provider(() -> licenseFileDefault)));
}
private void configureNoticeDefaultConvention(RegularFileProperty noticeFileProperty) {
File noticeFileDefault = new File(buildLayout.getRootDirectory(), "NOTICE.txt");
noticeFileProperty.convention(projectLayout.file(providerFactory.provider(() -> noticeFileDefault)));
}
}

View File

@ -5,8 +5,8 @@ plugins {
apply plugin:'elasticsearch.build'
ext.licenseFile = file("LICENSE")
ext.noticeFile = file("NOTICE")
licenseFile.set(file("LICENSE"))
noticeFile.set(file("NOTICE"))
dependencies {
api "junit:junit:${versions.junit}"

View File

@ -9,13 +9,11 @@ allprojects {
repositories {
mavenCentral()
}
dependencies {
testImplementation "junit:junit:4.12"
}
ext.licenseFile = file("$buildDir/dummy/license")
ext.noticeFile = file("$buildDir/dummy/notice")
testingConventions.naming {
// Reset default to no baseClass checks
Tests {

View File

@ -9,6 +9,9 @@
package org.elasticsearch.gradle.plugin;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import java.io.File;
import java.util.ArrayList;
@ -138,7 +141,11 @@ public class PluginPropertiesExtension {
}
public void setLicenseFile(File licenseFile) {
this.project.getExtensions().getExtraProperties().set("licenseFile", licenseFile);
ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties();
RegularFileProperty regularFileProperty = extraProperties.has("licenseFile") ?
(RegularFileProperty)extraProperties.get("licenseFile") :
project.getObjects().fileProperty();
regularFileProperty.set(licenseFile);
this.licenseFile = licenseFile;
}
@ -147,7 +154,11 @@ public class PluginPropertiesExtension {
}
public void setNoticeFile(File noticeFile) {
this.project.getExtensions().getExtraProperties().set("noticeFile", noticeFile);
ExtraPropertiesExtension extraProperties = this.project.getExtensions().getExtraProperties();
RegularFileProperty regularFileProperty = extraProperties.has("noticeFile") ?
(RegularFileProperty)extraProperties.get("noticeFile") :
project.getObjects().fileProperty();
regularFileProperty.set(noticeFile);
this.noticeFile = noticeFile;
}

View File

@ -70,6 +70,7 @@ public abstract class GradleIntegrationTestCase extends GradleUnitTestCase {
.withProjectDir(getProjectDir())
.withPluginClasspath()
.withTestKitDir(testkit)
.forwardOutput()
.withDebug(ManagementFactory.getRuntimeMXBean().getInputArguments().toString().indexOf("-agentlib:jdwp") > 0));
}

View File

@ -10,38 +10,11 @@ package org.elasticsearch.gradle.internal.test;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Collectors;
import static org.junit.Assert.fail;
public class TestUtils {
public static void setupJarJdkClasspath(File projectRoot) {
try {
URL originLocation = TestUtils.class.getClassLoader()
.loadClass("org.elasticsearch.jdk.JdkJarHellCheck")
.getProtectionDomain()
.getCodeSource()
.getLocation();
File targetFile = new File(
projectRoot,
"sample_jars/build/testrepo/org/elasticsearch/elasticsearch-core/current/elasticsearch-core-current.jar"
);
targetFile.getParentFile().mkdirs();
Path originalPath = Paths.get(originLocation.toURI());
Files.copy(originalPath, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (ClassNotFoundException | URISyntaxException | IOException e) {
e.printStackTrace();
fail("Cannot setup jdk jar hell classpath");
}
}
public static String normalizeString(String input, File projectRootDir) {
try {
String normalizedPathPrefix = projectRootDir.getCanonicalPath().replaceAll("\\\\", "/");

View File

@ -49,13 +49,6 @@ plugins {
id "com.diffplug.spotless" version "5.15.1" apply false
}
String licenseCommit
if (VersionProperties.elasticsearch.toString().endsWith('-SNAPSHOT')) {
licenseCommit = BuildParams.gitRevision ?: "master" // leniency for non git builds
} else {
licenseCommit = "v${version}"
}
/**
* This is a convenient method for declaring test artifact dependencies provided by the internal
* test artifact plugin. It replaces basically the longer dependency notation with explicit capability
@ -168,20 +161,13 @@ if (project.gradle.startParameter.taskNames.find { it.startsWith("checkPart") }
}
subprojects {
// common maven publishing configuration
group = 'org.elasticsearch'
version = VersionProperties.elasticsearch
description = "Elasticsearch subproject ${project.path}"
apply plugin: 'elasticsearch.base'
}
allprojects {
// We disable this plugin for now till we shaked out the issues we see
// e.g. see https://github.com/elastic/elasticsearch/issues/72169
// apply plugin:'elasticsearch.internal-test-rerun'
plugins.withType(BuildPlugin).whenPluginAdded {
project.licenseFile = project.rootProject.file('licenses/SSPL-1.0+ELASTIC-LICENSE-2.0.txt')
project.noticeFile = project.rootProject.file('NOTICE.txt')
}
plugins.withType(InternalPluginBuildPlugin).whenPluginAdded {
project.dependencies {

View File

@ -20,7 +20,7 @@ group = 'org.elasticsearch.client'
archivesBaseName = 'elasticsearch-rest-high-level-client'
// HLRC is published under the Elastic License
ext.projectLicenses.set(['Elastic License 2.0': ext.elasticLicenseUrl.get()])
projectLicenses.set(['Elastic License 2.0': ext.elasticLicenseUrl.get()])
restResources {
//we need to copy the yaml spec so we can check naming (see RestHighlevelClientTests#testApiNamingConventions)

View File

@ -29,8 +29,8 @@ group = 'org.elasticsearch.client'
archivesBaseName = 'elasticsearch-rest-client'
// LLRC is licenses under Apache 2.0
ext.projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
ext.licenseFile = rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
licenseFile.set(rootProject.file('licenses/APACHE-LICENSE-2.0.txt'))
dependencies {
api "org.apache.httpcomponents:httpclient:${versions.httpclient}"

View File

@ -28,8 +28,8 @@ group = 'org.elasticsearch.client'
archivesBaseName = 'elasticsearch-rest-client-sniffer'
// rest client sniffer is licenses under Apache 2.0
ext.projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
ext.licenseFile = rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
licenseFile.set(rootProject.file('licenses/APACHE-LICENSE-2.0.txt'))
dependencies {
api project(":client:rest")

View File

@ -15,8 +15,8 @@ sourceCompatibility = JavaVersion.VERSION_1_8
group = "${group}.client.test"
// rest client sniffer is licenses under Apache 2.0
ext.projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
ext.licenseFile = rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
licenseFile.set(rootProject.file('licenses/APACHE-LICENSE-2.0.txt'))
dependencies {
api "org.apache.httpcomponents:httpcore:${versions.httpcore}"

View File

@ -15,7 +15,7 @@ restResources {
// REST API specifications are published under the Apache 2.0 License
ext.projectLicenses.set(['The Apache Software License, Version 2.0': 'http://www.apache.org/licenses/LICENSE-2.0'])
ext.licenseFile = rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
licenseFile.set(rootProject.file('licenses/APACHE-LICENSE-2.0.txt'))
configurations {
// configuration to make use by external yaml rest test plugin in our examples

View File

@ -36,10 +36,12 @@ subprojects {
additionalLicense 'ELAST', 'Elastic License 2.0', '2.0; you may not use this file except in compliance with the Elastic License'
}
project.getPluginManager().withPlugin("elasticsearch.licensing") {
project.pluginManager.withPlugin("elasticsearch.licensing") {
ext.projectLicenses.set(['Elastic License 2.0': ext.elasticLicenseUrl.get()])
}
project.ext.licenseFile = rootProject.file('licenses/ELASTIC-LICENSE-2.0.txt')
project.ext.noticeFile = xpackRootProject.file('NOTICE.txt')
project.pluginManager.withPlugin("elasticsearch.build") {
project.ext.licenseFile.set(rootProject.file('licenses/ELASTIC-LICENSE-2.0.txt'))
project.ext.noticeFile.set(xpackRootProject.file('NOTICE.txt'))
}
}

View File

@ -30,23 +30,6 @@ artifacts {
restXpackTests(new File(projectDir, "src/yamlRestTest/resources/rest-api-spec/test"))
}
tasks.named("testJar").configure {
/*
* Stick the license and notice file in the jar. This isn't strictly
* needed because we don't publish it but it makes our super-paranoid
* tests happy.
*/
metaInf {
from(project.licenseFile.parent) {
include project.licenseFile.name
rename { 'LICENSE.txt' }
}
from(project.noticeFile.parent) {
include project.noticeFile.name
}
}
}
// location for keys and certificates
File extraResourceDir = file("$buildDir/extra_resource")
File nodeKey = file("$extraResourceDir/testnode.pem")