├── .github ├── CODEOWNERS └── PULL_REQUEST_TEMPLATE.md ├── .circleci └── template.sh ├── src ├── test │ ├── resources │ │ └── com │ │ │ └── palantir │ │ │ └── gradle │ │ │ └── circlestyle │ │ │ ├── settings.gradle │ │ │ ├── findbugsIncludeFilter.xml │ │ │ ├── checkstyle-violating-class │ │ │ ├── tested-class │ │ │ ├── findbugs-violating-class │ │ │ ├── empty-checkstyle-report.xml │ │ │ ├── non-compiling-class │ │ │ ├── checkstyle.xml │ │ │ ├── no-failures-checkstyle.xml │ │ │ ├── tested-class-tests │ │ │ ├── subproject.gradle │ │ │ ├── two-namecheck-failures-checkstyle.xml │ │ │ ├── build.gradle │ │ │ ├── two-namecheck-failures-checkstyle-report.xml │ │ │ ├── no-errors-findbugs.xml │ │ │ ├── synthetic-sourceline-findbugs.xml │ │ │ └── two-exit-errors-findbugs.xml │ └── java │ │ └── com │ │ └── palantir │ │ └── gradle │ │ └── circlestyle │ │ ├── CheckstyleReportHandlerTests.java │ │ ├── JUnitReportCreatorTests.java │ │ ├── JavacFailuresSupplierTest.java │ │ ├── FindBugsReportHandlerTests.java │ │ ├── CircleBuildFailureListenerTests.java │ │ ├── TestCommon.java │ │ ├── CircleStyleFinalizerTests.java │ │ ├── FailuresReportGeneratorTests.java │ │ └── CircleStylePluginIntegrationTests.java └── main │ ├── resources │ └── META-INF │ │ └── gradle-plugins │ │ └── com.palantir.circle.style.properties │ └── java │ └── com │ └── palantir │ └── gradle │ └── circlestyle │ ├── FailuresSupplier.java │ ├── Failure.java │ ├── ReportHandler.java │ ├── Tasks.java │ ├── Report.java │ ├── XmlReportFailuresSupplier.java │ ├── CheckstyleReportHandler.java │ ├── StyleTaskTimer.java │ ├── XmlUtils.java │ ├── CircleBuildFinishedAction.java │ ├── JavacFailuresSupplier.java │ ├── FailuresReportGenerator.java │ ├── CircleBuildFailureListener.java │ ├── JUnitReportCreator.java │ ├── FindBugsReportHandler.java │ ├── CircleStyleFinalizer.java │ └── CircleStylePlugin.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── images └── checkstyle-circle-failure.png ├── .bulldozer.yml ├── circle.yml ├── .gitignore ├── .policy.yml ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @palantir/infrastructure 2 | -------------------------------------------------------------------------------- /.circleci/template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export CIRCLECI_TEMPLATE=java-library-oss 3 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'foobar' 2 | 3 | include 'subproject' 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palantir/gradle-circle-style/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /images/checkstyle-circle-failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palantir/gradle-circle-style/HEAD/images/checkstyle-circle-failure.png -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/com.palantir.circle.style.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.palantir.gradle.circlestyle.CircleStylePlugin 2 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/findbugsIncludeFilter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/checkstyle-violating-class: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | public class MyClass { 4 | public static final int a_constant = 100; 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/tested-class: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | public class MyClass { 4 | public static int doubleIt(int input) { 5 | return 4; 6 | } 7 | 8 | private MyClass() { } 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/FailuresSupplier.java: -------------------------------------------------------------------------------- 1 | package com.palantir.gradle.circlestyle; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | interface FailuresSupplier { 7 | List getFailures() throws IOException; 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/findbugs-violating-class: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | public class MyClass { 4 | public void methodA() { 5 | System.exit(0); 6 | } 7 | 8 | public void methodB() { 9 | System.exit(1); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.bulldozer.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | merge: 3 | whitelist: 4 | labels: ["merge when ready"] 5 | blacklist: 6 | labels: ["do not merge"] 7 | method: squash 8 | options: 9 | squash: 10 | body: pull_request_body 11 | delete_after_merge: true 12 | update: 13 | whitelist: 14 | labels: ["update me"] 15 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/empty-checkstyle-report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | environment: 3 | TERM: dumb 4 | java: 5 | version: oraclejdk8 6 | 7 | dependencies: 8 | override: 9 | - ./gradlew resolveDependencies 10 | 11 | test: 12 | override: 13 | - ./gradlew check 14 | 15 | deployment: 16 | release: 17 | tag: /v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]*)*/ 18 | commands: 19 | - ./gradlew publishPlugins 20 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/non-compiling-class: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class MyClass { 7 | 8 | private final int a = "hello"; 9 | private final int b = 1; 10 | 11 | public void methodA() { 12 | b = 2; 13 | List foo = new ArrayList(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle ### 2 | 3 | .gradle 4 | **/build/ 5 | 6 | 7 | ### Java ### 8 | 9 | *.class 10 | *.log 11 | *.zip 12 | *.tar.gz 13 | *.rar 14 | hs_err_pid* 15 | 16 | ### Eclipse ### 17 | 18 | bin/ 19 | tmp/ 20 | *.tmp 21 | *.bak 22 | *.swp 23 | *~.nib 24 | .classpath 25 | .factorypath 26 | .loadpath 27 | .metadata 28 | .recommenders 29 | .project 30 | .settings/ 31 | /generated/ 32 | 33 | 34 | ### IntelliJ ### 35 | 36 | *.iml 37 | modules.xml 38 | .idea 39 | *.ipr 40 | *.iws 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/no-failures-checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/tested-class-tests: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.Test; 6 | 7 | public class MyClassTests { 8 | 9 | @Test 10 | public void doublesTwo() { 11 | int result = MyClass.doubleIt(2); 12 | assertThat(result).isEqualTo(4); 13 | } 14 | 15 | @Test 16 | public void doublesThree() { 17 | int result = MyClass.doubleIt(3); 18 | assertThat(result).isEqualTo(6); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Before this PR 4 | 5 | 6 | ## After this PR 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/subproject.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'checkstyle' 4 | id 'findbugs' 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | sourceCompatibility = 1.7 12 | 13 | dependencies { 14 | testCompile 'junit:junit:4.12' 15 | testCompile 'org.assertj:assertj-core:2.9.0' 16 | } 17 | 18 | tasks.withType(Checkstyle) { 19 | reports { 20 | xml.enabled = false 21 | html.enabled = true 22 | } 23 | } 24 | 25 | findbugs { 26 | includeFilter = rootProject.file("config/findbugs/findbugsIncludeFilter.xml") 27 | } 28 | 29 | tasks.withType(FindBugs) { 30 | reports { 31 | xml.enabled = false 32 | html.enabled = true 33 | } 34 | } 35 | 36 | task failingTask { 37 | doLast { 38 | throw new RuntimeException("This task will always fail") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/two-namecheck-failures-checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/Failure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.File; 19 | 20 | import org.inferred.freebuilder.FreeBuilder; 21 | 22 | @FreeBuilder 23 | interface Failure { 24 | 25 | String source(); 26 | File file(); 27 | int line(); 28 | String severity(); 29 | String message(); 30 | String details(); 31 | 32 | class Builder extends Failure_Builder { 33 | public Builder() { 34 | source(""); 35 | details(""); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/ReportHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.util.List; 19 | 20 | import org.gradle.api.Task; 21 | import org.gradle.api.reporting.ReportContainer; 22 | import org.gradle.api.reporting.Reporting; 23 | import org.gradle.api.reporting.SingleFileReport; 24 | import org.xml.sax.helpers.DefaultHandler; 25 | 26 | abstract class ReportHandler>> 27 | extends DefaultHandler { 28 | public abstract void configureTask(T task); 29 | public abstract List failures(); 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'checkstyle' 4 | id 'findbugs' 5 | } 6 | 7 | 8 | // Apply CircleStylePlugin 9 | String[] classpath = System.env.TEST_CLASSPATH.split(':') 10 | URL[] urls = new URL[classpath.length] 11 | for (int i = 0; i < classpath.length; ++i) { 12 | urls[i] = new File(classpath[i]).toURI().toURL() 13 | } 14 | ClassLoader testClassLoader = new URLClassLoader(urls, getClass().classLoader) 15 | def pluginClass = testClassLoader.loadClass('com.palantir.gradle.circlestyle.CircleStylePlugin') 16 | plugins.apply(pluginClass) 17 | 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | sourceCompatibility = 1.7 24 | 25 | dependencies { 26 | testCompile 'junit:junit:4.12' 27 | testCompile 'org.assertj:assertj-core:2.9.0' 28 | } 29 | 30 | tasks.withType(Checkstyle) { 31 | reports { 32 | xml.enabled = false 33 | html.enabled = true 34 | } 35 | } 36 | 37 | findbugs { 38 | includeFilter = file("config/findbugs/findbugsIncludeFilter.xml") 39 | } 40 | 41 | tasks.withType(FindBugs) { 42 | reports { 43 | xml.enabled = false 44 | html.enabled = true 45 | } 46 | } 47 | 48 | task failingTask { 49 | doLast { 50 | throw new RuntimeException("This task will always fail") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.policy.yml: -------------------------------------------------------------------------------- 1 | policy: 2 | approval: 3 | - or: 4 | - infra team approval 5 | - infra team co-approval 6 | - excavator only touched gradle files or versions.props 7 | - excavator only touched package.json and lock files 8 | 9 | approval_rules: 10 | 11 | - name: infra team approval 12 | requires: 13 | count: 1 14 | teams: 15 | - "palantir/infrastructure" 16 | 17 | # Require two *contributing* infra maintainers to approve changes 18 | - name: infra team co-approval 19 | options: 20 | allow_contributor: true 21 | requires: 22 | count: 2 23 | teams: 24 | - "palantir/infrastructure" 25 | 26 | - name: excavator only touched gradle files or versions.props 27 | requires: 28 | count: 0 29 | if: 30 | has_author_in: 31 | users: [ "svc-excavator-bot" ] 32 | only_changed_files: 33 | paths: 34 | - "^.*gradle$" 35 | - "^gradle/wrapper/.*" 36 | - "^gradlew$" 37 | - "^gradlew.bat$" 38 | - "^versions.props$" 39 | - "^versions.lock$" 40 | 41 | - name: excavator only touched package.json and lock files 42 | requires: 43 | count: 0 44 | if: 45 | has_author_in: 46 | users: [ "svc-excavator-bot" ] 47 | only_changed_files: 48 | paths: 49 | - "^.*yarn.lock$" 50 | - "^.*package.json$" 51 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/two-namecheck-failures-checkstyle-report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ERROR: Parameter name 'b' must match pattern '^[a-z][a-zA-Z0-9][a-zA-Z0-9]*$'. 6 | Category: com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck 7 | File: fooproject/src/main/java/org/example/server/FooApplication.java 8 | Line: 135 9 | 10 | 11 | 12 | ERROR: Parameter name 'c' must match pattern '^[a-z][a-zA-Z0-9][a-zA-Z0-9]*$'. 13 | Category: com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck 14 | File: fooproject/src/main/java/org/example/server/FooApplication.java 15 | Line: 181 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/Tasks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import org.gradle.api.Task; 19 | import org.gradle.api.UnknownTaskException; 20 | import org.gradle.api.tasks.TaskContainer; 21 | 22 | class Tasks { 23 | 24 | static T createTask(TaskContainer tasks, String preferredName, Class type) { 25 | String name = preferredName; 26 | int count = 1; 27 | while (true) { 28 | try { 29 | Task existingTask = tasks.getByName(name); 30 | if (type.isInstance(existingTask)) { 31 | return null; 32 | } 33 | } catch (UnknownTaskException e) { 34 | return tasks.create(name, type); 35 | } 36 | count++; 37 | name = preferredName + count; 38 | } 39 | } 40 | 41 | private Tasks() { } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/Report.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.util.List; 19 | 20 | import javax.annotation.Nullable; 21 | 22 | import org.inferred.freebuilder.FreeBuilder; 23 | 24 | @FreeBuilder 25 | interface Report { 26 | 27 | @FreeBuilder 28 | public interface Failure { 29 | String message(); 30 | String details(); 31 | 32 | Builder toBuilder(); 33 | class Builder extends Report_Failure_Builder { } 34 | } 35 | 36 | @FreeBuilder 37 | public interface TestCase { 38 | String name(); 39 | @Nullable Failure failure(); 40 | 41 | Builder toBuilder(); 42 | class Builder extends Report_TestCase_Builder { } 43 | } 44 | 45 | String name(); 46 | String subname(); 47 | long elapsedTimeNanos(); 48 | List testCases(); 49 | 50 | Builder toBuilder(); 51 | class Builder extends Report_Builder { } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gradle Circle Style 2 | =================== 3 | 4 | A plugin for Gradle that summarizes failed Gradle builds in [CircleCI], with special handling for [Checkstyle] and [FindBugs] failures. 5 | 6 | [Checkstyle]: https://docs.gradle.org/current/userguide/checkstyle_plugin.html 7 | [CircleCI]: https://circleci.com/ 8 | [FindBugs]: https://docs.gradle.org/current/userguide/findbugs_plugin.html 9 | 10 | [![Gradle plugins page](https://img.shields.io/github/release/palantir/gradle-circle-style.svg?maxAge=60)](https://plugins.gradle.org/plugin/com.palantir.circle.style) 11 | [![CircleCI](https://img.shields.io/circleci/project/github/palantir/gradle-circle-style/master.svg?maxAge=60)](https://circleci.com/gh/palantir/gradle-circle-style/tree/master) 12 | [![Apache 2.0 License](https://img.shields.io/github/license/palantir/gradle-circle-style.svg?maxAge=2592000)](http://www.apache.org/licenses/LICENSE-2.0) 13 | 14 | Quickstart 15 | ---------- 16 | 17 | Add the following to your project's top-level build.gradle file: 18 | 19 | ```gradle 20 | 21 | plugins { 22 | id 'com.palantir.circle.style' version '1.1.2' 23 | } 24 | ``` 25 | 26 | And now your CircleCI builds will fail with nice summaries: 27 | 28 | ![CHECKSTYLE — 1 FAILURE](images/checkstyle-circle-failure.png?raw=true "CircleCI failure image") 29 | 30 | Details 31 | ------- 32 | 33 | This plugin is enabled by the `CIRCLE_TEST_REPORTS` environment variable, set automatically on CircleCI builds. It then automatically enables XML output for Checkstyle and FindBugs plugins, and adds a finalizer task that collates their results (and any other Gradle build step failures) using the JUnit XML output that CircleCI expects. 34 | 35 | Note that FindBugs does not support generating both HTML and XML output, so HTML output will be disabled on CircleCI builds. (Checkstyle does not have this limitation.) 36 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/XmlReportFailuresSupplier.java: -------------------------------------------------------------------------------- 1 | package com.palantir.gradle.circlestyle; 2 | 3 | import static com.palantir.gradle.circlestyle.XmlUtils.parseXml; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | import org.gradle.api.Action; 11 | import org.gradle.api.Task; 12 | import org.gradle.api.reporting.ReportContainer; 13 | import org.gradle.api.reporting.Reporting; 14 | import org.gradle.api.reporting.SingleFileReport; 15 | 16 | class XmlReportFailuresSupplier implements FailuresSupplier { 17 | 18 | public static >> 19 | XmlReportFailuresSupplier create(final T task, final ReportHandler reportHandler) { 20 | // Ensure any necessary output is enabled 21 | task.doFirst(new Action() { 22 | @Override 23 | public void execute(Task ignored) { 24 | reportHandler.configureTask(task); 25 | } 26 | }); 27 | return new XmlReportFailuresSupplier(task, reportHandler); 28 | } 29 | 30 | private final Reporting> reporting; 31 | private final ReportHandler reportHandler; 32 | 33 | private XmlReportFailuresSupplier( 34 | Reporting> reporting, 35 | ReportHandler reportHandler) { 36 | this.reporting = reporting; 37 | this.reportHandler = reportHandler; 38 | } 39 | 40 | @Override 41 | public List getFailures() throws IOException { 42 | File sourceReport = reporting.getReports().findByName("xml").getDestination(); 43 | return parseXml(reportHandler, new FileInputStream(sourceReport)).failures(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/CheckstyleReportHandlerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.TestCommon.CHECKSTYLE_FAILURES; 19 | import static com.palantir.gradle.circlestyle.TestCommon.testFile; 20 | import static com.palantir.gradle.circlestyle.XmlUtils.parseXml; 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | import java.io.IOException; 24 | import java.util.List; 25 | 26 | import org.junit.Test; 27 | 28 | public class CheckstyleReportHandlerTests { 29 | 30 | @Test 31 | public void testNoErrors() throws IOException { 32 | List failures = 33 | parseXml(new CheckstyleReportHandler(), testFile("no-failures-checkstyle.xml").openStream()) 34 | .failures(); 35 | assertThat(failures).isEmpty(); 36 | } 37 | 38 | @Test 39 | public void testTwoErrors() throws IOException { 40 | List failures = 41 | parseXml(new CheckstyleReportHandler(), testFile("two-namecheck-failures-checkstyle.xml").openStream()) 42 | .failures(); 43 | assertThat(failures).containsExactlyElementsOf(CHECKSTYLE_FAILURES); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/JUnitReportCreatorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.JUnitReportCreator.reportToXml; 19 | import static com.palantir.gradle.circlestyle.TestCommon.REPORT; 20 | import static com.palantir.gradle.circlestyle.TestCommon.readTestFile; 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | import java.io.StringWriter; 24 | 25 | import javax.xml.transform.TransformerException; 26 | 27 | import org.junit.Test; 28 | import org.w3c.dom.Document; 29 | 30 | public class JUnitReportCreatorTests { 31 | 32 | @Test 33 | public void testNoErrors() throws TransformerException { 34 | Document junitReport = reportToXml(new Report.Builder() 35 | .name("myproject") 36 | .subname("checkstyleMain") 37 | .elapsedTimeNanos(123_000_000_000L) 38 | .build()); 39 | String xml = XmlUtils.write(new StringWriter(), junitReport).toString(); 40 | 41 | assertThat(xml).isEqualTo(readTestFile("empty-checkstyle-report.xml")); 42 | } 43 | 44 | @Test 45 | public void testTwoErrors() throws TransformerException { 46 | Document junitReport = reportToXml(REPORT); 47 | String xml = XmlUtils.write(new StringWriter(), junitReport).toString(); 48 | 49 | assertThat(xml).isEqualTo(readTestFile("two-namecheck-failures-checkstyle-report.xml")); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/CheckstyleReportHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.gradle.api.plugins.quality.Checkstyle; 23 | import org.xml.sax.Attributes; 24 | 25 | class CheckstyleReportHandler extends ReportHandler { 26 | 27 | private final List failures = new ArrayList<>(); 28 | private File file; 29 | 30 | @Override 31 | public void configureTask(Checkstyle task) { 32 | // Ensure XML output is enabled 33 | task.getReports().findByName("xml").setEnabled(true); 34 | } 35 | 36 | @Override 37 | public void startElement(String uri, String localName, String qName, Attributes attributes) { 38 | switch (qName) { 39 | case "file": 40 | file = new File(attributes.getValue("name")); 41 | break; 42 | 43 | case "error": 44 | failures.add(new Failure.Builder() 45 | .source(attributes.getValue("source")) 46 | .severity(attributes.getValue("severity").toUpperCase()) 47 | .file(file) 48 | .line(Integer.parseInt(attributes.getValue("line"))) 49 | .message(attributes.getValue("message")) 50 | .build()); 51 | 52 | default: 53 | break; 54 | } 55 | } 56 | 57 | @Override 58 | public List failures() { 59 | return failures; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/StyleTaskTimer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.util.LinkedHashMap; 19 | import java.util.Map; 20 | 21 | import org.gradle.api.Task; 22 | import org.gradle.api.execution.TaskExecutionListener; 23 | import org.gradle.api.plugins.quality.Checkstyle; 24 | import org.gradle.api.plugins.quality.FindBugs; 25 | import org.gradle.api.tasks.TaskState; 26 | import org.gradle.api.tasks.compile.JavaCompile; 27 | 28 | class StyleTaskTimer implements TaskExecutionListener { 29 | 30 | private final Map taskTimeNanosByTask = new LinkedHashMap<>(); 31 | private long lastStartTime; 32 | 33 | public long getTaskTimeNanos(Task styleTask) { 34 | if (!isStyleTask(styleTask)) { 35 | throw new ClassCastException("not a style task"); 36 | } 37 | Long taskTimeNanos = taskTimeNanosByTask.get(styleTask); 38 | if (taskTimeNanos == null) { 39 | throw new IllegalArgumentException("no time available for task"); 40 | } 41 | return taskTimeNanos; 42 | } 43 | 44 | @Override 45 | public void beforeExecute(Task task) { 46 | if (isStyleTask(task)) { 47 | lastStartTime = System.nanoTime(); 48 | } 49 | } 50 | 51 | @Override 52 | public void afterExecute(Task task, TaskState taskState) { 53 | if (isStyleTask(task)) { 54 | taskTimeNanosByTask.put(task, System.nanoTime() - lastStartTime); 55 | } 56 | } 57 | 58 | public static boolean isStyleTask(Task task) { 59 | return task instanceof Checkstyle || task instanceof FindBugs || task instanceof JavaCompile; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/XmlUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.Writer; 21 | 22 | import javax.xml.parsers.ParserConfigurationException; 23 | import javax.xml.parsers.SAXParserFactory; 24 | import javax.xml.transform.OutputKeys; 25 | import javax.xml.transform.Transformer; 26 | import javax.xml.transform.TransformerException; 27 | import javax.xml.transform.TransformerFactory; 28 | import javax.xml.transform.dom.DOMSource; 29 | import javax.xml.transform.stream.StreamResult; 30 | 31 | import org.w3c.dom.Document; 32 | import org.xml.sax.InputSource; 33 | import org.xml.sax.SAXException; 34 | import org.xml.sax.XMLReader; 35 | 36 | class XmlUtils { 37 | 38 | public static > T parseXml(T handler, InputStream report) throws IOException { 39 | try { 40 | XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); 41 | xmlReader.setContentHandler(handler); 42 | xmlReader.parse(new InputSource(report)); 43 | return handler; 44 | } catch (SAXException | ParserConfigurationException e) { 45 | throw new IOException(e); 46 | } 47 | } 48 | 49 | public static Writer write(Writer writer, Document document) throws TransformerException { 50 | Transformer t = TransformerFactory.newInstance().newTransformer(); 51 | t.setOutputProperty(OutputKeys.INDENT, "yes"); 52 | t.setOutputProperty(OutputKeys.METHOD, "xml"); 53 | t.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 54 | t.transform(new DOMSource(document), new StreamResult(writer)); 55 | return writer; 56 | } 57 | 58 | private XmlUtils() { } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/CircleBuildFinishedAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.JUnitReportCreator.reportToXml; 19 | 20 | import java.io.File; 21 | import java.io.FileWriter; 22 | import java.io.IOException; 23 | 24 | import javax.xml.transform.TransformerException; 25 | 26 | import org.gradle.BuildResult; 27 | import org.gradle.api.Action; 28 | import org.w3c.dom.Document; 29 | 30 | class CircleBuildFinishedAction implements Action { 31 | 32 | private final Integer container; 33 | private final File targetFile; 34 | private final long startTimeNanos; 35 | private final CircleBuildFailureListener failureListener; 36 | 37 | CircleBuildFinishedAction(Integer container, File targetFile, CircleBuildFailureListener failureListener) { 38 | this.container = container; 39 | this.targetFile = targetFile; 40 | this.failureListener = failureListener; 41 | startTimeNanos = System.nanoTime(); 42 | } 43 | 44 | @Override 45 | public void execute(BuildResult result) { 46 | String name = (container != null) ? "container " + container : "gradle"; 47 | Report report = new Report.Builder() 48 | .name(name) 49 | .subname(name) 50 | .elapsedTimeNanos(System.nanoTime() - startTimeNanos) 51 | .addAllTestCases(failureListener.getTestCases()) 52 | .build(); 53 | Document xml = reportToXml(report); 54 | 55 | targetFile.getParentFile().mkdirs(); 56 | try (FileWriter writer = new FileWriter(targetFile)) { 57 | XmlUtils.write(writer, xml); 58 | } catch (IOException | TransformerException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/JavacFailuresSupplier.java: -------------------------------------------------------------------------------- 1 | package com.palantir.gradle.circlestyle; 2 | 3 | import static java.lang.Integer.parseInt; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import org.gradle.api.logging.StandardOutputListener; 13 | import org.gradle.api.tasks.compile.JavaCompile; 14 | 15 | class JavacFailuresSupplier implements FailuresSupplier { 16 | 17 | public static JavacFailuresSupplier create(final JavaCompile javac) { 18 | // Capture standard output 19 | final StringBuilder errorStream = new StringBuilder(); 20 | StandardOutputListener listener = new StandardOutputListener() { 21 | @Override 22 | public void onOutput(CharSequence output) { 23 | errorStream.append(output); 24 | } 25 | }; 26 | javac.getLogging().addStandardErrorListener(listener); 27 | 28 | // Configure the finalizer task 29 | return new JavacFailuresSupplier(errorStream); 30 | } 31 | 32 | private static final Pattern ERROR_LINE = Pattern.compile("([^ ].*):(\\d+): error: (.*)"); 33 | 34 | private final StringBuilder errorStream; 35 | 36 | JavacFailuresSupplier(StringBuilder errorStream) { 37 | this.errorStream = errorStream; 38 | } 39 | 40 | @Override 41 | public List getFailures() throws IOException { 42 | List failures = new ArrayList<>(); 43 | Failure.Builder failureBuilder = null; 44 | StringBuilder details = null; 45 | for (String line : errorStream.toString().split("\n")) { 46 | if (failureBuilder != null) { 47 | if (line.startsWith(" ")) { 48 | details.append("\n").append(line); 49 | continue; 50 | } else { 51 | failures.add(failureBuilder.details(details.toString()).build()); 52 | failureBuilder = null; 53 | details = null; 54 | } 55 | } 56 | Matcher matcher = ERROR_LINE.matcher(line); 57 | if (matcher.matches()) { 58 | failureBuilder = new Failure.Builder() 59 | .file(new File(matcher.group(1))) 60 | .line(parseInt(matcher.group(2))) 61 | .severity("ERROR") 62 | .message(matcher.group(3)); 63 | details = new StringBuilder(); 64 | } 65 | } 66 | if (failureBuilder != null) { 67 | failures.add(failureBuilder.details(details.toString()).build()); 68 | } 69 | return failures; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/FailuresReportGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.File; 19 | import java.util.List; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | 23 | class FailuresReportGenerator { 24 | 25 | private static final Pattern JAVA_FILE_RX = Pattern.compile(".*src/\\w+/java/(.*)\\.java"); 26 | 27 | public static Report failuresReport( 28 | File rootDir, 29 | String projectName, 30 | String taskName, 31 | long elapsedTimeNanos, 32 | List failures) { 33 | Report.Builder report = new Report.Builder() 34 | .elapsedTimeNanos(elapsedTimeNanos) 35 | .name(projectName) 36 | .subname(taskName); 37 | 38 | for (Failure failure : failures) { 39 | String shortSource = failure.source().isEmpty() ? "" : failure.source().replaceAll(".*\\.", "") + " - "; 40 | String className = getClassName(failure.file()); 41 | 42 | Report.TestCase testCase = new Report.TestCase.Builder() 43 | .name(shortSource + className) 44 | .failure(new Report.Failure.Builder() 45 | .message(failure.file().getName() + ":" + failure.line() + ": " + failure.message()) 46 | .details( 47 | failure.severity() + ": " + failure.message() + failure.details() + "\n" 48 | + (failure.source().isEmpty() ? "" : "Category: " + failure.source() + "\n") 49 | + "File: " + rootDir.toPath().relativize(failure.file().toPath()) + "\n" 50 | + "Line: " + failure.line() + "\n") 51 | .build()) 52 | .build(); 53 | report.addTestCases(testCase); 54 | } 55 | 56 | return report.build(); 57 | } 58 | 59 | private static String getClassName(File file) { 60 | Matcher matcher = JAVA_FILE_RX.matcher(file.toString()); 61 | if (matcher.matches()) { 62 | return matcher.group(1).replace('/', '.'); 63 | } 64 | return file.toString(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/CircleBuildFailureListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.StyleTaskTimer.isStyleTask; 19 | 20 | import java.io.PrintWriter; 21 | import java.io.StringWriter; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import org.gradle.api.Task; 26 | import org.gradle.api.execution.TaskExecutionListener; 27 | import org.gradle.api.tasks.TaskExecutionException; 28 | import org.gradle.api.tasks.TaskState; 29 | import org.gradle.api.tasks.testing.Test; 30 | 31 | class CircleBuildFailureListener implements TaskExecutionListener { 32 | 33 | private final List testCases = new ArrayList<>(); 34 | 35 | @Override 36 | public void beforeExecute(Task task) { } 37 | 38 | @Override 39 | public synchronized void afterExecute(Task task, TaskState state) { 40 | if (isUntracked(task)) { 41 | Report.TestCase.Builder testCase = new Report.TestCase.Builder() 42 | .name(":" + task.getProject().getName() + ":" + task.getName()); 43 | 44 | Throwable failure = state.getFailure(); 45 | if (failure != null && isUntracked(task)) { 46 | if (failure instanceof TaskExecutionException && failure.getCause() != null) { 47 | failure = failure.getCause(); 48 | } 49 | StringWriter stackTrace = new StringWriter(); 50 | failure.printStackTrace(new PrintWriter(stackTrace)); 51 | 52 | testCase.failure(new Report.Failure.Builder() 53 | .message(getMessage(failure)) 54 | .details(stackTrace.toString()) 55 | .build()); 56 | } 57 | testCases.add(testCase.build()); 58 | } 59 | } 60 | 61 | public List getTestCases() { 62 | return testCases; 63 | } 64 | 65 | private static String getMessage(Throwable t) { 66 | if (t.getMessage() == null) { 67 | return t.getClass().getSimpleName(); 68 | } else { 69 | return t.getClass().getSimpleName() + ": " + t.getMessage(); 70 | } 71 | } 72 | 73 | private static boolean isUntracked(Task task) { 74 | return !(task instanceof Test) && !isStyleTask(task); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/JavacFailuresSupplierTest.java: -------------------------------------------------------------------------------- 1 | package com.palantir.gradle.circlestyle; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | import org.junit.Test; 9 | 10 | public class JavacFailuresSupplierTest { 11 | 12 | private static final String CLASS_FILE = "/tmp/ab1/src/main/java/com/example/MyClass.java"; 13 | private static final int LINE_1 = 8; 14 | private static final String ERROR_1 = "incompatible types: String cannot be converted to int"; 15 | private static final String DETAIL_1 = "\n private final int a = \"hello\"; " 16 | + "\n ^"; 17 | private static final int LINE_2 = 12; 18 | private static final String ERROR_2 = "cannot assign a value to final variable b"; 19 | private static final String DETAIL_2 = "\n b = 2; " 20 | + "\n ^ "; 21 | 22 | @Test 23 | public void noFailuresInEmptyOutput() throws IOException { 24 | String javacOutput = ""; 25 | JavacFailuresSupplier supplier = new JavacFailuresSupplier(new StringBuilder(javacOutput)); 26 | assertThat(supplier.getFailures()).isEmpty(); 27 | } 28 | 29 | @Test 30 | public void noFailuresInOutputWithOnlyWarnings() throws IOException { 31 | String javacOutput = "warning: [options] bootstrap class path not set in conjunction with -source 1.7\n" 32 | + "Note: " + CLASS_FILE + " uses unchecked or unsafe operations.\n" 33 | + "Note: Recompile with -Xlint:unchecked for details. \n" 34 | + "1 warning"; 35 | JavacFailuresSupplier supplier = new JavacFailuresSupplier(new StringBuilder(javacOutput)); 36 | assertThat(supplier.getFailures()).isEmpty(); 37 | } 38 | 39 | @Test 40 | public void twoFailuresInOutputWithNoWarnings() throws IOException { 41 | String javacOutput = CLASS_FILE + ":" + LINE_1 + ": error: " + ERROR_1 + DETAIL_1 + "\n" 42 | + CLASS_FILE + ":" + LINE_2 + ": error: " + ERROR_2 + DETAIL_2 + "\n"; 43 | JavacFailuresSupplier supplier = new JavacFailuresSupplier(new StringBuilder(javacOutput)); 44 | assertThat(supplier.getFailures()).containsExactly( 45 | new Failure.Builder() 46 | .file(new File(CLASS_FILE)) 47 | .line(LINE_1) 48 | .severity("ERROR") 49 | .message(ERROR_1) 50 | .details(DETAIL_1) 51 | .build(), 52 | new Failure.Builder() 53 | .file(new File(CLASS_FILE)) 54 | .line(12) 55 | .severity("ERROR") 56 | .message(ERROR_2) 57 | .details(DETAIL_2) 58 | .build()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/FindBugsReportHandlerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.TestCommon.ROOT; 19 | import static com.palantir.gradle.circlestyle.TestCommon.testFile; 20 | import static com.palantir.gradle.circlestyle.XmlUtils.parseXml; 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.util.List; 26 | 27 | import org.junit.Test; 28 | 29 | import com.google.common.collect.ImmutableList; 30 | 31 | public class FindBugsReportHandlerTests { 32 | 33 | private static final String DM_EXIT_MSG = " invokes System.exit(...), which shuts down the entire virtual machine"; 34 | private static final File CLASSFILE = new File(ROOT, "src/main/java/com/example/MyClass.java"); 35 | private static final List FINDBUGS_FAILURES = ImmutableList.of( 36 | new Failure.Builder() 37 | .source("DM_EXIT") 38 | .severity("ERROR") 39 | .file(CLASSFILE) 40 | .line(5) 41 | .message("com.example.MyClass.methodA()" + DM_EXIT_MSG) 42 | .build(), 43 | new Failure.Builder() 44 | .source("DM_EXIT") 45 | .severity("ERROR") 46 | .file(CLASSFILE) 47 | .line(9) 48 | .message("com.example.MyClass.methodB()" + DM_EXIT_MSG) 49 | .build()); 50 | 51 | @Test 52 | public void testNoErrors() throws IOException { 53 | List failures = 54 | parseXml(new FindBugsReportHandler(), testFile("no-errors-findbugs.xml").openStream()).failures(); 55 | assertThat(failures).isEmpty(); 56 | } 57 | 58 | @Test 59 | public void testTwoErrors() throws IOException { 60 | List failures = 61 | parseXml(new FindBugsReportHandler(), testFile("two-exit-errors-findbugs.xml").openStream()).failures(); 62 | assertThat(failures).containsExactlyElementsOf(FINDBUGS_FAILURES); 63 | } 64 | 65 | /** @see Issue 7 */ 66 | @Test 67 | public void testSyntheticSourceLine() throws IOException { 68 | parseXml(new FindBugsReportHandler(), testFile("synthetic-sourceline-findbugs.xml").openStream()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/JUnitReportCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import javax.xml.parsers.DocumentBuilderFactory; 19 | import javax.xml.parsers.ParserConfigurationException; 20 | 21 | import org.w3c.dom.Document; 22 | import org.w3c.dom.Element; 23 | 24 | class JUnitReportCreator { 25 | 26 | public static Document reportToXml(Report report) { 27 | try { 28 | Document xml = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 29 | String elapsedTimeString = String.format("%.03f", report.elapsedTimeNanos() / 1e9); 30 | 31 | Element testSuitesXml = xml.createElement("testsuites"); 32 | xml.appendChild(testSuitesXml); 33 | testSuitesXml.setAttribute("id", asId(report.name())); 34 | testSuitesXml.setAttribute("name", report.name()); 35 | testSuitesXml.setAttribute("tests", Integer.toString(report.testCases().size())); 36 | testSuitesXml.setAttribute("time", elapsedTimeString); 37 | 38 | Element testSuiteXml = xml.createElement("testsuite"); 39 | testSuitesXml.appendChild(testSuiteXml); 40 | testSuiteXml.setAttribute("id", asId(report.subname())); 41 | testSuiteXml.setAttribute("name", report.subname()); 42 | testSuiteXml.setAttribute("tests", Integer.toString(report.testCases().size())); 43 | testSuiteXml.setAttribute("time", elapsedTimeString); 44 | 45 | int failures = 0; 46 | for (Report.TestCase testCase : report.testCases()) { 47 | 48 | Element testCaseXml = xml.createElement("testcase"); 49 | testSuiteXml.appendChild(testCaseXml); 50 | testCaseXml.setAttribute("id", asId(testCase.name())); 51 | testCaseXml.setAttribute("name", testCase.name()); 52 | 53 | Report.Failure failure = testCase.failure(); 54 | if (failure != null) { 55 | failures++; 56 | Element failureXml = xml.createElement("failure"); 57 | testCaseXml.appendChild(failureXml); 58 | failureXml.setAttribute("message", failure.message()); 59 | failureXml.setAttribute("type", "ERROR"); 60 | failureXml.setTextContent(failure.details()); 61 | } 62 | } 63 | 64 | testSuitesXml.setAttribute("failures", Integer.toString(failures)); 65 | testSuiteXml.setAttribute("failures", Integer.toString(failures)); 66 | 67 | return xml; 68 | } catch (ParserConfigurationException e) { 69 | throw new RuntimeException(e); 70 | } 71 | } 72 | 73 | private static String asId(String name) { 74 | return name.replace(" - ", "."); 75 | } 76 | 77 | private JUnitReportCreator() { } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/no-errors-findbugs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /private/var/folders/tk/_zds4lpd3l507vhqb1pzmqdj080z60/T/junit4546550693118052288/build/classes/java/main 6 | /private/var/folders/tk/_zds4lpd3l507vhqb1pzmqdj080z60/T/junit4546550693118052288/src/main/java/com/example/MyClass.java 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/FindBugsReportHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.gradle.api.plugins.quality.FindBugs; 23 | import org.gradle.api.plugins.quality.FindBugsXmlReport; 24 | import org.gradle.api.reporting.SingleFileReport; 25 | import org.xml.sax.Attributes; 26 | 27 | class FindBugsReportHandler extends ReportHandler { 28 | 29 | private final List sources = new ArrayList<>(); 30 | private final List failures = new ArrayList<>(); 31 | private Failure.Builder failure = null; 32 | private StringBuilder content = null; 33 | private int depth = 0; 34 | 35 | @Override 36 | public void configureTask(FindBugs task) { 37 | for (SingleFileReport report : task.getReports()) { 38 | report.setEnabled(false); 39 | } 40 | FindBugsXmlReport xmlReport = (FindBugsXmlReport) task.getReports().findByName("xml"); 41 | xmlReport.setEnabled(true); 42 | xmlReport.setWithMessages(true); 43 | } 44 | 45 | @Override 46 | public void startElement(String uri, String localName, String qName, Attributes attributes) { 47 | depth++; 48 | switch (qName) { 49 | case "SrcDir": 50 | content = new StringBuilder(); 51 | break; 52 | 53 | case "BugInstance": 54 | depth = 0; 55 | failure = new Failure.Builder() 56 | .source(attributes.getValue("type")) 57 | .severity("ERROR"); 58 | break; 59 | 60 | case "LongMessage": 61 | content = new StringBuilder(); 62 | break; 63 | 64 | case "SourceLine": 65 | if (depth == 1) { 66 | String sourcepath = attributes.getValue("sourcepath"); 67 | File sourceFile = new File(sourcepath); 68 | for (String source : sources) { 69 | if (source.endsWith(sourcepath)) { 70 | sourceFile = new File(source); 71 | } 72 | } 73 | failure.file(sourceFile) 74 | .line(Integer.parseInt(attributes.getValue("start"))); 75 | } 76 | break; 77 | 78 | default: 79 | break; 80 | } 81 | } 82 | 83 | @Override 84 | public void characters(char[] ch, int start, int length) { 85 | if (content != null) { 86 | content.append(ch, start, length); 87 | } 88 | } 89 | 90 | @Override 91 | public void endElement(String uri, String localName, String qName) { 92 | switch (qName) { 93 | case "SrcDir": 94 | sources.add(content.toString()); 95 | content = null; 96 | break; 97 | 98 | case "LongMessage": 99 | failure.message(content.toString()); 100 | content = null; 101 | break; 102 | 103 | case "BugInstance": 104 | failures.add(failure.build()); 105 | failure = null; 106 | break; 107 | 108 | default: 109 | break; 110 | } 111 | depth--; 112 | } 113 | 114 | @Override 115 | public List failures() { 116 | return failures; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/CircleStyleFinalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.FailuresReportGenerator.failuresReport; 19 | import static com.palantir.gradle.circlestyle.JUnitReportCreator.reportToXml; 20 | 21 | import java.io.File; 22 | import java.io.FileWriter; 23 | import java.io.IOException; 24 | import java.util.List; 25 | 26 | import javax.inject.Inject; 27 | import javax.xml.transform.TransformerException; 28 | 29 | import org.gradle.api.DefaultTask; 30 | import org.gradle.api.Task; 31 | import org.gradle.api.tasks.TaskAction; 32 | import org.w3c.dom.Document; 33 | 34 | class CircleStyleFinalizer extends DefaultTask { 35 | 36 | public static void registerFinalizer( 37 | Task task, 38 | StyleTaskTimer timer, 39 | FailuresSupplier failuresSupplier, 40 | File reportDir) { 41 | CircleStyleFinalizer finalizer = Tasks.createTask( 42 | task.getProject().getTasks(), 43 | task.getName() + "CircleFinalizer", 44 | CircleStyleFinalizer.class); 45 | if (finalizer == null) { 46 | // Already registered (happens if the user applies us to the root project and subprojects) 47 | return; 48 | } 49 | finalizer.setStyleTask(task); 50 | finalizer.setStyleTaskTimer(timer); 51 | finalizer.setFailuresSupplier(failuresSupplier); 52 | finalizer.setTargetFile(new File(reportDir, task.getProject().getName() + "-" + task.getName() + ".xml")); 53 | 54 | task.finalizedBy(finalizer); 55 | } 56 | 57 | private Task styleTask; 58 | private StyleTaskTimer styleTaskTimer; 59 | private FailuresSupplier failuresSupplier; 60 | private File targetFile; 61 | 62 | @Inject 63 | public CircleStyleFinalizer() { } 64 | 65 | public Task getStyleTask() { 66 | return styleTask; 67 | } 68 | 69 | public void setStyleTask(Task styleTask) { 70 | this.styleTask = styleTask; 71 | } 72 | 73 | public StyleTaskTimer getStyleTaskTimer() { 74 | return styleTaskTimer; 75 | } 76 | 77 | public void setStyleTaskTimer(StyleTaskTimer styleTaskTimer) { 78 | this.styleTaskTimer = styleTaskTimer; 79 | } 80 | 81 | public FailuresSupplier getFailuresSupplier() { 82 | return failuresSupplier; 83 | } 84 | 85 | public void setFailuresSupplier(FailuresSupplier failuresSupplier) { 86 | this.failuresSupplier = failuresSupplier; 87 | } 88 | 89 | public File getTargetFile() { 90 | return targetFile; 91 | } 92 | 93 | public void setTargetFile(File targetFile) { 94 | this.targetFile = targetFile; 95 | } 96 | 97 | @TaskAction 98 | public void createCircleReport() throws IOException, TransformerException { 99 | if (!styleTask.getDidWork()) { 100 | setDidWork(false); 101 | return; 102 | } 103 | 104 | File rootDir = getProject().getRootProject().getProjectDir(); 105 | String projectName = getProject().getName(); 106 | List failures = failuresSupplier.getFailures(); 107 | long taskTimeNanos = styleTaskTimer.getTaskTimeNanos(styleTask); 108 | 109 | Document report = reportToXml(failuresReport( 110 | rootDir, projectName, styleTask.getName(), taskTimeNanos, failures)); 111 | targetFile.getParentFile().mkdirs(); 112 | try (FileWriter writer = new FileWriter(targetFile)) { 113 | XmlUtils.write(writer, report); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/synthetic-sourceline-findbugs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Parameter of final type 6 | Parameter 0 of new com.example.Application$StatusResource(Foo, Bar, Baz) is of final type com.example.Foo 7 | 8 | Value 0 9 | 10 | 11 | 12 | At Application.java:[lines 211-264] 13 | 14 | In class com.example.Application$StatusResource 15 | 16 | 17 | 18 | In method new com.example.Application$StatusResource(Foo, Bar, Supplier) 19 | 20 | 21 | 22 | At Foo.java:[lines 22-51] 23 | 24 | Type com.example.Foo 25 | 26 | 27 | At Application.java:[lines 218-222] 28 | 29 | 30 | 31 | Parameter of final type 32 | Parameter 1 of new com.example.Application$StatusResource(Foo, Bar, Supplier) is of final type com.example.Bar 33 | 34 | Value 1 35 | 36 | 37 | 38 | At Application.java:[lines 211-264] 39 | 40 | In class com.example.Application$StatusResource 41 | 42 | 43 | 44 | In method new com.example.Application$StatusResource(Foo, Bar, Supplier) 45 | 46 | 47 | 48 | At Bar.java:[lines 29-78] 49 | 50 | Type com.example.Bar 51 | 52 | 53 | At Application.java:[lines 218-222] 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/CircleBuildFailureListenerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.when; 21 | 22 | import com.palantir.gradle.circlestyle.Report.TestCase; 23 | import org.gradle.api.Task; 24 | import org.gradle.api.internal.tasks.TaskExecutionOutcome; 25 | import org.gradle.api.internal.tasks.TaskStateInternal; 26 | import org.gradle.api.plugins.quality.Checkstyle; 27 | import org.gradle.api.tasks.TaskState; 28 | import org.junit.Test; 29 | import org.mockito.Mockito; 30 | 31 | public class CircleBuildFailureListenerTests { 32 | 33 | private static final String PROJECT_1_NAME = "project1"; 34 | private static final String TASK_1_NAME = "task1"; 35 | private static final String PROJECT_2_NAME = "project2"; 36 | private static final String TASK_2_NAME = "task2"; 37 | 38 | private final CircleBuildFailureListener listener = new CircleBuildFailureListener(); 39 | 40 | @Test 41 | public void noTasks() { 42 | assertThat(listener.getTestCases()).isEmpty(); 43 | } 44 | 45 | @Test 46 | public void onlyTestAndStyleTasks() { 47 | listener.afterExecute(mock(org.gradle.api.tasks.testing.Test.class), succeeded()); 48 | listener.afterExecute(mock(Checkstyle.class), succeeded()); 49 | assertThat(listener.getTestCases()).isEmpty(); 50 | } 51 | 52 | @Test 53 | public void successfulTasks() { 54 | listener.afterExecute(task(PROJECT_1_NAME, TASK_1_NAME), succeeded()); 55 | listener.afterExecute(task(PROJECT_2_NAME, TASK_2_NAME), succeeded()); 56 | assertThat(listener.getTestCases()).containsExactly( 57 | new TestCase.Builder().name(":" + PROJECT_1_NAME + ":" + TASK_1_NAME).build(), 58 | new TestCase.Builder().name(":" + PROJECT_2_NAME + ":" + TASK_2_NAME).build()); 59 | } 60 | 61 | @Test 62 | public void failedTasks() { 63 | listener.afterExecute(task(PROJECT_1_NAME, TASK_1_NAME), failed("task 1 failed")); 64 | listener.afterExecute(task(PROJECT_2_NAME, TASK_2_NAME), failed("task 2 failed")); 65 | 66 | assertThat(listener.getTestCases()).hasSize(2); 67 | 68 | TestCase testCase1 = listener.getTestCases().get(0); 69 | assertThat(testCase1.name()).isEqualTo(":" + PROJECT_1_NAME + ":" + TASK_1_NAME); 70 | assertThat(testCase1.failure()).isNotNull(); 71 | assertThat(testCase1.failure().message()).isEqualTo("RuntimeException: task 1 failed"); 72 | assertThat(testCase1.failure().details()).startsWith("java.lang.RuntimeException: task 1 failed\n" 73 | + "\tat " + CircleBuildFailureListenerTests.class.getName() + ".failed"); 74 | 75 | TestCase testCase2 = listener.getTestCases().get(1); 76 | assertThat(testCase2.name()).isEqualTo(":" + PROJECT_2_NAME + ":" + TASK_2_NAME); 77 | assertThat(testCase2.failure()).isNotNull(); 78 | assertThat(testCase2.failure().message()).isEqualTo("RuntimeException: task 2 failed"); 79 | assertThat(testCase2.failure().details()).startsWith("java.lang.RuntimeException: task 2 failed\n" 80 | + "\tat " + CircleBuildFailureListenerTests.class.getName() + ".failed"); 81 | } 82 | 83 | private static Task task(String projectName, String taskName) { 84 | Task task = Mockito.mock(Task.class, Mockito.RETURNS_DEEP_STUBS); 85 | when(task.getProject().getName()).thenReturn(projectName); 86 | when(task.getName()).thenReturn(taskName); 87 | return task; 88 | } 89 | 90 | private static TaskState succeeded() { 91 | TaskStateInternal state = new TaskStateInternal(); 92 | state.setOutcome(TaskExecutionOutcome.EXECUTED); 93 | return state; 94 | } 95 | 96 | private static TaskState failed(String message) { 97 | TaskStateInternal state = new TaskStateInternal(); 98 | state.setOutcome(TaskExecutionOutcome.EXECUTED); 99 | state.setOutcome(new RuntimeException(message)); 100 | return state; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/TestCommon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import java.io.File; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.net.URL; 23 | import java.util.List; 24 | 25 | import org.gradle.internal.impldep.com.google.common.collect.ImmutableList; 26 | import org.junit.rules.TemporaryFolder; 27 | 28 | import com.google.common.base.Charsets; 29 | import com.google.common.io.Resources; 30 | 31 | import me.nallar.whocalled.WhoCalled; 32 | 33 | public class TestCommon { 34 | 35 | private static final String SOURCE = "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck"; 36 | public static final File ROOT = new File("/home/ubuntu/fooproject"); 37 | private static final String CLASSFILE = "fooproject/src/main/java/org/example/server/FooApplication.java"; 38 | private static final String MESSAGE_2 = "Parameter name 'c' must match pattern '^[a-z][a-zA-Z0-9][a-zA-Z0-9]*$'."; 39 | private static final String MESSAGE_1 = "Parameter name 'b' must match pattern '^[a-z][a-zA-Z0-9][a-zA-Z0-9]*$'."; 40 | public static final List CHECKSTYLE_FAILURES = ImmutableList.of( 41 | new Failure.Builder() 42 | .source(SOURCE) 43 | .severity("ERROR") 44 | .file(new File( 45 | ROOT, CLASSFILE)) 46 | .line(135) 47 | .message(MESSAGE_1) 48 | .build(), 49 | new Failure.Builder() 50 | .source(SOURCE) 51 | .severity("ERROR") 52 | .file(new File( 53 | ROOT, CLASSFILE)) 54 | .line(181) 55 | .message(MESSAGE_2) 56 | .build()); 57 | public static final long FAILED_CHECKSTYLE_TIME_NANOS = 321_000_000_000L; 58 | public static final Report REPORT = new Report.Builder() 59 | .name("fooproject") 60 | .subname("checkstyleTest") 61 | .elapsedTimeNanos(FAILED_CHECKSTYLE_TIME_NANOS) 62 | .addTestCases(new Report.TestCase.Builder() 63 | .name("ParameterNameCheck - org.example.server.FooApplication") 64 | .failure(new Report.Failure.Builder() 65 | .message("FooApplication.java:135: " + MESSAGE_1) 66 | .details("ERROR: " + MESSAGE_1 + "\n" 67 | + "Category: " + SOURCE + "\n" 68 | + "File: " + CLASSFILE + "\n" 69 | + "Line: 135\n") 70 | .build()) 71 | .build()) 72 | .addTestCases(new Report.TestCase.Builder() 73 | .name("ParameterNameCheck - org.example.server.FooApplication") 74 | .failure(new Report.Failure.Builder() 75 | .message("FooApplication.java:181: " + MESSAGE_2) 76 | .details("ERROR: " + MESSAGE_2 + "\n" 77 | + "Category: " + SOURCE + "\n" 78 | + "File: " + CLASSFILE + "\n" 79 | + "Line: 181\n") 80 | .build()) 81 | .build()) 82 | .build(); 83 | 84 | public static URL testFile(String filename) { 85 | return WhoCalled.$.getCallingClass().getResource(filename); 86 | } 87 | 88 | public static File copyTestFile(String source, TemporaryFolder root, String target) { 89 | File targetFile = new File(root.getRoot(), target); 90 | targetFile.getParentFile().mkdirs(); 91 | try (OutputStream stream = new FileOutputStream(targetFile)) { 92 | Resources.copy(WhoCalled.$.getCallingClass().getResource(source), stream); 93 | return targetFile; 94 | } catch (IOException e) { 95 | throw new AssertionError(e); 96 | } 97 | } 98 | 99 | public static String readTestFile(String filename) { 100 | try { 101 | return Resources.toString(WhoCalled.$.getCallingClass().getResource(filename), Charsets.UTF_8); 102 | } catch (IOException e) { 103 | throw new AssertionError(e); 104 | } 105 | } 106 | 107 | private TestCommon() { } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/gradle/circlestyle/CircleStylePlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.CircleStyleFinalizer.registerFinalizer; 19 | 20 | import java.io.File; 21 | 22 | import org.gradle.api.Action; 23 | import org.gradle.api.Plugin; 24 | import org.gradle.api.Project; 25 | import org.gradle.api.plugins.quality.Checkstyle; 26 | import org.gradle.api.plugins.quality.FindBugs; 27 | import org.gradle.api.tasks.compile.JavaCompile; 28 | import org.gradle.api.tasks.testing.Test; 29 | 30 | public class CircleStylePlugin implements Plugin { 31 | 32 | @Override 33 | public void apply(Project rootProject) { 34 | final String circleReportsDir = System.getenv("CIRCLE_TEST_REPORTS"); 35 | if (circleReportsDir == null) { 36 | return; 37 | } 38 | 39 | configureBuildFailureFinalizer(rootProject, circleReportsDir); 40 | 41 | final StyleTaskTimer timer = new StyleTaskTimer(); 42 | rootProject.getGradle().addListener(timer); 43 | 44 | rootProject.allprojects(new Action() { 45 | @Override 46 | public void execute(final Project project) { 47 | project.getTasks().withType(Test.class, new Action() { 48 | @Override 49 | public void execute(Test test) { 50 | File junitReportsDir = new File(circleReportsDir, "junit"); 51 | for (String component : test.getPath().substring(1).split(":")) { 52 | junitReportsDir = new File(junitReportsDir, component); 53 | } 54 | test.getReports().getJunitXml().setDestination(junitReportsDir); 55 | } 56 | }); 57 | project.getTasks().withType(Checkstyle.class, new Action() { 58 | @Override 59 | public void execute(Checkstyle checkstyle) { 60 | registerFinalizer( 61 | checkstyle, 62 | timer, 63 | XmlReportFailuresSupplier.create(checkstyle, new CheckstyleReportHandler()), 64 | new File(circleReportsDir, "checkstyle")); 65 | } 66 | }); 67 | project.getTasks().withType(FindBugs.class, new Action() { 68 | @Override 69 | public void execute(FindBugs findbugs) { 70 | registerFinalizer( 71 | findbugs, 72 | timer, 73 | XmlReportFailuresSupplier.create(findbugs, new FindBugsReportHandler()), 74 | new File(circleReportsDir, "findbugs")); 75 | } 76 | }); 77 | project.getTasks().withType(JavaCompile.class, new Action() { 78 | @Override 79 | public void execute(JavaCompile javac) { 80 | registerFinalizer( 81 | javac, 82 | timer, 83 | JavacFailuresSupplier.create(javac), 84 | new File(circleReportsDir, "javac")); 85 | } 86 | }); 87 | } 88 | }); 89 | } 90 | 91 | private static void configureBuildFailureFinalizer(Project rootProject, String circleReportsDir) { 92 | int attemptNumber = 1; 93 | File targetFile = new File(new File(circleReportsDir, "gradle"), "build.xml"); 94 | while (targetFile.exists()) { 95 | targetFile = new File(new File(circleReportsDir, "gradle"), "build" + (++attemptNumber) + ".xml"); 96 | } 97 | Integer container; 98 | try { 99 | container = Integer.parseInt(System.getenv("CIRCLE_NODE_INDEX")); 100 | } catch (NumberFormatException e) { 101 | container = null; 102 | } 103 | CircleBuildFailureListener listener = new CircleBuildFailureListener(); 104 | CircleBuildFinishedAction action = new CircleBuildFinishedAction(container, targetFile, listener); 105 | rootProject.getGradle().addListener(listener); 106 | rootProject.getGradle().buildFinished(action); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/CircleStyleFinalizerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.google.common.base.Charsets.UTF_8; 19 | import static com.palantir.gradle.circlestyle.TestCommon.FAILED_CHECKSTYLE_TIME_NANOS; 20 | import static com.palantir.gradle.circlestyle.TestCommon.ROOT; 21 | import static com.palantir.gradle.circlestyle.TestCommon.readTestFile; 22 | import static com.palantir.gradle.circlestyle.TestCommon.testFile; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.mockito.Mockito.mock; 25 | import static org.mockito.Mockito.when; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | 30 | import javax.xml.transform.TransformerException; 31 | 32 | import org.gradle.api.Project; 33 | import org.gradle.api.plugins.quality.Checkstyle; 34 | import org.gradle.api.reporting.SingleFileReport; 35 | import org.gradle.internal.impldep.com.google.common.io.Files; 36 | import org.gradle.testfixtures.ProjectBuilder; 37 | import org.junit.Rule; 38 | import org.junit.Test; 39 | import org.junit.rules.TemporaryFolder; 40 | 41 | import com.google.common.collect.ImmutableMap; 42 | import com.google.common.io.Resources; 43 | 44 | public class CircleStyleFinalizerTests { 45 | 46 | @Rule public final TemporaryFolder projectDir = new TemporaryFolder(); 47 | 48 | @Test 49 | public void translatesCheckstyleReport() throws IOException, TransformerException { 50 | Project project = ProjectBuilder.builder().withName("fooproject").withProjectDir(projectDir.getRoot()).build(); 51 | Checkstyle checkstyle = createCheckstyleTask(project); 52 | 53 | checkstyle.setDidWork(true); 54 | 55 | StyleTaskTimer timer = mock(StyleTaskTimer.class); 56 | when(timer.getTaskTimeNanos(checkstyle)).thenReturn(FAILED_CHECKSTYLE_TIME_NANOS); 57 | 58 | File targetFile = new File(projectDir.getRoot(), "reports/report.xml"); 59 | 60 | CircleStyleFinalizer finalizer = (CircleStyleFinalizer) project 61 | .task(ImmutableMap.of("type", CircleStyleFinalizer.class), "checkstyleTestCircleFinalizer"); 62 | finalizer.setStyleTask(checkstyle); 63 | finalizer.setStyleTaskTimer(timer); 64 | finalizer.setFailuresSupplier(XmlReportFailuresSupplier.create(checkstyle, new CheckstyleReportHandler())); 65 | finalizer.setTargetFile(targetFile); 66 | 67 | finalizer.createCircleReport(); 68 | 69 | String report = Resources.toString(targetFile.toURI().toURL(), UTF_8); 70 | String expectedReport = Resources.toString(testFile("two-namecheck-failures-checkstyle-report.xml"), UTF_8); 71 | 72 | assertThat(report).isEqualTo(expectedReport); 73 | } 74 | 75 | @Test 76 | public void doesNothingIfTaskSkipped() throws IOException, TransformerException { 77 | Project project = ProjectBuilder.builder().withName("fooproject").withProjectDir(projectDir.getRoot()).build(); 78 | Checkstyle checkstyle = createCheckstyleTask(project); 79 | 80 | checkstyle.setDidWork(false); 81 | 82 | StyleTaskTimer timer = mock(StyleTaskTimer.class); 83 | when(timer.getTaskTimeNanos(checkstyle)).thenReturn(FAILED_CHECKSTYLE_TIME_NANOS); 84 | 85 | File targetFile = new File(projectDir.getRoot(), "reports/report.xml"); 86 | 87 | CircleStyleFinalizer finalizer = (CircleStyleFinalizer) project 88 | .task(ImmutableMap.of("type", CircleStyleFinalizer.class), "checkstyleTestCircleFinalizer"); 89 | finalizer.setStyleTask(checkstyle); 90 | finalizer.setStyleTaskTimer(timer); 91 | finalizer.setFailuresSupplier(XmlReportFailuresSupplier.create(checkstyle, new CheckstyleReportHandler())); 92 | finalizer.setTargetFile(targetFile); 93 | 94 | finalizer.createCircleReport(); 95 | 96 | assertThat(targetFile).doesNotExist(); 97 | assertThat(finalizer.getDidWork()).isFalse(); 98 | } 99 | 100 | private Checkstyle createCheckstyleTask(Project project) throws IOException { 101 | Checkstyle checkstyle = project.getTasks().create("checkstyleTest", Checkstyle.class); 102 | SingleFileReport xmlReport = checkstyle.getReports().getByName("xml"); 103 | 104 | String originalReportXml = readTestFile("two-namecheck-failures-checkstyle.xml"); 105 | String modifiedReportXml = originalReportXml.replace(ROOT.toString(), projectDir.getRoot().getCanonicalPath().toString()); 106 | File modifiedReportFile = projectDir.newFile(); 107 | Files.write(modifiedReportXml, modifiedReportFile, UTF_8); 108 | 109 | xmlReport.setDestination(modifiedReportFile); 110 | return checkstyle; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/FailuresReportGeneratorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.palantir.gradle.circlestyle.FailuresReportGenerator.failuresReport; 19 | import static com.palantir.gradle.circlestyle.TestCommon.CHECKSTYLE_FAILURES; 20 | import static com.palantir.gradle.circlestyle.TestCommon.FAILED_CHECKSTYLE_TIME_NANOS; 21 | import static com.palantir.gradle.circlestyle.TestCommon.REPORT; 22 | import static com.palantir.gradle.circlestyle.TestCommon.ROOT; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | import java.io.File; 26 | import java.util.List; 27 | 28 | import javax.xml.transform.TransformerException; 29 | 30 | import org.junit.Test; 31 | 32 | import com.google.common.collect.ImmutableList; 33 | 34 | public class FailuresReportGeneratorTests { 35 | 36 | @Test 37 | public void testNoErrors() { 38 | Report report = failuresReport( 39 | ROOT, "fooproject", "checkstyleTest", FAILED_CHECKSTYLE_TIME_NANOS, ImmutableList.of()); 40 | assertThat(report).isEqualTo(new Report.Builder() 41 | .name("fooproject") 42 | .subname("checkstyleTest") 43 | .elapsedTimeNanos(FAILED_CHECKSTYLE_TIME_NANOS) 44 | .build()); 45 | } 46 | 47 | @Test 48 | public void testTwoErrors() { 49 | Report report = failuresReport( 50 | ROOT, "fooproject", "checkstyleTest", FAILED_CHECKSTYLE_TIME_NANOS, CHECKSTYLE_FAILURES); 51 | assertThat(report).isEqualTo(REPORT); 52 | } 53 | 54 | @Test 55 | public void testJavacErrors() throws TransformerException { 56 | List failures = ImmutableList.of( 57 | new Failure.Builder() 58 | .file(new File(ROOT, "src/main/java/com/example/MyClass.java")) 59 | .line(8) 60 | .severity("ERROR") 61 | .message("incompatible types: String cannot be converted to int") 62 | .details("\n private final int a = \"hello\"; " 63 | + "\n ^") 64 | .build(), 65 | new Failure.Builder() 66 | .file(new File(ROOT, "src/main/java/com/example/MyClass.java")) 67 | .line(12) 68 | .severity("ERROR") 69 | .message("cannot assign a value to final variable b") 70 | .details("\n b = 2; " 71 | + "\n ^ ") 72 | .build()); 73 | Report report = failuresReport(ROOT, "foobar", "compileJava", 293_000, failures); 74 | assertThat(report).isEqualTo(new Report.Builder() 75 | .name("foobar") 76 | .subname("compileJava") 77 | .elapsedTimeNanos(293_000) 78 | .addTestCases(new Report.TestCase.Builder() 79 | .name("com.example.MyClass") 80 | .failure(new Report.Failure.Builder() 81 | .message("MyClass.java:8: incompatible types: String cannot be converted to int") 82 | .details("ERROR: incompatible types: String cannot be converted to int\n" 83 | + " private final int a = \"hello\"; \n" 84 | + " ^\n" 85 | + "File: src/main/java/com/example/MyClass.java\n" 86 | + "Line: 8\n") 87 | .build()) 88 | .build()) 89 | .addTestCases(new Report.TestCase.Builder() 90 | .name("com.example.MyClass") 91 | .failure(new Report.Failure.Builder() 92 | .message("MyClass.java:12: cannot assign a value to final variable b") 93 | .details("ERROR: cannot assign a value to final variable b\n" 94 | + " b = 2; \n" 95 | + " ^ \n" 96 | + "File: src/main/java/com/example/MyClass.java\n" 97 | + "Line: 12\n") 98 | .build()) 99 | .build()) 100 | .build()); 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/test/resources/com/palantir/gradle/circlestyle/two-exit-errors-findbugs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /home/ubuntu/fooproject/build/classes/java/main 6 | /home/ubuntu/fooproject/src/main/java/com/example/MyClass.java 7 | 8 | 9 | Method invokes System.exit(...) 10 | com.example.MyClass.methodA() invokes System.exit(...), which shuts down the entire virtual machine 11 | 12 | 13 | At MyClass.java:[lines 3-10] 14 | 15 | In class com.example.MyClass 16 | 17 | 18 | 19 | In method com.example.MyClass.methodA() 20 | 21 | 22 | At MyClass.java:[line 5] 23 | 24 | 25 | 26 | Method invokes System.exit(...) 27 | com.example.MyClass.methodB() invokes System.exit(...), which shuts down the entire virtual machine 28 | 29 | 30 | At MyClass.java:[lines 3-10] 31 | 32 | In class com.example.MyClass 33 | 34 | 35 | 36 | In method com.example.MyClass.methodB() 37 | 38 | 39 | At MyClass.java:[line 9] 40 | 41 | 42 | 43 | Bad practice 44 | 45 | 46 | Method invokes System.exit(...) 47 |
Invoking System.exit shuts down the entire Java virtual machine. This 50 | should only been done when it is appropriate. Such calls make it 51 | hard or impossible for your code to be invoked by other code. 52 | Consider throwing a RuntimeException instead.

53 | 54 | ]]>
55 |
56 | 57 | Dubious method used 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/gradle/circlestyle/CircleStylePluginIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Palantir Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.palantir.gradle.circlestyle; 17 | 18 | import static com.google.common.base.Charsets.UTF_8; 19 | import static com.palantir.gradle.circlestyle.TestCommon.copyTestFile; 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.net.URL; 26 | import java.net.URLClassLoader; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | import org.gradle.testkit.runner.BuildResult; 31 | import org.gradle.testkit.runner.GradleRunner; 32 | import org.junit.Before; 33 | import org.junit.Rule; 34 | import org.junit.Test; 35 | import org.junit.contrib.java.lang.system.EnvironmentVariables; 36 | import org.junit.rules.TemporaryFolder; 37 | 38 | import com.google.common.base.Joiner; 39 | import com.google.common.io.Files; 40 | 41 | public class CircleStylePluginIntegrationTests { 42 | 43 | @Rule public final EnvironmentVariables env = new EnvironmentVariables(); 44 | @Rule public final TemporaryFolder projectDir = new TemporaryFolder(); 45 | 46 | private File reportsDir; 47 | 48 | @Before 49 | public void setUp() { 50 | reportsDir = new File(projectDir.getRoot(), "circle/reports"); 51 | env.set("CIRCLE_TEST_REPORTS", reportsDir.toString()); 52 | env.set("TEST_CLASSPATH", pluginClasspath()); 53 | 54 | copyTestFile("build.gradle", projectDir, "build.gradle"); 55 | copyTestFile("subproject.gradle", projectDir, "subproject/build.gradle"); 56 | copyTestFile("settings.gradle", projectDir, "settings.gradle"); 57 | copyTestFile("checkstyle.xml", projectDir, "config/checkstyle/checkstyle.xml"); 58 | copyTestFile("findbugsIncludeFilter.xml", projectDir, "config/findbugs/findbugsIncludeFilter.xml"); 59 | } 60 | 61 | @Test 62 | public void javacIntegrationTest() throws IOException { 63 | copyTestFile("non-compiling-class", projectDir, "src/main/java/com/example/MyClass.java"); 64 | 65 | BuildResult result = GradleRunner.create() 66 | .withProjectDir(projectDir.getRoot()) 67 | .withArguments("--stacktrace", "compileJava") 68 | .buildAndFail(); 69 | assertThat(result.getOutput()) 70 | .contains("Compilation failed") 71 | .contains("error: incompatible types") 72 | .contains("private final int a") 73 | .contains("error: cannot assign a value to final variable b") 74 | .contains("b = 2") 75 | .contains("uses unchecked or unsafe operations"); 76 | 77 | File report = new File(reportsDir, "javac/foobar-compileJava.xml"); 78 | assertThat(report).exists(); 79 | String reportXml = Files.asCharSource(report, UTF_8).read(); 80 | assertThat(reportXml) 81 | .contains("incompatible types") 82 | .contains("private final int a") 83 | .contains("cannot assign a value to final variable b") 84 | .contains("b = 2") 85 | .doesNotContain("uses unchecked or unsafe operations"); 86 | } 87 | 88 | @Test 89 | public void junitIntegrationTest() throws IOException { 90 | copyTestFile("tested-class", projectDir, "src/main/java/com/example/MyClass.java"); 91 | copyTestFile("tested-class-tests", projectDir, "src/test/java/com/example/MyClassTests.java"); 92 | 93 | BuildResult result = GradleRunner.create() 94 | .withProjectDir(projectDir.getRoot()) 95 | .withArguments("--stacktrace", "test") 96 | .buildAndFail(); 97 | assertThat(result.getOutput()).contains("2 tests completed, 1 failed"); 98 | 99 | File report = new File(reportsDir, "junit/test/TEST-com.example.MyClassTests.xml"); 100 | assertThat(report).exists(); 101 | String reportXml = Files.asCharSource(report, UTF_8).read(); 102 | assertThat(reportXml) 103 | .contains("tests=\"2\"") 104 | .contains("failures=\"1\"") 105 | .contains("org.junit.ComparisonFailure"); 106 | } 107 | 108 | @Test 109 | public void junitSubprojectIntegrationTest() throws IOException { 110 | copyTestFile("tested-class", projectDir, "subproject/src/main/java/com/example/MyClass.java"); 111 | copyTestFile("tested-class-tests", projectDir, "subproject/src/test/java/com/example/MyClassTests.java"); 112 | 113 | BuildResult result = GradleRunner.create() 114 | .withProjectDir(projectDir.getRoot()) 115 | .withArguments("--stacktrace", "subproject:test") 116 | .buildAndFail(); 117 | assertThat(result.getOutput()).contains("2 tests completed, 1 failed"); 118 | 119 | File report = new File(reportsDir, "junit/subproject/test/TEST-com.example.MyClassTests.xml"); 120 | assertThat(report).exists(); 121 | String reportXml = Files.asCharSource(report, UTF_8).read(); 122 | assertThat(reportXml) 123 | .contains("tests=\"2\"") 124 | .contains("failures=\"1\"") 125 | .contains("org.junit.ComparisonFailure"); 126 | } 127 | 128 | @Test 129 | public void checkstyleIntegrationTest() throws IOException { 130 | copyTestFile("checkstyle-violating-class", projectDir, "src/main/java/com/example/MyClass.java"); 131 | 132 | BuildResult result = GradleRunner.create() 133 | .withProjectDir(projectDir.getRoot()) 134 | .withArguments("--stacktrace", "checkstyleMain") 135 | .buildAndFail(); 136 | assertThat(result.getOutput()).contains("Checkstyle rule violations were found"); 137 | 138 | File report = new File(reportsDir, "checkstyle/foobar-checkstyleMain.xml"); 139 | assertThat(report).exists(); 140 | String reportXml = Files.asCharSource(report, UTF_8).read(); 141 | assertThat(reportXml).contains("Name 'a_constant' must match pattern"); 142 | } 143 | 144 | @Test 145 | public void findbugsIntegrationTest() throws IOException { 146 | copyTestFile("findbugs-violating-class", projectDir, "src/main/java/com/example/MyClass.java"); 147 | 148 | BuildResult result = GradleRunner.create() 149 | .withProjectDir(projectDir.getRoot()) 150 | .withArguments("--stacktrace", "findbugsMain") 151 | .buildAndFail(); 152 | assertThat(result.getOutput()).contains("FindBugs rule violations were found"); 153 | 154 | File report = new File(reportsDir, "findbugs/foobar-findbugsMain.xml"); 155 | assertThat(report).exists(); 156 | String reportXml = Files.asCharSource(report, UTF_8).read(); 157 | assertThat(reportXml).contains("methodA() invokes System.exit"); 158 | } 159 | 160 | @Test 161 | public void buildStepFailureIntegrationTest() throws IOException { 162 | BuildResult result = GradleRunner.create() 163 | .withProjectDir(projectDir.getRoot()) 164 | .withArguments("--stacktrace", "failingTask") 165 | .buildAndFail(); 166 | assertThat(result.getOutput()).contains("This task will always fail"); 167 | 168 | File report = new File(reportsDir, "gradle/build.xml"); 169 | assertThat(report).exists(); 170 | String reportXml = Files.asCharSource(report, UTF_8).read(); 171 | assertThat(reportXml).contains("message=\"RuntimeException: This task will always fail\""); 172 | } 173 | 174 | @Test 175 | public void findsUniqueBuildStepsReportFileName() throws IOException { 176 | assertTrue(new File(reportsDir, "gradle").mkdirs()); 177 | assertTrue(new File(reportsDir, "gradle/build.xml").createNewFile()); 178 | assertTrue(new File(reportsDir, "gradle/build2.xml").createNewFile()); 179 | 180 | BuildResult result = GradleRunner.create() 181 | .withProjectDir(projectDir.getRoot()) 182 | .withArguments("--stacktrace", "failingTask") 183 | .buildAndFail(); 184 | assertThat(result.getOutput()).contains("This task will always fail"); 185 | 186 | File report = new File(reportsDir, "gradle/build3.xml"); 187 | assertThat(report).exists(); 188 | String reportXml = Files.asCharSource(report, UTF_8).read(); 189 | assertThat(reportXml).contains("message=\"RuntimeException: This task will always fail\""); 190 | } 191 | 192 | @Test 193 | public void canCallGradleThreeTimesInARow() { 194 | GradleRunner.create() 195 | .withProjectDir(projectDir.getRoot()) 196 | .withArguments("--stacktrace", "dependencies") 197 | .build(); 198 | GradleRunner.create() 199 | .withProjectDir(projectDir.getRoot()) 200 | .withArguments("--stacktrace", "compileJava") 201 | .build(); 202 | GradleRunner.create() 203 | .withProjectDir(projectDir.getRoot()) 204 | .withArguments("--stacktrace", "compileTestJava") 205 | .build(); 206 | } 207 | 208 | private static String pluginClasspath() { 209 | URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); 210 | List classpath = new ArrayList<>(); 211 | for (URL url : classloader.getURLs()) { 212 | classpath.add(url.getFile()); 213 | } 214 | return Joiner.on(':').join(classpath); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | --------------------------------------------------------------------------------