diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 9ffba81ee4cad..b765b0cad6dd9 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -217,7 +217,7 @@ if (project != rootProject) { forbiddenPatterns { exclude '**/*.wav' // the file that actually defines nocommit - exclude '**/ForbiddenPatternsTask.groovy' + exclude '**/ForbiddenPatternsTask.java' } namingConventions { diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.groovy deleted file mode 100644 index e574d67f2ace1..0000000000000 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.groovy +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.precommit - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.InvalidUserDataException -import org.gradle.api.file.FileCollection -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.util.PatternFilterable -import org.gradle.api.tasks.util.PatternSet - -import java.util.regex.Pattern - -/** - * Checks for patterns in source files for the project which are forbidden. - */ -public class ForbiddenPatternsTask extends DefaultTask { - - /** The rules: a map from the rule name, to a rule regex pattern. */ - private Map patterns = new LinkedHashMap<>() - /** A pattern set of which files should be checked. */ - private PatternFilterable filesFilter = new PatternSet() - - @OutputFile - File outputMarker = new File(project.buildDir, "markers/forbiddenPatterns") - - public ForbiddenPatternsTask() { - description = 'Checks source files for invalid patterns like nocommits or tabs' - - // we always include all source files, and exclude what should not be checked - filesFilter.include('**') - // exclude known binary extensions - filesFilter.exclude('**/*.gz') - filesFilter.exclude('**/*.ico') - filesFilter.exclude('**/*.jar') - filesFilter.exclude('**/*.zip') - filesFilter.exclude('**/*.jks') - filesFilter.exclude('**/*.crt') - filesFilter.exclude('**/*.png') - - // add mandatory rules - patterns.put('nocommit', /nocommit|NOCOMMIT/) - patterns.put('nocommit should be all lowercase or all uppercase', - /((?i)nocommit)(? props) { - String name = props.remove('name') - if (name == null) { - throw new InvalidUserDataException('Missing [name] for invalid pattern rule') - } - String pattern = props.remove('pattern') - if (pattern == null) { - throw new InvalidUserDataException('Missing [pattern] for invalid pattern rule') - } - if (props.isEmpty() == false) { - throw new InvalidUserDataException("Unknown arguments for ForbiddenPatterns rule mapping: ${props.keySet()}") - } - // TODO: fail if pattern contains a newline, it won't work (currently) - patterns.put(name, pattern) - } - - /** Returns the files this task will check */ - @InputFiles - FileCollection files() { - List collections = new ArrayList<>() - for (SourceSet sourceSet : project.sourceSets) { - collections.add(sourceSet.allSource.matching(filesFilter)) - } - return project.files(collections.toArray()) - } - - @TaskAction - void checkInvalidPatterns() { - Pattern allPatterns = Pattern.compile('(' + patterns.values().join(')|(') + ')') - List failures = new ArrayList<>() - for (File f : files()) { - f.eachLine('UTF-8') { String line, int lineNumber -> - if (allPatterns.matcher(line).find()) { - addErrorMessages(failures, f, line, lineNumber) - } - } - } - if (failures.isEmpty() == false) { - throw new GradleException('Found invalid patterns:\n' + failures.join('\n')) - } - outputMarker.setText('done', 'UTF-8') - } - - // iterate through patterns to find the right ones for nice error messages - void addErrorMessages(List failures, File f, String line, int lineNumber) { - String path = project.getRootProject().projectDir.toURI().relativize(f.toURI()).toString() - for (Map.Entry pattern : patterns.entrySet()) { - if (Pattern.compile(pattern.value).matcher(line).find()) { - failures.add('- ' + pattern.key + ' on line ' + lineNumber + ' of ' + path) - } - } - } -} diff --git a/buildSrc/src/main/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.java b/buildSrc/src/main/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.java new file mode 100644 index 0000000000000..d68985ff17ab6 --- /dev/null +++ b/buildSrc/src/main/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTask.java @@ -0,0 +1,161 @@ +/* + * 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.precommit; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileTree; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.util.PatternFilterable; +import org.gradle.api.tasks.util.PatternSet; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * Checks for patterns in source files for the project which are forbidden. + */ +public class ForbiddenPatternsTask extends DefaultTask { + + /* + * A pattern set of which files should be checked. + */ + private final PatternFilterable filesFilter = new PatternSet() + // we always include all source files, and exclude what should not be checked + .include("**") + // exclude known binary extensions + .exclude("**/*.gz") + .exclude("**/*.ico") + .exclude("**/*.jar") + .exclude("**/*.zip") + .exclude("**/*.jks") + .exclude("**/*.crt") + .exclude("**/*.png"); + + /* + * The rules: a map from the rule name, to a rule regex pattern. + */ + private final Map patterns = new HashMap<>(); + + public ForbiddenPatternsTask() { + setDescription("Checks source files for invalid patterns like nocommits or tabs"); + getInputs().property("excludes", filesFilter.getExcludes()); + getInputs().property("rules", patterns); + + // add mandatory rules + patterns.put("nocommit", "nocommit|NOCOMMIT"); + patterns.put("nocommit should be all lowercase or all uppercase", "((?i)nocommit)(? sourceSet.getAllSource().matching(filesFilter)) + .reduce(FileTree::plus) + .orElse(getProject().files().getAsFileTree()); + } + + @TaskAction + public void checkInvalidPatterns() throws IOException { + Pattern allPatterns = Pattern.compile("(" + String.join(")|(", getPatterns().values()) + ")"); + List failures = new ArrayList<>(); + for (File f : files()) { + List lines; + try(Stream stream = Files.lines(f.toPath(), StandardCharsets.UTF_8)) { + lines = stream.collect(Collectors.toList()); + } catch (UncheckedIOException e) { + throw new IllegalArgumentException("Failed to read " + f + " as UTF_8", e); + } + List invalidLines = IntStream.range(0, lines.size()) + .filter(i -> allPatterns.matcher(lines.get(i)).find()) + .boxed() + .collect(Collectors.toList()); + + String path = getProject().getRootProject().getProjectDir().toURI().relativize(f.toURI()).toString(); + failures = invalidLines.stream() + .map(l -> new AbstractMap.SimpleEntry<>(l+1, lines.get(l))) + .flatMap(kv -> patterns.entrySet().stream() + .filter(p -> Pattern.compile(p.getValue()).matcher(kv.getValue()).find()) + .map(p -> "- " + p.getKey() + " on line " + kv.getKey() + " of " + path) + ) + .collect(Collectors.toList()); + } + if (failures.isEmpty() == false) { + throw new GradleException("Found invalid patterns:\n" + String.join("\n", failures)); + } + + File outputMarker = getOutputMarker(); + outputMarker.getParentFile().mkdirs(); + Files.write(outputMarker.toPath(), "done".getBytes(StandardCharsets.UTF_8)); + } + + @OutputFile + public File getOutputMarker() { + return new File(getProject().getBuildDir(), "markers/" + getName()); + } + + @Input + public Map getPatterns() { + return Collections.unmodifiableMap(patterns); + } + + public void exclude(String... excludes) { + filesFilter.exclude(excludes); + } + + public void rule(Map props) { + String name = props.remove("name"); + if (name == null) { + throw new InvalidUserDataException("Missing [name] for invalid pattern rule"); + } + String pattern = props.remove("pattern"); + if (pattern == null) { + throw new InvalidUserDataException("Missing [pattern] for invalid pattern rule"); + } + if (props.isEmpty() == false) { + throw new InvalidUserDataException("Unknown arguments for ForbiddenPatterns rule mapping: " + + props.keySet().toString()); + } + // TODO: fail if pattern contains a newline, it won't work (currently) + patterns.put(name, pattern); + } +} diff --git a/buildSrc/src/test/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTaskTests.java b/buildSrc/src/test/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTaskTests.java new file mode 100644 index 0000000000000..7d61cb46e77ea --- /dev/null +++ b/buildSrc/src/test/java/org/elasticsearch/gradle/precommit/ForbiddenPatternsTaskTests.java @@ -0,0 +1,111 @@ +package org.elasticsearch.gradle.precommit; + +import org.elasticsearch.gradle.test.GradleUnitTestCase; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.testfixtures.ProjectBuilder; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class ForbiddenPatternsTaskTests extends GradleUnitTestCase { + + public void testCheckInvalidPatternsWhenNoSourceFilesExist() throws Exception { + Project project = createProject(); + ForbiddenPatternsTask task = createTask(project); + + checkAndAssertTaskSuccessful(task); + } + + public void testCheckInvalidPatternsWhenSourceFilesExistNoViolation() throws Exception { + Project project = createProject(); + ForbiddenPatternsTask task = createTask(project); + + writeSourceFile(project, "src/main/java/Foo.java", "public void bar() {}"); + checkAndAssertTaskSuccessful(task); + } + + public void testCheckInvalidPatternsWhenSourceFilesExistHavingTab() throws Exception { + Project project = createProject(); + ForbiddenPatternsTask task = createTask(project); + + writeSourceFile(project, "src/main/java/Bar.java", "\tpublic void bar() {}"); + checkAndAssertTaskThrowsException(task); + } + + public void testCheckInvalidPatternsWithCustomRule() throws Exception { + Map rule = new HashMap<>(); + rule.put("name", "TODO comments are not allowed"); + rule.put("pattern", "\\/\\/.*(?i)TODO"); + + Project project = createProject(); + ForbiddenPatternsTask task = createTask(project); + task.rule(rule); + + writeSourceFile(project, "src/main/java/Moot.java", "GOOD LINE", "//todo", "// some stuff, toDo"); + checkAndAssertTaskThrowsException(task); + } + + public void testCheckInvalidPatternsWhenExcludingFiles() throws Exception { + Project project = createProject(); + ForbiddenPatternsTask task = createTask(project); + task.exclude("**/*.java"); + + writeSourceFile(project, "src/main/java/FooBarMoot.java", "\t"); + checkAndAssertTaskSuccessful(task); + } + + private Project createProject() { + Project project = ProjectBuilder.builder().build(); + project.getPlugins().apply(JavaPlugin.class); + + return project; + } + + private ForbiddenPatternsTask createTask(Project project) { + return project.getTasks().create("forbiddenPatterns", ForbiddenPatternsTask.class); + } + + private ForbiddenPatternsTask createTask(Project project, String taskName) { + return project.getTasks().create(taskName, ForbiddenPatternsTask.class); + } + + private void writeSourceFile(Project project, String name, String... lines) throws IOException { + File file = new File(project.getProjectDir(), name); + file.getParentFile().mkdirs(); + file.createNewFile(); + + if (lines.length != 0) + Files.write(file.toPath(), Arrays.asList(lines), StandardCharsets.UTF_8); + } + + private void checkAndAssertTaskSuccessful(ForbiddenPatternsTask task) throws IOException { + task.checkInvalidPatterns(); + assertTaskSuccessful(task.getProject(), task.getName()); + } + + private void checkAndAssertTaskThrowsException(ForbiddenPatternsTask task) throws IOException { + try { + task.checkInvalidPatterns(); + fail("GradleException was expected to be thrown in this case!"); + } catch (GradleException e) { + assertTrue(e.getMessage().startsWith("Found invalid patterns")); + } + } + + private void assertTaskSuccessful(Project project, String fileName) throws IOException { + File outputMarker = new File(project.getBuildDir(), "markers/" + fileName); + assertTrue(outputMarker.exists()); + + Optional result = Files.readAllLines(outputMarker.toPath(), StandardCharsets.UTF_8).stream().findFirst(); + assertTrue(result.isPresent()); + assertEquals("done", result.get()); + } +} diff --git a/server/build.gradle b/server/build.gradle index c879ec68d7a03..02204fef0fe07 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -156,6 +156,9 @@ compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-tr forbiddenPatterns { exclude '**/*.json' exclude '**/*.jmx' + exclude '**/*.dic' + exclude '**/*.binary' + exclude '**/*.st' } task generateModulesList { diff --git a/x-pack/qa/full-cluster-restart/build.gradle b/x-pack/qa/full-cluster-restart/build.gradle index 9504f527cdd63..c9bbce0701954 100644 --- a/x-pack/qa/full-cluster-restart/build.gradle +++ b/x-pack/qa/full-cluster-restart/build.gradle @@ -86,6 +86,9 @@ licenseHeaders { approvedLicenses << 'Apache' } +forbiddenPatterns { + exclude '**/system_key' +} /** * Subdirectories of this project are test rolling upgrades with various * configuration options based on their name. @@ -115,6 +118,10 @@ subprojects { approvedLicenses << 'Apache' } + forbiddenPatterns { + exclude '**/system_key' + } + String outputDir = "${buildDir}/generated-resources/${project.name}" // This is a top level task which we will add dependencies to below. diff --git a/x-pack/qa/rolling-upgrade/build.gradle b/x-pack/qa/rolling-upgrade/build.gradle index 68270032c8998..5e2c40564abdd 100644 --- a/x-pack/qa/rolling-upgrade/build.gradle +++ b/x-pack/qa/rolling-upgrade/build.gradle @@ -72,6 +72,10 @@ Project mainProject = project compileTestJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked" +forbiddenPatterns { + exclude '**/system_key' +} + /** * Subdirectories of this project are test rolling upgrades with various * configuration options based on their name. @@ -97,6 +101,10 @@ subprojects { } } + forbiddenPatterns { + exclude '**/system_key' + } + String outputDir = "${buildDir}/generated-resources/${project.name}" // This is a top level task which we will add dependencies to below.