├── .gitignore ├── .travis.yml ├── LICENSE ├── README.adoc ├── build.gradle ├── contrib └── pre-commit ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts └── travis-integ-tests.sh ├── settings.gradle ├── src ├── integTest │ └── groovy │ │ └── com │ │ └── github │ │ └── sherter │ │ └── googlejavaformatgradleplugin │ │ ├── AbstractIntegrationSpec.groovy │ │ ├── CmdlineSpec.groovy │ │ ├── ExtensionSpec.groovy │ │ ├── IntegrationTest.groovy │ │ ├── LoggingSpec.groovy │ │ ├── MultiprojectSpec.groovy │ │ ├── OptionsSpec.groovy │ │ ├── UpToDateSpec.groovy │ │ └── VerificationTaskTest.groovy ├── main │ ├── groovy │ │ └── com │ │ │ └── github │ │ │ └── sherter │ │ │ └── googlejavaformatgradleplugin │ │ │ ├── FormatTask.java │ │ │ ├── FormatterFactory.groovy │ │ │ ├── GoogleJavaFormat.groovy │ │ │ ├── GoogleJavaFormatExtension.groovy │ │ │ ├── GoogleJavaFormatPlugin.groovy │ │ │ ├── SharedContext.groovy │ │ │ ├── TaskConfigurator.java │ │ │ └── VerifyGoogleJavaFormat.groovy │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── sherter │ │ │ └── googlejavaformatgradleplugin │ │ │ ├── ConfigurationException.java │ │ │ ├── FileInfo.java │ │ │ ├── FileInfoDecoder.java │ │ │ ├── FileInfoEncoder.java │ │ │ ├── FileInfoStore.java │ │ │ ├── FileState.java │ │ │ ├── FileToStateMapper.java │ │ │ ├── FormatFileCallable.java │ │ │ ├── FormatterOptions.java │ │ │ ├── FormatterOptionsStore.java │ │ │ ├── PathException.java │ │ │ ├── PersistenceComponent.java │ │ │ ├── PersistenceModule.java │ │ │ ├── Utils.java │ │ │ └── VerifyFileCallable.java │ └── resources │ │ ├── META-INF │ │ └── gradle-plugins │ │ │ └── com.github.sherter.google-java-format.properties │ │ └── VERSION └── test │ └── groovy │ └── com │ └── github │ └── sherter │ └── googlejavaformatgradleplugin │ ├── ExtensionSpec.groovy │ ├── FileInfoDecoderTest.groovy │ ├── FileInfoEncoderTest.groovy │ ├── FileInfoStoreTest.groovy │ ├── FileInfoTest.groovy │ ├── FileToStateMapperTest.groovy │ ├── FormatFileCallableTest.groovy │ ├── FormatterFactoryTest.groovy │ ├── FormatterOptionsStoreTest.groovy │ ├── GoogleJavaFormatPluginTest.groovy │ └── VerifyFileCallableTest.groovy └── subprojects ├── format ├── build.gradle └── src │ ├── main │ └── groovy │ │ └── com │ │ └── github │ │ └── sherter │ │ └── googlejavaformatgradleplugin │ │ └── format │ │ ├── AbstractFormatterFactory.groovy │ │ ├── Configuration.java │ │ ├── Formatter.java │ │ ├── FormatterException.java │ │ ├── FormatterFactory.java │ │ ├── FormatterOption.java │ │ ├── Gjf.java │ │ ├── OneDotEightFactory.groovy │ │ ├── OneDotOneFactory.groovy │ │ ├── OneDotZeroFactory.groovy │ │ └── Style.java │ └── test │ └── groovy │ └── com │ └── github │ └── sherter │ └── googlejavaformatgradleplugin │ └── format │ ├── FormatterSpec.groovy │ └── Resolver.groovy └── test ├── build.gradle └── src ├── main └── groovy │ └── com │ └── github │ └── sherter │ └── googlejavaformatgradleplugin │ └── test │ ├── FileWithState.groovy │ └── Project.groovy └── test └── groovy └── com └── github └── sherter └── googlejavaformatgradleplugin └── test ├── FileWithStateTest.groovy └── ProjectTest.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | /.gradle/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: java 4 | 5 | before_cache: 6 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 7 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 8 | 9 | cache: 10 | directories: 11 | - $HOME/.gradle/caches/ 12 | - $HOME/.gradle/wrapper/ 13 | - $HOME/.groovy/grapes/ 14 | 15 | env: 16 | global: 17 | - secure: pIqlMA953ID3FDiRIzCgtOzCi1wM+JN1X67JoaEoaWZcK9/5UdojyERxbJQb2VwE8jdaRUxJmLIm2RCPiG5JLnDtFBP9y0I5zKfsMYYGqwxX8vj8OwDL5Uvxh20SmpTihPBMJy23pG8urJoenhUO5DFP3qvpv2MpXlENfuCllvCDlV9bIYectnyAfwtbFAZA+DxLfF7LDCfB7lxJZNWoVuFg3Ribc8z/iVlGZ/C0hVBGUR9L1CCKNSbplhXaFrsiRu+p1fCgVYXVuTWrVwjTtd8DKbY6atbakcy8r4EYIg9Acq4vOIY1f9ePQGNE5QqZqeQE9hd5hBChUdH/lnq9+XWsRlU+Q1/gJMoiH3/aNT/gx4wJ7hbijAtKQ63AZcavxqgffVCX+jxvTV+hsN//jLm96FLBuSTkqlu/RF/m/2ZfIqQrv/rf2qrRmT4ygKmQrSfL/55sVnTAD5g8Ss9jHJyBP4YqMCDKm1uHcWIiyqUxtOS+SseOOXadQzoHA5Pb/L7aA001H5o8+hdkY56/7ILDjaqwCyI31TdXlSeELDS6YYMOdUq3Jc79Al0b15xQIHy/tKXy7B+h7U7VP1rL4RaHr605JSUdHuhMCP5PdxjS45ldPkwNLQUt90MGtRl7229PUdgSLt8O17bpnCR4Dhs1SzDH5UeBv9y5bGEY3N0= 18 | - secure: Oq15JlYDBG3Z0YoxKAsaQlvakzzi5O/MqndVsYBKiro1xgBKiiyTo9pAdOHsyIuVOlzZZhlTyNSdcKDpjzN05HbcRvMo3zd/BigwesO6Rm02lPY4JpR9oxYa3pRHR+hcNBYoErkMNzwr5glRHZISuT8MTTBBKKzt/ltaPNkbvDMlIkNWLLqjTH+csJechsZ+Q8LAZYlRsTkEWjKI8COAnyYBaJJE4dPTa7TYm4eDwNaDWSumoPy8Xh7SwhyKGMSWhDTIMiboGUuF+qSl6RGoSxmHoMmEDuOp4JlVfQ+XsSBHdKFrjkSfW07nTQw2wSiHj4Jv2+g7PhMh6vLbsHWER0oUxLX6SfgZmyzyds501s+WCM7lHjf+CrXbNTg4b7LnI+YQog+LVJW/7HJwmwCOWENriFOQSsKuWG59lTh2fa3AzC22c/RcvJ+Ed/aZ2DBVtj5XavjhTI5C2K0W48ljX9GcKuNgawvMAOBUpmWDHcg7zLviMTVh3fpdTjMJZmfRiwH5Rh+FmsyWPdSrquDrdWQZ25UcP8OBVmlDdFhoyoJhdsVsKXP4DEFs7JoQi+GEy/PBloZgukjGifrnEXbv5CFSs8NozPqMk7170ts7ra7/vN9EP/DSkR/7z6//RINdmhh3KscAU2Ib58jqltK58/KqFYV1/iabeLJKVpkpVjg= 19 | - secure: DRVGtaHlV11hRFbfLVueXiQovFUmcBd92sI7Ot9HLVM8yZY1N7GzN4f9cr/OLm4a0ROUOaOa05j/dqL/7zkoG2rSPfOWcBNDvxEiIarVFh393zs3kbn+Qk8OLVF5jg7WHGV5PM/9gjJAdAnr3/IySweXHinhuoRP8/hZhIx56C3v9mq22xHvD8T+Nnm9x13AwNYd48uw1OyDBO1XdGMXEIfPrRFcDyRxokd/FxbryX6kkl5LFjoYY2F12m+LgeZsORVQ5zwHIhqZQ0SFruAy1zsXivEtAc5E8zgIHnI+8HbMq/vUNyvkfciyb4ajg1WW7N4o1A38fHgMLzcDCbcFHDr/sh4uupOraaz7mhsDXIDBj5y24L0nEKmV57trVis8BFIflkA/S7cGO+sYNvRVM/Xzow+xPeKUm9iwke6tzAn71Y0Z2lWaXipvGiYfi3VMTzwTJiR2zTnTlgx1yIydG8+RTd1AOmq4AHpmu2zifY15YGm2VGTO87TMfo1ZWMP9rC5vsmndgnYcdZmFXnmdtmN7k2N1w/XrQ+Ef9fcc7jLorWH4ZcaNtFtlL/GEYj0PMozPceRdT+pi9m9yW+g7Uvrvj2qeKtOtMTK3r7NSAB+dBhaDt9xtyC1qfNUfoxAynF7oO0XkMlouUU4uBQzQ9illKKqsZCk6wmxQi4JDqWk= 20 | - secure: PZCOTXWpKcTUEFvxWArVBHfJLA3ncArVJVmeWqGEe3f1SH3rDbtnHwAK9MZ+qfQvtmfP5KrsXbechosRGxmY4NzGTuxupzYIeNq3x73RFLYmhDNaf3yIHx5KbLsKm+/y7X11ORKEWTX0Ub68CAc8dyV5iV2CR/1gPpBcL2L7s4xqS9aeeVJj9C2fIOY9uLaCA4g5Vx3opBVLsh7kmBuOgl8ajg24hI4oajQmEatKfxIqZp892cPvOVZgsmR9Y83z6ZL48tmPKsttB5utBe8AQY7TnKwKqo3tFDvTFgO4Vkv+plTJQHU3mULoG7WixL+AvWS6+jhyl2pzhEovDYMHkWi4ozxbO87ZdZp9GIxLihsXmxIunwz7mBolZ1XE6vEwWoVhlWXuZ/fO1udi8UIvcxr01jZwYzVrhvpnpoT/j2goZr+V2kuVXEghm50saRDIP5rAPk4hLj9Q8G3SKObNU0xj8lN/fLM4XIvaZV26kFbDfREBfoV3GL7tHvEdg0gAyjO9d9VIJsomj+/K/e6L1GZCwiN2VgHPJWzgmNMofCFNF7GusA8gju6mO5qWxYZxq0y28p93uE4QJvxZUV9gNZ8fvDrK6xDylB06SDXQqyTxf+GnOv2bRYTTap2PSBgcFScr/Q7jTB65h/e0KlFle8dOEwDMm9E62r4WfS2E6sY= 21 | - ALL_GRADLE_VERSIONS=2.6,2.7,2.8,2.9,2.10,2.11,2.12,2.13,2.14.1,3.0,3.1,3.2.1,3.3,3.4.1,3.5.1,4.0.2,4.1,4.2.1,4.3.1,4.4.1,4.5.1,4.6,4.7,4.8.1,4.9,4.10.3,5.0,5.1.1,5.2.1,5.3.1,5.4.1,5.5.1,5.6.4,6.0.1,6.1.1,6.2.2,6.3,6.4.1,6.5,6.9.1 22 | - PARALLEL_INTEG_TEST_COUNT=3 23 | 24 | jdk: openjdk8 25 | install: true # overwrite default install step (./gradlew assemble) 26 | 27 | jobs: 28 | include: 29 | - stage: unit tests 30 | script: ./gradlew test 31 | - jdk: openjdk11 32 | script: ./gradlew test 33 | - stage: integration tests 34 | env: INDEX=0 35 | script: scripts/travis-integ-tests.sh 36 | - env: INDEX=1 37 | script: scripts/travis-integ-tests.sh 38 | - env: INDEX=2 39 | script: scripts/travis-integ-tests.sh 40 | - jdk: oraclejdk8 41 | env: GRADLE_VERSIONS=2.6,3.0,4.0,5.0,6.0,6.5 42 | script: scripts/travis-integ-tests.sh 43 | - jdk: openjdk11 44 | env: GRADLE_VERSIONS=4.9,5.0,6.0,6.5,6.9.1 45 | script: scripts/travis-integ-tests.sh 46 | - jdk: oraclejdk11 47 | env: GRADLE_VERSIONS=4.9,5.0,6.0,6.5,6.9.1 48 | script: scripts/travis-integ-tests.sh 49 | - jdk: openjdk14 50 | env: GRADLE_VERSIONS=6.3,6.5,6.9.1 51 | script: scripts/travis-integ-tests.sh 52 | - jdk: oraclejdk14 53 | env: GRADLE_VERSIONS=6.3,6.5,6.9.1 54 | script: scripts/travis-integ-tests.sh 55 | - stage: deploy 56 | if: (branch = master) AND (NOT (type = pull_request)) 57 | script: ./gradlew publish 58 | - if: tag IS present 59 | script: ./gradlew publishPlugins -Dgradle.publish.key=$PLUGIN_PORTAL_KEY -Dgradle.publish.secret=$PLUGIN_PORTAL_SECRET 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Simon Herter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = google-java-format-gradle-plugin 2 | :release-version: 0.9 3 | :default-google-java-format-version: 1.8 4 | :snapshot-version: 0.9-SNAPSHOT 5 | 6 | 7 | A https://github.com/gradle/gradle[Gradle] plugin that utilizes 8 | https://github.com/google/google-java-format[google-java-format] to 9 | format the Java source files in your Gradle project. 10 | 11 | image:https://travis-ci.org/sherter/google-java-format-gradle-plugin.svg?branch=master["Build 12 | Status", 13 | link="https://travis-ci.org/sherter/google-java-format-gradle-plugin"] 14 | 15 | == Quick Start 16 | * Apply the plugin in your build script (follow https://plugins.gradle.org/plugin/com.github.sherter.google-java-format[these instructions] 17 | for Gradle versions below `2.1`) 18 | + 19 | [source,groovy] 20 | [subs="attributes"] 21 | ---- 22 | plugins { 23 | id 'com.github.sherter.google-java-format' version '{release-version}' 24 | } 25 | ---- 26 | + 27 | 28 | * Make sure you have defined a repository that contains version `{default-google-java-format-version}` of `google-java-format` 29 | + 30 | [source,groovy] 31 | ---- 32 | repositories { 33 | mavenCentral() 34 | } 35 | ---- 36 | 37 | * Execute the task `googleJavaFormat` to format all `*.java` files in the project 38 | + 39 | [source,shell] 40 | ---- 41 | $ ./gradlew goJF 42 | ---- 43 | + 44 | * Execute the task `verifyGoogleJavaFormat` to verify that all `*.java` files are formatted properly 45 | + 46 | [source,shell] 47 | ---- 48 | $ ./gradlew verGJF 49 | ---- 50 | 51 | == Extended Usage 52 | * The plugin adds the extension `googleJavaFormat` to your project. Adjust the variable `toolVersion` to use a specific version of `google-java-format`. You can even define `SNAPSHOT` versions, but make sure that you have added a repository to the project that contains this version (e.g. `mavenLocal()`). For plugin version `{release-version}` this value defaults to `{default-google-java-format-version}`. On every new release the default value will be updated to the latest version of `google-java-format`. 53 | + 54 | [source,groovy] 55 | [subs="attributes"] 56 | ---- 57 | googleJavaFormat { 58 | toolVersion = '1.1-SNAPSHOT' 59 | } 60 | ---- 61 | 62 | * Choose betweeen `'GOOGLE'` (default) and `'AOSP'` style by setting the style option: 63 | + 64 | [source,groovy] 65 | ---- 66 | googleJavaFormat { 67 | options style: 'AOSP' 68 | } 69 | ---- 70 | 71 | * The extension object allows you to configure the default inputs for all tasks related to this plugin. It provides the same methods as a `https://docs.gradle.org/2.0/javadoc/org/gradle/api/tasks/SourceTask.html[SourceTask]` to set these inputs. Initially, a `https://docs.gradle.org/current/javadoc/org/gradle/api/file/FileTree.html[FileTree]` that contains all `*.java` files in the project directory (and recursivly all subdirectories, excluding files in a (sub)project's `buildDir`) is added with one `source` method call. However, this initial value for the default inputs can be overwritten (using `setSource`), extended (using additional `source` calls) and/or further filtered (using `include` and/or `exclude` patterns, see https://docs.gradle.org/2.0/javadoc/org/gradle/api/tasks/util/PatternFilterable.html[Ant-style exclude patterns]). The chapter about `https://docs.gradle.org/current/userguide/working_with_files.html[Working With Files]` in the Gradle user guide might be worth reading. 72 | + 73 | [source,groovy] 74 | [subs="attributes"] 75 | ---- 76 | googleJavaFormat { 77 | source = sourceSets*.allJava 78 | source 'src/special_dir' 79 | include '**/*.java' 80 | exclude '**/*Template.java' 81 | exclude 'src/test/template_*' 82 | } 83 | ---- 84 | 85 | * All tasks are of type `https://docs.gradle.org/2.0/javadoc/org/gradle/api/tasks/SourceTask.html[SourceTask]` and can be configured accordingly. In addition to the default tasks `googleJavaFormat` and `verifyGoogleJavaFormat` you can define custom tasks if you like. The task type `VerifyGoogleJavaFormat` also implements the interface `https://docs.gradle.org/2.0/javadoc/org/gradle/api/tasks/VerificationTask.html[VerificationTask]`. 86 | + 87 | [source,groovy] 88 | ---- 89 | import com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormat 90 | import com.github.sherter.googlejavaformatgradleplugin.VerifyGoogleJavaFormat 91 | 92 | task format(type: GoogleJavaFormat) { 93 | source 'src/main' 94 | source 'src/test' 95 | include '**/*.java' 96 | exclude '**/*Template.java' 97 | } 98 | 99 | task verifyFormatting(type: VerifyGoogleJavaFormat) { 100 | source 'src/main' 101 | include '**/*.java' 102 | ignoreFailures true 103 | } 104 | ---- 105 | If you set sources in a task (by calling `setSource` and/or `source`), the default inputs from the extension object are *not* added to the final set of sources to process. However, if you don't modify the sources in the task and only add `include` and/or `exclude` patterns, the default inputs are first added and then further filtered according the the patterns. 106 | 107 | * An additional include filter can be applied on the command line at execution time by setting the `.include` system property. All input files that remain after applying the filters in `build.gradle` also have to match at least one of the patterns in the comma separated list in order to be eventually processed by the task. Note that this only allows you to further _reduce_ the number of processed files. You _can not_ use this to add another `include` method call to a task. (See https://github.com/sherter/google-java-format-gradle-plugin/blob/master/contrib/pre-commit[contrib/pre-commit] for a useful application of this feature). 108 | + 109 | [source,shell] 110 | ---- 111 | $ ./gradlew verGJF -DverifyGoogleJavaFormat.include="*Foo.java,bar/Baz.java" 112 | ---- 113 | 114 | 115 | == Snapshots 116 | On every push to the master branch https://travis-ci.org/[Travis] runs 117 | the tests and, if all tests pass, publishes the built artifact to 118 | https://oss.sonatype.org/content/repositories/snapshots/[Sonatype's 119 | `snapshots` repository]. Use the following build script snippet for 120 | the current snapshot version: 121 | 122 | [source,groovy] 123 | [subs="attributes"] 124 | ---- 125 | buildscript { 126 | repositories { 127 | maven { 128 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 129 | } 130 | } 131 | dependencies { 132 | classpath 'com.github.sherter.googlejavaformatgradleplugin:google-java-format-gradle-plugin:{snapshot-version}' 133 | } 134 | } 135 | 136 | apply plugin: 'com.github.sherter.google-java-format' 137 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | id 'maven-publish' 4 | id 'com.gradle.plugin-publish' version '0.12.0' 5 | id 'com.github.johnrengelman.shadow' version '5.1.0' 6 | id "com.github.sherter.google-java-format" version '0.8' 7 | } 8 | 9 | 10 | version = file('src/main/resources/VERSION').text.trim() 11 | 12 | sourceCompatibility = 1.8 13 | targetCompatibility = 1.8 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | sourceSets { 20 | integTest 21 | } 22 | 23 | configurations { 24 | bundle 25 | compile.extendsFrom bundle 26 | integTestCompile.extendsFrom testCompile 27 | } 28 | 29 | dependencies { 30 | implementation gradleApi() 31 | bundle(project(':format')) { 32 | exclude group: 'org.codehaus.groovy', module: 'groovy-all' 33 | } 34 | bundle 'com.google.guava:guava:30.0-jre' 35 | bundle 'com.google.dagger:dagger:2.23.2' 36 | annotationProcessor 'com.google.dagger:dagger-compiler:2.23.2' 37 | annotationProcessor 'com.google.auto.value:auto-value:1.6.5' 38 | compileOnly 'com.google.auto.value:auto-value-annotations:1.6.5' 39 | 40 | testImplementation('org.spockframework:spock-core:1.3-groovy-2.5') { 41 | exclude group: 'org.codehaus.groovy' 42 | } 43 | testImplementation 'com.google.jimfs:jimfs:1.1' 44 | 45 | integTestImplementation project(':test') 46 | integTestImplementation gradleTestKit() 47 | integTestImplementation('org.spockframework:spock-core:1.3-groovy-2.5') { 48 | exclude group: 'org.codehaus.groovy' 49 | } 50 | integTestImplementation sourceSets.main.output 51 | } 52 | 53 | def integTestGradleVersion = System.getenv('GRADLE_VERSION') ?: '2.6' 54 | 55 | tasks.register("integrationTest", Test) { 56 | dependsOn publishToMavenLocal 57 | group 'Verification' 58 | testClassesDirs = sourceSets.integTest.output.classesDirs 59 | classpath = sourceSets.integTest.runtimeClasspath 60 | systemProperty 'GRADLE_VERSION', integTestGradleVersion 61 | reports.html.destination = new File(reportsDir, 'integrationTest' + File.separator + integTestGradleVersion) 62 | mustRunAfter tasks.named("test") 63 | } 64 | 65 | tasks.named("check").configure { 66 | dependsOn integrationTest 67 | } 68 | 69 | 70 | tasks.shadowJar { 71 | classifier = null // necessary for com.gradle.plugin-publish to pick this artifact up 72 | configurations = [project.configurations.bundle] 73 | relocate('dagger', 'com.github.sherter.googlejavaformat.dagger') 74 | relocate('com.google.common', 'com.github.sherter.googlejavaformatgradleplugin.com.google.common') 75 | exclude('META-INF/maven/**/*') 76 | } 77 | // removes the dependsOn relation between "publishPlugins" and "jar" and forces 78 | // the correct (the shadowed) artifact to be uploaded to the plugin portal 79 | configurations.archives.artifacts.clear() 80 | artifacts { 81 | archives tasks.shadowJar 82 | } 83 | 84 | pluginBundle { 85 | website = 'https://github.com/sherter/google-java-format-gradle-plugin' 86 | vcsUrl = 'https://github.com/sherter/google-java-format-gradle-plugin.git' 87 | description = 'Format your Java source files with google-java-format' 88 | tags = ['java', 'format', 'style'] 89 | 90 | plugins { 91 | googleJavaFormatGradlePlugin { 92 | id = 'com.github.sherter.google-java-format' 93 | displayName = 'google-java-format gradle plugin' 94 | } 95 | } 96 | 97 | withDependencies { List list -> 98 | list.clear() 99 | } 100 | } 101 | 102 | publishing { 103 | publications { 104 | maven(MavenPublication) { 105 | groupId 'com.github.sherter.googlejavaformatgradleplugin' 106 | artifactId 'google-java-format-gradle-plugin' 107 | 108 | project.shadow.component(it) 109 | } 110 | } 111 | if (version.endsWith('-SNAPSHOT')) { 112 | repositories { 113 | maven { 114 | name = 'SonatypeSnapshot' 115 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 116 | credentials { 117 | username System.env.SONATYPE_SNAPSHOTS_USERNAME 118 | password System.env.SONATYPE_SNAPSHOTS_PASSWORD 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /contrib/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify proper formatting. 4 | 5 | files=$(git diff --cached --name-only --diff-filter=ACM | paste -s -d",") 6 | # this only works if ignoreFailures is set to false (which it is by default) 7 | ./gradlew verifyGJF -DverifyGoogleJavaFormat.include="$files" &>/dev/null && exit 0 8 | 9 | echo "Some files are not formatted properly. Please run:" 10 | echo "./gradlew gJF -DgoogleJavaFormat.include=\"$files\"" 11 | exit 1 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherter/google-java-format-gradle-plugin/19bf3cb96acac8a2a41c3dcacb6af00d6f09cb8e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /scripts/travis-integ-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # This script runs the integration tests against multiple Gradle versions. 5 | # It is used in the travis 'script' step (see ../.travis.yml). 6 | 7 | 8 | # Determine the 'versions_array' that contains all Gradle versions we have 9 | # to run the integration tests against in this job. 10 | if [[ "${GRADLE_VERSIONS=unset}" == "unset" ]]; then 11 | # The GRADLE_VERSIONS to test against in this job are _not_ set explicitly. 12 | # This means we test against some versions from ALL_GRADLE_VERSIONS, for which 13 | # this specific job is responsible (when testing ALL_GRADLE_VERSIONS 14 | # tests are split among multiple jobs for performance reasons). 15 | IFS=',' read -r -a all_versions_array <<< "$ALL_GRADLE_VERSIONS" 16 | length=$(( (${#all_versions_array[@]} + PARALLEL_INTEG_TEST_COUNT - 1) / PARALLEL_INTEG_TEST_COUNT )) 17 | versions_array=(${all_versions_array[@]:((INDEX * length)):length}) 18 | else 19 | # The GRADLE_VERSIONS to test against _are_ set explicitly (used to only run 20 | # integration tests against some gradle versions when varying JDK). 21 | IFS=',' read -r -a versions_array <<< "$GRADLE_VERSIONS" 22 | fi 23 | 24 | (set -x; JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 ./gradlew publishToMavenLocal) 25 | 26 | echo 27 | echo 28 | echo "*********************************************************************************" 29 | echo "This job runs the integration tests against the following versions of Gradle:" 30 | joint_versions=$(printf ", %s" "${versions_array[@]}") 31 | echo "${joint_versions:2}" 32 | echo "*********************************************************************************" 33 | echo 34 | echo 35 | 36 | for element in "${versions_array[@]}"; do 37 | (set -x; GRADLE_VERSION="$element" ./gradlew integrationTest --exclude-task publishToMavenLocal) 38 | done 39 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'format', 'test' 2 | project(':format').projectDir = new File(settingsDir, 'subprojects/format') 3 | project(':test').projectDir = new File(settingsDir, 'subprojects/test') 4 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/AbstractIntegrationSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Gjf 4 | import com.github.sherter.googlejavaformatgradleplugin.test.Project 5 | import org.gradle.testkit.runner.GradleRunner 6 | import org.junit.Rule 7 | import org.junit.rules.TemporaryFolder 8 | import spock.lang.Specification 9 | import spock.util.environment.Jvm 10 | 11 | abstract class AbstractIntegrationSpec extends Specification { 12 | 13 | static final String buildScriptBlock = """\ 14 | |buildscript { 15 | | repositories { 16 | | mavenLocal() 17 | | } 18 | | dependencies { 19 | | classpath group: 'com.github.sherter.googlejavaformatgradleplugin', 20 | | name: 'google-java-format-gradle-plugin', 21 | | version: '${GoogleJavaFormatPlugin.PLUGIN_VERSION}' 22 | | } 23 | |} 24 | |""".stripMargin() 25 | 26 | static final String applyPlugin = """\ 27 | |$buildScriptBlock 28 | |apply plugin: 'com.github.sherter.google-java-format' 29 | |""".stripMargin() 30 | 31 | // if a dependency is not in teskit's dependency cache, try to load it from 32 | // the local repository first, before reaching out to the web 33 | static final String defaultRepositories = '''\ 34 | |repositories { 35 | | mavenLocal() 36 | | mavenCentral() 37 | |} 38 | |'''.stripMargin() 39 | 40 | // google-java-format targets Java 11 since version 1.8 41 | static final String downgradeToolVersionIfLatestNotSupportedOnCurrentJvm = new BigDecimal(Jvm.current.javaSpecificationVersion) < BigDecimal.valueOf(11) ? """\ 42 | |googleJavaFormat { 43 | | toolVersion = '${Gjf.SUPPORTED_VERSIONS.get(Gjf.SUPPORTED_VERSIONS.indexOf('1.8') - 1)}' 44 | |} 45 | |""".stripMargin() : '' 46 | 47 | @Rule 48 | TemporaryFolder temporaryFolder 49 | GradleRunner runner 50 | Project project 51 | 52 | def setup() { 53 | runner = GradleRunner.create() 54 | .withGradleVersion(System.properties['GRADLE_VERSION']) 55 | .withProjectDir(temporaryFolder.root) 56 | project = new Project(temporaryFolder.root) 57 | additionalSetup() 58 | } 59 | 60 | void additionalSetup() {}; 61 | } 62 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/CmdlineSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import org.gradle.testkit.runner.UnexpectedBuildFailure 4 | import spock.lang.Unroll 5 | 6 | @Unroll 7 | class CmdlineSpec extends AbstractIntegrationSpec { 8 | 9 | def "specify include pattern on command line"() { 10 | given: 11 | project.createFile(['build.gradle'], """\ 12 | |$applyPlugin 13 | |$defaultRepositories 14 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 15 | |""".stripMargin()) 16 | def foo = project.createFile(['Foo.java'], 'class Foo { }') 17 | def bar = project.createFile(['Bar.java'], 'class Bar { }') 18 | 19 | when: "formatting task is executed" 20 | runner.withArguments("googleJavaFormat", "-DgoogleJavaFormat.include=*Bar*").build() 21 | 22 | then: "only included files are formatted" 23 | !foo.contentHasChanged() 24 | bar.contentHasChanged() 25 | 26 | when: 27 | runner.withArguments("verifyGoogleJavaFormat").build() 28 | 29 | then: 30 | thrown(UnexpectedBuildFailure) 31 | 32 | when: 33 | runner.withArguments("verifyGoogleJavaFormat", "-DverifyGoogleJavaFormat.include=*Bar*").build() 34 | 35 | then: 36 | notThrown(Throwable) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/ExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import spock.lang.Unroll 4 | 5 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 6 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_VERIFY_TASK_NAME 7 | 8 | @Unroll 9 | class ExtensionSpec extends AbstractIntegrationSpec { 10 | 11 | def 'change default inputs in extension'() { 12 | given: 13 | def foo = project.createFile(['Foo.java'], 'class Foo { void bar() {}}') 14 | project.createFile(['build.gradle'], """\ 15 | |$applyPlugin 16 | |$defaultRepositories 17 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 18 | |$extension 19 | |""".stripMargin()) 20 | 21 | when: 22 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 23 | 24 | then: 25 | foo.read() == expected 26 | 27 | where: 28 | extension | expected 29 | "" | "class Foo {\n void bar() {}\n}\n" 30 | "googleJavaFormat { setSource 'bar' }" | "class Foo { void bar() {}}" 31 | "googleJavaFormat { source 'bar' }" | "class Foo {\n void bar() {}\n}\n" 32 | "googleJavaFormat { exclude 'Foo*' }" | "class Foo { void bar() {}}" 33 | "googleJavaFormat { include '**/*bar*' }" | "class Foo { void bar() {}}" 34 | } 35 | 36 | def 'task exclude/includes filter default inputs further and source() overwrites them (formatting task)'() { 37 | given: 38 | def foo = project.createFile(['Foo.java'], ' class Foo {}') 39 | def bar = project.createFile(['src', 'Bar.java'], ' class Bar {}') 40 | project.createFile(['build.gradle'], """\ 41 | |$applyPlugin 42 | |$defaultRepositories 43 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 44 | |tasks.googleJavaFormat { 45 | |$task 46 | |} 47 | |googleJavaFormat { 48 | |$extension 49 | |} 50 | |""".stripMargin()) 51 | 52 | when: 53 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 54 | 55 | then: 56 | foo.read() == expectedFoo 57 | bar.read() == expectedBar 58 | 59 | where: 60 | extension | task |expectedFoo | expectedBar 61 | "" | "exclude '**/Bar.java'" | "class Foo {}\n" | " class Bar {}" 62 | "" | "include 'Foo.java'" | "class Foo {}\n" | " class Bar {}" 63 | "setSource 'Foo.java'" | "" | "class Foo {}\n" | " class Bar {}" 64 | "setSource 'Foo.java'" | "source('src').include('*.java')" | " class Foo {}" | "class Bar {}\n" 65 | } 66 | 67 | def 'task exclude/includes filter default inputs further and source() overwrites them (verification task)'() { 68 | given: 69 | def foo = project.createFile(['Foo.java'], ' class Foo {}') 70 | def bar = project.createFile(['src', 'Bar.java'], ' class Bar {}') 71 | project.createFile(['build.gradle'], """\ 72 | |$applyPlugin 73 | |$defaultRepositories 74 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 75 | |tasks.verifyGoogleJavaFormat { 76 | |$task 77 | |} 78 | |googleJavaFormat { 79 | |$extension 80 | |} 81 | |""".stripMargin()) 82 | 83 | when: 84 | def result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).buildAndFail() 85 | 86 | then: 87 | result.output =~ /$regex1/ 88 | !(result.output =~ /$regex2/) 89 | 90 | where: 91 | extension | task | regex1 | regex2 92 | "" | "exclude '**/Bar.java'" | "Foo.java" | "Bar.java" 93 | "" | "include 'Foo.java'" | "Foo.java" | "Bar.java" 94 | "setSource 'Foo.java'" | "" | "Foo.java" | "Bar.java" 95 | "setSource 'Foo.java'" | "source('src').include('*.java')" | "Bar.java" | "Foo.java" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/IntegrationTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import spock.lang.Unroll 4 | 5 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 6 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_VERIFY_TASK_NAME 7 | 8 | @Unroll 9 | class IntegrationTest extends AbstractIntegrationSpec { 10 | 11 | def "exclude a file from the default format task"() { 12 | given: 13 | project.createFile(['build.gradle'], """\ 14 | |$applyPlugin 15 | |$defaultRepositories 16 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 17 | |tasks.$DEFAULT_FORMAT_TASK_NAME { 18 | | exclude '**/Bar.java' 19 | |} 20 | |""".stripMargin()) 21 | def foo = project.createFile(['Foo.java'], 'class Foo { }') 22 | def bar = project.createFile(['Bar.java'], 'class Bar { }') 23 | 24 | when: "formatting task is executed" 25 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 26 | 27 | then: "source files are formatted properly afterwards" 28 | foo.contentHasChanged() 29 | 30 | // Time resolution doesn't seem to be good enough or some caching mechanism prevents 31 | // the following commented out check from succeeding in some cases. 32 | // It seems to be particularly unreliable for the combination (oraclejdk8,gradle3). 33 | 34 | // foo.lastModifiedTimeHasChanged() 35 | 36 | !bar.contentHasChanged() 37 | !bar.lastModifiedTimeHasChanged() 38 | result.output.contains('BUILD SUCCESSFUL') 39 | } 40 | 41 | def "define additional format task"() { 42 | given: 43 | project.createFile(['build.gradle'], """\ 44 | |$applyPlugin 45 | |$defaultRepositories 46 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 47 | |task customFormatTask(type: $GoogleJavaFormat.name) { 48 | | source 'src/Foo.java' 49 | |} 50 | |""".stripMargin()) 51 | def foo = project.createFile(['src', 'Foo.java'], 'class Foo { }') 52 | def bar = project.createFile(['Bar.java'], 'class Bar { }') 53 | 54 | when: "formatting task is executed" 55 | def result = runner.withArguments('customFormatTask').build() 56 | 57 | then: "source files are formatted properly afterwards" 58 | foo.read() == 'class Foo {}\n' 59 | !bar.contentHasChanged() 60 | !bar.lastModifiedTimeHasChanged() 61 | result.output.contains('BUILD SUCCESSFUL') 62 | } 63 | 64 | def 'report unformatted java sources'() { 65 | given: "a java project with java plugin applied" 66 | project.createFile(['build.gradle'], """\ 67 | |$applyPlugin 68 | |$defaultRepositories 69 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 70 | |""".stripMargin()) 71 | project.createFile(['Foo.java'], 'class Foo {}\n') 72 | project.createFile(['Bar.java'], 'class Bar { }') 73 | 74 | when: 75 | def result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).buildAndFail() 76 | 77 | then: 78 | !result.output.contains('Foo.java') 79 | result.output.contains('Bar.java') 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/LoggingSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 4 | 5 | class LoggingSpec extends AbstractIntegrationSpec { 6 | 7 | def "format task logs previously unformatted files as now formatted"() { 8 | given: 9 | project.createFile(['build.gradle'], """\ 10 | |$applyPlugin 11 | |$defaultRepositories 12 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 13 | |""".stripMargin()) 14 | project.createFile(['src', 'main', 'java', 'Foo.java'], 'class Foo { }') 15 | project.createFile(['Bar.java'], 'class Bar {}\n') 16 | 17 | when: 18 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 19 | 20 | then: 21 | result.output =~ /Foo\.java: formatted successfully/ 22 | !(result.output =~ /Bar\.java: formatted successfully/) 23 | } 24 | 25 | def "format task logs already formatted files as UP-TO-DATE"() { 26 | given: 27 | project.createFile(['build.gradle'], """\ 28 | |$applyPlugin 29 | |$defaultRepositories 30 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 31 | |""".stripMargin()) 32 | project.createFile(['src', 'main', 'java', 'Foo.java'], 'class Foo { }') 33 | project.createFile(['Bar.java'], 'class Bar {}\n') 34 | 35 | when: 36 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME, '--info').build() 37 | 38 | then: 39 | !(result.output =~ /Foo\.java: UP-TO-DATE/) 40 | result.output =~ /Bar\.java: UP-TO-DATE/ 41 | 42 | when: 43 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME, '--info').build() 44 | 45 | then: 46 | result.output =~ /Foo\.java: UP-TO-DATE/ 47 | result.output =~ /Bar\.java: UP-TO-DATE/ 48 | } 49 | 50 | def "format task logs non-Java files in its inputs and makes the build fail"() { 51 | given: 52 | project.createFile(['build.gradle'], """\ 53 | |$applyPlugin 54 | |$defaultRepositories 55 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 56 | |""".stripMargin()) 57 | project.createFile(['src', 'main', 'java', 'Foo.java'], 'class Foo { }') 58 | project.createFile(['Baz.java'], 'this is not Java') 59 | project.createFile(['Bar.java'], 'class Bar {}\n') 60 | project.createFile(['OtherNon.java'], 'is not Java') 61 | 62 | when: 63 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME, '--info').buildAndFail() 64 | 65 | then: 66 | result.output =~ /Foo\.java: formatted successfully/ 67 | result.output =~ /Bar\.java: UP-TO-DATE/ 68 | // (?s) makes the regex match newlines with . (dot) operators 69 | result.output =~ /(?s)Failed to format the following files.*Baz\.java/ 70 | result.output =~ /(?s)Failed to format the following files.*OtherNon\.java/ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/MultiprojectSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.test.FileWithState 4 | 5 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 6 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_VERIFY_TASK_NAME 7 | 8 | class MultiprojectSpec extends AbstractIntegrationSpec { 9 | 10 | FileWithState buildfile 11 | 12 | void additionalSetup() { 13 | buildfile = project.createFile(['build.gradle'], """\ 14 | |$buildScriptBlock 15 | | 16 | |subprojects { 17 | | apply plugin: 'com.github.sherter.google-java-format' 18 | | $defaultRepositories 19 | | $downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 20 | | tasks.verifyGoogleJavaFormat.ignoreFailures = true 21 | |} 22 | |""".stripMargin()) 23 | project.createFile(['settings.gradle'], """\ 24 | |include 'sub2', 'sub1', 'sub3' 25 | |""".stripMargin()) 26 | } 27 | 28 | 29 | def "multiproject format"() { 30 | project.createFile(['sub1', 'Foo1.java'], 'class Foo { } ') 31 | project.createFile(['sub2', 'Foo2.java'], 'class Foo { } ') 32 | project.createFile(['sub3', 'Foo3.java'], 'class Foo { } ') 33 | 34 | when: 35 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 36 | 37 | then: 38 | result.output.contains('sub1/Foo1.java') 39 | result.output.contains('sub2/Foo2.java') 40 | result.output.contains('sub3/Foo3.java') 41 | } 42 | 43 | def "multiproject verify"() { 44 | project.createFile(['sub1', 'Foo1.java'], 'class Foo { } ') 45 | project.createFile(['sub2', 'Foo2.java'], 'class Foo { } ') 46 | project.createFile(['sub3', 'Foo3.java'], 'class Foo { } ') 47 | 48 | when: 49 | def result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).build() 50 | 51 | then: 52 | result.output.contains('sub1/Foo1.java') 53 | result.output.contains('sub2/Foo2.java') 54 | result.output.contains('sub3/Foo3.java') 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/OptionsSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 4 | 5 | class OptionsSpec extends AbstractIntegrationSpec { 6 | 7 | def 'default format task incorporates specified style'() { 8 | given: 9 | def foo = project.createFile(['Foo.java'], 'class Foo { void bar() {}}') 10 | project.createFile(['build.gradle'], """\ 11 | |$applyPlugin 12 | |$defaultRepositories 13 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 14 | |$extension 15 | |""".stripMargin()) 16 | 17 | when: 18 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 19 | 20 | then: 21 | foo.read() == expected 22 | 23 | where: 24 | extension | expected 25 | "" | "class Foo {\n void bar() {}\n}\n" // == GOOGLE 26 | "googleJavaFormat { options style: 'GOOGLE' }" | "class Foo {\n void bar() {}\n}\n" 27 | "googleJavaFormat { options style: 'AOSP' }" | "class Foo {\n void bar() {}\n}\n" 28 | } 29 | 30 | def 'custom format task incorporates specified style'() { 31 | given: 32 | def foo = project.createFile(['Foo.java'], 'class Foo { void bar() {}}') 33 | project.createFile(['build.gradle'], """\ 34 | |$applyPlugin 35 | |$defaultRepositories 36 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 37 | |task myFormat(type: ${GoogleJavaFormat.name}) { 38 | | source 'Foo.java' 39 | |} 40 | |$extension 41 | |""".stripMargin()) 42 | 43 | when: 44 | runner.withArguments('myFormat').build() 45 | 46 | then: 47 | foo.read() == expected 48 | 49 | where: 50 | extension | expected 51 | "" | "class Foo {\n void bar() {}\n}\n" // == GOOGLE 52 | "googleJavaFormat { options style: 'GOOGLE' }" | "class Foo {\n void bar() {}\n}\n" 53 | "googleJavaFormat { options style: 'AOSP' }" | "class Foo {\n void bar() {}\n}\n" 54 | } 55 | 56 | def 'default verify task incorporates specified style'() { 57 | given: 58 | def foo = project.createFile(['Foo.java'], content) 59 | project.createFile(['build.gradle'], """\ 60 | |$applyPlugin 61 | |$defaultRepositories 62 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 63 | |$extension 64 | |""".stripMargin()) 65 | 66 | when: 67 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 68 | 69 | then: 70 | result.output =~ /BUILD SUCCESSFUL/ 71 | 72 | where: 73 | extension | content 74 | "" | "class Foo {\n void bar() {}\n}\n" // == GOOGLE 75 | "googleJavaFormat { options style: 'GOOGLE' }" | "class Foo {\n void bar() {}\n}\n" 76 | "googleJavaFormat { options style: 'AOSP' }" | "class Foo {\n void bar() {}\n}\n" 77 | } 78 | 79 | def 'custom verify task incorporates specified style'() { 80 | given: 81 | def foo = project.createFile(['Foo.java'], content) 82 | project.createFile(['build.gradle'], """\ 83 | |$applyPlugin 84 | |$defaultRepositories 85 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 86 | |$extension 87 | |task myVerify(type: ${VerifyGoogleJavaFormat.name}) { 88 | | source 'Foo.java' 89 | |} 90 | |""".stripMargin()) 91 | 92 | when: 93 | def result = runner.withArguments('myVerify').build() 94 | 95 | then: 96 | result.output =~ /BUILD SUCCESSFUL/ 97 | 98 | where: 99 | extension | content 100 | "" | "class Foo {\n void bar() {}\n}\n" // == GOOGLE 101 | "googleJavaFormat { options style: 'GOOGLE' }" | "class Foo {\n void bar() {}\n}\n" 102 | "googleJavaFormat { options style: 'AOSP' }" | "class Foo {\n void bar() {}\n}\n" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/UpToDateSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.test.FileWithState 4 | 5 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME 6 | import static com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin.DEFAULT_VERIFY_TASK_NAME 7 | 8 | class UpToDateSpec extends AbstractIntegrationSpec { 9 | 10 | FileWithState buildfile 11 | 12 | void additionalSetup() { 13 | buildfile = project.createFile(['build.gradle'], """\ 14 | |$applyPlugin 15 | |$defaultRepositories 16 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 17 | |""".stripMargin()) 18 | } 19 | 20 | def "format task is up-to-date in an empty project"() { 21 | expect: 22 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build().output =~ /(UP-TO-DATE|NO-SOURCE)/ 23 | } 24 | 25 | def "format task is up-to-date when executed the second time"() { 26 | project.createFile(['Foo.java'], 'class Foo { }') 27 | 28 | when: 29 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 30 | 31 | then: 32 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 33 | 34 | when: 35 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 36 | 37 | then: 38 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 39 | } 40 | 41 | def "format task is up-to-date when verification task succeeded before"() { 42 | project.createFile(['Foo.java'], 'class Foo {}\n') 43 | 44 | when: 45 | def result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).build() 46 | 47 | then: 48 | result.output =~ /BUILD SUCCESSFUL/ 49 | 50 | when: 51 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 52 | 53 | then: 54 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 55 | } 56 | 57 | def "format task is not up-to-date anymore, if a new file was added"() { 58 | project.createFile(['Foo.java'], 'class Foo {}\n') 59 | 60 | when: 61 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 62 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 63 | 64 | then: 65 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 66 | 67 | when: 68 | project.createFile(['Bar.java'], 'class Bar {}\n') 69 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 70 | 71 | then: 72 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 73 | } 74 | 75 | def "format task is not up-to-date anymore, if a file was modified"() { 76 | def foo = project.createFile(['Foo.java'], 'class Foo {}\n') 77 | 78 | when: 79 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 80 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 81 | 82 | then: 83 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 84 | 85 | when: 86 | foo.write('class Foo { void bar() {} }') 87 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 88 | 89 | then: 90 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 91 | } 92 | 93 | def "format task is still up-to-date after removing a file"() { 94 | def foo = project.createFile(['Foo.java'], 'class Foo {}\n') 95 | 96 | when: 97 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 98 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 99 | 100 | then: 101 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 102 | 103 | when: 104 | foo.delete() 105 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 106 | 107 | then: 108 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 109 | } 110 | 111 | def "format task is not up-to-date anymore, if the toolVersion has changed"() { 112 | project.createFile(['Foo.java'], 'class Foo {}') 113 | buildfile.write("""\ 114 | |$applyPlugin 115 | |$defaultRepositories 116 | |googleJavaFormat { 117 | | toolVersion = '1.0' 118 | |} 119 | |""".stripMargin()) 120 | 121 | when: 122 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 123 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 124 | 125 | then: 126 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 127 | 128 | when: 129 | buildfile.write("""\ 130 | |$applyPlugin 131 | |$defaultRepositories 132 | |googleJavaFormat { 133 | | toolVersion = '1.1' 134 | |} 135 | |""".stripMargin()) 136 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 137 | 138 | then: 139 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 140 | } 141 | 142 | def "format task is not up-to-date anymore, if the style option has changed"() { 143 | project.createFile(['Foo.java'], 'class Foo {}') 144 | buildfile.write("""\ 145 | |$applyPlugin 146 | |$defaultRepositories 147 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 148 | |googleJavaFormat { 149 | | options style: 'GOOGLE' 150 | |} 151 | |""".stripMargin()) 152 | 153 | when: 154 | runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 155 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 156 | 157 | then: 158 | result.output =~ /(UP-TO-DATE|NO-SOURCE)/ 159 | 160 | when: 161 | buildfile.write("""\ 162 | |$applyPlugin 163 | |$defaultRepositories 164 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 165 | |googleJavaFormat { 166 | | options style: 'AOSP' 167 | |} 168 | |""".stripMargin()) 169 | result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME).build() 170 | 171 | then: 172 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 173 | } 174 | 175 | def "verify task is not up-to-date when executed a second time"() { 176 | project.createFile(['Foo.java'], 'class Foo {}\n') 177 | 178 | when: 179 | def result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).build() 180 | 181 | then: 182 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 183 | 184 | when: 185 | result = runner.withArguments(DEFAULT_VERIFY_TASK_NAME).build() 186 | 187 | then: 188 | !(result.output =~ /(UP-TO-DATE|NO-SOURCE)/) 189 | } 190 | 191 | def "format task doesn't modify a file, if it is already formatted correctly"() { 192 | def foo = project.createFile(['Foo.java'], 'class Foo {}\n') 193 | 194 | when: 195 | def result = runner.withArguments(DEFAULT_FORMAT_TASK_NAME, '--info').build() 196 | 197 | then: 198 | result.output =~ /Foo\.java: UP-TO-DATE/ 199 | !foo.lastModifiedTimeHasChanged() 200 | !foo.contentHasChanged() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/integTest/groovy/com/github/sherter/googlejavaformatgradleplugin/VerificationTaskTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | class VerificationTaskTest extends AbstractIntegrationSpec { 4 | 5 | final static String customTaskName = 'verifyCustom' 6 | 7 | def 'no inputs results in UP-TO-DATE task'() { 8 | given: 9 | project.createFile(['build.gradle'], """\ 10 | |$applyPlugin 11 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 12 | |task $customTaskName(type: ${VerifyGoogleJavaFormat.name}) 13 | |""".stripMargin()) 14 | 15 | when: 16 | def result = runner.withArguments(customTaskName).build() 17 | 18 | then: 19 | result.output =~ /:$customTaskName (UP-TO-DATE|NO-SOURCE)/ 20 | } 21 | 22 | def 'dependency resolution failure'() { 23 | given: 24 | project.createFile(['build.gradle'], """\ 25 | |$applyPlugin 26 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 27 | |task $customTaskName(type: ${VerifyGoogleJavaFormat.name}) { 28 | | source 'build.gradle' 29 | |} 30 | |""".stripMargin()) 31 | 32 | when: 33 | def result = runner.withArguments(customTaskName).buildAndFail() 34 | 35 | then: 36 | result.output =~ /Could not resolve all (files|dependencies) for configuration ':googleJavaFormat/ 37 | } 38 | 39 | def 'no reports for correctly formatted input source file'() { 40 | given: 41 | project.createFile(['Foo.java'], 'class Foo {}\n') 42 | project.createFile(['build.gradle'], """\ 43 | |$applyPlugin 44 | |$defaultRepositories 45 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 46 | |task $customTaskName(type: ${VerifyGoogleJavaFormat.name}) { 47 | | source 'Foo.java' 48 | |} 49 | |""".stripMargin()) 50 | 51 | 52 | when: 53 | def result = runner.withArguments(customTaskName).build() 54 | 55 | then: 56 | result.output.contains(":$customTaskName\n") 57 | result.output =~ /BUILD SUCCESSFUL.*\n/ 58 | } 59 | 60 | def 'report badly formatted input source file'() { 61 | given: 62 | project.createFile(['Foo.java'], ' class Foo { }') 63 | project.createFile(['build.gradle'], """\ 64 | |$applyPlugin 65 | |$defaultRepositories 66 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 67 | |task $customTaskName(type: ${VerifyGoogleJavaFormat.name}) { 68 | | source 'Foo.java' 69 | |} 70 | |""".stripMargin()) 71 | 72 | when: 73 | def result = runner.withArguments(customTaskName).buildAndFail() 74 | 75 | then: 76 | result.output.contains('Foo.java') 77 | } 78 | 79 | def 'ignore verification failures'() { 80 | given: 81 | project.createFile(['Foo.java'], ' class Foo { }') 82 | project.createFile(['build.gradle'], """\ 83 | |$applyPlugin 84 | |$defaultRepositories 85 | |$downgradeToolVersionIfLatestNotSupportedOnCurrentJvm 86 | |task $customTaskName(type: ${VerifyGoogleJavaFormat.name}) { 87 | | source 'Foo.java' 88 | | ignoreFailures true 89 | |} 90 | |""".stripMargin()) 91 | 92 | when: 93 | def result = runner.withArguments(customTaskName).build() 94 | 95 | then: 96 | result.output.contains('Foo.java') 97 | } 98 | 99 | def 'check task (if present) depends on default verification task'() { 100 | when: 101 | def buildFile = project.createFile(['build.gradle'], applyPlugin) 102 | def result = runner.withArguments('tasks', '--all').build() 103 | 104 | then: 105 | result.output.readLines().find { s -> s.matches(~/^verifyGoogleJavaFormat$/) } 106 | !result.output.contains('check') 107 | 108 | when: 'after base plugin' 109 | buildFile.write("""\ 110 | |$buildScriptBlock 111 | |apply plugin: JavaBasePlugin 112 | |apply plugin: 'com.github.sherter.google-java-format' 113 | |""".stripMargin()) 114 | result = runner.withArguments('check').build() 115 | 116 | then: 117 | result.output =~ /(?s):verifyGoogleJavaFormat.*:check/ 118 | 119 | when: 'before base plugin' 120 | buildFile.write("""\ 121 | |$buildScriptBlock 122 | |apply plugin: 'com.github.sherter.google-java-format' 123 | |apply plugin: JavaBasePlugin 124 | |""".stripMargin()) 125 | result = runner.withArguments('check').build() 126 | 127 | then: 128 | result.output =~ /(?s):verifyGoogleJavaFormat.*:check/ 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/FormatTask.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import org.gradle.api.tasks.SourceTask; 4 | 5 | abstract class FormatTask extends SourceTask { 6 | 7 | protected SharedContext sharedContext; 8 | 9 | abstract void accept(TaskConfigurator configurator); 10 | 11 | boolean hasSources() { 12 | return !getSource().isEmpty(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/FormatterFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterOption 5 | import com.github.sherter.googlejavaformatgradleplugin.format.Gjf 6 | import com.github.sherter.googlejavaformatgradleplugin.format.Style 7 | import com.google.common.collect.ImmutableSet 8 | import com.google.common.collect.ImmutableTable 9 | import groovy.transform.PackageScope 10 | import org.gradle.api.Project 11 | import org.gradle.api.artifacts.Configuration 12 | import org.gradle.api.artifacts.ResolveException 13 | import org.gradle.api.logging.Logger 14 | 15 | class FormatterFactory { 16 | 17 | static final ImmutableTable optionMapping = 18 | ImmutableTable.builder() 19 | .put('style', 'GOOGLE', FormatterOption.GOOGLE_STYLE) 20 | .put('style', 'AOSP', FormatterOption.AOSP_STYLE) 21 | .build() 22 | 23 | 24 | private final Project project 25 | private final Logger logger 26 | 27 | FormatterFactory(Project project, Logger logger) { 28 | this.project = Objects.requireNonNull(project) 29 | this.logger = Objects.requireNonNull(logger) 30 | } 31 | 32 | Formatter create(String toolVersion, ImmutableSet options) throws ResolveException { 33 | Objects.requireNonNull(toolVersion) 34 | def configuration = setupConfiguration(toolVersion) 35 | def classpath = configuration.resolve() 36 | def classLoader = new URLClassLoader(classpath.collect { it.toURI().toURL() } as URL[], ClassLoader.getSystemClassLoader()) 37 | boolean versionIsSupported = toolVersion in Gjf.SUPPORTED_VERSIONS 38 | if (!versionIsSupported) { 39 | logger.info('Version {} of google-java-format-gradle-plugin is not tested against version {} of ' + 40 | 'google-java-format. This should not be a problem if the task is executed without failures.', 41 | GoogleJavaFormatPlugin.PLUGIN_VERSION, toolVersion) 42 | } 43 | 44 | if (options.contains(FormatterOption.AOSP_STYLE)) { 45 | return Gjf.newFormatter(classLoader, new com.github.sherter.googlejavaformatgradleplugin.format.Configuration( 46 | toolVersion, Style.AOSP)) 47 | } else { 48 | return Gjf.newFormatter(classLoader, new com.github.sherter.googlejavaformatgradleplugin.format.Configuration( 49 | toolVersion, Style.GOOGLE)) 50 | } 51 | } 52 | 53 | @PackageScope 54 | static ImmutableSet mapOptions(Map optionsInDsl) { 55 | Set mapped = new HashSet(optionsInDsl.size()) 56 | for (Map.Entry entry : optionsInDsl) { 57 | def option = (FormatterOption) optionMapping.get(entry.key, entry.value) 58 | if (option == null) { 59 | throw new IllegalArgumentException("invalid option: $entry") 60 | } 61 | mapped.add(option) 62 | } 63 | return ImmutableSet.copyOf(mapped); 64 | } 65 | 66 | private Configuration setupConfiguration(String toolVersion) { 67 | def configuration = project.configurations.maybeCreate("googleJavaFormat$toolVersion"); 68 | def dependency = project.dependencies.create( 69 | group: Gjf.GROUP_ID, 70 | name: Gjf.ARTIFACT_ID, 71 | version: toolVersion 72 | ) 73 | configuration.dependencies.add(dependency) 74 | return configuration 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/GoogleJavaFormat.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 4 | import com.google.common.collect.Iterables 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.PackageScope 7 | import org.gradle.api.GradleException; 8 | import org.gradle.api.logging.Logger 9 | import org.gradle.api.logging.Logging 10 | import org.gradle.api.tasks.TaskAction 11 | 12 | import java.nio.file.Path 13 | import java.util.concurrent.ExecutionException; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Future; 16 | 17 | @CompileStatic 18 | class GoogleJavaFormat extends FormatTask { 19 | 20 | private static final Logger logger = Logging.getLogger(GoogleJavaFormat.class) 21 | 22 | @Override 23 | void accept(TaskConfigurator configurator) { 24 | configurator.configure(this) 25 | for (Path file : formattedSources) { 26 | logger.info("{}: UP-TO-DATE", file) 27 | } 28 | } 29 | 30 | private List formattedSources 31 | private Iterable filteredSources 32 | private List invalidSources 33 | 34 | @PackageScope 35 | void setFormattedSources(List formattedSources) { 36 | this.formattedSources = formattedSources 37 | } 38 | 39 | @PackageScope 40 | void setFilteredSources(Iterable filteredSources) { 41 | this.filteredSources = filteredSources 42 | } 43 | 44 | @PackageScope 45 | List invalidSources() { 46 | return invalidSources 47 | } 48 | 49 | @PackageScope 50 | void setInvalidSources(List invalidSources) { 51 | this.invalidSources = invalidSources 52 | } 53 | 54 | @TaskAction 55 | void formatSources() { 56 | Map errors = [:] 57 | boolean successful = true 58 | if (!Iterables.isEmpty(filteredSources)) { 59 | Formatter formatter = sharedContext.formatter() 60 | FileToStateMapper mapper = sharedContext.mapper() 61 | ExecutorService executor = sharedContext.executor() 62 | 63 | List> futureResults = new ArrayList>() 64 | for (Path file : filteredSources) { 65 | futureResults.add(executor.submit(new FormatFileCallable(formatter, file))) 66 | } 67 | 68 | for (Future futureResult : futureResults) { 69 | try { 70 | FileInfo info = futureResult.get(); 71 | mapper.putIfNewer(info); 72 | if (info.state() == FileState.INVALID) { 73 | invalidSources.add(info.path()) 74 | errors.put(info.path().toString(), info.error()); 75 | } 76 | } catch (ExecutionException e) { 77 | def pathException = e.getCause() as PathException 78 | logger.error('{}: failed to process file', pathException.path(), pathException.getCause()) 79 | successful = false 80 | } 81 | } 82 | } 83 | if (Iterables.size(invalidSources) > 0) { 84 | successful = false 85 | logger.error('\n\nFailed to format the following files ({} "{}" {}):\n', 86 | 'you can exclude them from this task, see', 87 | 'https://github.com/sherter/google-java-format-gradle-plugin', 88 | 'for details') 89 | for (Path file : invalidSources) { 90 | logger.error("{}\n > Reason: {}\n", file.toString(), errors.get(file.toString())) 91 | } 92 | } 93 | if (!successful) { 94 | throw new GradleException("Not all files were formatted.") 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/GoogleJavaFormatExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterOption 4 | import com.github.sherter.googlejavaformatgradleplugin.format.Gjf 5 | import groovy.transform.CompileStatic 6 | import org.gradle.api.Project 7 | import org.gradle.api.file.ConfigurableFileTree 8 | import org.gradle.api.file.FileTree 9 | import org.gradle.api.file.FileTreeElement 10 | import org.gradle.api.specs.Spec 11 | import org.gradle.api.tasks.util.PatternFilterable 12 | import org.gradle.api.tasks.util.PatternSet 13 | 14 | @CompileStatic 15 | class GoogleJavaFormatExtension { 16 | 17 | static final String DEFAULT_TOOL_VERSION = Gjf.SUPPORTED_VERSIONS.last() 18 | 19 | private final Project project 20 | private String toolVersion = null 21 | private Map options = null 22 | private final List source = new ArrayList(); 23 | private final PatternFilterable patternSet = new PatternSet(); 24 | 25 | GoogleJavaFormatExtension(Project project) { 26 | this.project = project 27 | source(defaultInputs()) 28 | } 29 | 30 | private FileTree defaultInputs() { 31 | ConfigurableFileTree javaFiles = project.fileTree(dir: project.projectDir, includes: ['**/*.java']) 32 | project.allprojects { Project p -> 33 | javaFiles.exclude p.buildDir.path.substring(project.projectDir.path.length() + 1) 34 | } 35 | return javaFiles 36 | } 37 | 38 | 39 | void setToolVersion(String version) { 40 | if (options != null) { 41 | throw new ConfigurationException('toolVersion must be set before configuring options ' + 42 | '(available options depend on the version)') 43 | } 44 | if (toolVersion != null) { 45 | throw new ConfigurationException('toolVersion must not be set twice') 46 | } 47 | toolVersion = version 48 | } 49 | 50 | String getToolVersion() { 51 | if (toolVersion == null) { 52 | return DEFAULT_TOOL_VERSION 53 | } 54 | return toolVersion 55 | } 56 | 57 | void setOptions(Map options) { 58 | throw new ConfigurationException("Not allowed; use method 'options(Map)' to configure options"); 59 | } 60 | 61 | Map getOptions() { 62 | if (options == null) { 63 | return Collections.emptyMap() 64 | } 65 | return Collections.unmodifiableMap(options) 66 | } 67 | 68 | void options(Map newOptions) { 69 | if (options == null) { 70 | options = [:] 71 | } 72 | def existing = newOptions.keySet().find { options.containsKey(it) } 73 | if (existing != null) { 74 | throw new ConfigurationException("Option '$existing' was set twice") 75 | } 76 | for (Map.Entry entry : newOptions.entrySet()) { 77 | if(!FormatterFactory.optionMapping.containsRow(entry.key)) { 78 | throw new ConfigurationException("Unsupported option '${entry.key}'") 79 | } 80 | FormatterOption option = (FormatterOption) FormatterFactory.optionMapping.get(entry.key, entry.value) 81 | if (option == null) { 82 | throw new ConfigurationException("Unsupported value '${entry.value}' for option '${entry.key}'") 83 | } 84 | } 85 | options.putAll(newOptions) 86 | } 87 | 88 | FileTree getSource() { 89 | ArrayList copy = new ArrayList(this.source); 90 | FileTree src = project.files(copy).getAsFileTree(); 91 | return src == null ? project.files().getAsFileTree() : src.matching(patternSet); 92 | } 93 | 94 | void setSource(Object source) { 95 | this.source.clear(); 96 | this.source.add(source); 97 | } 98 | 99 | GoogleJavaFormatExtension source(Object... sources) { 100 | for (Object source : sources) { 101 | this.source.add(source); 102 | } 103 | return this; 104 | } 105 | 106 | GoogleJavaFormatExtension include(String... includes) { 107 | patternSet.include(includes); 108 | return this; 109 | } 110 | 111 | GoogleJavaFormatExtension include(Iterable includes) { 112 | patternSet.include(includes); 113 | return this; 114 | } 115 | 116 | GoogleJavaFormatExtension include(Spec includeSpec) { 117 | patternSet.include(includeSpec); 118 | return this; 119 | } 120 | 121 | GoogleJavaFormatExtension include(Closure includeSpec) { 122 | patternSet.include(includeSpec); 123 | return this; 124 | } 125 | 126 | GoogleJavaFormatExtension exclude(String... excludes) { 127 | patternSet.exclude(excludes); 128 | return this; 129 | } 130 | 131 | GoogleJavaFormatExtension exclude(Iterable excludes) { 132 | patternSet.exclude(excludes); 133 | return this; 134 | } 135 | 136 | GoogleJavaFormatExtension exclude(Spec excludeSpec) { 137 | patternSet.exclude(excludeSpec); 138 | return this; 139 | } 140 | 141 | GoogleJavaFormatExtension exclude(Closure excludeSpec) { 142 | patternSet.exclude(excludeSpec); 143 | return this; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/GoogleJavaFormatPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import groovy.transform.CompileStatic 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.api.Task 7 | import org.gradle.language.base.plugins.LifecycleBasePlugin 8 | import org.gradle.util.GradleVersion 9 | 10 | @CompileStatic 11 | class GoogleJavaFormatPlugin implements Plugin { 12 | 13 | static final String PLUGIN_VERSION 14 | static { 15 | // workaround; reading resources fails sometimes 16 | // see https://discuss.gradle.org/t/getresourceasstream-returns-null-in-plugin-in-daemon-mode/2385 17 | new URLConnection(new URL('file:///')) { 18 | { setDefaultUseCaches(false) } 19 | void connect() throws IOException {} 20 | } 21 | this.PLUGIN_VERSION = GoogleJavaFormatPlugin.class.getResourceAsStream('/VERSION').text.trim() 22 | } 23 | 24 | static final String EXTENSION_NAME = 'googleJavaFormat' 25 | static final String DEFAULT_FORMAT_TASK_NAME = 'googleJavaFormat' 26 | static final String DEFAULT_VERIFY_TASK_NAME = 'verifyGoogleJavaFormat' 27 | 28 | static final boolean SUPPORTS_LAZY_TASKS = GradleVersion.current() >= GradleVersion.version("4.9") 29 | 30 | private Project project 31 | private GoogleJavaFormatExtension extension 32 | 33 | @Override 34 | void apply(Project project) { 35 | this.project = project 36 | createExtension() 37 | createDefaultTasks() 38 | 39 | SharedContext context = new SharedContext(project, extension) 40 | TaskConfigurator configurator = new TaskConfigurator(context) 41 | project.gradle.taskGraph.beforeTask { Task task -> 42 | if (task.project == this.project && task instanceof FormatTask) { 43 | task.accept(configurator) 44 | } 45 | } 46 | } 47 | 48 | private void createExtension() { 49 | extension = project.extensions.create(EXTENSION_NAME, GoogleJavaFormatExtension, project) 50 | } 51 | 52 | 53 | private void createDefaultTasks() { 54 | def defaultVerifyTask 55 | if (SUPPORTS_LAZY_TASKS) { 56 | this.project.tasks.register(DEFAULT_FORMAT_TASK_NAME, GoogleJavaFormat) 57 | defaultVerifyTask = this.project.tasks.register(DEFAULT_VERIFY_TASK_NAME, VerifyGoogleJavaFormat) 58 | } else { 59 | this.project.tasks.create(DEFAULT_FORMAT_TASK_NAME, GoogleJavaFormat) 60 | defaultVerifyTask = this.project.tasks.create(DEFAULT_VERIFY_TASK_NAME, VerifyGoogleJavaFormat) 61 | } 62 | makeCheckTaskDependOn(defaultVerifyTask) 63 | } 64 | 65 | private void makeCheckTaskDependOn(Object task) { 66 | if (SUPPORTS_LAZY_TASKS) { 67 | project.plugins.withType(LifecycleBasePlugin) { 68 | project.tasks.named('check').configure { 69 | it.dependsOn(task) 70 | } 71 | } 72 | } else { 73 | def checkTask = project.tasks.findByName('check') 74 | if (checkTask != null) { 75 | checkTask.dependsOn(task) 76 | } else { 77 | project.tasks.whenTaskAdded { Task t -> 78 | if (t.name == 'check') { 79 | t.dependsOn(task) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/SharedContext.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 4 | import com.google.common.collect.ImmutableSet 5 | import org.gradle.api.Project 6 | 7 | import java.util.concurrent.ExecutorService 8 | import java.util.concurrent.Executors 9 | 10 | class SharedContext { 11 | 12 | private final Project project 13 | private ExecutorService executor 14 | private Formatter formatter 15 | private FileToStateMapper mapper 16 | GoogleJavaFormatExtension extension 17 | 18 | SharedContext(Project project, GoogleJavaFormatExtension extension) { 19 | this.project = Objects.requireNonNull(project) 20 | this.extension = extension 21 | } 22 | 23 | synchronized ExecutorService executor() { 24 | if (executor == null) { 25 | // single threaded for now 26 | // further synchronization for FormatFileTasks and VerifyFileTasks 27 | // is required if we want to write to files concurrently 28 | executor = Executors.newSingleThreadExecutor() 29 | } 30 | return executor 31 | } 32 | 33 | synchronized FileToStateMapper mapper() { 34 | if (mapper == null) { 35 | mapper = new FileToStateMapper() 36 | PersistenceModule module = new PersistenceModule(project) 37 | PersistenceComponent component = DaggerPersistenceComponent.builder().persistenceModule(module).build() 38 | def optionsStore = setupOptionsStore(component.optionsStore()); 39 | def fileInfoStore = setupFileStore(component.fileInfoStore(), mapper) 40 | try { 41 | def options = optionsStore.read() 42 | def extension = project.extensions.getByName(GoogleJavaFormatPlugin.EXTENSION_NAME) 43 | if ((options.version() != extension.toolVersion) || 44 | (!options.options().equals(FormatterFactory.mapOptions(extension.getOptions())))) { 45 | project.logger.info("Formatter options changed; invalidating saved file states") 46 | fileInfoStore.clear() 47 | } else { 48 | ImmutableSet states = ImmutableSet.of() 49 | try { 50 | states = fileInfoStore.read() 51 | } catch (IOException e) { 52 | project.getLogger().error("Failed to load formatting states from disk", e) 53 | } 54 | for(FileInfo info : states) { 55 | mapper.putIfNewer(info) 56 | } 57 | } 58 | } catch (IOException e) { 59 | 60 | } 61 | } 62 | return mapper 63 | } 64 | 65 | FormatterOptionsStore setupOptionsStore(FormatterOptionsStore optionsStore) { 66 | project.gradle.buildFinished { 67 | def extension = project.extensions.getByName(GoogleJavaFormatPlugin.EXTENSION_NAME) 68 | def options = FormatterOptions.create(extension.toolVersion, 69 | FormatterFactory.mapOptions(extension.getOptions())) 70 | try { 71 | optionsStore.write(options); 72 | } finally { 73 | optionsStore.close(); 74 | } 75 | } 76 | return optionsStore 77 | } 78 | 79 | private FileInfoStore setupFileStore(FileInfoStore store, FileToStateMapper mapper) { 80 | project.gradle.buildFinished { 81 | try { 82 | store.update(mapper) 83 | } catch (IOException e) { 84 | project.getLogger().error("Failed to write formatting states to disk", e) 85 | } finally { 86 | store.close() 87 | } 88 | } 89 | return store 90 | } 91 | 92 | synchronized Formatter formatter() { 93 | if (formatter == null) { 94 | GoogleJavaFormatExtension extension = (GoogleJavaFormatExtension) project.getExtensions().getByName(GoogleJavaFormatPlugin.getEXTENSION_NAME()) 95 | formatter = new FormatterFactory(project, project.logger).create(extension.toolVersion, 96 | FormatterFactory.mapOptions(extension.getOptions())) 97 | } 98 | return formatter 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/TaskConfigurator.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableListMultimap; 5 | import com.google.common.collect.Iterables; 6 | import java.nio.file.Path; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import org.gradle.api.file.FileTree; 10 | import org.gradle.api.tasks.util.PatternSet; 11 | 12 | class TaskConfigurator { 13 | 14 | private final SharedContext context; 15 | 16 | TaskConfigurator(SharedContext context) { 17 | this.context = context; 18 | } 19 | 20 | void configureFormatTask(FormatTask task) { 21 | task.sharedContext = context; 22 | if (!task.hasSources()) { 23 | // TODO Remove cast to Object when dropping support for Gradle versions below 4.0 24 | // Gradle 4.0 introduced 'void setSource(FileTree source)' in addition 25 | // to 'void setSource(Object source)'. 26 | // Casting to Object makes sure that we can run on Gradle versions < 4.0 27 | task.setSource((Object) context.getExtension().getSource()); 28 | } 29 | String value = System.getProperty(task.getName() + ".include"); 30 | if (value != null) { 31 | String[] patterns = value.split(","); 32 | FileTree filteredSources = task.getSource().matching(new PatternSet().include(patterns)); 33 | // TODO Remove cast to Object when dropping support for Gradle versions below 4.0 34 | task.setSource((Object) filteredSources); 35 | } 36 | } 37 | 38 | void configure(GoogleJavaFormat task) { 39 | configureFormatTask(task); 40 | ImmutableListMultimap mapping = 41 | context.mapper().reverseMap(Utils.toPaths(task.getSource().getFiles())); 42 | task.setFormattedSources(mapping.get(FileState.FORMATTED)); 43 | task.setInvalidSources(new ArrayList<>(mapping.get(FileState.INVALID))); 44 | ImmutableList unformatted = mapping.get(FileState.UNFORMATTED); 45 | ImmutableList unknown = mapping.get(FileState.UNKNOWN); 46 | if (Iterables.size(task.invalidSources()) 47 | + Iterables.size(unformatted) 48 | + Iterables.size(unknown) 49 | == 0) { 50 | // task is up-to-date, make sure it is skipped 51 | task.setSource(Collections.emptyList()); 52 | } else { 53 | task.setFilteredSources(Iterables.concat(unformatted, unknown)); 54 | } 55 | } 56 | 57 | void configure(VerifyGoogleJavaFormat task) { 58 | configureFormatTask(task); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/VerifyGoogleJavaFormat.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.google.common.base.Joiner 4 | import org.gradle.api.GradleException 5 | import org.gradle.api.tasks.SourceTask 6 | import org.gradle.api.tasks.TaskAction 7 | import org.gradle.api.tasks.VerificationTask 8 | 9 | import java.nio.file.Path 10 | import java.util.concurrent.ExecutionException 11 | import java.util.concurrent.Future 12 | 13 | import static com.github.sherter.googlejavaformatgradleplugin.FileState.* 14 | import static com.github.sherter.googlejavaformatgradleplugin.Utils.toPaths 15 | 16 | class VerifyGoogleJavaFormat extends FormatTask implements VerificationTask { 17 | 18 | @Override 19 | void accept(TaskConfigurator configurator) { 20 | configurator.configure(this) 21 | } 22 | 23 | private boolean ignoreFailures = false 24 | 25 | boolean getIgnoreFailures() { 26 | return ignoreFailures 27 | } 28 | 29 | void setIgnoreFailures(boolean ignoreFailures) { 30 | this.ignoreFailures = ignoreFailures 31 | } 32 | 33 | @TaskAction 34 | void verifySources() { 35 | def mapping = sharedContext.mapper().reverseMap(toPaths(getSource().files)) 36 | 37 | def formatted = new ArrayList(mapping.get(FORMATTED)) 38 | def unformatted = new ArrayList(mapping.get(UNFORMATTED)) 39 | def invalid = new ArrayList(mapping.get(INVALID)) 40 | 41 | def successful = processUnknown(mapping.get(UNKNOWN), formatted, unformatted, invalid) 42 | for(Path p : formatted) { 43 | logger.info('{}: verified proper formatting', p) 44 | } 45 | if (unformatted.size() > 0) { 46 | logger.lifecycle('\n\nThe following files are not formatted properly:\n') 47 | for(Path file : unformatted) { 48 | logger.lifecycle(file.toString()) 49 | } 50 | } 51 | if (invalid.size() > 0) { 52 | logger.lifecycle('\n\nFailed to verify format of the following files ({} "{}" {}):\n', 53 | 'you can configure this task to exclude them, see', 54 | 'https://github.com/sherter/google-java-format-gradle-plugin', 55 | 'for details') 56 | for (Path file : invalid) { 57 | logger.lifecycle(file.toString()) 58 | } 59 | } 60 | 61 | def problems = [] as List 62 | if (!successful) { 63 | problems.add('I/O errors') 64 | } 65 | if (invalid.size() > 0) { 66 | problems.add('syntax errors') 67 | } 68 | if (unformatted.size() > 0) { 69 | problems.add('formatting style violations') 70 | } 71 | if (problems.size() > 0) { 72 | def message = 'Problems: ' + Joiner.on(', ').join(problems) 73 | if (ignoreFailures) { 74 | logger.warn(message) 75 | } else { 76 | throw new GradleException(message) 77 | } 78 | } 79 | } 80 | 81 | private boolean processUnknown(List unknown, List formatted, List unformatted, List invalid) { 82 | boolean successful = true 83 | if (unknown.size() > 0) { 84 | def mapper = sharedContext.mapper() 85 | def formatter = sharedContext.formatter() 86 | def executor = sharedContext.executor() 87 | 88 | def futures = unknown.collect { Path file -> 89 | executor.submit(new VerifyFileCallable(formatter, file)) 90 | } 91 | for (Future futureInfo : futures) { 92 | try { 93 | def info = futureInfo.get() 94 | mapper.putIfNewer(info) 95 | if (info.state() == FORMATTED) { 96 | formatted.add(info.path()) 97 | } else if (info.state() == UNFORMATTED) { 98 | unformatted.add(info.path()) 99 | } else if (info.state() == INVALID) { 100 | invalid.add(info.path()) 101 | } else { 102 | //throw new AssertionError('no other states possible') 103 | } 104 | } catch (ExecutionException e) { 105 | def pathException = e.getCause() as PathException 106 | logger.error('{}: failed to process file', pathException.path(), pathException.getCause()) 107 | successful = false 108 | } 109 | } 110 | } 111 | return successful 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | public class ConfigurationException extends RuntimeException { 4 | 5 | ConfigurationException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import java.nio.file.Path; 5 | import java.nio.file.attribute.FileTime; 6 | 7 | /** 8 | * Immutable value type that associates a file at a specific point in time with its formatting 9 | * state. 10 | */ 11 | @AutoValue 12 | abstract class FileInfo { 13 | 14 | /** 15 | * Constructs a new object of type {@link FileInfo} storing the given values. 16 | * 17 | *

If {@code path} is not already absolute and normalized, an absolute and normalized path that 18 | * is equivalent to {@code path} is stored instead. 19 | * 20 | * @throws IllegalArgumentException if {@code state} == {@link FileState#UNKNOWN} 21 | */ 22 | static FileInfo create(Path path, FileTime lastModified, long size, FileState state) { 23 | return create(path, lastModified, size, state, ""); 24 | } 25 | 26 | /** 27 | * Constructs a new object of type {@link FileInfo} storing the given values. 28 | * 29 | *

If {@code path} is not already absolute and normalized, an absolute and normalized path that 30 | * is equivalent to {@code path} is stored instead. 31 | * 32 | * @throws IllegalArgumentException if {@code state} == {@link FileState#UNKNOWN} 33 | */ 34 | static FileInfo create( 35 | Path path, FileTime lastModified, long size, FileState state, String error) { 36 | if (state == FileState.UNKNOWN) { 37 | throw new IllegalArgumentException( 38 | "constructing file info with state UNKNOWN is not allowed"); 39 | } 40 | Path modifiedPath = path.toAbsolutePath().normalize(); 41 | return new AutoValue_FileInfo(modifiedPath, lastModified, size, state, error); 42 | } 43 | 44 | /** 45 | * Returns the path of the file this object contains information about. The returned path is 46 | * {@link Path#isAbsolute() absolute} and {@link Path#normalize() normalized}. 47 | */ 48 | abstract Path path(); 49 | 50 | abstract FileTime lastModified(); 51 | 52 | abstract long size(); 53 | 54 | abstract FileState state(); 55 | 56 | abstract String error(); 57 | 58 | /** 59 | * Returns true if and only if {@code this} FileInfo represents the file at a strictly later point 60 | * in time than the given FileInfo. 61 | * 62 | * @throws IllegalArgumentException if {@code other} represents a different file 63 | */ 64 | boolean isMoreRecentThan(FileInfo other) { 65 | if (!path().equals(other.path())) { 66 | throw new IllegalArgumentException("Can't compare info for different files"); 67 | } 68 | return lastModified().compareTo(other.lastModified()) > 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileInfoDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.base.Joiner; 4 | import com.google.common.base.Splitter; 5 | import com.google.common.collect.Iterables; 6 | import java.io.IOError; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URLDecoder; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Path; 11 | import java.nio.file.attribute.FileTime; 12 | import java.util.ArrayList; 13 | import java.util.Base64; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.concurrent.TimeUnit; 17 | import javax.inject.Inject; 18 | import javax.inject.Named; 19 | 20 | /** Immutable and therefore thread safe. */ 21 | class FileInfoDecoder { 22 | private final Path basePath; // absolute and normalized 23 | 24 | /** 25 | * Constructs a new {@link FileInfoDecoder} that resolves serialized path strings against {@code 26 | * basePath}. 27 | * 28 | * @throws IOError if {@code basePath} is not absolute and {@link Path#toAbsolutePath()} fails 29 | */ 30 | @Inject 31 | FileInfoDecoder(@Named("base path") Path basePath) { 32 | this.basePath = Objects.requireNonNull(basePath).toAbsolutePath().normalize(); 33 | } 34 | 35 | /** 36 | * Deserialize the given {@code serializedFileInfo}. 37 | * 38 | * @throws IllegalArgumentException if {@code serializedFileInfo} is not a valid serialization of 39 | * a {@link FileInfo} object according to {@link FileInfoEncoder} 40 | */ 41 | FileInfo decode(CharSequence serializedFileInfo) { 42 | String[] elements = Iterables.toArray(Splitter.on(',').split(serializedFileInfo), String.class); 43 | if (elements.length != 5) { 44 | throw new IllegalArgumentException("Invalid number of elements"); 45 | } 46 | return FileInfo.create( 47 | decodePath(elements[0]), 48 | FileTime.from(decodeLong(elements[1]), TimeUnit.NANOSECONDS), 49 | decodeLong(elements[2]), 50 | decodeState(elements[3]), 51 | decodeError(elements[4])); 52 | } 53 | 54 | private Path decodePath(CharSequence path) { 55 | Iterable nameElements = Splitter.on('/').split(path); 56 | List decodedNameElements = new ArrayList<>(); 57 | for (String element : nameElements) { 58 | try { 59 | String decoded = URLDecoder.decode(element, StandardCharsets.UTF_8.name()); 60 | decodedNameElements.add(decoded); 61 | } catch (UnsupportedEncodingException e) { 62 | throw new AssertionError("Java spec requires UTF-8 to be supported"); 63 | } 64 | } 65 | String relativePathString = 66 | Joiner.on(basePath.getFileSystem().getSeparator()).join(decodedNameElements); 67 | return basePath.resolve(relativePathString); 68 | } 69 | 70 | private FileState decodeState(String state) { 71 | try { 72 | return FileState.valueOf(state); 73 | } catch (IllegalArgumentException e) { 74 | throw new IllegalArgumentException("Not a valid state: " + state); 75 | } 76 | } 77 | 78 | private long decodeLong(String number) { 79 | try { 80 | return Long.valueOf(number); 81 | } catch (NumberFormatException e) { 82 | throw new IllegalArgumentException("Not a valid long value: " + number); 83 | } 84 | } 85 | 86 | private String decodeError(String error) { 87 | return new String(Base64.getDecoder().decode(error)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileInfoEncoder.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.base.Joiner; 4 | import java.io.IOError; 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLEncoder; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Path; 9 | import java.util.ArrayList; 10 | import java.util.Base64; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.concurrent.TimeUnit; 14 | import javax.inject.Inject; 15 | import javax.inject.Named; 16 | 17 | /** Immutable and therefore thread safe. */ 18 | class FileInfoEncoder { 19 | private final Path basePath; // absolute and normalized 20 | 21 | /** 22 | * Constructs a new {@link FileInfoEncoder} that encodes a {@link FileInfo}'s path to a unix path 23 | * string that is relative to {@code basePath}. 24 | * 25 | * @throws IOError if {@code basePath} is not absolute and {@link Path#toAbsolutePath()} fails 26 | */ 27 | @Inject 28 | FileInfoEncoder(@Named("base path") Path basePath) { 29 | this.basePath = Objects.requireNonNull(basePath).toAbsolutePath().normalize(); 30 | } 31 | 32 | String encode(FileInfo fileInfo) { 33 | return encodePath(fileInfo.path()) 34 | + ',' 35 | + fileInfo.lastModified().to(TimeUnit.NANOSECONDS) 36 | + ',' 37 | + fileInfo.size() 38 | + ',' 39 | + fileInfo.state().name() 40 | + ',' 41 | + encodeError(fileInfo.error()); 42 | } 43 | 44 | private String encodePath(Path p) { 45 | assert p.isAbsolute(); 46 | Path relativeToBase = basePath.relativize(p); 47 | List urlEncodedElements = new ArrayList<>(); 48 | for (Path element : relativeToBase) { 49 | try { 50 | String encoded = URLEncoder.encode(element.toString(), StandardCharsets.UTF_8.name()); 51 | urlEncodedElements.add(encoded); 52 | } catch (UnsupportedEncodingException e) { 53 | throw new AssertionError("Java spec requires UTF-8 to be supported"); 54 | } 55 | } 56 | return Joiner.on('/').join(urlEncodedElements); 57 | } 58 | 59 | /* Encode the error string as base64 to make sure that there are no commas in the text. */ 60 | private String encodeError(String error) { 61 | return Base64.getEncoder().encodeToString(error.getBytes(StandardCharsets.UTF_8)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileInfoStore.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import java.io.BufferedReader; 5 | import java.io.BufferedWriter; 6 | import java.io.IOException; 7 | import java.nio.channels.Channels; 8 | import java.nio.channels.FileChannel; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.StandardOpenOption; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | import javax.inject.Inject; 17 | import javax.inject.Named; 18 | import org.gradle.api.logging.Logger; 19 | 20 | /** 21 | * Persistent fileInfoStore for {@link FileInfo} objects. 22 | * 23 | *

It's not safe to use instances of this class concurrently in multiple threads. 24 | */ 25 | class FileInfoStore { 26 | 27 | private final Logger log; 28 | private final Path path; 29 | private final FileInfoDecoder decoder; 30 | private final FileInfoEncoder encoder; 31 | private Map readCache; 32 | private FileChannel channel; 33 | 34 | @Inject 35 | FileInfoStore( 36 | Logger logger, 37 | @Named("storage") Path path, 38 | FileInfoEncoder encoder, 39 | FileInfoDecoder decoder) { 40 | this.log = Objects.requireNonNull(logger); 41 | this.path = Objects.requireNonNull(path).toAbsolutePath().normalize(); 42 | this.encoder = Objects.requireNonNull(encoder); 43 | this.decoder = Objects.requireNonNull(decoder); 44 | } 45 | 46 | /** 47 | * Reads serialized {@link FileInfo} objects from this {@link FileInfoStore}'s backing file and 48 | * returns deserialized {@link FileInfo} objects. 49 | * 50 | *

The method succeeds if the backing file can be accessed as required and the file's general 51 | * format is intact. Decoding errors for single elements are logged, but don't prevent the method 52 | * from succeeding. 53 | * 54 | * @throws IOException if an I/O error occurs 55 | */ 56 | ImmutableSet read() throws IOException { 57 | log.debug("Reading file states from {}", path); 58 | if (channel == null) { 59 | init(); 60 | } 61 | channel.position(0); 62 | BufferedReader reader = 63 | new BufferedReader(Channels.newReader(channel, StandardCharsets.UTF_8.name())); 64 | readCache = new HashMap<>(); 65 | String line; 66 | int lineNumber = 1; 67 | while ((line = reader.readLine()) != null) { 68 | try { 69 | FileInfo r = decoder.decode(line); 70 | readCache.put(r.path(), r); 71 | } catch (IllegalArgumentException e) { 72 | log.error("{}:{}: couldn't decode '{}': {}", path, lineNumber, line, e.getMessage()); 73 | } 74 | lineNumber++; 75 | } 76 | return ImmutableSet.copyOf(readCache.values()); 77 | } 78 | 79 | private void init() throws IOException { 80 | Files.createDirectories(path.getParent()); 81 | channel = 82 | FileChannel.open( 83 | path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); 84 | } 85 | 86 | /** 87 | * Insert the given {@link FileInfo}'s into the persistent fileInfoStore. 88 | * 89 | *

If the fileInfoStore already contains information about a path that is referenced in an 90 | * element in {@code updates}, then this information is replaced. If {@code updates} contain 91 | * multiple {@link FileInfo} objects for the same path, the last one in iteration order is 92 | * inserted. 93 | * 94 | * @throws IOException if an I/O error occurs 95 | */ 96 | void update(Iterable updates) throws IOException { 97 | if (readCache == null) { 98 | read(); 99 | } 100 | Map cacheCopy = new HashMap<>(readCache); 101 | for (FileInfo result : updates) { 102 | cacheCopy.put(result.path(), result); 103 | } 104 | log.debug("Writing updated file states to {}:\n{}", path, cacheCopy.values()); 105 | channel.truncate(0); 106 | BufferedWriter writer = 107 | new BufferedWriter(Channels.newWriter(channel, StandardCharsets.UTF_8.name())); 108 | for (FileInfo result : cacheCopy.values()) { 109 | writer.write(encoder.encode(result)); 110 | writer.newLine(); 111 | } 112 | writer.flush(); 113 | readCache = cacheCopy; 114 | } 115 | 116 | void clear() throws IOException { 117 | if (channel == null) { 118 | init(); 119 | } 120 | channel.truncate(0); 121 | channel.force(false); 122 | } 123 | 124 | void close() throws IOException { 125 | if (channel == null) { 126 | return; 127 | } 128 | channel.close(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileState.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | enum FileState { 4 | FORMATTED, 5 | UNFORMATTED, 6 | INVALID, 7 | UNKNOWN; 8 | 9 | static final FileState values[] = values(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FileToStateMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.ImmutableListMultimap; 5 | import com.google.common.collect.Iterators; 6 | import com.google.common.collect.Multimaps; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.attribute.FileTime; 11 | import java.util.ConcurrentModificationException; 12 | import java.util.Iterator; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | import org.gradle.api.logging.Logger; 15 | import org.gradle.api.logging.Logging; 16 | 17 | /** Map files to their {@link FileState}. Designed for concurrent use in multiple threads. */ 18 | class FileToStateMapper implements Iterable { 19 | 20 | private static final Logger log = Logging.getLogger(FileToStateMapper.class); 21 | 22 | private final ConcurrentHashMap infoCache = new ConcurrentHashMap<>(); 23 | private final Object replaceRemoveLock = new Object(); 24 | 25 | private final Function mapFunction = 26 | new Function() { 27 | @Override 28 | public FileState apply(Path path) { 29 | return map(path); 30 | } 31 | }; 32 | 33 | /** 34 | * Associate {@code files} with their {@link FileState}. 35 | * 36 | * @see #map(Path) 37 | */ 38 | ImmutableListMultimap reverseMap(Iterable files) { 39 | return Multimaps.index(files, mapFunction); 40 | } 41 | 42 | /** 43 | * Checks if we have information about the given {@code file} and returns the associated {@link 44 | * FileState} if this information is still valid. Otherwise returns {@link FileState#UNKNOWN}. 45 | * 46 | *

The information is valid if {@link FileInfo#lastModified()} and {@link FileInfo#size()} 47 | * equals the {@code file}'s current size and last modified time. This method always tries to 48 | * access the file system to verify this. Returns {@link FileState#UNKNOWN} if accessing the file 49 | * system fails. 50 | */ 51 | FileState map(Path file) { 52 | file = file.toAbsolutePath().normalize(); 53 | FileInfo info = infoCache.get(file); 54 | if (info == null) { 55 | return FileState.UNKNOWN; 56 | } 57 | FileTime currentLastModified; 58 | long currentSize; 59 | try { 60 | currentLastModified = Files.getLastModifiedTime(file); 61 | currentSize = Files.size(file); 62 | } catch (IOException e) { 63 | return FileState.UNKNOWN; 64 | } 65 | if (info.lastModified().equals(currentLastModified) && info.size() == currentSize) { 66 | return info.state(); 67 | } 68 | log.debug("change detected; invalidating cached state for file '{}'", file); 69 | log.debug("timestamps (old - new): {} {}", info.lastModified(), currentLastModified); 70 | log.debug("sizes (old - new): {} {}", info.size(), currentSize); 71 | synchronized (replaceRemoveLock) { 72 | infoCache.remove(file); 73 | } 74 | return FileState.UNKNOWN; 75 | } 76 | 77 | /** Return the cached information (probably out-of-date) about a path or null if not in cache. */ 78 | FileInfo get(Path path) { 79 | return infoCache.get(path.toAbsolutePath().normalize()); 80 | } 81 | 82 | /** 83 | * Returns the {@code FileInfo} that is currently associated with the given {@code fileInfo}'s 84 | * path. If the given {@code fileInfo} is more recent than the currently associated one (according 85 | * to {@link FileInfo#isMoreRecentThan(FileInfo)}) than the old one is replaced by the given 86 | * {@code fileInfo} 87 | */ 88 | FileInfo putIfNewer(FileInfo fileInfo) { 89 | // handle the case where no info was previously associated with the file 90 | FileInfo existingResult = infoCache.putIfAbsent(fileInfo.path(), fileInfo); 91 | if (existingResult == null) { 92 | return null; 93 | } 94 | // we already have information about this file, extra locking required! 95 | synchronized (replaceRemoveLock) { 96 | // get info for this file again, because it could have been 97 | // replaced by other threads while we were waiting for the lock, 98 | // i.e the (at this point non-null) value returned by putIfAbsent 99 | // is at this point not necessarily the latest info associated with the file 100 | existingResult = infoCache.get(fileInfo.path()); 101 | if (existingResult == null || fileInfo.isMoreRecentThan(existingResult)) { 102 | infoCache.put(fileInfo.path(), fileInfo); 103 | } 104 | return existingResult; 105 | } 106 | } 107 | 108 | /** 109 | * Returns a "weakly consistent", unmodifiable iterator that will never throw {@link 110 | * ConcurrentModificationException}, and guarantees to traverse elements as they existed upon 111 | * construction of the iterator, and may (but is not guaranteed to) reflect any modifications 112 | * subsequent to construction. 113 | */ 114 | @Override 115 | public Iterator iterator() { 116 | return Iterators.unmodifiableIterator(infoCache.values().iterator()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FormatFileCallable.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter; 4 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.Objects; 9 | import java.util.concurrent.Callable; 10 | import org.gradle.api.logging.Logger; 11 | import org.gradle.api.logging.Logging; 12 | 13 | class FormatFileCallable implements Callable { 14 | 15 | private static final Logger logger = Logging.getLogger(FormatFileCallable.class); 16 | 17 | private final Formatter formatter; 18 | private final Path file; 19 | 20 | FormatFileCallable(Formatter formatter, Path file) { 21 | this.file = Objects.requireNonNull(file); 22 | this.formatter = Objects.requireNonNull(formatter); 23 | } 24 | 25 | /** 26 | * Returns a {@link FileInfo} object that describes the state of the file after trying to format 27 | * it (in place). The {@link FileInfo#state() state} will either be {@link FileState#FORMATTED} or 28 | * {@link FileState#INVALID}, if Java syntax errors where found. 29 | * 30 | * @throws Exception if and only if file system access fails at some point 31 | */ 32 | @Override 33 | public FileInfo call() throws PathException { 34 | try { 35 | byte[] content = Files.readAllBytes(file); 36 | String utf8Decoded = new String(content, StandardCharsets.UTF_8.name()); 37 | String formatted; 38 | try { 39 | formatted = formatter.format(utf8Decoded); 40 | } catch (FormatterException e) { 41 | String error = e.getMessage(); 42 | return FileInfo.create( 43 | file, Files.getLastModifiedTime(file), content.length, FileState.INVALID, error); 44 | } 45 | if (utf8Decoded.equals(formatted)) { 46 | logger.info("{}: UP-TO-DATE", file); 47 | return FileInfo.create( 48 | file, Files.getLastModifiedTime(file), content.length, FileState.FORMATTED); 49 | } 50 | byte[] utf8Encoded = formatted.getBytes(StandardCharsets.UTF_8.name()); 51 | Files.write(file, utf8Encoded); 52 | logger.lifecycle("{}: formatted successfully", file); 53 | return FileInfo.create( 54 | file, Files.getLastModifiedTime(file), utf8Encoded.length, FileState.FORMATTED); 55 | } catch (Throwable t) { 56 | throw new PathException(file, t); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FormatterOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterOption; 4 | import com.google.auto.value.AutoValue; 5 | import com.google.common.collect.ImmutableSet; 6 | import javax.annotation.Nullable; 7 | 8 | @AutoValue 9 | abstract class FormatterOptions { 10 | 11 | static FormatterOptions create(@Nullable String version, ImmutableSet options) { 12 | return new AutoValue_FormatterOptions(version, options); 13 | } 14 | 15 | @Nullable 16 | abstract String version(); 17 | 18 | abstract ImmutableSet options(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/FormatterOptionsStore.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterOption; 4 | import com.google.common.base.Function; 5 | import com.google.common.base.Joiner; 6 | import com.google.common.base.Splitter; 7 | import com.google.common.collect.ImmutableSet; 8 | import com.google.common.collect.Iterables; 9 | import java.io.IOException; 10 | import java.io.Reader; 11 | import java.io.Writer; 12 | import java.nio.channels.Channels; 13 | import java.nio.channels.FileChannel; 14 | import java.nio.charset.StandardCharsets; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.nio.file.StandardOpenOption; 18 | import java.util.Properties; 19 | import javax.inject.Inject; 20 | import javax.inject.Named; 21 | 22 | class FormatterOptionsStore { 23 | 24 | private final Path backingFile; 25 | private FileChannel channel; 26 | 27 | @Inject 28 | FormatterOptionsStore(@Named("settings") Path backingFile) { 29 | this.backingFile = backingFile.toAbsolutePath().normalize(); 30 | } 31 | 32 | FormatterOptions read() throws IOException { 33 | if (channel == null) { 34 | init(); 35 | } 36 | channel.position(0); 37 | Reader reader = Channels.newReader(channel, StandardCharsets.UTF_8.name()); 38 | Properties properties = new Properties(); 39 | properties.load(reader); 40 | return FormatterOptions.create( 41 | properties.getProperty("toolVersion"), parseOptions(properties.getProperty("options"))); 42 | } 43 | 44 | private ImmutableSet parseOptions(String optionList) { 45 | if (optionList == null || optionList.equals("")) { 46 | return ImmutableSet.of(); 47 | } 48 | Iterable options = Splitter.on(',').split(optionList); 49 | Iterable parsed = 50 | Iterables.transform( 51 | options, 52 | new Function() { 53 | @Override 54 | public FormatterOption apply(String s) { 55 | return Enum.valueOf(FormatterOption.class, s); 56 | } 57 | }); 58 | return ImmutableSet.copyOf(parsed); 59 | } 60 | 61 | private void init() throws IOException { 62 | Files.createDirectories(backingFile.getParent()); 63 | channel = 64 | FileChannel.open( 65 | backingFile, 66 | StandardOpenOption.CREATE, 67 | StandardOpenOption.READ, 68 | StandardOpenOption.WRITE); 69 | } 70 | 71 | void write(FormatterOptions options) throws IOException { 72 | if (channel == null) { 73 | init(); 74 | } 75 | channel.truncate(0); 76 | Writer writer = Channels.newWriter(channel, StandardCharsets.UTF_8.name()); 77 | Properties properties = new Properties(); 78 | properties.setProperty("toolVersion", options.version()); 79 | properties.setProperty("options", serialize(options.options())); 80 | properties.store(writer, "Generated; DO NOT CHANGE!!!"); 81 | } 82 | 83 | private String serialize(ImmutableSet options) { 84 | Iterable names = 85 | Iterables.transform( 86 | options, 87 | new Function() { 88 | @Override 89 | public String apply(FormatterOption formatterOption) { 90 | return formatterOption.name(); 91 | } 92 | }); 93 | return Joiner.on(",").join(names); 94 | } 95 | 96 | void close() throws IOException { 97 | if (channel == null) { 98 | return; 99 | } 100 | channel.close(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/PathException.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import java.nio.file.Path; 4 | 5 | class PathException extends Exception { 6 | 7 | private final Path path; 8 | 9 | PathException(Path path, Throwable cause) { 10 | super(cause); 11 | this.path = path; 12 | } 13 | 14 | Path path() { 15 | return path; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/PersistenceComponent.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import dagger.Component; 4 | 5 | @Component(modules = PersistenceModule.class) 6 | interface PersistenceComponent { 7 | FileInfoStore fileInfoStore(); 8 | 9 | FormatterOptionsStore optionsStore(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/PersistenceModule.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.io.CharStreams; 4 | import dagger.Module; 5 | import dagger.Provides; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import javax.inject.Named; 13 | import org.gradle.api.Project; 14 | import org.gradle.api.logging.Logger; 15 | 16 | @Module 17 | class PersistenceModule { 18 | 19 | private final Project project; 20 | 21 | PersistenceModule(Project project) { 22 | this.project = project; 23 | } 24 | 25 | @Provides 26 | Logger provideLogger() { 27 | return project.getLogger(); 28 | } 29 | 30 | @Provides 31 | @Named("plugin version") 32 | String providePluginVersion() { 33 | try (InputStream is = getClass().getResourceAsStream("/VERSION")) { 34 | return CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8)).trim(); 35 | } catch (IOException e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | @Provides 41 | @Named("base path") 42 | Path provideBasePath() { 43 | return project.getProjectDir().toPath(); 44 | } 45 | 46 | @Provides 47 | @Named("output path") 48 | Path provideOutputPath(@Named("plugin version") String pluginVersion) { 49 | Path buildDir = project.getBuildDir().toPath(); 50 | return buildDir.resolve(Paths.get("google-java-format", pluginVersion)); 51 | } 52 | 53 | @Provides 54 | @Named("storage") 55 | Path provideStoragePath(@Named("output path") Path outputPath) { 56 | return outputPath.resolve("fileStates.txt"); 57 | } 58 | 59 | @Provides 60 | @Named("settings") 61 | Path provideSettingsPath(@Named("output path") Path outputPath) { 62 | return outputPath.resolve("settings.txt"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/Utils.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.collect.Iterables; 5 | import java.io.File; 6 | import java.nio.file.Path; 7 | 8 | class Utils { 9 | 10 | private static final Function toPath = 11 | new Function() { 12 | @Override 13 | public Path apply(File file) { 14 | return file.toPath(); 15 | } 16 | }; 17 | 18 | private static final Function toFiles = 19 | new Function() { 20 | @Override 21 | public File apply(Path path) { 22 | return path.toFile(); 23 | } 24 | }; 25 | 26 | static Iterable toPaths(Iterable files) { 27 | return Iterables.transform(files, toPath); 28 | } 29 | 30 | static Iterable toFiles(Iterable paths) { 31 | return Iterables.transform(paths, toFiles); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/sherter/googlejavaformatgradleplugin/VerifyFileCallable.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin; 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter; 4 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.Objects; 9 | import java.util.concurrent.Callable; 10 | 11 | class VerifyFileCallable implements Callable { 12 | 13 | private final Formatter formatter; 14 | private final Path file; 15 | 16 | VerifyFileCallable(Formatter formatter, Path file) { 17 | this.file = Objects.requireNonNull(file); 18 | this.formatter = Objects.requireNonNull(formatter); 19 | } 20 | 21 | @Override 22 | public FileInfo call() throws PathException { 23 | try { 24 | byte[] content = Files.readAllBytes(file); 25 | String utf8Decoded = new String(content, StandardCharsets.UTF_8.name()); 26 | String formatted; 27 | FileState state; 28 | try { 29 | formatted = formatter.format(utf8Decoded); 30 | state = formatted.equals(utf8Decoded) ? FileState.FORMATTED : FileState.UNFORMATTED; 31 | } catch (FormatterException e) { 32 | state = FileState.INVALID; 33 | } 34 | return FileInfo.create(file, Files.getLastModifiedTime(file), content.length, state); 35 | } catch (Throwable t) { 36 | throw new PathException(file, t); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/com.github.sherter.google-java-format.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.github.sherter.googlejavaformatgradleplugin.GoogleJavaFormatPlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/VERSION: -------------------------------------------------------------------------------- 1 | 0.9 2 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/ExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import org.gradle.testfixtures.ProjectBuilder 4 | import spock.lang.Specification 5 | 6 | class ExtensionSpec extends Specification { 7 | 8 | def ext = new GoogleJavaFormatExtension(ProjectBuilder.builder().build()) 9 | def extClosure = ext.&with 10 | 11 | def 'default tool version is returned if none is set explicitly'() { 12 | expect: 13 | ext.toolVersion == GoogleJavaFormatExtension.DEFAULT_TOOL_VERSION 14 | ext.@toolVersion == null 15 | } 16 | 17 | def 'empty options are returned if none were set'() { 18 | expect: 19 | ext.options == [:] 20 | ext.@options == null 21 | } 22 | 23 | def 'returned options are immutable'() { 24 | when: 25 | ext.options.put('', '') 26 | 27 | then: 28 | thrown(UnsupportedOperationException) 29 | 30 | when: 31 | extClosure { 32 | options style: 'GOOGLE' 33 | } 34 | 35 | then: 36 | ext.options.containsKey('style') 37 | 38 | when: 39 | ext.options.put('baz', 'foo') 40 | 41 | then: 42 | thrown(UnsupportedOperationException) 43 | } 44 | 45 | def 'setting options directly throws'() { 46 | when: 47 | ext.options = [foo: 'bar', baz: 'foo'] 48 | 49 | then: 50 | ConfigurationException e = thrown() 51 | e.message.contains('Not allowed') 52 | } 53 | 54 | def 'set tool version twice throws'() { 55 | when: 56 | extClosure { 57 | toolVersion = '1.0' 58 | toolVersion = '0.1-alpha' 59 | } 60 | 61 | then: 62 | ConfigurationException e = thrown() 63 | e.message.matches(/.*toolVersion.*twice.*/) 64 | } 65 | 66 | def 'set tool version after adding options throws'() { 67 | when: 68 | extClosure { 69 | options style: 'GOOGLE' 70 | toolVersion = '1.0' 71 | } 72 | 73 | then: 74 | ConfigurationException e = thrown() 75 | e.message.matches(/.*toolVersion.*before.*options.*/) 76 | } 77 | 78 | def 'overwrite previously set option throws'() { 79 | when: 80 | extClosure { 81 | options style: 'GOOGLE' 82 | options style: 'AOSP' 83 | } 84 | 85 | then: 86 | ConfigurationException e = thrown() 87 | e.message.matches(/.*style.*twice.*/) 88 | } 89 | 90 | def 'unknown option type'() { 91 | when: 92 | extClosure { 93 | options foo: 'bar' 94 | } 95 | 96 | then: 97 | ConfigurationException e = thrown() 98 | e.message.contains("Unsupported option 'foo'") 99 | } 100 | 101 | def 'unknown option value'() { 102 | when: 103 | extClosure { 104 | options style: 'FOO' 105 | } 106 | 107 | then: 108 | ConfigurationException e = thrown() 109 | e.message.contains('Unsupported value') 110 | e.message.contains("option 'style'") 111 | e.message.contains("value 'FOO'") 112 | } 113 | 114 | def 'options are accepted for unsupported toolVersion'() { 115 | when: 116 | extClosure { 117 | toolVersion = '12345' 118 | options style: 'GOOGLE' 119 | } 120 | 121 | then: 122 | notThrown(ConfigurationException) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FileInfoDecoderTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import spock.lang.Specification 4 | 5 | import java.nio.file.Paths 6 | import java.nio.file.attribute.FileTime 7 | 8 | import static com.github.sherter.googlejavaformatgradleplugin.FileInfo.create 9 | import static com.github.sherter.googlejavaformatgradleplugin.FileState.* 10 | 11 | class FileInfoDecoderTest extends Specification { 12 | 13 | def 'decode valid'() { 14 | given: 15 | def decoder = new FileInfoDecoder(Paths.get('')) 16 | 17 | expect: 18 | decoder.decode(encoded) equals decoded 19 | 20 | where: 21 | encoded | decoded 22 | 'foo,13000000,10,UNFORMATTED,' | create(Paths.get('foo'), FileTime.fromMillis(13), 10, UNFORMATTED) 23 | 'b%2Cr,-231000000,0,INVALID,ZXJyb3I=' | create(Paths.get('b,r'), FileTime.fromMillis(-231), 0, INVALID, "error") 24 | '../foo/bar,0,0,FORMATTED,' | create(Paths.get('../foo/bar'), FileTime.fromMillis(0), 0, FORMATTED) 25 | } 26 | 27 | def 'decode invalid'() { 28 | given: 29 | def decoder = new FileInfoDecoder(Paths.get('')) 30 | 31 | when: 32 | decoder.decode(encoded) 33 | 34 | then: 35 | IllegalArgumentException e = thrown() 36 | e.message == errorMessage 37 | 38 | where: 39 | encoded | errorMessage 40 | 'b,r,t,-231,0,UNKNOWN,' | 'Invalid number of elements' 41 | 'b%r,231,0,UNKNOWN,' | 'URLDecoder: Incomplete trailing escape (%) pattern' 42 | 'foo,0,0,STATE,' | 'Not a valid state: STATE' 43 | 'foo,abc,0,STATE,' | 'Not a valid long value: abc' 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FileInfoEncoderTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import spock.lang.Shared 4 | import spock.lang.Specification 5 | 6 | import java.nio.file.Paths 7 | import java.nio.file.attribute.FileTime 8 | 9 | import static com.github.sherter.googlejavaformatgradleplugin.FileInfo.create 10 | import static com.github.sherter.googlejavaformatgradleplugin.FileState.INVALID 11 | import static com.github.sherter.googlejavaformatgradleplugin.FileState.UNFORMATTED 12 | 13 | class FileInfoEncoderTest extends Specification { 14 | 15 | @Shared encoder = new FileInfoEncoder(Paths.get('')) 16 | 17 | def 'serialized form is as expected'() { 18 | expect: 19 | encoder.encode(info) == serialized 20 | 21 | where: 22 | info | serialized 23 | 24 | create(Paths.get('foo'), FileTime.fromMillis(0), 0, INVALID, "error") | 'foo,0,0,INVALID,ZXJyb3I=' 25 | create(Paths.get('foo'), FileTime.fromMillis(0), 0, UNFORMATTED) | 'foo,0,0,UNFORMATTED,' 26 | create(Paths.get('/foo'), FileTime.fromMillis(0), 0, UNFORMATTED) | 27 | (1..encoder.basePath.nameCount).inject('') {result, i -> result + '../'} + 28 | 'foo,0,0,UNFORMATTED,' 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FileInfoStoreTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.google.common.jimfs.Configuration 4 | import com.google.common.jimfs.Jimfs 5 | import org.gradle.api.logging.Logger 6 | import org.slf4j.helpers.MessageFormatter 7 | import spock.lang.Specification 8 | 9 | import java.nio.charset.StandardCharsets 10 | import java.nio.file.FileSystem 11 | import java.nio.file.Files 12 | import java.nio.file.Path 13 | import java.nio.file.attribute.FileTime 14 | 15 | class FileInfoStoreTest extends Specification { 16 | 17 | Logger log = Mock() 18 | FileSystem fs = Jimfs.newFileSystem( 19 | Configuration.unix().toBuilder().setWorkingDirectory('/base').build()) 20 | def encoder = new FileInfoEncoder(fs.getPath('')) 21 | def decoder = new FileInfoDecoder(fs.getPath('')) 22 | def backingFile = fs.getPath('storage.txt') 23 | 24 | def 'read returns expected result and decoding errors are logged'() { 25 | given: 26 | def store = new FileInfoStore(log, backingFile, encoder, decoder) 27 | Files.write(backingFile, 28 | '''foo,0,0,FORMATTED, 29 | |this-one-errors 30 | |baz,0,0,INVALID,ZXJyb3I= 31 | |'''.stripMargin().getBytes(StandardCharsets.UTF_8)) 32 | 33 | when: 34 | def readResult = store.read() 35 | 36 | then: 37 | readResult.equals([ FileInfo.create(fs.getPath('foo'), FileTime.fromMillis(0), 0, FileState.FORMATTED, ""), 38 | FileInfo.create(fs.getPath('baz'), FileTime.fromMillis(0), 0, FileState.INVALID,"error") ] as Set) 39 | 1 * log.error(*_) >> { args -> 40 | assert MessageFormatter.arrayFormat(args[0], args[1]).message == 41 | "/base/storage.txt:2: couldn't decode 'this-one-errors': Invalid number of elements" 42 | } 43 | } 44 | 45 | def 'multiple info objects for same file in updates'() { 46 | given: 47 | def store = new FileInfoStore(log, backingFile, encoder, decoder) 48 | when: 49 | store.update([ FileInfo.create(fs.getPath('foo'), FileTime.fromMillis(0), 0, FileState.FORMATTED, ""), 50 | FileInfo.create(fs.getPath('baz'), FileTime.fromMillis(0), 0, FileState.UNFORMATTED), 51 | FileInfo.create(fs.getPath('foo'), FileTime.fromMillis(0), 10, FileState.INVALID,"error")]) 52 | 53 | then: 54 | def lines = Files.readAllLines(backingFile, StandardCharsets.UTF_8) 55 | (lines as Set).equals([ 'baz,0,0,UNFORMATTED,', 'foo,0,10,INVALID,ZXJyb3I='] as Set) 56 | } 57 | 58 | def 'replace existing info'() { 59 | given: 60 | def store = new FileInfoStore(log, backingFile, encoder, decoder) 61 | Files.write(backingFile, 'foo,0,0,FORMATTED,\n'.getBytes(StandardCharsets.UTF_8)) 62 | 63 | when: 64 | store.update([ FileInfo.create(fs.getPath('foo'), FileTime.fromMillis(10), 10, FileState.UNFORMATTED) ]) 65 | 66 | then: 67 | def lines = Files.readAllLines(backingFile, StandardCharsets.UTF_8) 68 | (lines as Set).equals([ 'foo,10000000,10,UNFORMATTED,' ] as Set) 69 | } 70 | 71 | def 'create parent directories of backing file if necessary'() { 72 | given: 73 | backingFile = fs.getPath('/base/project/build/sub/states.txt') 74 | def store = new FileInfoStore(log, backingFile, encoder, decoder) 75 | 76 | expect: 77 | store.read().equals([] as Set) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FileInfoTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import spock.lang.Specification 4 | 5 | import java.nio.file.Paths 6 | import java.nio.file.attribute.FileTime 7 | 8 | class FileInfoTest extends Specification { 9 | 10 | def 'create file info with unknown state fails'() { 11 | when: 12 | FileInfo.create(Paths.get(''), FileTime.fromMillis(0), 0, FileState.UNKNOWN) 13 | 14 | then: 15 | thrown(IllegalArgumentException) 16 | } 17 | 18 | def 'path is absolute and normalized'() { 19 | expect: 20 | def info = FileInfo.create(path, FileTime.fromMillis(0L), 0, FileState.INVALID) 21 | info.path().isAbsolute() 22 | info.path().equals(info.path().normalize()) 23 | 24 | where: 25 | path << [ Paths.get('foo/bar'), 26 | Paths.get('foo/../bar'), 27 | Paths.get('foo/bar').toAbsolutePath(), 28 | Paths.get('../foo/bar').toAbsolutePath()] 29 | } 30 | 31 | def 'newer than other result'() { 32 | given: 33 | def oldResult = FileInfo.create(Paths.get(''), FileTime.fromMillis(0L), 10L, FileState.FORMATTED) 34 | def newResult = FileInfo.create(Paths.get(''), FileTime.fromMillis(10L), 5L, FileState.INVALID) 35 | def unrelated = FileInfo.create(Paths.get('foo'), FileTime.fromMillis(0L), 0L, FileState.UNFORMATTED) 36 | 37 | expect: 38 | !oldResult.isMoreRecentThan(newResult) && newResult.isMoreRecentThan(oldResult) // symmetric 39 | !oldResult.isMoreRecentThan(oldResult) // not reflexive 40 | 41 | when: 42 | oldResult.isMoreRecentThan(unrelated) 43 | 44 | then: 45 | thrown(IllegalArgumentException) 46 | } 47 | 48 | def 'decoded result equals original result'() { 49 | given: 50 | def encoder = new FileInfoEncoder(Paths.get('/projectDir')) 51 | def decoder = new FileInfoDecoder(Paths.get('/projectDir')) 52 | 53 | expect: 54 | println result 55 | decoder.decode(encoder.encode(result)).equals(result) 56 | 57 | where: 58 | result << [FileInfo.create(Paths.get('/foo/bar/baz'), FileTime.fromMillis(0L), 0L, FileState.INVALID), 59 | FileInfo.create(Paths.get('/projectDir/foo/bar/baz'), FileTime.fromMillis(123098L), 928374L, FileState.UNFORMATTED), 60 | FileInfo.create(Paths.get('/../fo,o/../b%a,r/b\\az'), FileTime.fromMillis(12309L), 986L, FileState.FORMATTED) ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FileToStateMapperTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.google.common.jimfs.Configuration 4 | import com.google.common.jimfs.Jimfs 5 | import spock.lang.Specification 6 | 7 | import java.nio.charset.StandardCharsets 8 | import java.nio.file.Files 9 | import java.nio.file.attribute.FileTime 10 | import java.util.concurrent.Executors 11 | import java.util.concurrent.ThreadLocalRandom 12 | import java.util.concurrent.TimeUnit 13 | 14 | class FileToStateMapperTest extends Specification { 15 | 16 | def fs = Jimfs.newFileSystem( 17 | Configuration.forCurrentPlatform() 18 | .toBuilder() 19 | .setWorkingDirectory('/work') 20 | .build()) 21 | def mapper = new FileToStateMapper() 22 | 23 | def 'contains newest result after putting multiple results in parallel'() { 24 | given: 25 | def resultList = (1..100000).collect { 26 | FileTime randomFileTime = FileTime.fromMillis(ThreadLocalRandom.current().nextLong(Long.MAX_VALUE)) 27 | FileInfo.create(fs.getPath('foo'), randomFileTime, 0L, FileState.FORMATTED) 28 | } 29 | def newestResult = resultList.max { result -> result.lastModified().toMillis() } 30 | def executor = Executors.newFixedThreadPool(20) 31 | 32 | when: 33 | resultList.each { result -> 34 | executor.execute({ mapper.putIfNewer(result) } as Runnable) 35 | } 36 | executor.shutdown() 37 | executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS) 38 | 39 | then: 40 | mapper.get(fs.getPath('foo').toAbsolutePath()) == newestResult 41 | } 42 | 43 | def 'get non existing file info'() { 44 | expect: 45 | mapper.get(fs.getPath('foo')) == null 46 | } 47 | 48 | def 'get file info for relative path returns info for equivalent absolute and normalized path'() { 49 | given: 50 | def path = fs.getPath('foo') 51 | def info = FileInfo.create(path, FileTime.fromMillis(0), 0, FileState.FORMATTED) 52 | 53 | when: 54 | mapper.putIfNewer(info) 55 | 56 | then: 57 | mapper.get(path).equals(info) 58 | } 59 | 60 | def 'map unknown path to file state'() { 61 | expect: 62 | mapper.map(fs.getPath('')) == FileState.UNKNOWN 63 | } 64 | 65 | def 'map path to its known state'() { 66 | given: 67 | def path = fs.getPath('foo') 68 | Files.createFile(path) 69 | Files.setLastModifiedTime(path, FileTime.fromMillis(0)) 70 | 71 | when: 72 | mapper.putIfNewer(FileInfo.create(path, FileTime.fromMillis(0), 0, FileState.FORMATTED)) 73 | 74 | then: 75 | mapper.map(fs.getPath('../work/foo')) == FileState.FORMATTED 76 | 77 | when: 78 | def bytes = 'content'.getBytes(StandardCharsets.UTF_8) 79 | Files.write(path, bytes) 80 | def time = Files.getLastModifiedTime(path) 81 | mapper.putIfNewer(FileInfo.create(path, time, bytes.length, FileState.FORMATTED)) 82 | 83 | then: 84 | mapper.map(path) == FileState.FORMATTED 85 | } 86 | 87 | def 'map path to state when existing file info is outdated'() { 88 | given: 89 | def path = fs.getPath('foo') 90 | Files.createFile(path) 91 | Files.setLastModifiedTime(path, FileTime.fromMillis(1)) 92 | 93 | when: "last modified times don't match" 94 | def info = FileInfo.create(path, FileTime.fromMillis(0), 0, FileState.FORMATTED) 95 | mapper.putIfNewer(info) 96 | assert mapper.get(path) == info 97 | 98 | then: 99 | mapper.map(path) == FileState.UNKNOWN 100 | mapper.get(path) == null 101 | 102 | when: "file sizes don't match" 103 | info = FileInfo.create(path, FileTime.fromMillis(1), 10, FileState.FORMATTED) 104 | mapper.putIfNewer(info) 105 | assert mapper.get(path) == info 106 | 107 | then: 108 | mapper.map(path) == FileState.UNKNOWN 109 | mapper.get(path) == null 110 | } 111 | 112 | def 'reverse map contains all paths'() { 113 | given: 114 | def paths = [ fs.getPath('foo'), fs.getPath('foo').toAbsolutePath(), fs.getPath('bar') ] 115 | 116 | when: 117 | def multimap = mapper.reverseMap(paths) 118 | 119 | then: 120 | multimap.keySet().size() == 1 121 | multimap.containsKey(FileState.UNKNOWN) 122 | (multimap.get(FileState.UNKNOWN) as Set).equals(paths as Set) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FormatFileCallableTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterException 5 | import com.google.common.jimfs.Jimfs 6 | import spock.lang.Specification 7 | 8 | import java.nio.charset.StandardCharsets 9 | import java.nio.file.Files 10 | import java.nio.file.NoSuchFileException 11 | 12 | class FormatFileCallableTest extends Specification { 13 | 14 | def formatter = Mock(Formatter) 15 | def file = Jimfs.newFileSystem().getPath('Foo.java').toAbsolutePath() 16 | def task = new FormatFileCallable(formatter, file) 17 | 18 | def 'format non existing file throws'() { 19 | when: 20 | task.call() 21 | 22 | then: 23 | PathException e = thrown() 24 | e.getCause() instanceof NoSuchFileException 25 | 0 * formatter.format(_) 26 | } 27 | 28 | def 'format invalid java file'() { 29 | given: 30 | def fileContent = 'Hello World!'.getBytes(StandardCharsets.UTF_8.name()) 31 | Files.write(file, fileContent) 32 | def modifiedTime = Files.getLastModifiedTime(file) 33 | 34 | when: 35 | def result = task.call() 36 | 37 | then: 38 | 1 * formatter.format('Hello World!') >> { throw new FormatterException("", new Exception()) } 39 | result.path() == file 40 | result.state() == FileState.INVALID 41 | result.lastModified() == modifiedTime 42 | result.size() == fileContent.length 43 | } 44 | 45 | 46 | def 'format file that is already formatted properly'() { 47 | given: 48 | def fileContent = 'Hello World!'.getBytes(StandardCharsets.UTF_8.name()) 49 | Files.write(file, fileContent) 50 | def modifiedTime = Files.getLastModifiedTime(file) 51 | 52 | when: 53 | def result = task.call() 54 | 55 | then: 56 | 1 * formatter.format(_) >> { args -> args[0] } 57 | result.path() == file 58 | result.state() == FileState.FORMATTED 59 | result.lastModified() == modifiedTime 60 | result.size() == fileContent.length 61 | } 62 | 63 | def 'format and write formatted content to file'() { 64 | given: 65 | Files.write(file, 'unformatted'.getBytes(StandardCharsets.UTF_8.name())) 66 | 67 | when: 68 | def result = task.call() 69 | 70 | then: 71 | 1 * formatter.format('unformatted') >> 'formatted' 72 | result.path() == file 73 | result.state() == FileState.FORMATTED 74 | result.lastModified() == Files.getLastModifiedTime(file) 75 | result.size() == Files.size(file) 76 | 'formatted' == new String(Files.readAllBytes(file), StandardCharsets.UTF_8.name()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FormatterFactoryTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterException 4 | import com.github.sherter.googlejavaformatgradleplugin.format.Gjf 5 | import com.google.common.collect.ImmutableSet 6 | import org.gradle.api.Project 7 | import org.gradle.api.artifacts.ResolveException 8 | import org.gradle.api.logging.Logger 9 | import org.gradle.testfixtures.ProjectBuilder 10 | import spock.lang.IgnoreIf 11 | import spock.lang.Specification 12 | import spock.lang.Unroll 13 | import spock.util.environment.Jvm 14 | 15 | import static org.junit.Assume.assumeTrue 16 | 17 | @IgnoreIf({ javaVersion < 1.8 }) 18 | class FormatterFactoryTest extends Specification { 19 | 20 | def 'fail to create a formatter'() { 21 | given: 22 | Project project = ProjectBuilder.builder().build() 23 | FormatterFactory factory = new FormatterFactory(project, Mock(Logger)) 24 | 25 | when: 26 | factory.create(null, ImmutableSet.of()) 27 | 28 | then: 29 | thrown NullPointerException 30 | 31 | when: 32 | factory.create('this-version-is-not-in-any-repository', ImmutableSet.of()) 33 | 34 | then: 35 | thrown ResolveException 36 | 37 | when: 'a version is used that was actually released' 38 | factory.create('0.1-alpha', ImmutableSet.of()) 39 | 40 | then: 'resolution fails nevertheless since no repository was defined' 41 | thrown ResolveException 42 | } 43 | 44 | 45 | // install all versions of google-java-format to local maven repository in order to speed the tests: 46 | // 47 | // mvn org.apache.maven.plugins:maven-dependency-plugin:2.10:get \ 48 | // -DrepoUrl=https://jcenter.bintray.com \ 49 | // -Dartifact=com.google.googlejavaformat:google-java-format:0.1-alpha 50 | 51 | @Unroll 52 | def 'create and use formatter (v#version)'() { 53 | assumeTrue(Gjf.SUPPORTED_VERSIONS.indexOf(version) < Gjf.SUPPORTED_VERSIONS.indexOf('1.8') || 54 | new BigDecimal(Jvm.current.javaSpecificationVersion) >= BigInteger.valueOf(11)) 55 | given: 56 | Project project = ProjectBuilder.builder().build() 57 | project.repositories { 58 | mavenLocal() 59 | mavenCentral() 60 | } 61 | FormatterFactory factory = new FormatterFactory(project, Mock(Logger)) 62 | 63 | when: 64 | def formatter = factory.create(version, ImmutableSet.of()) 65 | 66 | then: 67 | formatter != null 68 | 69 | when: 70 | def result = formatter.format('') 71 | 72 | then: 73 | result == '\n' 74 | 75 | when: 76 | formatter.format('x') 77 | 78 | then: 79 | FormatterException e = thrown() 80 | 81 | where: 82 | version << Gjf.SUPPORTED_VERSIONS 83 | } 84 | 85 | def 'log tool version support'() { 86 | given: 87 | Project project = ProjectBuilder.builder().build() 88 | project.repositories { 89 | mavenLocal() 90 | maven { 91 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 92 | } 93 | mavenCentral() 94 | } 95 | Logger logger = Mock() 96 | def factory = new FormatterFactory(project, logger) 97 | 98 | when: 99 | factory.create(Gjf.SUPPORTED_VERSIONS.first(), ImmutableSet.of()) 100 | 101 | then: 102 | 0 * logger._ 103 | 104 | when: 105 | try { 106 | factory.create(unsupportedVersion, ImmutableSet.of()) 107 | } catch (any) {} 108 | 109 | then: 110 | 1 * logger.info(*_) 111 | 112 | where: 113 | unsupportedVersion = '0.1-SNAPSHOT' 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/FormatterOptionsStoreTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.google.common.collect.ImmutableSet 4 | import com.google.common.jimfs.Jimfs 5 | import spock.lang.Specification 6 | 7 | import java.nio.file.Files 8 | 9 | class FormatterOptionsStoreTest extends Specification { 10 | 11 | def fs = Jimfs.newFileSystem() 12 | def backingFile = fs.getPath('settings') 13 | 14 | 15 | def 'read valid options from store'() { 16 | given: 17 | Files.write(backingFile, 'toolVersion=123'.getBytes('UTF-8')) 18 | def store = new FormatterOptionsStore(backingFile); 19 | 20 | when: 21 | def options = store.read() 22 | 23 | then: 24 | options.version() == '123' 25 | } 26 | 27 | def 'read from non-existing file'() { 28 | given: 29 | def store = new FormatterOptionsStore(backingFile); 30 | 31 | when: 32 | def options = store.read() 33 | 34 | then: 35 | options.version() == null 36 | } 37 | 38 | def 'read options equal written options'() { 39 | given: 40 | def store = new FormatterOptionsStore(backingFile); 41 | 42 | when: 43 | store.write(option) 44 | 45 | then: 46 | store.read() == option 47 | 48 | where: 49 | option << [FormatterOptions.create('0.1-alpha', ImmutableSet.of()), 50 | FormatterOptions.create('1.0', ImmutableSet.of()), 51 | FormatterOptions.create('-123', ImmutableSet.of())] 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/GoogleJavaFormatPluginTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import org.gradle.testfixtures.ProjectBuilder 4 | import spock.lang.Specification 5 | 6 | class GoogleJavaFormatPluginTest extends Specification { 7 | 8 | def "applying the plugin to a project"() { 9 | given: "a new, empty gradle project" 10 | def project = ProjectBuilder.builder().build() 11 | 12 | when: "plugin is applied" 13 | project.apply plugin: GoogleJavaFormatPlugin 14 | 15 | then: "plugin extension exists" 16 | def extension = project.extensions.findByName(GoogleJavaFormatPlugin.EXTENSION_NAME) 17 | extension != null 18 | extension instanceof GoogleJavaFormatExtension 19 | 20 | and: "format task exists" 21 | def formatTask = project.tasks.findByName(GoogleJavaFormatPlugin.DEFAULT_FORMAT_TASK_NAME) 22 | formatTask != null 23 | formatTask instanceof GoogleJavaFormat 24 | 25 | and: "verify task exists" 26 | def verifyTask = project.tasks.findByName(GoogleJavaFormatPlugin.DEFAULT_VERIFY_TASK_NAME) 27 | verifyTask != null 28 | verifyTask instanceof VerifyGoogleJavaFormat 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/VerifyFileCallableTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin 2 | 3 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.FormatterException 5 | import com.google.common.jimfs.Jimfs 6 | import spock.lang.Specification 7 | 8 | import java.nio.charset.StandardCharsets 9 | import java.nio.file.Files 10 | import java.nio.file.NoSuchFileException 11 | 12 | class VerifyFileCallableTest extends Specification { 13 | 14 | def formatter = Mock(Formatter) 15 | def file = Jimfs.newFileSystem().getPath('Foo.java').toAbsolutePath() 16 | def task = new VerifyFileCallable(formatter, file) 17 | 18 | def 'verify non existing file throws'() { 19 | when: 20 | task.call() 21 | 22 | then: 23 | PathException e = thrown() 24 | e.getCause() instanceof NoSuchFileException 25 | 0 * formatter.format(_) 26 | } 27 | 28 | def 'verify invalid java file'() { 29 | given: 30 | Files.write(file, 'foo'.getBytes(StandardCharsets.UTF_8.name())) 31 | def modified = Files.getLastModifiedTime(file) 32 | 33 | when: 34 | def result = task.call() 35 | 36 | then: 37 | 1 * formatter.format('foo') >> { throw new FormatterException("error", new Exception()) } 38 | result.state() == FileState.INVALID 39 | result.lastModified() == modified 40 | result.size() == Files.size(file) 41 | result.path() == file 42 | } 43 | 44 | def 'verify unformatted java file'() { 45 | given: 46 | Files.write(file, 'foo'.getBytes(StandardCharsets.UTF_8.name())) 47 | def modified = Files.getLastModifiedTime(file) 48 | 49 | when: 50 | def result = task.call() 51 | 52 | then: 53 | 1 * formatter.format('foo') >> 'bar' 54 | result.state() == FileState.UNFORMATTED 55 | result.lastModified() == modified 56 | result.size() == Files.size(file) 57 | result.path() == file 58 | } 59 | 60 | def 'verify correctly formatted java file'() { 61 | given: 62 | Files.write(file, 'foo'.getBytes(StandardCharsets.UTF_8.name())) 63 | def modified = Files.getLastModifiedTime(file) 64 | 65 | when: 66 | def result = task.call() 67 | 68 | then: 69 | 1 * formatter.format('foo') >> 'foo' 70 | result.state() == FileState.FORMATTED 71 | result.lastModified() == modified 72 | result.size() == Files.size(file) 73 | result.path() == file 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /subprojects/format/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | sourceCompatibility = 1.8 10 | 11 | dependencies { 12 | implementation 'org.codehaus.groovy:groovy-all:2.5.10' // v2.5.10 is the first version which runs on a Java 14 JVM. 13 | // At runtime, we must still support v2.3.10 (bundled with 14 | // Gradle v2.6) on Java < 14 JVMs. 15 | implementation 'com.google.guava:guava:30.0-jre' 16 | testImplementation('org.spockframework:spock-core:1.3-groovy-2.5') { 17 | exclude group: 'org.codehaus.groovy', module: 'groovy-all' 18 | } 19 | 20 | // for retrieving google-java-format and its dependencies from maven central 21 | testImplementation 'org.apache.maven:maven-resolver-provider:3.6.1' 22 | testImplementation 'org.apache.maven.resolver:maven-resolver-connector-basic:1.4.0' 23 | testImplementation 'org.apache.maven.resolver:maven-resolver-transport-file:1.4.0' 24 | testImplementation 'org.apache.maven.resolver:maven-resolver-transport-http:1.4.0' 25 | testImplementation 'org.slf4j:slf4j-nop:1.7.26' 26 | } 27 | 28 | test { 29 | // download google-java-format and transitive dependencies to $buildDir/grapes 30 | systemProperty 'maven.repo.local', new File(buildDir, "local-maven-repository") 31 | } 32 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/AbstractFormatterFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | // overwrite groovy default import of java.util.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.PackageScope 7 | 8 | @CompileStatic 9 | @PackageScope 10 | abstract class AbstractFormatterFactory implements FormatterFactory { 11 | 12 | static final String formatterClassName = 'com.google.googlejavaformat.java.Formatter' 13 | static final String javaFormatterOptionsClassName = 'com.google.googlejavaformat.java.JavaFormatterOptions' 14 | static final String styleEnumName = 'com.google.googlejavaformat.java.JavaFormatterOptions$Style' 15 | static final String importOrdererClassName = 'com.google.googlejavaformat.java.ImportOrderer' 16 | 17 | 18 | final ClassLoader classLoader 19 | final Configuration config 20 | 21 | AbstractFormatterFactory(ClassLoader classLoader, Configuration config) { 22 | this.classLoader = classLoader; 23 | this.config = config; 24 | } 25 | 26 | /** 27 | *

31 | */ 32 | Object constructFormatter() { 33 | def options = constructJavaFormatterOptions() 34 | def clazz = classLoader.loadClass(formatterClassName) 35 | return clazz.newInstance(options) 36 | } 37 | 38 | abstract Object constructJavaFormatterOptions() 39 | 40 | /** 41 | * 45 | */ 46 | Object constructStyle() { 47 | def clazz = classLoader.loadClass(styleEnumName) 48 | switch (config.style) { 49 | case Style.GOOGLE: 50 | return Enum.valueOf(clazz, 'GOOGLE') 51 | case Style.AOSP: 52 | return Enum.valueOf(clazz, 'AOSP') 53 | default: 54 | // if we end up here: shame on the person who added the unknown style and didn't update all the cases! 55 | throw new AssertionError() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | public final class Configuration { 4 | public final String version; 5 | public final Style style; 6 | 7 | public Configuration(String version, Style style) { 8 | this.version = version; 9 | this.style = style; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/Formatter.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | /** 4 | * Represents the common functionality that all versions of google-java-format provide. 6 | * 7 | *

This abstraction makes it easy to use different versions of {@code google-java-format} 8 | * uniformly. 9 | */ 10 | public interface Formatter { 11 | String format(String source) throws FormatterException; 12 | } 13 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/FormatterException.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | /** Wraps any exceptions thrown by implementations of the {@link Formatter} interface. */ 4 | public class FormatterException extends Exception { 5 | FormatterException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/FormatterFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | interface FormatterFactory { 4 | 5 | Formatter create() throws ReflectiveOperationException; 6 | } 7 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/FormatterOption.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | /** 6 | * A {@code FormatterOption} configures a {@link Formatter} to behave in a certain way. 7 | * 8 | *

Different versions of google-java-format are configured 10 | * differently. In order to provide a unified configuration interface for all versions, this class 11 | * contains (an abstraction) of every option that is supported by at least one version of {@code 12 | * google-java-format}. 13 | */ 14 | public enum FormatterOption { 15 | 16 | /** Supported by {@code google-java-format} version {@code 1.0}. */ 17 | AOSP_STYLE("1.0"), 18 | 19 | /** Supported by {@code google-java-format} version {@code 1.0}. */ 20 | GOOGLE_STYLE("1.0"), 21 | NO_JAVADOC_FORMATTER("1.0"), 22 | ECLIPSE_JAVADOC_FORMATTER("1.0"), 23 | SORT_IMPORTS("1.0"); 24 | 25 | public final ImmutableList supportedVersions; 26 | 27 | FormatterOption(String... supportedVersions) { 28 | this.supportedVersions = ImmutableList.copyOf(supportedVersions); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/Gjf.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | /** Static factory method for creating new {@link Formatter}s. */ 6 | public class Gjf { 7 | 8 | public static final String GROUP_ID = "com.google.googlejavaformat"; 9 | public static final String ARTIFACT_ID = "google-java-format"; 10 | 11 | public static final ImmutableList SUPPORTED_VERSIONS = 12 | ImmutableList.of("1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "1.10.0", "1.11.0"); 13 | 14 | /** 15 | * Constructs a new formatter that delegates to google-java-format. 17 | * 18 | * @param classLoader load {@code google-java-format} classes from this {@code ClassLoader} 19 | * @param config configure the formatter according to this configuration 20 | * @throws ReflectiveOperationException if the requested {@code Formatter} cannot be constructed 21 | */ 22 | public static Formatter newFormatter(ClassLoader classLoader, Configuration config) 23 | throws ReflectiveOperationException { 24 | return newFormatterFactory(classLoader, config).create(); 25 | } 26 | 27 | private static FormatterFactory newFormatterFactory( 28 | ClassLoader classLoader, Configuration config) { 29 | switch (config.version) { 30 | case "1.0": 31 | return new OneDotZeroFactory(classLoader, config); 32 | case "1.1": 33 | case "1.2": 34 | case "1.3": 35 | case "1.4": 36 | case "1.5": 37 | case "1.6": 38 | case "1.7": 39 | return new OneDotOneFactory(classLoader, config); 40 | case "1.8": 41 | case "1.9": 42 | case "1.10.0": 43 | case "1.11.0": 44 | default: 45 | return new OneDotEightFactory(classLoader, config); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/OneDotEightFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.PackageScope 5 | import groovy.transform.TypeCheckingMode 6 | 7 | @CompileStatic 8 | @PackageScope 9 | final class OneDotEightFactory extends OneDotOneFactory { 10 | 11 | // @PackageScope not available for constructors in Gradle's (v2.0) groovy version (v2.3.2) 12 | protected OneDotEightFactory(ClassLoader classLoader, Configuration config) { 13 | super(classLoader, config) 14 | } 15 | 16 | /** See com.google.googlejavaformat.java.RemoveUnusedImports */ 17 | @CompileStatic(TypeCheckingMode.SKIP) 18 | @Override 19 | Closure constructRemoveUnusedImportsClosure() { 20 | def clazz = classLoader.loadClass(removeUnusedImportsClassName) 21 | def remover = clazz.newInstance() 22 | return { String source -> 23 | remover.removeUnusedImports(source) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/OneDotOneFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format 2 | 3 | // overwrite groovy default import of java.util.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.PackageScope 7 | import groovy.transform.TypeCheckingMode 8 | 9 | @CompileStatic 10 | @PackageScope 11 | class OneDotOneFactory extends AbstractFormatterFactory { 12 | 13 | protected static final String removeUnusedImportsClassName = 'com.google.googlejavaformat.java.RemoveUnusedImports' 14 | private static final String javadocOnlyImportsEnumName = removeUnusedImportsClassName + '$JavadocOnlyImports' 15 | 16 | // @PackageScope not available for constructors in Gradle's (v2.0) groovy version (v2.3.2) 17 | protected OneDotOneFactory(ClassLoader classLoader, Configuration config) { 18 | super(classLoader, config) 19 | } 20 | 21 | @Override 22 | @CompileStatic(TypeCheckingMode.SKIP) 23 | public Formatter create() throws ReflectiveOperationException { 24 | def formatter = constructFormatter() 25 | def reorderImports = constructReorderImportsClosure() 26 | def removeUnusedImports = constructRemoveUnusedImportsClosure() 27 | return { String source -> 28 | Error cause; 29 | try { 30 | def tmp = reorderImports.call(source) 31 | tmp = removeUnusedImports.call(tmp) 32 | return formatter.formatSource(tmp) 33 | } catch (e) { 34 | if ("com.google.googlejavaformat.java.FormatterException".equals(e.getClass().getCanonicalName())) { 35 | String error = "Google Java Formatter error: " + e.getMessage() 36 | throw new FormatterException(error, e) 37 | } 38 | cause = e; // Unknown error 39 | } catch (Error e) { 40 | cause = e; // Unknown error 41 | } 42 | String error = "An unexpected error happened: " + cause.toString() 43 | throw new FormatterException(error, cause) 44 | } 45 | } 46 | 47 | /** See com.google.googlejavaformat.java.JavaFormatterOptions */ 48 | @Override 49 | @CompileStatic(TypeCheckingMode.SKIP) 50 | Object constructJavaFormatterOptions() { 51 | def style = constructStyle() 52 | def clazz = classLoader.loadClass(javaFormatterOptionsClassName) 53 | def builder = clazz.builder(); 54 | return builder.style(style).build(); 55 | } 56 | 57 | 58 | /** See com.google.googlejavaformat.java.ImportOrderer */ 59 | @CompileStatic(TypeCheckingMode.SKIP) 60 | Closure constructReorderImportsClosure() { 61 | def clazz = classLoader.loadClass(importOrdererClassName) 62 | return { String text -> 63 | clazz.reorderImports(text) 64 | } 65 | } 66 | 67 | /** See com.google.googlejavaformat.java.RemoveUnusedImports */ 68 | @CompileStatic(TypeCheckingMode.SKIP) 69 | protected Closure constructRemoveUnusedImportsClosure() { 70 | def clazz = classLoader.loadClass(removeUnusedImportsClassName) 71 | def remover = clazz.newInstance() 72 | def javadocOnlyImports = constructJavadocOnlyImports() 73 | return { String source -> 74 | remover.removeUnusedImports(source, javadocOnlyImports) 75 | } 76 | } 77 | 78 | /** See com.google.googlejavaformat.java.RemoveUnusedImports$JavadocOnlyImports */ 79 | private Object constructJavadocOnlyImports() { 80 | def clazz = classLoader.loadClass(javadocOnlyImportsEnumName) 81 | return Enum.valueOf(clazz, 'KEEP') 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/OneDotZeroFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format 2 | 3 | // overwrite groovy default import of java.util.Formatter 4 | import com.github.sherter.googlejavaformatgradleplugin.format.Formatter 5 | import groovy.transform.CompileStatic 6 | import groovy.transform.PackageScope 7 | import groovy.transform.TypeCheckingMode 8 | 9 | @CompileStatic 10 | @PackageScope 11 | final class OneDotZeroFactory extends AbstractFormatterFactory { 12 | 13 | private static final String javadocFormatterEnumName = 'com.google.googlejavaformat.java.JavaFormatterOptions$JavadocFormatter' 14 | private static final String sortImportsEnumName = 'com.google.googlejavaformat.java.JavaFormatterOptions$SortImports' 15 | 16 | // @PackageScope not available for constructors in Gradle's (v2.0) groovy version (v2.3.2) 17 | protected OneDotZeroFactory(ClassLoader classLoader, Configuration config) { 18 | super(classLoader, config) 19 | } 20 | 21 | @Override 22 | @CompileStatic(TypeCheckingMode.SKIP) 23 | public Formatter create() throws ReflectiveOperationException { 24 | def formatter = constructFormatter() 25 | def reorderImports = constructReorderImportsClosure() 26 | return { String source -> 27 | Error cause; 28 | try { 29 | def importOrderedSource = reorderImports.call(source) 30 | return formatter.formatSource(importOrderedSource) 31 | } catch (e) { 32 | if ("com.google.googlejavaformat.java.FormatterException".equals(e.getClass().getCanonicalName())) { 33 | String error = "Google Java Formatter error: " + e.getMessage() 34 | throw new FormatterException(error, e) 35 | } 36 | cause = e; // Unknown error 37 | } catch (Error e) { 38 | cause = e; // Unknown error 39 | } 40 | String error = "An unexpected error happened: " + cause.toString() 41 | throw new FormatterException(error, cause) 42 | } 43 | } 44 | 45 | /** See com.google.googlejavaformat.java.JavaFormatterOptions */ 46 | @Override 47 | Object constructJavaFormatterOptions() { 48 | def javadocFormatter = constructJavadocFormatter() 49 | def style = constructStyle() 50 | def sortImports = constructSortImports() 51 | def clazz = classLoader.loadClass(javaFormatterOptionsClassName) 52 | return clazz.newInstance(javadocFormatter, style, sortImports) 53 | } 54 | 55 | /** See com.google.googlejavaformat.java.JavaFormatterOptions$JavadocFormatter */ 56 | private Object constructJavadocFormatter() { 57 | def clazz = classLoader.loadClass(javadocFormatterEnumName) 58 | return Enum.valueOf(clazz, 'ECLIPSE') 59 | } 60 | 61 | /** See com.google.googlejavaformat.java.JavaFormatterOptions$SortImports */ 62 | private Object constructSortImports() { 63 | def clazz = classLoader.loadClass(sortImportsEnumName) 64 | 65 | // It doesn't matter which value we return here, it has no influence whatsoever. 66 | // See https://github.com/google/google-java-format/issues/42 67 | return Enum.valueOf(clazz, 'NO') 68 | } 69 | 70 | 71 | /** See com.google.googlejavaformat.java.ImportOrderer */ 72 | @CompileStatic(TypeCheckingMode.SKIP) 73 | Closure constructReorderImportsClosure() { 74 | def clazz = classLoader.loadClass(importOrdererClassName) 75 | return { String text -> 76 | clazz.reorderImports('', text) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /subprojects/format/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/format/Style.java: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format; 2 | 3 | public enum Style { 4 | AOSP, 5 | GOOGLE 6 | } 7 | -------------------------------------------------------------------------------- /subprojects/format/src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/format/FormatterSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format 2 | 3 | 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | import spock.util.environment.Jvm 7 | 8 | import static org.junit.Assume.assumeTrue 9 | 10 | @Unroll 11 | class FormatterSpec extends Specification { 12 | 13 | def 'AOSP-style formatting (v#version)'() { 14 | skipIfJvmNotSupported(version) 15 | given: 16 | def conf = new Configuration(version, Style.AOSP) 17 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 18 | 19 | expect: 20 | formatter.format('class Test { public static void main(String[] args) { } }') == 21 | '''class Test { 22 | | public static void main(String[] args) {} 23 | |} 24 | |'''.stripMargin() 25 | 26 | where: 27 | version << Gjf.SUPPORTED_VERSIONS 28 | } 29 | 30 | def 'GOOGLE-style formatting (v#version)'() { 31 | skipIfJvmNotSupported(version) 32 | given: 33 | def conf = new Configuration(version, Style.GOOGLE) 34 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 35 | 36 | expect: 37 | formatter.format('class Test { public static void main(String[] args) { } }') == 38 | '''class Test { 39 | | public static void main(String[] args) {} 40 | |} 41 | |'''.stripMargin() 42 | 43 | where: 44 | version << Gjf.SUPPORTED_VERSIONS 45 | } 46 | 47 | def 'formatter formats javadoc (v#version)'() { 48 | skipIfJvmNotSupported(version) 49 | given: 50 | def conf = new Configuration(version, Style.GOOGLE) 51 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 52 | def javadoc = '''/** This is (was) malformed javadoc. 53 | |*

 54 |                         |*System.err.println
 55 |                         |*   
56 | | */ 57 | |'''.stripMargin() 58 | expect: 59 | formatter.format(javadoc).startsWith( 60 | '/**' + System.lineSeparator() + 61 | ' * This is (was) malformed javadoc.') 62 | 63 | where: 64 | version << Gjf.SUPPORTED_VERSIONS 65 | } 66 | 67 | def 'formatter orders imports (v#version)'() { 68 | skipIfJvmNotSupported(version) 69 | given: 70 | def conf = new Configuration(version, Style.GOOGLE) 71 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 72 | def unordered = '''import z.Z; 73 | |import a.A; 74 | | 75 | |class C { 76 | | A a; 77 | | Z z; 78 | |} 79 | |'''.stripMargin() 80 | def ordered = '''import a.A; 81 | |import z.Z; 82 | | 83 | |class C { 84 | | A a; 85 | | Z z; 86 | |} 87 | |'''.stripMargin() 88 | expect: 89 | formatter.format(unordered) == ordered 90 | 91 | where: 92 | version << Gjf.SUPPORTED_VERSIONS 93 | } 94 | 95 | def 'formatter removes unused imports (v#version)'() { 96 | skipIfJvmNotSupported(version) 97 | given: 98 | def conf = new Configuration(version, Style.GOOGLE) 99 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 100 | 101 | expect: 102 | formatter.format('''import z.Z; 103 | |import a.A; 104 | | 105 | |class C { 106 | | A a; 107 | |} 108 | |'''.stripMargin()) == '''import a.A; 109 | | 110 | |class C { 111 | | A a; 112 | |} 113 | |'''.stripMargin() 114 | 115 | 116 | where: 117 | version << Gjf.SUPPORTED_VERSIONS - ['1.0'] 118 | } 119 | 120 | def 'formatter throws when given invalid source code (v#version)'() { 121 | skipIfJvmNotSupported(version) 122 | given: 123 | def conf = new Configuration(version, Style.GOOGLE) 124 | def formatter = Gjf.newFormatter(Resolver.resolve(version), conf) 125 | 126 | when: 127 | formatter.format('Hello World!') 128 | then: 129 | thrown(FormatterException) 130 | 131 | where: 132 | version << Gjf.SUPPORTED_VERSIONS 133 | } 134 | 135 | private static skipIfJvmNotSupported(version) { 136 | assumeTrue(Gjf.SUPPORTED_VERSIONS.indexOf(version) < Gjf.SUPPORTED_VERSIONS.indexOf('1.8') || 137 | new BigDecimal(Jvm.current.javaSpecificationVersion) >= BigInteger.valueOf(11)) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /subprojects/format/src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/format/Resolver.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.format 2 | 3 | import org.apache.maven.repository.internal.MavenRepositorySystemUtils 4 | import org.eclipse.aether.DefaultRepositorySystemSession 5 | import org.eclipse.aether.RepositorySystem 6 | import org.eclipse.aether.RepositorySystemSession 7 | import org.eclipse.aether.artifact.DefaultArtifact 8 | import org.eclipse.aether.collection.CollectRequest 9 | import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory 10 | import org.eclipse.aether.graph.Dependency 11 | import org.eclipse.aether.impl.DefaultServiceLocator 12 | import org.eclipse.aether.repository.LocalRepository 13 | import org.eclipse.aether.repository.RemoteRepository 14 | import org.eclipse.aether.repository.RepositoryPolicy 15 | import org.eclipse.aether.resolution.ArtifactResult 16 | import org.eclipse.aether.resolution.DependencyRequest 17 | import org.eclipse.aether.spi.connector.RepositoryConnectorFactory 18 | import org.eclipse.aether.spi.connector.transport.TransporterFactory 19 | import org.eclipse.aether.transport.file.FileTransporterFactory 20 | import org.eclipse.aether.transport.http.HttpTransporterFactory 21 | import org.eclipse.aether.util.artifact.JavaScopes 22 | 23 | class Resolver { 24 | 25 | private static final RemoteRepository MAVEN_CENTRAL_REPOSITORY = new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build() 26 | private static final LocalRepository LOCAL_REPOSITORY = new LocalRepository(System.getProperty("maven.repo.local")) 27 | 28 | private static final RepositorySystem system = createRepositorySystem() 29 | private static final RepositorySystemSession session = createRepositorySystemSession(system) 30 | 31 | static ClassLoader resolve(String version) { 32 | DependencyRequest request = createGoogleJavaFormatRequest(version) 33 | List artifactResults = system.resolveDependencies(session, request).getArtifactResults() 34 | 35 | URL[] artifactLocations = artifactResults.collect { 36 | result -> result.getArtifact().getFile().toURI().toURL() 37 | } 38 | URLClassLoader classLoader = new URLClassLoader(artifactLocations, ClassLoader.getSystemClassLoader()) 39 | return classLoader 40 | } 41 | 42 | 43 | private static DependencyRequest createGoogleJavaFormatRequest(String version) { 44 | def coordinates = new DefaultArtifact("${Gjf.GROUP_ID}:${Gjf.ARTIFACT_ID}:${version}") 45 | def collectRequest = new CollectRequest() 46 | .setRoot(new Dependency(coordinates, JavaScopes.RUNTIME)) 47 | .setRepositories([MAVEN_CENTRAL_REPOSITORY]) 48 | return new DependencyRequest().setCollectRequest(collectRequest) 49 | } 50 | 51 | private static RepositorySystem createRepositorySystem() { 52 | DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator() 53 | locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class) 54 | locator.addService(TransporterFactory.class, FileTransporterFactory.class) 55 | locator.addService(TransporterFactory.class, HttpTransporterFactory.class) 56 | 57 | return locator.getService(RepositorySystem.class) 58 | } 59 | 60 | private static RepositorySystemSession createRepositorySystemSession(RepositorySystem system) { 61 | DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession() 62 | session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_NEVER) 63 | session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, LOCAL_REPOSITORY)) 64 | return session 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /subprojects/test/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation localGroovy() 11 | testImplementation('org.spockframework:spock-core:1.3-groovy-2.5') { 12 | // gradle always loads its own groovy version, which leads to conflicts 13 | exclude group: 'org.codehaus.groovy' 14 | } 15 | testImplementation 'com.google.jimfs:jimfs:1.1' 16 | } 17 | 18 | targetCompatibility = 1.8 19 | sourceCompatibility = 1.8 20 | -------------------------------------------------------------------------------- /subprojects/test/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/test/FileWithState.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.test 2 | 3 | import groovy.transform.PackageScope 4 | 5 | import java.nio.charset.StandardCharsets 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | import java.nio.file.attribute.FileTime 9 | 10 | class FileWithState { 11 | 12 | private final Path file 13 | private byte[] lastWrittenContent 14 | private FileTime lastWrittenTime 15 | 16 | private FileWithState(Path file, byte[] content, FileTime time) { 17 | this.file = file 18 | this.lastWrittenContent = content 19 | this.lastWrittenTime = time 20 | } 21 | 22 | @PackageScope 23 | static FileWithState create(Path file) { 24 | Files.createDirectories(file.parent) 25 | Files.createFile(file) 26 | return new FileWithState(file, [] as byte[], Files.getLastModifiedTime(file)) 27 | } 28 | 29 | /** 30 | * Returns true if the file's current last modified timestamp is not equal 31 | * to the timestamp that was recorded when {@code write} was called last (or 32 | * creation time, if never called). 33 | */ 34 | boolean lastModifiedTimeHasChanged() { 35 | return !Files.getLastModifiedTime(file).equals(lastWrittenTime) 36 | } 37 | 38 | /** 39 | * Returns true if the file's current content is not equal to the content 40 | * that was recorded when {@code write} was called last (or when it's empty and 41 | * write was never called before). 42 | */ 43 | boolean contentHasChanged() { 44 | return !Arrays.equals(readBytes(), lastWrittenContent) 45 | } 46 | 47 | 48 | byte[] readBytes() { 49 | return Files.readAllBytes(file) 50 | } 51 | 52 | /** Reads and returns file content decoded as UTF8. */ 53 | String read() { 54 | return new String(readBytes(), StandardCharsets.UTF_8) 55 | } 56 | 57 | /** 58 | * Writes new content to the file and remembers the content and the last modified time 59 | * so we can detect changes from outside with {@link FileWithState#lastModifiedTimeHasChanged()} 60 | * and {@link FileWithState#contentHasChanged()}. 61 | */ 62 | void write(byte[] newContent) { 63 | Files.write(file, newContent) 64 | lastWrittenContent = newContent 65 | lastWrittenTime = Files.getLastModifiedTime(file) 66 | } 67 | 68 | /** 69 | * Same as {@link FileWithState#write(byte[])}, encoded with UTF8. 70 | */ 71 | void write(String newContent) { 72 | write(newContent.getBytes(StandardCharsets.UTF_8)) 73 | } 74 | 75 | /** 76 | * Deletes the file from the file system. 77 | */ 78 | void delete() { 79 | Files.delete(file) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /subprojects/test/src/main/groovy/com/github/sherter/googlejavaformatgradleplugin/test/Project.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.test 2 | 3 | import java.nio.charset.StandardCharsets 4 | import java.nio.file.Path 5 | 6 | class Project { 7 | 8 | final Path rootDirectory 9 | 10 | Project(File projectDir) { 11 | this.rootDirectory = projectDir.toPath() 12 | } 13 | 14 | /** 15 | * Creates and returns a {@link FileWithState} from the given path elements 16 | * relative to the project directory. If a parent directory does not exist, 17 | * it will be created automatically. 18 | */ 19 | FileWithState createFile(Iterable pathElements) { 20 | def file = rootDirectory.resolve(pathElements.join(File.separator)) 21 | return FileWithState.create(file) 22 | } 23 | 24 | /** 25 | * This method is for convenience only. First creates a {@link FileWithState} and 26 | * then writes some initial content. 27 | */ 28 | FileWithState createFile(Iterable pathElements, byte[] content) { 29 | def fileWithState = createFile(pathElements) 30 | fileWithState.write(content) 31 | return fileWithState 32 | } 33 | 34 | /** 35 | * This method is for convinience only. First creates a {@link FileWithState} and 36 | * then writes the content encoded as UTF8. 37 | */ 38 | FileWithState createFile(Iterable pathElements, String content) { 39 | return createFile(pathElements, content.getBytes(StandardCharsets.UTF_8)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /subprojects/test/src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/test/FileWithStateTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.test 2 | 3 | import com.google.common.jimfs.Jimfs 4 | import spock.lang.Specification 5 | 6 | import java.nio.charset.StandardCharsets 7 | import java.nio.file.FileSystem 8 | import java.nio.file.Files 9 | import java.nio.file.Path 10 | import java.nio.file.attribute.FileTime 11 | 12 | class FileWithStateTest extends Specification { 13 | 14 | FileSystem fs = Jimfs.newFileSystem() 15 | Path foo = fs.getPath('foo').toAbsolutePath() 16 | 17 | def 'create file with missing parent directories'() { 18 | given: 19 | def file = fs.getPath('foo', 'bar', 'baz') 20 | 21 | when: 22 | FileWithState.create(file) 23 | 24 | then: 25 | Files.exists(file) 26 | Files.readAllBytes(file) == [] as byte[] 27 | } 28 | 29 | def 'state checks after creation'() { 30 | def file = FileWithState.create(foo) 31 | 32 | expect: 33 | !file.contentHasChanged() 34 | !file.lastModifiedTimeHasChanged() 35 | } 36 | 37 | def 'state checks after write'() { 38 | def file = FileWithState.create(foo) 39 | 40 | when: 41 | file.write('blub') 42 | 43 | then: 44 | !file.contentHasChanged() 45 | !file.lastModifiedTimeHasChanged() 46 | } 47 | 48 | def 'state checks after modification from outside'() { 49 | def file = FileWithState.create(foo) 50 | 51 | when: 52 | Files.setLastModifiedTime(foo, FileTime.fromMillis(0)) 53 | 54 | then: 55 | file.lastModifiedTimeHasChanged() 56 | !file.contentHasChanged() 57 | 58 | when: 59 | Files.write(foo, 'new content'.getBytes(StandardCharsets.UTF_8)) 60 | 61 | then: 62 | file.contentHasChanged() 63 | file.lastModifiedTimeHasChanged() 64 | } 65 | 66 | def 'file is deleted after calling delete'() { 67 | def file = FileWithState.create(foo) 68 | 69 | when: 70 | file.delete() 71 | 72 | then: 73 | !Files.exists(foo) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /subprojects/test/src/test/groovy/com/github/sherter/googlejavaformatgradleplugin/test/ProjectTest.groovy: -------------------------------------------------------------------------------- 1 | package com.github.sherter.googlejavaformatgradleplugin.test 2 | 3 | import org.junit.Rule 4 | import org.junit.rules.TemporaryFolder 5 | import spock.lang.Specification 6 | 7 | class ProjectTest extends Specification { 8 | 9 | @Rule TemporaryFolder temporaryFolder 10 | File rootDir 11 | Project project 12 | 13 | void setup() { 14 | rootDir = temporaryFolder.root 15 | project = new Project(rootDir) 16 | } 17 | 18 | def 'create file'() { 19 | when: 20 | project.createFile(['foo']) 21 | 22 | then: 23 | def file = new File(temporaryFolder.root, 'foo') 24 | file.exists() 25 | file.readBytes() == [] as byte[] 26 | } 27 | 28 | def 'create file with non-existing parent directories'() { 29 | when: 30 | project.createFile(['foo', 'bar', 'baz']) 31 | 32 | then: 33 | def file = new File(temporaryFolder.root, 'foo/bar/baz') 34 | file.exists() 35 | file.readBytes() == [] as byte[] 36 | } 37 | 38 | def 'create file with initial content'() { 39 | when: 40 | project.createFile(['foo'], [1, 2, 3] as byte[]) 41 | 42 | then: 43 | def file = new File(temporaryFolder.root, 'foo') 44 | file.exists() 45 | file.readBytes() == [1, 2, 3] as byte[] 46 | } 47 | 48 | def 'create file with initial string as content'() { 49 | when: 50 | project.createFile(['foo'], 'bar') 51 | 52 | then: 53 | def file = new File(temporaryFolder.root, 'foo') 54 | file.exists() 55 | file.readLines('UTF-8') == ['bar'] 56 | } 57 | 58 | } 59 | --------------------------------------------------------------------------------