Browse Source

RFC: Test that example plugins build stand-alone (#32235)

Add tests for build-tools to make sure example plugins build stand-alone using it.

This will catch issues such as referencing files from the buildSrc directly, breaking external uses of build-tools.
Alpar Torok 7 years ago
parent
commit
d16562eab5

+ 8 - 0
build.gradle

@@ -87,8 +87,15 @@ subprojects {
           }
           }
         }
         }
       }
       }
+      repositories {
+        maven {
+          name = 'localTest'
+          url = "${rootProject.buildDir}/local-test-repo"
+        }
+      }
     }
     }
   }
   }
+
   plugins.withType(BuildPlugin).whenPluginAdded {
   plugins.withType(BuildPlugin).whenPluginAdded {
     project.licenseFile = project.rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
     project.licenseFile = project.rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
     project.noticeFile = project.rootProject.file('NOTICE.txt')
     project.noticeFile = project.rootProject.file('NOTICE.txt')
@@ -228,6 +235,7 @@ subprojects {
     "org.elasticsearch.client:elasticsearch-rest-high-level-client:${version}": ':client:rest-high-level',
     "org.elasticsearch.client:elasticsearch-rest-high-level-client:${version}": ':client:rest-high-level',
     "org.elasticsearch.client:test:${version}": ':client:test',
     "org.elasticsearch.client:test:${version}": ':client:test',
     "org.elasticsearch.client:transport:${version}": ':client:transport',
     "org.elasticsearch.client:transport:${version}": ':client:transport',
+    "org.elasticsearch.plugin:elasticsearch-scripting-painless-spi:${version}": ':modules:lang-painless:spi',
     "org.elasticsearch.test:framework:${version}": ':test:framework',
     "org.elasticsearch.test:framework:${version}": ':test:framework',
     "org.elasticsearch.distribution.integ-test-zip:elasticsearch:${version}": ':distribution:archives:integ-test-zip',
     "org.elasticsearch.distribution.integ-test-zip:elasticsearch:${version}": ':distribution:archives:integ-test-zip',
     "org.elasticsearch.distribution.zip:elasticsearch:${version}": ':distribution:archives:zip',
     "org.elasticsearch.distribution.zip:elasticsearch:${version}": ':distribution:archives:zip',

+ 13 - 0
buildSrc/build.gradle

@@ -162,11 +162,24 @@ if (project != rootProject) {
   // it's fine as we run them as part of :buildSrc
   // it's fine as we run them as part of :buildSrc
   test.enabled = false
   test.enabled = false
   task integTest(type: Test) {
   task integTest(type: Test) {
+    // integration test requires the local testing repo for example plugin builds
+    dependsOn project.rootProject.allprojects.collect {
+      it.tasks.matching { it.name == 'publishNebulaPublicationToLocalTestRepository'}
+    }
     exclude "**/*Tests.class"
     exclude "**/*Tests.class"
     include "**/*IT.class"
     include "**/*IT.class"
     testClassesDirs = sourceSets.test.output.classesDirs
     testClassesDirs = sourceSets.test.output.classesDirs
     classpath = sourceSets.test.runtimeClasspath
     classpath = sourceSets.test.runtimeClasspath
     inputs.dir(file("src/testKit"))
     inputs.dir(file("src/testKit"))
+    // tell BuildExamplePluginsIT where to find the example plugins
+    systemProperty (
+            'test.build-tools.plugin.examples',
+            files(
+                    project(':example-plugins').subprojects.collect { it.projectDir }
+            ).asPath,
+    )
+    systemProperty 'test.local-test-repo-path', "${rootProject.buildDir}/local-test-repo"
+    systemProperty 'test.lucene-snapshot-revision', (versions.lucene =~ /\w+-snapshot-([a-z0-9]+)/)[0][1]
   }
   }
   check.dependsOn(integTest)
   check.dependsOn(integTest)
 
 

+ 1 - 2
buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

@@ -554,7 +554,7 @@ class BuildPlugin implements Plugin<Project> {
                 project.publishing {
                 project.publishing {
                     publications {
                     publications {
                         nebula(MavenPublication) {
                         nebula(MavenPublication) {
-                            artifact project.tasks.shadowJar
+                            artifacts = [ project.tasks.shadowJar ]
                             artifactId = project.archivesBaseName
                             artifactId = project.archivesBaseName
                             /*
                             /*
                             * Configure the pom to include the "shadow" as compile dependencies
                             * Configure the pom to include the "shadow" as compile dependencies
@@ -584,7 +584,6 @@ class BuildPlugin implements Plugin<Project> {
                 }
                 }
             }
             }
         }
         }
-
     }
     }
 
 
     /** Adds compiler settings to the project */
     /** Adds compiler settings to the project */

+ 0 - 2
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginBuildPlugin.groovy

@@ -25,7 +25,6 @@ import org.elasticsearch.gradle.NoticeTask
 import org.elasticsearch.gradle.test.RestIntegTestTask
 import org.elasticsearch.gradle.test.RestIntegTestTask
 import org.elasticsearch.gradle.test.RunTask
 import org.elasticsearch.gradle.test.RunTask
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.InvalidUserDataException
-import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.Task
 import org.gradle.api.XmlProvider
 import org.gradle.api.XmlProvider
@@ -39,7 +38,6 @@ import java.nio.file.Path
 import java.nio.file.StandardCopyOption
 import java.nio.file.StandardCopyOption
 import java.util.regex.Matcher
 import java.util.regex.Matcher
 import java.util.regex.Pattern
 import java.util.regex.Pattern
-
 /**
 /**
  * Encapsulates build configuration for an Elasticsearch plugin.
  * Encapsulates build configuration for an Elasticsearch plugin.
  */
  */

+ 26 - 4
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesExtension.groovy

@@ -20,6 +20,7 @@ package org.elasticsearch.gradle.plugin
 
 
 import org.gradle.api.Project
 import org.gradle.api.Project
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
 
 
 /**
 /**
  * A container for plugin properties that will be written to the plugin descriptor, for easy
  * A container for plugin properties that will be written to the plugin descriptor, for easy
@@ -55,18 +56,39 @@ class PluginPropertiesExtension {
     boolean requiresKeystore = false
     boolean requiresKeystore = false
 
 
     /** A license file that should be included in the built plugin zip. */
     /** A license file that should be included in the built plugin zip. */
-    @Input
-    File licenseFile = null
+    private File licenseFile = null
 
 
     /**
     /**
      * A notice file that should be included in the built plugin zip. This will be
      * A notice file that should be included in the built plugin zip. This will be
      * extended with notices from the {@code licenses/} directory.
      * extended with notices from the {@code licenses/} directory.
      */
      */
-    @Input
-    File noticeFile = null
+    private File noticeFile = null
+
+    Project project = null
 
 
     PluginPropertiesExtension(Project project) {
     PluginPropertiesExtension(Project project) {
         name = project.name
         name = project.name
         version = project.version
         version = project.version
+        this.project = project
+    }
+
+    @InputFile
+    File getLicenseFile() {
+        return licenseFile
+    }
+
+    void setLicenseFile(File licenseFile) {
+        project.ext.licenseFile = licenseFile
+        this.licenseFile = licenseFile
+    }
+
+    @InputFile
+    File getNoticeFile() {
+        return noticeFile
+    }
+
+    void setNoticeFile(File noticeFile) {
+        project.ext.noticeFile = noticeFile
+        this.noticeFile = noticeFile
     }
     }
 }
 }

+ 0 - 1
buildSrc/src/main/groovy/org/elasticsearch/gradle/plugin/PluginPropertiesTask.groovy

@@ -23,7 +23,6 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Task
 import org.gradle.api.Task
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.OutputFile
-
 /**
 /**
  * Creates a plugin descriptor.
  * Creates a plugin descriptor.
  */
  */

+ 7 - 4
buildSrc/src/main/groovy/org/elasticsearch/gradle/test/RestIntegTestTask.groovy

@@ -31,6 +31,7 @@ import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.TaskState
 import org.gradle.api.tasks.TaskState
+import org.gradle.plugins.ide.idea.IdeaPlugin
 
 
 import java.nio.charset.StandardCharsets
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Files
@@ -243,10 +244,12 @@ public class RestIntegTestTask extends DefaultTask {
                 }
                 }
             }
             }
         }
         }
-        project.idea {
-            module {
-                if (scopes.TEST != null) {
-                    scopes.TEST.plus.add(project.configurations.restSpec)
+        if (project.plugins.hasPlugin(IdeaPlugin)) {
+            project.idea {
+                module {
+                    if (scopes.TEST != null) {
+                        scopes.TEST.plus.add(project.configurations.restSpec)
+                    }
                 }
                 }
             }
             }
         }
         }

+ 164 - 0
buildSrc/src/test/java/org/elasticsearch/gradle/BuildExamplePluginsIT.java

@@ -0,0 +1,164 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.gradle;
+
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+import org.apache.commons.io.FileUtils;
+import org.elasticsearch.gradle.test.GradleIntegrationTestCase;
+import org.gradle.testkit.runner.GradleRunner;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class BuildExamplePluginsIT extends GradleIntegrationTestCase {
+
+    private static List<File> EXAMPLE_PLUGINS = Collections.unmodifiableList(
+        Arrays.stream(
+            Objects.requireNonNull(System.getProperty("test.build-tools.plugin.examples"))
+                .split(File.pathSeparator)
+        ).map(File::new).collect(Collectors.toList())
+    );
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    public final File examplePlugin;
+
+    public BuildExamplePluginsIT(File examplePlugin) {
+        this.examplePlugin = examplePlugin;
+    }
+
+    @BeforeClass
+    public static void assertProjectsExist() {
+        assertEquals(
+            EXAMPLE_PLUGINS,
+            EXAMPLE_PLUGINS.stream().filter(File::exists).collect(Collectors.toList())
+        );
+    }
+
+    @ParametersFactory
+    public static Iterable<Object[]> parameters() {
+        return EXAMPLE_PLUGINS
+            .stream()
+            .map(each -> new Object[] {each})
+            .collect(Collectors.toList());
+    }
+
+    public void testCurrentExamplePlugin() throws IOException {
+        FileUtils.copyDirectory(examplePlugin, tmpDir.getRoot());
+        // just get rid of deprecation warnings
+        Files.write(
+            getTempPath("settings.gradle"),
+            "enableFeaturePreview('STABLE_PUBLISHING')\n".getBytes(StandardCharsets.UTF_8)
+        );
+
+        adaptBuildScriptForTest();
+
+        Files.write(
+            tmpDir.newFile("NOTICE.txt").toPath(),
+            "dummy test notice".getBytes(StandardCharsets.UTF_8)
+        );
+
+        GradleRunner.create()
+            .withProjectDir(tmpDir.getRoot())
+            .withArguments("clean", "check", "-s", "-i", "--warning-mode=all", "--scan")
+            .withPluginClasspath()
+            .build();
+    }
+
+    private void adaptBuildScriptForTest() throws IOException {
+        // Add the local repo as a build script URL so we can pull in build-tools and apply the plugin under test
+        // + is ok because we have no other repo and just want to pick up latest
+        writeBuildScript(
+            "buildscript {\n" +
+                "    repositories {\n" +
+                "        maven {\n" +
+                "            url = '" + getLocalTestRepoPath() + "'\n" +
+                "        }\n" +
+                "    }\n" +
+                "    dependencies {\n" +
+                "        classpath \"org.elasticsearch.gradle:build-tools:+\"\n" +
+                "    }\n" +
+                "}\n"
+        );
+        // get the original file
+        Files.readAllLines(getTempPath("build.gradle"), StandardCharsets.UTF_8)
+            .stream()
+            .map(line -> line + "\n")
+            .forEach(this::writeBuildScript);
+        // Add a repositories section to be able to resolve dependencies
+        String luceneSnapshotRepo = "";
+        String luceneSnapshotRevision = System.getProperty("test.lucene-snapshot-revision");
+        if (luceneSnapshotRepo != null) {
+            luceneSnapshotRepo =  "  maven {\n" +
+                "    url \"http://s3.amazonaws.com/download.elasticsearch.org/lucenesnapshots/" + luceneSnapshotRevision + "\"\n" +
+                "  }\n";
+        }
+        writeBuildScript("\n" +
+                "repositories {\n" +
+                "  maven {\n" +
+                "    url \"" + getLocalTestRepoPath()  + "\"\n" +
+                "  }\n" +
+                luceneSnapshotRepo +
+                "}\n"
+        );
+        Files.delete(getTempPath("build.gradle"));
+        Files.move(getTempPath("build.gradle.new"), getTempPath("build.gradle"));
+        System.err.print("Generated build script is:");
+        Files.readAllLines(getTempPath("build.gradle")).forEach(System.err::println);
+    }
+
+    private Path getTempPath(String fileName) {
+        return new File(tmpDir.getRoot(), fileName).toPath();
+    }
+
+    private Path writeBuildScript(String script) {
+        try {
+            Path path = getTempPath("build.gradle.new");
+            return Files.write(
+                path,
+                script.getBytes(StandardCharsets.UTF_8),
+                Files.exists(path) ? StandardOpenOption.APPEND : StandardOpenOption.CREATE_NEW
+            );
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getLocalTestRepoPath() {
+        String property = System.getProperty("test.local-test-repo-path");
+        Objects.requireNonNull(property, "test.local-test-repo-path not passed to tests");
+        File file = new File(property);
+        assertTrue("Expected " + property + " to exist, but it did not!", file.exists());
+        return file.getAbsolutePath();
+    }
+
+}

+ 2 - 1
plugins/examples/custom-settings/build.gradle

@@ -16,13 +16,14 @@
  * specific language governing permissions and limitations
  * specific language governing permissions and limitations
  * under the License.
  * under the License.
  */
  */
-
 apply plugin: 'elasticsearch.esplugin'
 apply plugin: 'elasticsearch.esplugin'
 
 
 esplugin {
 esplugin {
   name 'custom-settings'
   name 'custom-settings'
   description 'An example plugin showing how to register custom settings'
   description 'An example plugin showing how to register custom settings'
   classname 'org.elasticsearch.example.customsettings.ExampleCustomSettingsPlugin'
   classname 'org.elasticsearch.example.customsettings.ExampleCustomSettingsPlugin'
+  licenseFile rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
+  noticeFile rootProject.file('NOTICE.txt')
 }
 }
 
 
 integTestCluster {
 integTestCluster {

+ 3 - 2
plugins/examples/painless-whitelist/build.gradle

@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * specific language governing permissions and limitations
  * under the License.
  * under the License.
  */
  */
-
 apply plugin: 'elasticsearch.esplugin'
 apply plugin: 'elasticsearch.esplugin'
 
 
 esplugin {
 esplugin {
@@ -24,10 +23,12 @@ esplugin {
   description 'An example whitelisting additional classes and methods in painless'
   description 'An example whitelisting additional classes and methods in painless'
   classname 'org.elasticsearch.example.painlesswhitelist.MyWhitelistPlugin'
   classname 'org.elasticsearch.example.painlesswhitelist.MyWhitelistPlugin'
   extendedPlugins = ['lang-painless']
   extendedPlugins = ['lang-painless']
+  licenseFile rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
+  noticeFile rootProject.file('NOTICE.txt')
 }
 }
 
 
 dependencies {
 dependencies {
-  compileOnly project(':modules:lang-painless')
+  compileOnly "org.elasticsearch.plugin:elasticsearch-scripting-painless-spi:${versions.elasticsearch}"
 }
 }
 
 
 if (System.getProperty('tests.distribution') == null) {
 if (System.getProperty('tests.distribution') == null) {

+ 3 - 1
plugins/examples/rescore/build.gradle

@@ -16,11 +16,13 @@
  * specific language governing permissions and limitations
  * specific language governing permissions and limitations
  * under the License.
  * under the License.
  */
  */
-
 apply plugin: 'elasticsearch.esplugin'
 apply plugin: 'elasticsearch.esplugin'
 
 
 esplugin {
 esplugin {
   name 'example-rescore'
   name 'example-rescore'
   description 'An example plugin implementing rescore and verifying that plugins *can* implement rescore'
   description 'An example plugin implementing rescore and verifying that plugins *can* implement rescore'
   classname 'org.elasticsearch.example.rescore.ExampleRescorePlugin'
   classname 'org.elasticsearch.example.rescore.ExampleRescorePlugin'
+  licenseFile rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
+  noticeFile rootProject.file('NOTICE.txt')
 }
 }
+

+ 3 - 2
plugins/examples/rest-handler/build.gradle

@@ -16,13 +16,14 @@
  * specific language governing permissions and limitations
  * specific language governing permissions and limitations
  * under the License.
  * under the License.
  */
  */
-
 apply plugin: 'elasticsearch.esplugin'
 apply plugin: 'elasticsearch.esplugin'
 
 
 esplugin {
 esplugin {
   name 'rest-handler'
   name 'rest-handler'
   description 'An example plugin showing how to register a REST handler'
   description 'An example plugin showing how to register a REST handler'
   classname 'org.elasticsearch.example.resthandler.ExampleRestHandlerPlugin'
   classname 'org.elasticsearch.example.resthandler.ExampleRestHandlerPlugin'
+  licenseFile rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
+  noticeFile rootProject.file('NOTICE.txt')
 }
 }
 
 
 // No unit tests in this example
 // No unit tests in this example
@@ -40,4 +41,4 @@ integTestCluster {
 }
 }
 integTestRunner {
 integTestRunner {
   systemProperty 'external.address', "${ -> exampleFixture.addressAndPort }"
   systemProperty 'external.address', "${ -> exampleFixture.addressAndPort }"
-}
+}

+ 3 - 1
plugins/examples/script-expert-scoring/build.gradle

@@ -16,13 +16,15 @@
  * specific language governing permissions and limitations
  * specific language governing permissions and limitations
  * under the License.
  * under the License.
  */
  */
-
 apply plugin: 'elasticsearch.esplugin'
 apply plugin: 'elasticsearch.esplugin'
 
 
 esplugin {
 esplugin {
   name 'script-expert-scoring'
   name 'script-expert-scoring'
   description 'An example script engine to use low level Lucene internals for expert scoring'
   description 'An example script engine to use low level Lucene internals for expert scoring'
   classname 'org.elasticsearch.example.expertscript.ExpertScriptPlugin'
   classname 'org.elasticsearch.example.expertscript.ExpertScriptPlugin'
+  licenseFile rootProject.file('licenses/APACHE-LICENSE-2.0.txt')
+  noticeFile rootProject.file('NOTICE.txt')
 }
 }
 
 
 test.enabled = false
 test.enabled = false
+