├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── checkstyle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples └── springmvc-tomcat │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ └── main │ └── java │ └── com │ └── example │ └── tomcat │ ├── Controller.java │ └── TomcatApplication.java ├── settings.gradle ├── spotbugs-exclude.xml └── src ├── main └── java │ └── com │ └── github │ └── ayltai │ └── gradle │ └── plugin │ ├── Constants.java │ ├── SpringGraalNativeExtension.java │ ├── SpringGraalNativePlugin.java │ ├── SpringGraalNativeTask.java │ ├── SpringNativeMode.java │ └── internal │ ├── ArchiveUtils.java │ ├── DownloadUtils.java │ ├── PlatformUtils.java │ └── VersionNumberComparator.java └── test ├── java └── com │ └── github │ └── ayltai │ └── gradle │ └── plugin │ ├── SpringGraalNativeTaskTests.java │ ├── UnitTests.java │ └── internal │ ├── ArchiveUtilsTests.java │ ├── DownloadUtilsTests.java │ └── VersionNumberComparatorTests.java └── resources └── spring-boot-0.0.1-SNAPSHOT.jar /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: 'ayltai' 7 | 8 | --- 9 | 10 | **Things to check before reporting a bug** 11 | 12 | - [ ] I have searched [GraalVM GitHub repository](https://github.com/oracle/graal/issues) and could not find a similar issue 13 | - [ ] I have searched [Spring experimental GraalVM Native project](https://github.com/spring-projects-experimental/spring-graalvm-native/issues) and could not find a similar issue 14 | - [ ] I have attached a sample project so that the issue can be reproduced on other computers 15 | 16 | **Describe the bug** 17 | 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | 22 | Steps to reproduce the behavior: 23 | 24 | 1. Go to '...' 25 | 2. Click on '...' 26 | 3. Scroll down to '...' 27 | 4. See error 28 | 29 | **Expected behavior** 30 | 31 | A clear and concise description of what you expected to happen. 32 | 33 | **Screenshots** 34 | 35 | If applicable, add screenshots to help explain your problem. 36 | 37 | **Desktop (please complete the following information):** 38 | 39 | - OS: [e.g. macOS] 40 | - Version [e.g. 10.15.5] 41 | 42 | **Additional context** 43 | 44 | Add any other context about the problem here. 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: 'ayltai' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | timezone: Asia/Manila 8 | open-pull-requests-limit: 99 9 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: CD 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - name: Git checkout 10 | uses: actions/checkout@v2 11 | - name: Set up JDK 8 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: 8 15 | - name: Publish Gradle plugin 16 | run: ./gradlew -Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} publishPlugins 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | checkstyle: 5 | runs-on: ubuntu-20.04 6 | steps: 7 | - name: Git checkout 8 | uses: actions/checkout@v2 9 | - name: Set up JDK 11 10 | uses: actions/setup-java@v1 11 | with: 12 | java-version: 11 13 | - name: Run Checkstyle linter 14 | run: ./gradlew checkstyleMain 15 | - name: Upload Checkstyle report 16 | uses: actions/upload-artifact@v2 17 | with: 18 | name: checkstyle 19 | path: build/reports/checkstyle/ 20 | spotbugs: 21 | runs-on: ubuntu-20.04 22 | steps: 23 | - name: Git checkout 24 | uses: actions/checkout@v2 25 | - name: Set up JDK 11 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: 11 29 | - name: Run SpotBugs linter 30 | run: ./gradlew spotbugsMain 31 | - name: Upload SpotBugs report 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: spotbugs 35 | path: build/reports/sportbugs/ 36 | test: 37 | runs-on: ubuntu-20.04 38 | steps: 39 | - name: Git checkout 40 | uses: actions/checkout@v2 41 | - name: Set up JDK 11 42 | uses: actions/setup-java@v1 43 | with: 44 | java-version: 11 45 | - name: Validate Gradle plugin 46 | run: ./gradlew validatePlugins 47 | - name: Run tests 48 | run: ./gradlew test 49 | - name: Upload tests report 50 | uses: actions/upload-artifact@v2 51 | with: 52 | name: test 53 | path: build/reports/tests/test/ 54 | - name: Generate coverage report 55 | run: ./gradlew jacocoTestReport 56 | - name: Upload coverage report 57 | uses: actions/upload-artifact@v2 58 | with: 59 | name: coverage 60 | path: build/reports/jacoco/test/html/ 61 | - name: Run SonarQube scanner 62 | run: ./gradlew sonarqube 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 66 | - name: Upload coverage report to Codecov 67 | uses: codecov/codecov-action@v1 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Windows 5 | Thumbs.db 6 | [Dd]esktop.ini 7 | $RECYCLE.BIN/ 8 | *.lnk 9 | 10 | # JetBrains 11 | .idea/ 12 | *.iml 13 | 14 | # Java 15 | *.class 16 | hs_err_pid* 17 | *.hprof 18 | 19 | # Gradle 20 | .gradle 21 | build/ 22 | !gradle-wrapper.jar 23 | 24 | # Generated 25 | *.log 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alan Tai 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.md: -------------------------------------------------------------------------------- 1 | # Spring GraalVM native Gradle plugin 2 | 3 | [![GitHub workflow status](https://img.shields.io/github/workflow/status/ayltai/spring-graalvm-native-plugin/CI?style=flat)](https://github.com/ayltai/spring-graalvm-native-plugin/actions) 4 | [![Codacy grade](https://img.shields.io/codacy/grade/271a4011f13d4200842d3334c21e8b2f.svg?style=flat)](https://app.codacy.com/app/AlanTai/spring-graalvm-native-plugin/dashboard) 5 | [![Sonar quality gate](https://img.shields.io/sonar/quality_gate/ayltai_spring-graalvm-native-plugin?style=flat&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 6 | [![Sonar violations (short format)](https://img.shields.io/sonar/violations/ayltai_spring-graalvm-native-plugin?style=flat&format=short&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 7 | [![Sonar Test Success Rate](https://img.shields.io/sonar/test_success_density/ayltai_spring-graalvm-native-plugin?style=flat&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 8 | [![Code Coverage](https://img.shields.io/codecov/c/github/ayltai/spring-graalvm-native-plugin.svg?style=flat)](https://codecov.io/gh/ayltai/spring-graalvm-native-plugin) 9 | [![Sonar Coverage](https://img.shields.io/sonar/coverage/ayltai_spring-graalvm-native-plugin?style=flat&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 10 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ayltai_spring-graalvm-native-plugin&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 11 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=ayltai_spring-graalvm-native-plugin&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 12 | [![Sonar Tech Debt](https://img.shields.io/sonar/tech_debt/ayltai_spring-graalvm-native-plugin?style=flat&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 13 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=ayltai_spring-graalvm-native-plugin&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=ayltai_spring-graalvm-native-plugin) 14 | ![Maintenance](https://img.shields.io/maintenance/yes/2021?style=flat) 15 | [![Release](https://img.shields.io/github/release/ayltai/spring-graalvm-native-plugin.svg?style=flat)](https://github.com/ayltai/spring-graalvm-native-plugin/releases) 16 | [![License](https://img.shields.io/github/license/ayltai/spring-graalvm-native-plugin.svg?style=flat)](https://github.com/ayltai/spring-graalvm-native-plugin/blob/master/LICENSE) 17 | 18 | Supports for building Spring Boot applications as GraalVM native images. 19 | 20 | [https://plugins.gradle.org/plugin/com.github.ayltai.spring-graalvm-native-plugin](https://plugins.gradle.org/plugin/com.github.ayltai.spring-graalvm-native-plugin) 21 | 22 | [![Buy me a coffee](https://img.shields.io/static/v1?label=Buy%20me%20a&message=coffee&color=important&style=flat&logo=buy-me-a-coffee&logoColor=white)](https://buymeacoff.ee/ayltai) 23 | 24 | ## Quick start 25 | 26 | ### Apply Gradle plugin 27 | 28 | #### Groovy 29 | Using the [plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block): 30 | ```groovy 31 | plugins { 32 | id 'com.github.ayltai.spring-graalvm-native-plugin' version '1.4.10' 33 | } 34 | ``` 35 | 36 | Using [legacy plugin application](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application): 37 | ```groovy 38 | buildscript { 39 | repositories { 40 | maven { 41 | url 'https://plugins.gradle.org/m2/' 42 | } 43 | } 44 | 45 | dependencies { 46 | classpath 'gradle.plugin.com.github.ayltai:spring-graalvm-native-plugin:1.4.10' 47 | } 48 | } 49 | 50 | apply plugin: 'com.github.ayltai.spring-graalvm-native-plugin' 51 | ``` 52 | 53 | #### Kotlin 54 | Using the [plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block): 55 | ```groovy 56 | plugins { 57 | id('com.github.ayltai.spring-graalvm-native-plugin') version '1.4.10' 58 | } 59 | ``` 60 | 61 | Using [legacy plugin application](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application): 62 | ```groovy 63 | buildscript { 64 | repositories { 65 | maven { 66 | url = uri('https://plugins.gradle.org/m2/') 67 | } 68 | } 69 | 70 | dependencies { 71 | classpath('gradle.plugin.com.github.ayltai:spring-graalvm-native-plugin:1.4.10') 72 | } 73 | } 74 | ``` 75 | 76 | ### Specify build arguments 77 | This plugin uses the following example Gradle extension for configuration: 78 | ```groovy 79 | nativeImage { 80 | mainClassName = 'com.example.springboot.Application' 81 | 82 | reportExceptionStackTraces = true 83 | removeUnusedAutoConfig = true 84 | removeYamlSupport = true 85 | maxHeapSize = '6G' 86 | } 87 | ``` 88 | 89 | More configuration options can be found [here](https://github.com/ayltai/spring-graalvm-native-plugin#configuration). 90 | 91 | ### Update Spring Boot annotation 92 | You need to specify `proxyBeanMethods = false` for your `@SpringBootApplication` annotation: 93 | ```java 94 | @SpringBootApplication(proxyBeanMethods = false) 95 | public class TomcatApplication { 96 | public static void main(final String[] args) { 97 | SpringApplication.run(TomcatApplication.class, args); 98 | } 99 | } 100 | ``` 101 | 102 | ### Build GraalVM Native Image 103 | 1. Run the Gradle task `buildNativeImage` 104 | 2. The native image can be located at `/native` 105 | 106 | ## Sample project 107 | [samples](https://github.com/ayltai/spring-graalvm-native-plugin/tree/master/samples) contains various samples that demonstrate the basic usage of this Gradle plugin. 108 | 109 | ## Configuration 110 | | Property | Type | Description | 111 | |----------|------|-------------| 112 | | `toolVersion` | `String` | The GraalVM Community Edition version to download. Default to `21.1.0`. | 113 | | `javaVersion` | `String` | The JDK version to be downloaded with GraalVM Community Edition. Default to `8`. | 114 | | `download` | `String` | Specify when to download GraalVM Community Edition. Supports `default` which downloads GraalVM tools only if they are not already downloaded, `always` which always (re-)download GraalVM tools, and `skip` which skips downloading GraalVM tools and assumes they are already installed. | 115 | | `mainClassName` (Required) | `String` | The fully qualified name of the Java class that contains a `main` method for the entry point of the Native Image executable. | 116 | | `traceClassInitialization` | `boolean` | **(Deprecated. Use `traceClassInitializationEnabled` if you use GraalVM 20.2.0 or below, and `traceClassInitializationFor` if you use GraalVM 20.3.0 or above.)** Provides useful information to debug class initialization issues. | 117 | | `traceClassInitializationEnabled` | `boolean` | **(For GraalVM 20.2.0 or below)** Provides useful information to debug class initialization issues. | 118 | | `traceClassInitializationFor` | `List` | **(For GraalVM 20.3.0 or above)** A comma-separated list of fully qualified class names that class initialization is traced for. | 119 | | `removeSaturatedTypeFlows` | `boolean` | Reduces build time and decrease build memory consumption, especially for big projects. | 120 | | `reportExceptionStackTraces` | `boolean` | Provides more detail should something go wrong. | 121 | | `printAnalysisCallTree` | `boolean` | Helps to find what classes, methods, and fields are used and why. You can find more details in GraalVM [reports documentation](https://github.com/oracle/graal/blob/master/substratevm/REPORTS.md). | 122 | | `disableToolchainChecking` | `boolean` | Prevent native-toolchain checking. | 123 | | `enableAllSecurityServices` | `boolean` | Adds all security service classes to the generated image. Required for HTTPS and crypto. | 124 | | `enableHttp` | `boolean` | Enables HTTP support in the generated image. | 125 | | `enableHttps` | `boolean` | Enables HTTPS support in the generated image. | 126 | | `enableUrlProtocols` | `List` | Specifies a list of URL protocols to be enabled in the generated image. | 127 | | `staticallyLinked` | `boolean` | Builds a statically linked executable, useful to deploy on a `FROM scratch` Docker image. | 128 | | `warnMissingSelectorHints` | `boolean` | Switches the feature from a hard error for missing hints to a warning. | 129 | | `removeUnusedAutoConfig` | `boolean` | Disables removal of unused configurations. | 130 | | `verbose` | `boolean` | Makes image building output more verbose. | 131 | | `removeYamlSupport` | `boolean` | Removes Yaml support from Spring Boot, enabling faster compilation and smaller executables. | 132 | | `removeXmlSupport` | `boolean` | Removes XML support from Spring Boot, enabling faster compilation and smaller executables. | 133 | | `removeSpelSupport` | `boolean` | Removes SpEL support from Spring Boot, enabling faster compilation and smaller executables. | 134 | | `removeJmxSupport` | `boolean` | Removes JMX support from Spring Boot, enabling faster compilation and smaller executables. | 135 | | `verify` | `boolean` | Switches on the verifier mode. | 136 | | `springNativeVerbose` | `boolean` | Outputs lots of information about the feature behavior as it processes auto-configuration and chooses which to include. | 137 | | `springNativeMode` | `String` | Switches how much configuration the feature actually provides to native-image. The default is `reflection` which provides resource, initialization, proxy, and reflection (using auto-configuration hints) configuration for native images as well as substitutions. `agent` should be used if only wishing the feature to provide substitutions and initialization configuration - in this mode you should have used the agent to collect the rest of the configuration. `functional` is when working with functional bean registration (Spring Fu style). In this mode the feature will provide initialization and resource configuration but nothing more. `init` should be used if only wishing to provide initialization configuration and substitutions. | 138 | | `dumpConfig` | `String` | Dumps the configuration to the specified file. | 139 | | `maxHeapSize` | `String` | Maximum allowed Java heap size for building GraalVM Native Image. | 140 | | `initializeAtBuildTime` | `List` | Use it with specific classes or package to initialize classes at build time. | 141 | | `extraArgs` | `List` | Add any `native-image` compiler arguments not covered by `spring-graalvm-native-plugin`. | 142 | 143 | See [Spring GraalVM Native configuration options](https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#native-image-options) for more details. 144 | 145 | ## GitHub action 146 | [setup-graalvm](https://github.com/marketplace/actions/setup-graalvm-action) is a GitHub action that sets up a GraalVM environment for your [GitHub workflow](https://github.com/features/actions). 147 | 148 | ## License 149 | [MIT](https://github.com/ayltai/spring-graalvm-native-plugin/blob/master/LICENSE) 150 | 151 | ## References 152 | * [GraalVM](https://www.graalvm.org) 153 | * [GraalVM Native Image](https://www.graalvm.org/docs/reference-manual/native-image) 154 | * [Spring Native](https://github.com/spring-projects-experimental/spring-native) 155 | * [Spring Native configuration options](https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#native-image-options) 156 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'java-gradle-plugin' 4 | id 'com.gradle.plugin-publish' version '0.21.0' 5 | id 'checkstyle' 6 | id 'com.github.spotbugs' version '5.0.8' 7 | id 'org.sonarqube' version '3.4.0.2513' 8 | id 'jacoco' 9 | id 'com.github.ben-manes.versions' version '0.42.0' 10 | } 11 | 12 | apply plugin: 'maven-publish' 13 | 14 | group 'com.github.ayltai' 15 | version '1.4.10' 16 | 17 | sourceCompatibility = JavaVersion.VERSION_1_8 18 | targetCompatibility = JavaVersion.VERSION_1_8 19 | 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | ext { 25 | junitVersion = '5.8.2' 26 | } 27 | 28 | dependencies { 29 | // Utilities 30 | implementation 'org.apache.commons:commons-compress:1.21' 31 | implementation 'org.apache.commons:commons-lang3:3.12.0' 32 | 33 | // Testing 34 | testImplementation 'org.mockito:mockito-core:4.6.1' 35 | testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" 36 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" 37 | } 38 | 39 | test { 40 | useJUnitPlatform() 41 | } 42 | 43 | //region Checkstyle configurations 44 | 45 | checkstyle { 46 | toolVersion = '8.41.1' 47 | configFile = rootProject.file('./checkstyle.xml') 48 | ignoreFailures = true 49 | showViolations = true 50 | } 51 | 52 | tasks.withType(Checkstyle) { 53 | source = 'src/main/java' 54 | 55 | include '**/*.java' 56 | exclude '**/*Test.java' 57 | 58 | reports { 59 | xml.enabled = false 60 | html.enabled = true 61 | xml.destination = file("$project.buildDir/reports/checkstyle/checkstyle-output.xml") 62 | html.destination = file("$project.buildDir/reports/checkstyle/checkstyle-output.html") 63 | } 64 | } 65 | 66 | //endregion 67 | 68 | //region SpotBugs configurations 69 | 70 | spotbugs { 71 | toolVersion.set('4.2.3') 72 | ignoreFailures.set(true) 73 | excludeFilter.set(file('./spotbugs-exclude.xml')) 74 | 75 | effort = 'max' 76 | reportLevel = 'medium' 77 | } 78 | 79 | spotbugsMain { 80 | reports { 81 | xml.enabled = false 82 | html.enabled = true 83 | } 84 | } 85 | 86 | //endregion 87 | 88 | //region JaCoCo configurations 89 | 90 | jacoco { 91 | toolVersion = '0.8.6' 92 | } 93 | 94 | jacocoTestReport { 95 | dependsOn 'test' 96 | 97 | reports { 98 | xml.enabled = true 99 | html.enabled = true 100 | } 101 | } 102 | 103 | tasks.withType(Test) { 104 | jacoco.includeNoLocationClasses = true 105 | jacoco.excludes = [ 'jdk.internal.*', ] 106 | } 107 | 108 | //endregion 109 | 110 | sonarqube { 111 | properties { 112 | property 'sonar.host.url', 'https://sonarcloud.io' 113 | property 'sonar.organization', 'ayltai' 114 | property 'sonar.projectKey', 'ayltai_spring-graalvm-native-plugin' 115 | property 'sonar.login', System.getenv('SONAR_TOKEN') 116 | } 117 | } 118 | 119 | //region Gradle plugin publication configuration 120 | 121 | pluginBundle { 122 | website = 'https://github.com/ayltai/spring-graalvm-native-plugin' 123 | vcsUrl = 'https://github.com/ayltai/spring-graalvm-native-plugin.git' 124 | tags = [ 'graal', 'graalvm', 'native', 'image', 'spring', 'boot', 'java', ] 125 | } 126 | 127 | gradlePlugin { 128 | plugins { 129 | nativeImage { 130 | id = 'com.github.ayltai.spring-graalvm-native-plugin' 131 | displayName = 'Spring GraalVM native Gradle plugin' 132 | description = 'Support for building Spring Boot applications as GraalVM native images' 133 | implementationClass = 'com.github.ayltai.gradle.plugin.SpringGraalNativePlugin' 134 | } 135 | } 136 | } 137 | 138 | //endregion 139 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 12 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 38 | 41 | 42 | 43 | 46 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 61 | 64 | 65 | 66 | 69 | 70 | 71 | 74 | 77 | 80 | 81 | 82 | 85 | 88 | 91 | 94 | 97 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 113 | 114 | 115 | 118 | 121 | 124 | 125 | 126 | 129 | 132 | 135 | 136 | 137 | 140 | 143 | 144 | 145 | 148 | 149 | 150 | 153 | 156 | 157 | 158 | 161 | 164 | 167 | 170 | 171 | 172 | 175 | 178 | 179 | 180 | 183 | 186 | 189 | 192 | 193 | 194 | 197 | 200 | 201 | 202 | 205 | 208 | 209 | 210 | 213 | 216 | 217 | 218 | 219 | 222 | 225 | 228 | 231 | 232 | 233 | 236 | 239 | 242 | 245 | 248 | 251 | 252 | 253 | 256 | 259 | 260 | 261 | 262 | 263 | 266 | 269 | 272 | 275 | 278 | 281 | 282 | 283 | 284 | 285 | 288 | 291 | 292 | 293 | 296 | 299 | 300 | 301 | 304 | 307 | 310 | 311 | 312 | 313 | 314 | 317 | 318 | 319 | 322 | 325 | 326 | 327 | 330 | 333 | 334 | 335 | 338 | 339 | 340 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 357 | 358 | 359 | 362 | 363 | 364 | 367 | 368 | 369 | 372 | 373 | 374 | 377 | 378 | 379 | 382 | 383 | 384 | 387 | 388 | 389 | 392 | 393 | 394 | 395 | 396 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayltai/spring-graalvm-native-plugin/266e4c629fa6643cd2009097f71c79bf13a71709/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-7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /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 Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '2.3.1.RELEASE' 4 | id 'io.spring.dependency-management' version '1.0.9.RELEASE' 5 | id 'com.github.ayltai.spring-graalvm-native-plugin' version '1.0.7' 6 | } 7 | 8 | group 'com.example' 9 | version '0.0.1-SNAPSHOT' 10 | 11 | sourceCompatibility = 8 12 | targetCompatibility = 8 13 | 14 | repositories { 15 | jcenter() 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | implementation ('org.springframework.boot:spring-boot-starter-web') { 21 | exclude group : 'org.apache.tomcat.embed', module : 'tomcat-embed-websocket' 22 | } 23 | } 24 | 25 | nativeImage { 26 | mainClassName = 'com.example.tomcat.TomcatApplication' 27 | traceClassInitialization = true 28 | reportExceptionStackTraces = true 29 | removeUnusedAutoConfig = true 30 | removeYamlSupport = true 31 | removeSpelSupport = true 32 | removeJmxSupport = true 33 | maxHeapSize = '4G' 34 | } 35 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayltai/spring-graalvm-native-plugin/266e4c629fa6643cd2009097f71c79bf13a71709/samples/springmvc-tomcat/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/springmvc-tomcat/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/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 Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'springmvc-tomcat' 2 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/src/main/java/com/example/tomcat/Controller.java: -------------------------------------------------------------------------------- 1 | package com.example.tomcat; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class Controller { 8 | @GetMapping("/") 9 | public String foo() { 10 | return "Hello from tomcat"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/springmvc-tomcat/src/main/java/com/example/tomcat/TomcatApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.tomcat; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(proxyBeanMethods = false) 7 | public class TomcatApplication { 8 | public static void main(final String[] args) { 9 | SpringApplication.run(TomcatApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-graalvm-native-plugin' 2 | -------------------------------------------------------------------------------- /spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/Constants.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | public final class Constants { 4 | public static final String SPRING_NATIVE_VERSION = "0.10.1"; 5 | public static final String DEFAULT_TOOL_VERSION = "21.1.0"; 6 | public static final String DEFAULT_JAVA_VERSION = "8"; 7 | public static final String DOWNLOAD_ALWAYS = "always"; 8 | public static final String DOWNLOAD_DEFAULT = "default"; 9 | public static final String DOWNLOAD_SKIP = "skip"; 10 | 11 | private Constants() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/SpringGraalNativeExtension.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | import java.util.List; 4 | import javax.annotation.Nonnull; 5 | import javax.annotation.Nullable; 6 | import javax.inject.Inject; 7 | 8 | import org.gradle.api.model.ObjectFactory; 9 | import org.gradle.api.provider.ListProperty; 10 | import org.gradle.api.provider.Property; 11 | 12 | public class SpringGraalNativeExtension { 13 | private static final String SPRING_NATIVE_MODE = SpringNativeMode.REFLECTION; 14 | 15 | //region Gradle plugin properties 16 | 17 | protected final Property toolVersion; 18 | protected final Property javaVersion; 19 | protected final Property download; 20 | protected final Property traceClassInitialization; 21 | protected final Property traceClassInitializationEnabled; 22 | protected final ListProperty traceClassInitializationFor; 23 | protected final Property removeSaturatedTypeFlows; 24 | protected final Property reportExceptionStackTraces; 25 | protected final Property printAnalysisCallTree; 26 | protected final Property disableToolchainChecking; 27 | protected final Property enableAllSecurityServices; 28 | protected final Property enableHttp; 29 | protected final Property enableHttps; 30 | protected final ListProperty enableUrlProtocols; 31 | protected final Property staticallyLinked; 32 | protected final Property warnMissingSelectorHints; 33 | protected final Property removeUnusedAutoConfig; 34 | protected final Property verbose; 35 | protected final Property removeYamlSupport; 36 | protected final Property removeXmlSupport; 37 | protected final Property removeSpelSupport; 38 | protected final Property removeJmxSupport; 39 | protected final Property verify; 40 | protected final Property springNativeVerbose; 41 | protected final Property springNativeMode; 42 | protected final Property dumpConfig; 43 | protected final Property mainClassName; 44 | protected final Property maxHeapSize; 45 | protected final ListProperty initializeAtBuildTime; 46 | protected final ListProperty extraArgs; 47 | 48 | //endregion 49 | 50 | @Inject 51 | public SpringGraalNativeExtension(@Nonnull final ObjectFactory factory) { 52 | this.toolVersion = factory.property(String.class); 53 | this.javaVersion = factory.property(String.class); 54 | this.download = factory.property(String.class); 55 | this.traceClassInitialization = factory.property(Boolean.class); 56 | this.traceClassInitializationEnabled = factory.property(Boolean.class); 57 | this.traceClassInitializationFor = factory.listProperty(String.class); 58 | this.removeSaturatedTypeFlows = factory.property(Boolean.class); 59 | this.reportExceptionStackTraces = factory.property(Boolean.class); 60 | this.printAnalysisCallTree = factory.property(Boolean.class); 61 | this.disableToolchainChecking = factory.property(Boolean.class); 62 | this.enableAllSecurityServices = factory.property(Boolean.class); 63 | this.enableHttp = factory.property(Boolean.class); 64 | this.enableHttps = factory.property(Boolean.class); 65 | this.enableUrlProtocols = factory.listProperty(String.class); 66 | this.staticallyLinked = factory.property(Boolean.class); 67 | this.verbose = factory.property(Boolean.class); 68 | this.warnMissingSelectorHints = factory.property(Boolean.class); 69 | this.removeUnusedAutoConfig = factory.property(Boolean.class); 70 | this.removeYamlSupport = factory.property(Boolean.class); 71 | this.removeXmlSupport = factory.property(Boolean.class); 72 | this.removeSpelSupport = factory.property(Boolean.class); 73 | this.removeJmxSupport = factory.property(Boolean.class); 74 | this.verify = factory.property(Boolean.class); 75 | this.springNativeVerbose = factory.property(Boolean.class); 76 | this.springNativeMode = factory.property(String.class); 77 | this.dumpConfig = factory.property(String.class); 78 | this.mainClassName = factory.property(String.class); 79 | this.maxHeapSize = factory.property(String.class); 80 | this.initializeAtBuildTime = factory.listProperty(String.class); 81 | this.extraArgs = factory.listProperty(String.class); 82 | } 83 | 84 | //region Properties 85 | 86 | /** 87 | * Returns the version of GraalVM Community Edition to download. Default is {@code 20.2.0}. 88 | * @return The version of GraalVM Community Edition to download. 89 | */ 90 | public String getToolVersion() { 91 | return this.toolVersion.getOrElse(Constants.DEFAULT_TOOL_VERSION); 92 | } 93 | 94 | /** 95 | * Sets the version of GraalVM Community Edition to download. 96 | * @param toolVersion The version of GraalVM Community Edition to download. 97 | */ 98 | public void setToolVersion(@Nonnull final String toolVersion) { 99 | this.toolVersion.set(toolVersion); 100 | } 101 | 102 | /** 103 | * Returns the version of JDK to be downloaded with GraalVM Community Edition. Default is {@code 8}. 104 | * @return The version of JDK to be downloaded with GraalVM Community Edition. 105 | */ 106 | public String getJavaVersion() { 107 | return this.javaVersion.getOrElse(Constants.DEFAULT_JAVA_VERSION); 108 | } 109 | 110 | /** 111 | * Sets the version of JDK to be downloaded with GraalVM Community Edition. 112 | * @param javaVersion The version of JDK to be downloaded with GraalVM Community Edition. 113 | */ 114 | public void setJavaVersion(@Nonnull final String javaVersion) { 115 | this.javaVersion.set(javaVersion); 116 | } 117 | 118 | /** 119 | * Returns when to download GraalVM Community Edition. Supports `default` (to download and cache a standalone version of GraalVM tools), `always` (to always download and overwrite any existing standalone version of GraalVM tools), and `skip` (not to download GraalVM tools assuming that GraalVM is already installed on the system). 120 | * @return A string that represents when to download GraalVM Community Edition. 121 | */ 122 | public String getDownload() { 123 | return this.download.getOrElse(Constants.DOWNLOAD_DEFAULT); 124 | } 125 | 126 | /** 127 | * Specifies when to download GraalVM Community Edition. Supports `default` (to download and cache a standalone version of GraalVM tools), `always` (to always download and overwrite any existing standalone version of GraalVM tools), and `skip` (not to download GraalVM tools assuming that GraalVM is already installed on the system). 128 | * @param download A string that represents when to download GraalVM Community Edition. 129 | */ 130 | public void setDownload(@Nonnull final String download) { 131 | this.download.set(download); 132 | } 133 | 134 | /** 135 | * Returns {@code true} if useful information to debug class initialization issues is provided. 136 | * @return {@code true} if useful information to debug class initialization issues is provided. 137 | * @deprecated Use {@link #getTraceClassInitializationEnabled()} instead. 138 | */ 139 | @Deprecated 140 | public boolean getTraceClassInitialization() { 141 | return this.traceClassInitialization.getOrElse(false); 142 | } 143 | 144 | /** 145 | * Sets to {@code true} if useful information to debug class initialization issues is provided. 146 | * @param traceClassInitialization {@code true} if useful information to debug class initialization issues is provided. 147 | * @deprecated Use {@link #setTraceClassInitializationEnabled(boolean)} instead. 148 | */ 149 | @Deprecated 150 | public void setTraceClassInitialization(final boolean traceClassInitialization) { 151 | this.traceClassInitialization.set(traceClassInitialization); 152 | } 153 | 154 | /** 155 | * Returns {@code true} if useful information to debug class initialization issues is provided. 156 | *

This is only valid for GraalVM 20.2.0 or below. For GraalVM 20.3.0 or above, use {@link #getTraceClassInitializationFor()}.

157 | * @return {@code true} if useful information to debug class initialization issues is provided. 158 | */ 159 | public boolean getTraceClassInitializationEnabled() { 160 | return this.traceClassInitializationEnabled.getOrElse(false); 161 | } 162 | 163 | /** 164 | * Sets to {@code true} if useful information to debug class initialization issues is provided. 165 | *

This is only valid for GraalVM 20.2.0 or below. For GraalVM 20.3.0 or above, use {@link #setTraceClassInitializationFor(List)}.

166 | * @param traceClassInitializationEnabled {@code true} if useful information to debug class initialization issues is provided. 167 | */ 168 | public void setTraceClassInitializationEnabled(final boolean traceClassInitializationEnabled) { 169 | this.traceClassInitializationEnabled.set(traceClassInitializationEnabled); 170 | } 171 | 172 | /** 173 | * Returns a comma-separated list of fully qualified class names that class initialization is traced for. 174 | *

This is only valid for GraalVM 20.3.0 or above. For GraalVM 20.2.0 or below, use {@link #getTraceClassInitializationEnabled()}.

175 | * @return A comma-separated list of fully qualified class names that class initialization is traced for. 176 | */ 177 | @Nullable 178 | public List getTraceClassInitializationFor() { 179 | return this.traceClassInitializationFor.getOrNull(); 180 | } 181 | 182 | /** 183 | * Sets a comma-separated list of fully qualified class names that class initialization is traced for. 184 | *

This is valid only for GraalVM 20.3.0 or above. For GraalVM 20.2.0 or below, use {@link #setTraceClassInitializationEnabled(boolean)}.

185 | * @param traceClassInitializationFor A comma-separated list of fully qualified class names that class initialization is traced for. 186 | */ 187 | public void setTraceClassInitializationFor(@Nullable final List traceClassInitializationFor) { 188 | this.traceClassInitializationFor.set(traceClassInitializationFor); 189 | } 190 | 191 | /** 192 | * Returns {@code true} if build time is reduced and build memory consumption is decreased, especially for big projects. 193 | * @return {@code true} if build time is reduced and build memory consumption is decreased, especially for big projects. 194 | */ 195 | public boolean getRemoveSaturatedTypeFlows() { 196 | return this.removeSaturatedTypeFlows.getOrElse(false); 197 | } 198 | 199 | /** 200 | * Sets to {@code true} if build time is reduced and build memory consumption is decreased, especially for big projects. 201 | * @param removeSaturatedTypeFlows {@code true} if build time is reduced and build memory consumption is decreased, especially for big projects. 202 | */ 203 | public void setRemoveSaturatedTypeFlows(final boolean removeSaturatedTypeFlows) { 204 | this.removeSaturatedTypeFlows.set(removeSaturatedTypeFlows); 205 | } 206 | 207 | /** 208 | * Returns {@code true} if more details are provided should something goes wrong. 209 | * @return {@code true} if more details are provided should something goes wrong. 210 | */ 211 | public boolean getReportExceptionStackTraces() { 212 | return this.reportExceptionStackTraces.getOrElse(false); 213 | } 214 | 215 | /** 216 | * Sets to {@code true} if more details are provided should something goes wrong. 217 | * @param reportExceptionStackTraces {@code true} if more details are provided should something goes wrong. 218 | */ 219 | public void setReportExceptionStackTraces(final boolean reportExceptionStackTraces) { 220 | this.reportExceptionStackTraces.set(reportExceptionStackTraces); 221 | } 222 | 223 | /** 224 | * Returns {@code true} to help to find what classes, methods, and fields are used and why. 225 | * @return {@code true} to help to find what classes, methods, and fields are used and why. 226 | */ 227 | public boolean getPrintAnalysisCallTree() { 228 | return this.printAnalysisCallTree.getOrElse(false); 229 | } 230 | 231 | /** 232 | * Sets to {@code true} to help to find what classes, methods, and fields are used and why. 233 | * @param printAnalysisCallTree {@code true} to help to find what classes, methods, and fields are used and why. 234 | */ 235 | public void setPrintAnalysisCallTree(final boolean printAnalysisCallTree) { 236 | this.printAnalysisCallTree.set(printAnalysisCallTree); 237 | } 238 | 239 | /** 240 | * Returns {@code true} to prevent native-toolchain checking. 241 | * @return {@code true} to prevent native-toolchain checking. 242 | */ 243 | public boolean getDisableToolchainChecking() { 244 | return this.disableToolchainChecking.getOrElse(false); 245 | } 246 | 247 | /** 248 | * Sets to {@code true} to prevent native-toolchain checking. 249 | * @param disableToolchainChecking {@code true} to prevent native-toolchain checking. 250 | */ 251 | public void setDisableToolchainChecking(final boolean disableToolchainChecking) { 252 | this.disableToolchainChecking.set(disableToolchainChecking); 253 | } 254 | 255 | /** 256 | * Returns {@code true} if security services are enabled for HTTPS and crypto applications. 257 | * @return {@code true} if security services are enabled for HTTPS and crypto applications. 258 | */ 259 | public boolean getEnableAllSecurityServices() { 260 | return this.enableAllSecurityServices.getOrElse(false); 261 | } 262 | 263 | /** 264 | * Sets to {@code true} if security services are enabled for HTTPS and crypto applications. 265 | * @param enableAllSecurityServices {@code true} if security services are enabled for HTTPS and crypto applications. 266 | */ 267 | public void setEnableAllSecurityServices(final boolean enableAllSecurityServices) { 268 | this.enableAllSecurityServices.set(enableAllSecurityServices); 269 | } 270 | 271 | /** 272 | * Returns {@code true} if HTTP support is enabled. 273 | * @return {@code true} if HTTP support is enabled. 274 | */ 275 | public boolean getEnableHttp() { 276 | return this.enableHttp.getOrElse(false); 277 | } 278 | 279 | /** 280 | * Sets to {@code true} if HTTP support is enabled. 281 | * @param enableHttp {@code true} if HTTP support is enabled. 282 | */ 283 | public void setEnableHttp(final boolean enableHttp) { 284 | this.enableHttp.set(enableHttp); 285 | } 286 | 287 | /** 288 | * Returns {@code true} if HTTPS support is enabled. 289 | * @return {@code true} if HTTPS support is enabled. 290 | */ 291 | public boolean getEnableHttps() { 292 | return this.enableHttps.getOrElse(false); 293 | } 294 | 295 | /** 296 | * Sets to {@code true} if HTTPS support is enabled. 297 | * @param enableHttps {@code true} if HTTPS support is enabled. 298 | */ 299 | public void setEnableHttps(final boolean enableHttps) { 300 | this.enableHttps.set(enableHttps); 301 | } 302 | 303 | /** 304 | * Returns a list of URL protocols to be enabled. 305 | * @return A list of URL protocols to be enabled. 306 | */ 307 | @Nullable 308 | public List getEnableUrlProtocols() { 309 | return this.enableUrlProtocols.getOrNull(); 310 | } 311 | 312 | /** 313 | * Specifies a list of URL protocols to be enabled. 314 | * @param enableUrlProtocols A list of URL protocols to be enabled. 315 | */ 316 | public void setEnableUrlProtocols(@Nullable final List enableUrlProtocols) { 317 | this.enableUrlProtocols.set(enableUrlProtocols); 318 | } 319 | 320 | /** 321 | * Returns {@code true} if a statically linked executable is to be built which is useful to deploy on a {@code FROM scratch} Docker image. 322 | * @return {@code true} if a statically linked executable is to be built. 323 | */ 324 | public boolean getStaticallyLinked() { 325 | return this.staticallyLinked.getOrElse(false); 326 | } 327 | 328 | /** 329 | * Sets to {@code true} if a statically linked executable is to be built. 330 | * @param staticallyLinked {@code true} if a statically linked executable is to be built. 331 | */ 332 | public void setStaticallyLinked(final boolean staticallyLinked) { 333 | this.staticallyLinked.set(staticallyLinked); 334 | } 335 | 336 | /** 337 | * Returns {@code true} if image building output is more verbose. 338 | * @return {@code true} if image building output is more verbose. 339 | */ 340 | public boolean getVerbose() { 341 | return this.verbose.getOrElse(false); 342 | } 343 | 344 | /** 345 | * Sets to {@code true} if image building output is more verbose. 346 | * @param verbose {@code true} if image building output is more verbose. 347 | */ 348 | public void setVerbose(final boolean verbose) { 349 | this.verbose.set(verbose); 350 | } 351 | 352 | /** 353 | * Returns {@code true} if a hard error for missing hints is switched to a warning. 354 | * @return {@code true} if a hard error for missing hints is switched to a warning. 355 | * @see Troubleshooting 356 | */ 357 | public boolean getWarnMissingSelectorHints() { 358 | return this.warnMissingSelectorHints.getOrElse(false); 359 | } 360 | 361 | /** 362 | * Sets to {@code true} if a hard error for missing hints is switched to a warning. 363 | * @param warnMissingSelectorHints {@code true} if a hard error for missing hints is switched to a warning.. 364 | */ 365 | public void setWarnMissingSelectorHints(final boolean warnMissingSelectorHints) { 366 | this.warnMissingSelectorHints.set(warnMissingSelectorHints); 367 | } 368 | 369 | /** 370 | * Returns {@code true} if the removal of unused configurations is disabled. 371 | * @return {@code true} if the removal of unused configurations is disabled. 372 | */ 373 | public boolean getRemoveUnusedAutoConfig() { 374 | return this.removeUnusedAutoConfig.getOrElse(false); 375 | } 376 | 377 | /** 378 | * Sets to {@code true} if the removal of unused configurations is disabled. 379 | * @param removeUnusedAutoConfig {@code true} if the removal of unused configurations is disabled. 380 | */ 381 | public void setRemoveUnusedAutoConfig(final boolean removeUnusedAutoConfig) { 382 | this.removeUnusedAutoConfig.set(removeUnusedAutoConfig); 383 | } 384 | 385 | /** 386 | * Returns {@code true} if Yaml support is removed from Spring Boot, enabling faster compilation and smaller executables. 387 | * @return {@code true} if Yaml support is removed from Spring Boot. 388 | */ 389 | public boolean getRemoveYamlSupport() { 390 | return this.removeYamlSupport.getOrElse(false); 391 | } 392 | 393 | /** 394 | * Sets to {@code true} if Yaml support is removed from Spring Boot. 395 | * @param removeYamlSupport {@code true} if Yaml support is removed from Spring Boot. 396 | */ 397 | public void setRemoveYamlSupport(final boolean removeYamlSupport) { 398 | this.removeYamlSupport.set(removeYamlSupport); 399 | } 400 | 401 | /** 402 | * Returns {@code true} if XML support is removed from Spring Boot, enabling faster compilation and smaller executables. 403 | * @return {@code true} if XML support is removed from Spring Boot. 404 | */ 405 | public boolean getRemoveXmlSupport() { 406 | return this.removeXmlSupport.getOrElse(false); 407 | } 408 | 409 | /** 410 | * Sets to {@code true} if XML support is removed from Spring Boot. 411 | * @param removeXmlSupport {@code true} if XML support is removed from Spring Boot. 412 | */ 413 | public void setRemoveXmlSupport(final boolean removeXmlSupport) { 414 | this.removeXmlSupport.set(removeXmlSupport); 415 | } 416 | 417 | /** 418 | * Returns {@code true} if SpEL support is removed from Spring Boot, enabling faster compilation and smaller executables. 419 | * @return {@code true} if SpEL support is removed from Spring Boot. 420 | */ 421 | public boolean getRemoveSpelSupport() { 422 | return this.removeSpelSupport.getOrElse(false); 423 | } 424 | 425 | /** 426 | * Sets to {@code true} if SpEL support is removed from Spring Boot. 427 | * @param removeSpelSupport {@code true} if SpEL support is removed from Spring Boot. 428 | */ 429 | public void setRemoveSpelSupport(final boolean removeSpelSupport) { 430 | this.removeSpelSupport.set(removeSpelSupport); 431 | } 432 | 433 | /** 434 | * Returns {@code true} if JMX support is removed from Spring Boot, enabling faster compilation and smaller executables. 435 | * @return {@code true} if JMX support is removed from Spring Boot. 436 | */ 437 | public boolean getRemoveJmxSupport() { 438 | return this.removeJmxSupport.getOrElse(false); 439 | } 440 | 441 | /** 442 | * Sets to {@code true} if JMX support is removed from Spring Boot. 443 | * @param removeJmxSupport {@code true} if JMX support is removed from Spring Boot. 444 | */ 445 | public void setRemoveJmxSupport(final boolean removeJmxSupport) { 446 | this.removeJmxSupport.set(removeJmxSupport); 447 | } 448 | 449 | /** 450 | * Returns {@code true} if the verifier mode is switched on. 451 | * @return {@code true} if the verifier mode is switched on. 452 | * @see Troubleshooting 453 | */ 454 | public boolean getVerify() { 455 | return this.verify.getOrElse(false); 456 | } 457 | 458 | /** 459 | * Sets to {@code true} if the verifier mode is switched on. 460 | * @param verify {@code true} if the verifier mode is switched on. 461 | */ 462 | public void setVerify(final boolean verify) { 463 | this.verify.set(verify); 464 | } 465 | 466 | /** 467 | * Returns {@code true} if a lot of information about the feature behavior outputs is enabled as it processes auto-configuration and chooses which to include. 468 | * @return {@code true} if a lot of information about the feature behavior outputs is enabled as it processes auto-configuration and chooses which to include. 469 | */ 470 | public boolean getSpringNativeVerbose() { 471 | return this.springNativeVerbose.getOrElse(false); 472 | } 473 | 474 | /** 475 | * Sets to {@code true} if a lot of information about the feature behavior outputs is enabled as it processes auto-configuration and chooses which to include. 476 | * @param springNativeVerbose {@code true} if a lot of information about the feature behavior outputs is enabled as it processes auto-configuration and chooses which to include. 477 | */ 478 | public void setSpringNativeVerbose(final boolean springNativeVerbose) { 479 | this.springNativeVerbose.set(springNativeVerbose); 480 | } 481 | 482 | /** 483 | * Returns the mode that switches how much configuration the feature actually provides to native-image. 484 | * @return The mode that switches how much configuration the feature actually provides to native-image. 485 | * @see SpringNativeMode#AGENT 486 | * @see SpringNativeMode#INIT 487 | * @see SpringNativeMode#REFLECTION 488 | * @see SpringNativeMode#FUNCTIONAL 489 | */ 490 | @Nonnull 491 | public String getSpringNativeMode() { 492 | return this.springNativeMode.getOrElse(SpringGraalNativeExtension.SPRING_NATIVE_MODE); 493 | } 494 | 495 | /** 496 | * Sets the mode that switches how much configuration the feature actually provides to native-image. 497 | * @param springNativeMode The mode that switches how much configuration the feature actually provides to native-image. 498 | */ 499 | public void setSpringNativeMode(@Nonnull final String springNativeMode) { 500 | this.springNativeMode.set(springNativeMode); 501 | } 502 | 503 | /** 504 | * Returns the file path to dump the configuration to. 505 | * @return The file path to dump the configuration to. 506 | */ 507 | @Nullable 508 | public String getDumpConfig() { 509 | return this.dumpConfig.getOrNull(); 510 | } 511 | 512 | /** 513 | * Sets the file path to dump the configuration to. 514 | * @param dumpConfig The file path to dump the configuration to. 515 | */ 516 | public void setDumpConfig(@Nullable final String dumpConfig) { 517 | this.dumpConfig.set(dumpConfig); 518 | } 519 | 520 | /** 521 | * Returns the fully qualified name of the Java class that contains the {@code main()} method as the entry point of the executable. 522 | * @return The fully qualified name of the Java class that contains the {@code main()} method as the entry point of the executable. 523 | */ 524 | @Nullable 525 | public String getMainClassName() { 526 | return this.mainClassName.getOrNull(); 527 | } 528 | 529 | /** 530 | * Sets the fully qualified name of the Java class that contains the {@code main()} method as the entry point of the executable. 531 | * @param mainClassName The fully qualified name of the Java class that contains the {@code main()} method as the entry point of the executable. 532 | */ 533 | public void setMainClassName(@Nullable final String mainClassName) { 534 | this.mainClassName.set(mainClassName); 535 | } 536 | 537 | /** 538 | * Returns the maximum Java heap size allowed for building the native image. 539 | * @return The maximum Java heap size allowed for building the native image. 540 | */ 541 | @Nullable 542 | public String getMaxHeapSize() { 543 | return this.maxHeapSize.getOrNull(); 544 | } 545 | 546 | /** 547 | * Sets the maximum Java heap size allowed for building the native image. 548 | * @param maxHeapSize The maximum Java heap size allowed for building the native image. 549 | */ 550 | public void setMaxHeapSize(@Nullable final String maxHeapSize) { 551 | this.maxHeapSize.set(maxHeapSize); 552 | } 553 | 554 | /** 555 | * Returns the classes to be initialized by default at build time. 556 | * @return The classes to be initialized by default at build time. 557 | */ 558 | @Nullable 559 | public List getInitializeAtBuildTime() { 560 | return this.initializeAtBuildTime.getOrNull(); 561 | } 562 | 563 | /** 564 | * Sets the classes to be initialized by default at build time. 565 | * @param initializeAtBuildTime The classes to be initialized by default at build time. 566 | */ 567 | public void setInitializeAtBuildTime(@Nullable final List initializeAtBuildTime) { 568 | this.initializeAtBuildTime.set(initializeAtBuildTime); 569 | } 570 | 571 | /** 572 | * Returns additional arguments for native-image 573 | * @return additional arguments for native-image 574 | */ 575 | @Nullable 576 | public List getExtraArgs() { 577 | return this.extraArgs.getOrNull(); 578 | } 579 | 580 | /** 581 | * Sets additional arguments for native-image 582 | * @param extraArgs additional arguments for native-image 583 | */ 584 | public void setExtraArgs(@Nullable final List extraArgs) { 585 | this.extraArgs.set(extraArgs); 586 | } 587 | 588 | //endregion 589 | } 590 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/SpringGraalNativePlugin.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | import org.gradle.api.Plugin; 6 | import org.gradle.api.Project; 7 | import org.gradle.api.Task; 8 | 9 | public class SpringGraalNativePlugin implements Plugin { 10 | //region Constants 11 | 12 | static final String TASK_NAME = "buildNativeImage"; 13 | static final String DEPENDENT_TASK = "bootJar"; 14 | 15 | private static final String DEPENDENT_REPO = "https://repo.spring.io/release"; 16 | private static final String DEPENDENT_ARTIFACT = "org.springframework.experimental:spring-native:" + Constants.SPRING_NATIVE_VERSION; 17 | 18 | //endregion 19 | 20 | @Override 21 | public void apply(@Nonnull final Project project) { 22 | project.getRepositories() 23 | .maven(repo -> repo.setUrl(SpringGraalNativePlugin.DEPENDENT_REPO)); 24 | 25 | project.getConfigurations() 26 | .maybeCreate("implementation") 27 | .getDependencies() 28 | .add(project.getDependencies() 29 | .create(SpringGraalNativePlugin.DEPENDENT_ARTIFACT)); 30 | 31 | final SpringGraalNativeExtension extension = project.getExtensions().create("nativeImage", SpringGraalNativeExtension.class, project.getObjects()); 32 | 33 | project.getTasks() 34 | .register(SpringGraalNativePlugin.TASK_NAME, SpringGraalNativeTask.class, project.getObjects()) 35 | .configure(task -> { 36 | task.dependsOn(SpringGraalNativePlugin.getDependency(project)); 37 | 38 | task.toolVersion.set(extension.getToolVersion()); 39 | task.javaVersion.set(extension.getJavaVersion()); 40 | task.download.set(extension.getDownload()); 41 | task.traceClassInitialization.set(extension.getTraceClassInitialization()); 42 | task.traceClassInitializationEnabled.set(extension.getTraceClassInitializationEnabled()); 43 | task.traceClassInitializationFor.set(extension.getTraceClassInitializationFor()); 44 | task.removeSaturatedTypeFlows.set(extension.getRemoveSaturatedTypeFlows()); 45 | task.reportExceptionStackTraces.set(extension.getReportExceptionStackTraces()); 46 | task.printAnalysisCallTree.set(extension.getPrintAnalysisCallTree()); 47 | task.disableToolchainChecking.set(extension.getDisableToolchainChecking()); 48 | task.enableAllSecurityServices.set(extension.getEnableAllSecurityServices()); 49 | task.enableHttp.set(extension.getEnableHttp()); 50 | task.enableHttps.set(extension.getEnableHttps()); 51 | task.enableUrlProtocols.set(extension.getEnableUrlProtocols()); 52 | task.staticallyLinked.set(extension.getStaticallyLinked()); 53 | task.verbose.set(extension.getVerbose()); 54 | task.warnMissingSelectorHints.set(extension.getWarnMissingSelectorHints()); 55 | task.removeUnusedAutoConfig.set(extension.getRemoveUnusedAutoConfig()); 56 | task.removeYamlSupport.set(extension.getRemoveYamlSupport()); 57 | task.removeXmlSupport.set(extension.getRemoveXmlSupport()); 58 | task.removeSpelSupport.set(extension.getRemoveSpelSupport()); 59 | task.removeJmxSupport.set(extension.getRemoveJmxSupport()); 60 | task.verify.set(extension.getVerify()); 61 | task.springNativeVerbose.set(extension.getSpringNativeVerbose()); 62 | task.springNativeMode.set(extension.getSpringNativeMode()); 63 | task.dumpConfig.set(extension.getDumpConfig()); 64 | task.mainClassName.set(extension.getMainClassName()); 65 | task.maxHeapSize.set(extension.getMaxHeapSize()); 66 | task.initializeAtBuildTime.set(extension.getInitializeAtBuildTime()); 67 | task.extraArgs.set(extension.getExtraArgs()); 68 | }); 69 | } 70 | 71 | @Nonnull 72 | protected static Task getDependency(@Nonnull final Project project) { 73 | return project.getTasks().getByName(SpringGraalNativePlugin.DEPENDENT_TASK); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/SpringGraalNativeTask.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | import javax.annotation.Nonnull; 14 | import javax.inject.Inject; 15 | 16 | import com.github.ayltai.gradle.plugin.internal.ArchiveUtils; 17 | import com.github.ayltai.gradle.plugin.internal.DownloadUtils; 18 | import com.github.ayltai.gradle.plugin.internal.PlatformUtils; 19 | import com.github.ayltai.gradle.plugin.internal.VersionNumberComparator; 20 | 21 | import org.gradle.api.InvalidUserDataException; 22 | import org.gradle.api.logging.LogLevel; 23 | import org.gradle.api.logging.Logger; 24 | import org.gradle.api.model.ObjectFactory; 25 | import org.gradle.api.provider.ListProperty; 26 | import org.gradle.api.provider.Property; 27 | import org.gradle.api.resources.ResourceException; 28 | import org.gradle.api.tasks.Exec; 29 | import org.gradle.api.tasks.Internal; 30 | import org.gradle.api.tasks.TaskAction; 31 | import org.gradle.api.tasks.bundling.Jar; 32 | import org.gradle.internal.os.OperatingSystem; 33 | 34 | import org.slf4j.LoggerFactory; 35 | 36 | public class SpringGraalNativeTask extends Exec { 37 | private static final Logger LOGGER = (Logger)LoggerFactory.getLogger(SpringGraalNativeTask.class); 38 | 39 | //region Constants 40 | 41 | protected static final String DIR_OUTPUT = "native"; 42 | 43 | private static final String DIR_BOOT_INF = "BOOT-INF"; 44 | private static final String DIR_META_INF = "META-INF"; 45 | private static final String FILE_MANIFEST = "MANIFEST.MF"; 46 | 47 | private static final String DOWNLOAD_URL = "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-%1$s/graalvm-ce-java%2$s-%3$s-%4$s-%1$s.%5$s"; 48 | 49 | //endregion 50 | 51 | //region Properties 52 | 53 | protected final Property toolVersion; 54 | protected final Property javaVersion; 55 | protected final Property download; 56 | protected final Property traceClassInitialization; 57 | protected final Property traceClassInitializationEnabled; 58 | protected final ListProperty traceClassInitializationFor; 59 | protected final Property removeSaturatedTypeFlows; 60 | protected final Property reportExceptionStackTraces; 61 | protected final Property printAnalysisCallTree; 62 | protected final Property disableToolchainChecking; 63 | protected final Property enableAllSecurityServices; 64 | protected final Property enableHttp; 65 | protected final Property enableHttps; 66 | protected final ListProperty enableUrlProtocols; 67 | protected final Property staticallyLinked; 68 | protected final Property verbose; 69 | protected final Property warnMissingSelectorHints; 70 | protected final Property removeUnusedAutoConfig; 71 | protected final Property removeYamlSupport; 72 | protected final Property removeXmlSupport; 73 | protected final Property removeSpelSupport; 74 | protected final Property removeJmxSupport; 75 | protected final Property verify; 76 | protected final Property springNativeVerbose; 77 | protected final Property springNativeMode; 78 | protected final Property dumpConfig; 79 | protected final Property mainClassName; 80 | protected final Property maxHeapSize; 81 | protected final ListProperty initializeAtBuildTime; 82 | protected final ListProperty extraArgs; 83 | 84 | //endregion 85 | 86 | @Inject 87 | public SpringGraalNativeTask(@Nonnull final ObjectFactory factory) { 88 | this.toolVersion = factory.property(String.class); 89 | this.javaVersion = factory.property(String.class); 90 | this.download = factory.property(String.class); 91 | this.traceClassInitialization = factory.property(Boolean.class); 92 | this.traceClassInitializationEnabled = factory.property(Boolean.class); 93 | this.traceClassInitializationFor = factory.listProperty(String.class); 94 | this.removeSaturatedTypeFlows = factory.property(Boolean.class); 95 | this.reportExceptionStackTraces = factory.property(Boolean.class); 96 | this.printAnalysisCallTree = factory.property(Boolean.class); 97 | this.disableToolchainChecking = factory.property(Boolean.class); 98 | this.enableAllSecurityServices = factory.property(Boolean.class); 99 | this.enableHttp = factory.property(Boolean.class); 100 | this.enableHttps = factory.property(Boolean.class); 101 | this.enableUrlProtocols = factory.listProperty(String.class); 102 | this.staticallyLinked = factory.property(Boolean.class); 103 | this.verbose = factory.property(Boolean.class); 104 | this.warnMissingSelectorHints = factory.property(Boolean.class); 105 | this.removeUnusedAutoConfig = factory.property(Boolean.class); 106 | this.removeYamlSupport = factory.property(Boolean.class); 107 | this.removeXmlSupport = factory.property(Boolean.class); 108 | this.removeSpelSupport = factory.property(Boolean.class); 109 | this.removeJmxSupport = factory.property(Boolean.class); 110 | this.verify = factory.property(Boolean.class); 111 | this.springNativeVerbose = factory.property(Boolean.class); 112 | this.springNativeMode = factory.property(String.class); 113 | this.dumpConfig = factory.property(String.class); 114 | this.mainClassName = factory.property(String.class); 115 | this.maxHeapSize = factory.property(String.class); 116 | this.initializeAtBuildTime = factory.listProperty(String.class); 117 | this.extraArgs = factory.listProperty(String.class); 118 | 119 | this.setGroup("build"); 120 | this.setDescription("Builds a native image for Spring Boot applications using GraalVM tools"); 121 | } 122 | 123 | @Nonnull 124 | @Internal 125 | protected String getDownloadUrl() { 126 | final String platform = PlatformUtils.getPlatform(); 127 | return String.format(SpringGraalNativeTask.DOWNLOAD_URL, this.toolVersion.getOrElse(Constants.DEFAULT_TOOL_VERSION), this.javaVersion.getOrElse(Constants.DEFAULT_JAVA_VERSION), platform, PlatformUtils.getArchitecture(), "windows".equals(platform) ? "zip" : "tar.gz"); 128 | } 129 | 130 | @Nonnull 131 | @Internal 132 | protected File getToolsDir() { 133 | return Paths.get(this.getProject().getBuildDir().getAbsolutePath(), "tmp", SpringGraalNativePlugin.TASK_NAME, DownloadUtils.getOutputPath(this.toolVersion.getOrElse(Constants.DEFAULT_TOOL_VERSION), this.javaVersion.getOrElse(Constants.DEFAULT_JAVA_VERSION))).toFile(); 134 | } 135 | 136 | @Nonnull 137 | protected String getClassPath(@Nonnull final String classesPath, @Nonnull final File outputDir) { 138 | final File[] files = Paths.get(outputDir.getAbsolutePath(), SpringGraalNativeTask.DIR_BOOT_INF, "lib").toFile().listFiles(); 139 | return files == null ? classesPath : classesPath + (OperatingSystem.current().isWindows() ? ";" : ":") + Stream.of(files) 140 | .map(File::getAbsolutePath) 141 | .collect(Collectors.joining(OperatingSystem.current().isWindows() ? ";" : ":")); 142 | } 143 | 144 | @Nonnull 145 | protected Iterable getCommandLineArgs(@Nonnull final String classPath) { 146 | final List args = new ArrayList<>(); 147 | 148 | if (Constants.DOWNLOAD_SKIP.equals(this.download.getOrElse(Constants.DOWNLOAD_DEFAULT))) { 149 | args.add(PlatformUtils.isWindows() ? "native-image.cmd" : "native-image"); 150 | } else { 151 | args.add(Paths.get(this.getToolsDir().getAbsolutePath(), "bin", PlatformUtils.isWindows() ? "native-image.cmd" : "native-image").toString()); 152 | } 153 | 154 | args.add("--allow-incomplete-classpath"); 155 | args.add("--report-unsupported-elements-at-runtime"); 156 | args.add("--no-fallback"); 157 | args.add("--no-server"); 158 | args.add("--install-exit-handlers"); 159 | 160 | if (VersionNumberComparator.getInstance().compare(this.toolVersion.getOrElse(Constants.DEFAULT_TOOL_VERSION), Constants.DEFAULT_TOOL_VERSION) < 0) { 161 | SpringGraalNativeTask.appendCommandLineArg(args, "-H:+TraceClassInitialization", this.traceClassInitializationEnabled.getOrNull() == null ? this.traceClassInitialization : this.traceClassInitializationEnabled); 162 | } else { 163 | if (this.traceClassInitializationFor.isPresent() && !this.traceClassInitializationFor.get().isEmpty()) args.add("--trace-class-initialization=" + String.join(",", this.traceClassInitializationFor.get())); 164 | } 165 | 166 | SpringGraalNativeTask.appendCommandLineArg(args, "-H:+RemoveSaturatedTypeFlows", this.removeSaturatedTypeFlows); 167 | SpringGraalNativeTask.appendCommandLineArg(args, "-H:+ReportExceptionStackTraces", this.reportExceptionStackTraces); 168 | SpringGraalNativeTask.appendCommandLineArg(args, "-H:+PrintAnalysisCallTree", this.printAnalysisCallTree); 169 | SpringGraalNativeTask.appendCommandLineArg(args, "-H:-CheckToolchain", this.disableToolchainChecking); 170 | SpringGraalNativeTask.appendCommandLineArg(args, "--enable-all-security-services", this.enableAllSecurityServices); 171 | SpringGraalNativeTask.appendCommandLineArg(args, "--enable-http", this.enableHttp); 172 | SpringGraalNativeTask.appendCommandLineArg(args, "--enable-https", this.enableHttps); 173 | SpringGraalNativeTask.appendCommandLineArg(args, "--static", this.staticallyLinked); 174 | SpringGraalNativeTask.appendCommandLineArg(args, "--verbose", this.verbose); 175 | SpringGraalNativeTask.appendCommandLineArg(args, "-Dspring.native.missing-selector-hints=warning", this.warnMissingSelectorHints); 176 | SpringGraalNativeTask.appendCommandLineArg(args, "-Dspring.native.remove-unused-autoconfig=true", this.removeUnusedAutoConfig); 177 | 178 | if (this.enableUrlProtocols.isPresent() && !this.enableUrlProtocols.get().isEmpty()) args.add("--enable-url-protocols=" + String.join(",", this.enableUrlProtocols.get())); 179 | if (this.removeYamlSupport.isPresent()) args.add("-Dspring.native.remove-yaml-support=" + this.removeYamlSupport.get()); 180 | if (this.removeXmlSupport.isPresent()) args.add("-Dspring.xml.ignore=" + this.removeXmlSupport.get()); 181 | if (this.removeSpelSupport.isPresent()) args.add("-Dspring.spel.ignore=" + this.removeSpelSupport.get()); 182 | if (this.removeJmxSupport.isPresent()) args.add("-Dspring.native.remove-jmx-support=" + this.removeJmxSupport.get()); 183 | if (this.verify.isPresent()) args.add("-Dspring.native.verify=" + this.verify.get()); 184 | if (this.springNativeVerbose.isPresent()) args.add("-Dspring.native.verbose=" + this.springNativeVerbose.get()); 185 | if (this.springNativeMode.isPresent()) args.add("-Dspring.native.mode=" + this.springNativeMode.get()); 186 | if (this.maxHeapSize.isPresent() && !this.maxHeapSize.get().isEmpty()) args.add("-J-Xmx" + this.maxHeapSize.get()); 187 | if (this.initializeAtBuildTime.isPresent() && !this.initializeAtBuildTime.get().isEmpty()) args.add("--initialize-at-build-time=" + String.join(",", this.initializeAtBuildTime.get())); 188 | if (this.extraArgs.isPresent() && !this.extraArgs.get().isEmpty()) args.addAll(this.extraArgs.get()); 189 | 190 | args.add("-H:Name=" + this.getProject().getName()); 191 | args.add("-cp"); 192 | args.add(classPath); 193 | args.add(this.mainClassName.get()); 194 | 195 | if (SpringGraalNativeTask.LOGGER.isEnabled(LogLevel.DEBUG)) SpringGraalNativeTask.LOGGER.debug(String.join(" ", args)); 196 | 197 | return args; 198 | } 199 | 200 | protected static void appendCommandLineArg(@Nonnull final List args, @Nonnull final String arg, @Nonnull final Property property) { 201 | if (Boolean.TRUE.equals(property.getOrNull())) args.add(arg); 202 | } 203 | 204 | @TaskAction 205 | @Override 206 | protected void exec() { 207 | if (!this.mainClassName.isPresent()) throw new InvalidUserDataException("mainClassName is null"); 208 | 209 | DownloadUtils.download(this.getDownloadUrl(), Paths.get(this.getProject().getBuildDir().getAbsolutePath(), "tmp", SpringGraalNativePlugin.TASK_NAME).toFile(), this.download.getOrElse(Constants.DOWNLOAD_DEFAULT)); 210 | 211 | final File outputDir = new File(this.getProject().getBuildDir().getAbsolutePath(), SpringGraalNativeTask.DIR_OUTPUT); 212 | 213 | try { 214 | final Path classesPath = Paths.get(outputDir.getAbsolutePath(), SpringGraalNativeTask.DIR_BOOT_INF, "classes"); 215 | 216 | this.deleteOutputDir(outputDir); 217 | this.copyFiles(classesPath, outputDir); 218 | 219 | this.workingDir(outputDir) 220 | .commandLine(this.getCommandLineArgs(this.getClassPath(classesPath.toString(), outputDir))); 221 | 222 | super.exec(); 223 | } catch (final IOException e) { 224 | throw new ResourceException(e.getMessage(), e); 225 | } 226 | } 227 | 228 | protected void deleteOutputDir(@Nonnull final File outputDir) throws IOException { 229 | if (outputDir.exists()) { 230 | SpringGraalNativeTask.LOGGER.info("Clear output directory"); 231 | 232 | try (Stream stream = Files.walk(outputDir.toPath())) { 233 | stream.map(Path::toFile) 234 | .sorted(Comparator.reverseOrder()) 235 | .forEach(file -> { 236 | try { 237 | Files.deleteIfExists(file.toPath()); 238 | } catch (final IOException e) { 239 | throw new ResourceException("Failed to delete directory or file: " + file.getAbsolutePath(), e); 240 | } 241 | }); 242 | } 243 | } else { 244 | SpringGraalNativeTask.LOGGER.info("Skip clearing output directory as it does not exist"); 245 | } 246 | } 247 | 248 | protected void copyFiles(@Nonnull final Path classesPath, @Nonnull final File outputDir) { 249 | try { 250 | ArchiveUtils.decompressJar(((Jar)SpringGraalNativePlugin.getDependency(this.getProject())).getArchiveFile().get().getAsFile(), outputDir); 251 | 252 | SpringGraalNativeTask.LOGGER.info("Copy dependencies to output directory"); 253 | 254 | final File destination = new File(classesPath.toString(), SpringGraalNativeTask.DIR_META_INF); 255 | if (!destination.exists() && !destination.mkdirs()) throw new ResourceException("Failed to create directory: " + destination.getAbsolutePath()); 256 | 257 | Files.copy(Paths.get(outputDir.getAbsolutePath(), SpringGraalNativeTask.DIR_META_INF, SpringGraalNativeTask.FILE_MANIFEST), Paths.get(classesPath.toString(), SpringGraalNativeTask.DIR_META_INF, SpringGraalNativeTask.FILE_MANIFEST)); 258 | } catch (final IOException | IllegalAccessException e) { 259 | throw new ResourceException(e.getMessage(), e); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/SpringNativeMode.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | public final class SpringNativeMode { 4 | /** 5 | * This should be used if only wishing the feature to provide substitutions and initialization configuration. In this mode you should have used the agent to collect the rest of the configuration. 6 | */ 7 | public static final String AGENT = "agent"; 8 | 9 | /** 10 | * Initialization-only configuration provided from the feature. 11 | */ 12 | public static final String INIT = "init"; 13 | 14 | /** 15 | * Default mode, provide everything 16 | */ 17 | public static final String REFLECTION = "reflection"; 18 | 19 | /** 20 | * This should be used when working with functional bean registration (Spring Fu style). In this mode the feature will provide initialization and resource configuration but nothing more. 21 | */ 22 | public static final String FUNCTIONAL = "functional"; 23 | 24 | private SpringNativeMode() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/internal/ArchiveUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.nio.file.FileSystems; 10 | import java.nio.file.Files; 11 | import javax.annotation.Nonnull; 12 | 13 | import org.gradle.api.resources.ResourceException; 14 | 15 | import org.apache.commons.compress.archivers.ArchiveEntry; 16 | import org.apache.commons.compress.archivers.ArchiveInputStream; 17 | import org.apache.commons.compress.archivers.jar.JarArchiveInputStream; 18 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 19 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 20 | import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; 21 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 22 | import org.apache.commons.compress.utils.IOUtils; 23 | 24 | public final class ArchiveUtils { 25 | private ArchiveUtils() { 26 | } 27 | 28 | public static void decompressTarGZip(@Nonnull final File archive, @Nonnull final File outputDir) throws IOException, IllegalAccessException { 29 | try (ArchiveInputStream inputStream = new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(archive)), true))) { 30 | ArchiveUtils.decompress(inputStream, outputDir); 31 | } 32 | } 33 | 34 | public static void decompressZip(@Nonnull final File archive, @Nonnull final File outputDir) throws IOException, IllegalAccessException { 35 | try (ArchiveInputStream inputStream = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(archive)))) { 36 | ArchiveUtils.decompress(inputStream, outputDir); 37 | } 38 | } 39 | 40 | public static void decompressJar(@Nonnull final File archive, @Nonnull final File outputDir) throws IOException, IllegalAccessException { 41 | try (ArchiveInputStream inputStream = new JarArchiveInputStream(new BufferedInputStream(new FileInputStream(archive)))) { 42 | ArchiveUtils.decompress(inputStream, outputDir); 43 | } 44 | } 45 | 46 | public static void decompress(@Nonnull final ArchiveInputStream inputStream, @Nonnull final File outputDir) throws IOException, IllegalAccessException { 47 | ArchiveEntry entry; 48 | while ((entry = inputStream.getNextEntry()) != null) { 49 | final File destination = new File(outputDir, entry.getName()); 50 | if (!destination.getCanonicalPath().startsWith(outputDir.getCanonicalPath() + File.separator)) throw new IllegalAccessException("Archive entry is outside of the output directory: " + entry.getName()); 51 | 52 | if (!entry.isDirectory()) { 53 | final File parent = destination.getParentFile(); 54 | if (!parent.exists() && !parent.mkdirs()) throw new ResourceException("Failed to create output directory: " + parent.getAbsolutePath()); 55 | 56 | if (entry instanceof TarArchiveEntry) { 57 | final TarArchiveEntry tarArchiveEntry = (TarArchiveEntry)entry; 58 | 59 | if (tarArchiveEntry.isSymbolicLink()) { 60 | Files.createSymbolicLink(FileSystems.getDefault().getPath(destination.getAbsolutePath()), FileSystems.getDefault().getPath(tarArchiveEntry.getLinkName())); 61 | 62 | continue; 63 | } 64 | } 65 | 66 | try (OutputStream outputStream = new FileOutputStream(destination)) { 67 | IOUtils.copy(inputStream, outputStream); 68 | 69 | if ("bin".equals(parent.getName()) && !destination.setExecutable(true)) throw new ResourceException("Failed to set executable permission: " + destination.getAbsolutePath()); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/internal/DownloadUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.net.URL; 7 | import java.nio.channels.Channels; 8 | import java.nio.channels.FileChannel; 9 | import java.nio.channels.ReadableByteChannel; 10 | import java.nio.file.Paths; 11 | import javax.annotation.Nonnull; 12 | 13 | import org.gradle.api.resources.ResourceException; 14 | 15 | import com.github.ayltai.gradle.plugin.Constants; 16 | 17 | import org.apache.commons.lang3.StringUtils; 18 | 19 | public final class DownloadUtils { 20 | private static final String OUTPUT_PATH = "graalvm-ce-java%2$s-%1$s"; 21 | 22 | private DownloadUtils() { 23 | } 24 | 25 | @Nonnull 26 | public static String getOutputPath(@Nonnull final String toolVersion, @Nonnull final String javaVersion) { 27 | final String outputPath = String.format(DownloadUtils.OUTPUT_PATH, toolVersion, javaVersion); 28 | return PlatformUtils.isMacOS() ? Paths.get(outputPath, "Contents", "Home").toString() : outputPath; 29 | } 30 | 31 | public static void download(@Nonnull final String downloadUrl, @Nonnull final File outputDir, @Nonnull final String downloadStrategy) { 32 | if (!outputDir.exists() && !outputDir.mkdirs()) throw new ResourceException("Failed to create temporary directory: " + outputDir.getAbsolutePath()); 33 | 34 | final File outputFile = new File(outputDir, downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1)); 35 | if (Constants.DOWNLOAD_DEFAULT.equals(downloadStrategy) && !outputFile.exists() || Constants.DOWNLOAD_ALWAYS.equals(downloadStrategy)) { 36 | try ( 37 | ReadableByteChannel readableByteChannel = Channels.newChannel(new URL(downloadUrl).openStream()); 38 | FileOutputStream outputStream = new FileOutputStream(outputFile); 39 | FileChannel fileChannel = outputStream.getChannel()) { 40 | fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); 41 | 42 | final boolean isWindows = PlatformUtils.isWindows(); 43 | if (isWindows) { 44 | ArchiveUtils.decompressZip(outputFile, outputDir); 45 | } else { 46 | ArchiveUtils.decompressTarGZip(outputFile, outputDir); 47 | } 48 | 49 | final int result = new ProcessBuilder() 50 | .command(Paths.get(outputDir.getAbsolutePath(), DownloadUtils.getOutputPath(StringUtils.substringBetween(downloadUrl, "/vm-", "/"), StringUtils.substringBetween(downloadUrl, "-java", "-")), "bin", "gu").toString(), "install", "native-image") 51 | .start() 52 | .waitFor(); 53 | 54 | if (result != 0) throw new ResourceException("Failed to install GraalVM Native Image. Error code: " + result); 55 | } catch (final IOException | IllegalAccessException | InterruptedException e) { 56 | throw new ResourceException(e.getMessage(), e); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/internal/PlatformUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | public final class PlatformUtils { 6 | private static final String ARCH_AMD64 = "amd64"; 7 | private static final String ARCH_ARM = "arm"; 8 | private static final String WINDOWS = "windows"; 9 | private static final String MACOS = "darwin"; 10 | 11 | private PlatformUtils() { 12 | } 13 | 14 | @Nonnull 15 | public static String getPlatform() { 16 | final String osName = System.getProperty("os.name"); 17 | return osName.startsWith("Windows") ? PlatformUtils.WINDOWS : osName.startsWith("Mac") ? PlatformUtils.MACOS : osName.startsWith("FreeBSD") ? "freebsd" : osName.startsWith("SunOS") ? "solaris" : "linux"; 18 | } 19 | 20 | @Nonnull 21 | public static String getArchitecture() { 22 | final String osArch = System.getProperty("os.arch"); 23 | return "x86".equals(osArch) || "i386".equals(osArch) ? "386" : "x86_64".equals(osArch) || PlatformUtils.ARCH_AMD64.equals(osArch) ? PlatformUtils.ARCH_AMD64 : osArch.startsWith(PlatformUtils.ARCH_ARM) ? PlatformUtils.ARCH_ARM : PlatformUtils.ARCH_AMD64; 24 | } 25 | 26 | public static boolean isWindows() { 27 | return PlatformUtils.WINDOWS.equals(PlatformUtils.getPlatform()); 28 | } 29 | 30 | public static boolean isMacOS() { 31 | return PlatformUtils.MACOS.equals(PlatformUtils.getPlatform()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/ayltai/gradle/plugin/internal/VersionNumberComparator.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import java.util.Comparator; 4 | 5 | /* 6 | * (Copied from https://gist.github.com/adamcbuckley/8ccae8b1ede0a65edb1756ea39bb6f2a) 7 | */ 8 | public final class VersionNumberComparator { 9 | public static VersionNumberComparator.AlphaDecimalComparator getInstance() { 10 | return new VersionNumberComparator.AlphaDecimalComparator<>(Comparator.comparing(CharSequence::toString, Comparator.naturalOrder()), false); 11 | } 12 | 13 | /** 14 | * Compares char sequences, taking into account their numeric part if one exists. 15 | */ 16 | public static class AlphaDecimalComparator implements Comparator { 17 | private final Comparator alphaComparator; 18 | private final Comparator decimalComparator; 19 | 20 | AlphaDecimalComparator(final Comparator alphaComparator, final boolean leadingZeroesFirst) { 21 | this(alphaComparator, DecimalComparator.getInstance(leadingZeroesFirst)); 22 | } 23 | 24 | private AlphaDecimalComparator(final Comparator alphaComparator, final Comparator decimalComparator) { 25 | this.alphaComparator = alphaComparator; 26 | this.decimalComparator = decimalComparator; 27 | } 28 | 29 | @Override 30 | public Comparator reversed() { 31 | return new AlphaDecimalComparator<>(alphaComparator.reversed(), decimalComparator.reversed()); 32 | } 33 | 34 | @Override 35 | public int compare(T cs1, T cs2) { 36 | Decomposer d1 = new Decomposer(cs1); 37 | Decomposer d2 = new Decomposer(cs2); 38 | for (; ; ) { 39 | int cmp; 40 | if ((cmp = alphaComparator.compare(d1.get(), d2.get())) != 0 || (cmp = decimalComparator.compare(d1.get(), d2.get())) != 0) { 41 | return cmp; 42 | } 43 | if (d1.eos() && d2.eos()) return 0; 44 | } 45 | } 46 | 47 | /** 48 | * Given a CharSequence, splits it into a series of subsequences so that 49 | * every character of the very first subsequence (possibly empty) is 50 | * not a decimal digit; then every character of the second subsequence 51 | * is a decimal digit, and so on. 52 | */ 53 | private static class Decomposer { 54 | private final CharSequence sequence; 55 | private boolean expectingDecimal = false; 56 | private int index = 0; 57 | 58 | Decomposer(final CharSequence sequence) { 59 | this.sequence = sequence; 60 | } 61 | 62 | CharSequence get() { 63 | int start = index, end = start, len = sequence.length() - start; 64 | while (len > 0) { 65 | int cp = Character.codePointAt(sequence, end); 66 | int ct = Character.getType(cp); 67 | boolean isDecimal = (ct == Character.DECIMAL_DIGIT_NUMBER); 68 | if (isDecimal ^ expectingDecimal) { 69 | break; 70 | } 71 | int cpWidth = Character.charCount(cp); 72 | end += cpWidth; 73 | len -= cpWidth; 74 | } 75 | expectingDecimal = !expectingDecimal; 76 | return sequence.subSequence(start, index = end); 77 | } 78 | 79 | boolean eos() { 80 | return index >= sequence.length(); 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * The comparator for comparing character sequences that consist solely 87 | * of decimal digits. The result of comparing is as if the values were 88 | * compared numerically. 89 | */ 90 | public static class DecimalComparator implements Comparator { 91 | private static final Comparator 92 | DECIMAL_COMPARATOR_LEADING_ZEROES_FIRST = new DecimalComparator(true) { 93 | @Override 94 | public Comparator reversed() { 95 | return DECIMAL_COMPARATOR_LEADING_ZEROES_FIRST_REVERSED; 96 | } 97 | }; 98 | 99 | private static final Comparator 100 | DECIMAL_COMPARATOR_LEADING_ZEROES_LAST = new DecimalComparator(false) { 101 | @Override 102 | public Comparator reversed() { 103 | return DECIMAL_COMPARATOR_LEADING_ZEROES_LAST_REVERSED; 104 | } 105 | }; 106 | 107 | private static final Comparator 108 | DECIMAL_COMPARATOR_LEADING_ZEROES_FIRST_REVERSED = new DecimalComparator(true) { 109 | @Override 110 | public Comparator reversed() { 111 | return DECIMAL_COMPARATOR_LEADING_ZEROES_FIRST; 112 | } 113 | 114 | @Override 115 | public int compare(final CharSequence cs1, final CharSequence cs2) { 116 | return super.compare(cs2, cs1); 117 | } 118 | }; 119 | 120 | private static final Comparator 121 | DECIMAL_COMPARATOR_LEADING_ZEROES_LAST_REVERSED = new DecimalComparator(false) { 122 | @Override 123 | public Comparator reversed() { 124 | return DECIMAL_COMPARATOR_LEADING_ZEROES_LAST; 125 | } 126 | 127 | @Override 128 | public int compare(final CharSequence cs1, final CharSequence cs2) { 129 | return super.compare(cs2, cs1); 130 | } 131 | }; 132 | 133 | private final boolean leadingZeroesFirst; 134 | 135 | public DecimalComparator(final boolean leadingZeroesFirst) { 136 | this.leadingZeroesFirst = leadingZeroesFirst; 137 | } 138 | 139 | static Comparator getInstance(final boolean leadingZeroesFirst) { 140 | return leadingZeroesFirst ? DECIMAL_COMPARATOR_LEADING_ZEROES_FIRST : DECIMAL_COMPARATOR_LEADING_ZEROES_LAST; 141 | } 142 | 143 | private boolean canSkipLeadingZeroes(final CharSequence s, final int len) { 144 | for (int i = 0; i < len; ) { 145 | int cp = Character.codePointAt(s, i); 146 | if (Character.digit(cp, 10) != 0) return false; 147 | i += Character.charCount(cp); 148 | } 149 | return true; 150 | } 151 | 152 | @Override 153 | public int compare(CharSequence cs1, CharSequence cs2) { 154 | int len1 = Character.codePointCount(cs1, 0, cs1.length()); 155 | int len2 = Character.codePointCount(cs2, 0, cs2.length()); 156 | int dlen = len1 - len2; 157 | if (len1 == 0 || len2 == 0) { 158 | return dlen; 159 | } else if (dlen > 0) { 160 | if (!canSkipLeadingZeroes(cs1, dlen)) return 1; 161 | int off = Character.offsetByCodePoints(cs1, 0, dlen); 162 | cs1 = cs1.subSequence(off, cs1.length()); 163 | } else if (dlen < 0) { 164 | if (!canSkipLeadingZeroes(cs2, -dlen)) return -1; 165 | int off = Character.offsetByCodePoints(cs2, 0, -dlen); 166 | cs2 = cs2.subSequence(off, cs2.length()); 167 | } 168 | int cmp = 0; 169 | for (int i1 = 0, i2 = 0; i1 < cs1.length(); ) { 170 | int cp1 = Character.codePointAt(cs1, i1); 171 | int cp2 = Character.codePointAt(cs2, i2); 172 | if (cp1 != cp2) { 173 | if (cmp == 0) { 174 | cmp = cp1 - cp2; 175 | } 176 | int cmpNum = Character.digit(cp1, 10) - Character.digit(cp2, 10); 177 | if (cmpNum != 0) return cmpNum; 178 | } 179 | i1 += Character.charCount(cp1); 180 | i2 += Character.charCount(cp2); 181 | } 182 | return dlen == 0 ? cmp : (leadingZeroesFirst ^ (dlen < 0) ? -1 : 1); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/test/java/com/github/ayltai/gradle/plugin/SpringGraalNativeTaskTests.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.StreamSupport; 8 | import javax.annotation.Nonnull; 9 | 10 | import org.gradle.api.Task; 11 | import org.gradle.api.internal.provider.MissingValueException; 12 | 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.Test; 15 | 16 | public final class SpringGraalNativeTaskTests extends UnitTests { 17 | //region Constants 18 | 19 | private static final String CLASS_PATH = "test"; 20 | private static final String CLASSES_PATH = "test1:test2"; 21 | private static final String MAIN_CLASS_NAME = "main"; 22 | 23 | //endregion 24 | 25 | @Test 26 | public void testSetUp() { 27 | Assertions.assertTrue(this.getTask() instanceof SpringGraalNativeTask); 28 | } 29 | 30 | @Test 31 | public void testDeleteOutputDir() throws IOException { 32 | final File outputDir = this.getOutputDir(); 33 | Assertions.assertFalse(outputDir.exists()); 34 | 35 | final SpringGraalNativeTask task = this.getTask(); 36 | task.deleteOutputDir(outputDir); 37 | Assertions.assertFalse(outputDir.exists()); 38 | 39 | Assertions.assertTrue(outputDir.mkdirs()); 40 | Assertions.assertTrue(outputDir.exists()); 41 | 42 | task.deleteOutputDir(outputDir); 43 | Assertions.assertFalse(outputDir.exists()); 44 | } 45 | 46 | @Test 47 | public void testGetClassPath() { 48 | final SpringGraalNativeTask task = this.getTask(); 49 | final String classPath = task.getClassPath(SpringGraalNativeTaskTests.CLASSES_PATH, this.getOutputDir()); 50 | 51 | Assertions.assertEquals(SpringGraalNativeTaskTests.CLASSES_PATH, classPath); 52 | } 53 | 54 | @Test 55 | public void given_missingMainClassName_when_getCommandLineArgsIsCalled_then_throwsMissingValueException() { 56 | Assertions.assertThrows(MissingValueException.class, () -> ((SpringGraalNativeTask)this.getTask()).getCommandLineArgs(SpringGraalNativeTaskTests.CLASS_PATH)); 57 | } 58 | 59 | @Test 60 | public void testGetCommandLineArgs() { 61 | final SpringGraalNativeTask task = this.getTask(); 62 | task.extraArgs.add("-H:Xyz=uvw"); 63 | task.extraArgs.add("-H:Def=abc"); 64 | task.download.set(Constants.DOWNLOAD_SKIP); 65 | task.mainClassName.set(SpringGraalNativeTaskTests.MAIN_CLASS_NAME); 66 | 67 | List args = StreamSupport.stream(task.getCommandLineArgs(SpringGraalNativeTaskTests.CLASS_PATH).spliterator(), false).collect(Collectors.toList()); 68 | Assertions.assertEquals("native-image", args.get(0)); 69 | Assertions.assertTrue(args.contains("-H:Name=" + this.project.getName())); 70 | Assertions.assertTrue(args.contains(SpringGraalNativeTaskTests.MAIN_CLASS_NAME)); 71 | Assertions.assertFalse(args.contains("-Dspring.native.remove-yaml-support=true")); 72 | Assertions.assertFalse(args.contains("-Dspring.xml.ignore=true")); 73 | Assertions.assertFalse(args.contains("-Dspring.spel.ignore=true")); 74 | Assertions.assertFalse(args.contains("-Dspring.native.remove-jmx-support=true")); 75 | 76 | task.removeYamlSupport.set(true); 77 | task.removeXmlSupport.set(true); 78 | task.removeSpelSupport.set(true); 79 | task.removeJmxSupport.set(true); 80 | 81 | args = StreamSupport.stream(task.getCommandLineArgs(SpringGraalNativeTaskTests.CLASS_PATH).spliterator(), false).collect(Collectors.toList()); 82 | Assertions.assertTrue(args.contains("-H:Xyz=uvw"),args + " should contain '-H:Xyz=uvw'"); 83 | Assertions.assertTrue(args.contains("-H:Def=abc"), args + " should contain '-H:Def=abc'"); 84 | Assertions.assertTrue(args.contains("-Dspring.native.remove-yaml-support=true"), args + " should contain '-Dspring.native.remove-yaml-support=true'"); 85 | Assertions.assertTrue(args.contains("-Dspring.xml.ignore=true"), args + " should contain '-Dspring.xml.ignore=true'"); 86 | Assertions.assertTrue(args.contains("-Dspring.spel.ignore=true"), args + " should contain '-Dspring.spel.ignore=true'"); 87 | Assertions.assertTrue(args.contains("-Dspring.native.remove-jmx-support=true"), args + " should contain '-Dspring.native.remove-jmx-support=true'"); 88 | } 89 | 90 | @Nonnull 91 | private T getTask() { 92 | return (T)project.getTasks().getByName(SpringGraalNativePlugin.TASK_NAME); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/github/ayltai/gradle/plugin/UnitTests.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import javax.annotation.Nonnull; 6 | 7 | import org.gradle.api.Project; 8 | import org.gradle.api.file.RegularFile; 9 | import org.gradle.api.file.RegularFileProperty; 10 | import org.gradle.api.tasks.bundling.Jar; 11 | import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder; 12 | import org.gradle.testfixtures.ProjectBuilder; 13 | 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.mockito.Mockito; 16 | 17 | public abstract class UnitTests { 18 | private static final String PROJECT_NAME = "sample"; 19 | 20 | protected Project project; 21 | protected File jarFile; 22 | 23 | @BeforeEach 24 | public void setUp() throws IOException { 25 | final TemporaryFolder temporaryFolder = TemporaryFolder.builder().assureDeletion().build(); 26 | temporaryFolder.create(); 27 | 28 | this.project = ProjectBuilder.builder() 29 | .withName(UnitTests.PROJECT_NAME) 30 | .withProjectDir(temporaryFolder.newFolder()) 31 | .build(); 32 | 33 | this.jarFile = new File(this.getClass() 34 | .getClassLoader() 35 | .getResource("spring-boot-0.0.1-SNAPSHOT.jar") 36 | .getFile()); 37 | 38 | final RegularFile regularFile = Mockito.mock(RegularFile.class); 39 | Mockito.doReturn(this.jarFile).when(regularFile).getAsFile(); 40 | 41 | final RegularFileProperty archiveFile = Mockito.mock(RegularFileProperty.class); 42 | Mockito.doReturn(regularFile).when(archiveFile).get(); 43 | 44 | final Jar jar = Mockito.spy(this.project.getTasks().create(SpringGraalNativePlugin.DEPENDENT_TASK, Jar.class)); 45 | Mockito.doReturn(archiveFile).when(jar).getArchiveFile(); 46 | 47 | this.project.getPluginManager().apply("com.github.ayltai.spring-graalvm-native-plugin"); 48 | } 49 | 50 | @Nonnull 51 | protected File getOutputDir() { 52 | return new File(this.project.getBuildDir().getAbsolutePath(), SpringGraalNativeTask.DIR_OUTPUT); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/github/ayltai/gradle/plugin/internal/ArchiveUtilsTests.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import com.github.ayltai.gradle.plugin.UnitTests; 7 | 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | public final class ArchiveUtilsTests extends UnitTests { 12 | @Test 13 | public void testDecompressJar() throws IOException, IllegalAccessException { 14 | Assertions.assertFalse(this.getOutputDir().exists()); 15 | 16 | ArchiveUtils.decompressJar(this.jarFile, this.getOutputDir()); 17 | 18 | Assertions.assertTrue(this.getOutputDir().exists()); 19 | Assertions.assertTrue(new File(new File(this.getOutputDir(), "BOOT-INF"), "classes").exists()); 20 | Assertions.assertTrue(new File(new File(this.getOutputDir(), "BOOT-INF"), "lib").exists()); 21 | Assertions.assertTrue(new File(new File(this.getOutputDir(), "META-INF"), "MANIFEST.MF").exists()); 22 | Assertions.assertTrue(new File(new File(this.getOutputDir(), "org"), "springframework").exists()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/github/ayltai/gradle/plugin/internal/DownloadUtilsTests.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import java.io.File; 4 | import java.nio.file.Paths; 5 | import javax.annotation.Nonnull; 6 | 7 | import com.github.ayltai.gradle.plugin.Constants; 8 | import com.github.ayltai.gradle.plugin.UnitTests; 9 | 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | public final class DownloadUtilsTests extends UnitTests { 14 | private static final String DOWNLOAD_URL = "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-%1$s/graalvm-ce-java%2$s-%3$s-%4$s-%1$s.%5$s"; 15 | 16 | @Test 17 | public void testDownload() { 18 | final String downloadUrl = this.getDownloadUrl(); 19 | final File outputFile = Paths.get(this.getOutputDir().getAbsolutePath(), DownloadUtils.getOutputPath(Constants.DEFAULT_TOOL_VERSION, Constants.DEFAULT_JAVA_VERSION)).toFile(); 20 | 21 | Assertions.assertFalse(outputFile.exists()); 22 | 23 | DownloadUtils.download(downloadUrl, this.getOutputDir(), Constants.DOWNLOAD_ALWAYS); 24 | 25 | Assertions.assertTrue(outputFile.exists()); 26 | } 27 | 28 | @Nonnull 29 | protected String getDownloadUrl() { 30 | final String platform = PlatformUtils.getPlatform(); 31 | return String.format(DownloadUtilsTests.DOWNLOAD_URL, Constants.DEFAULT_TOOL_VERSION, Constants.DEFAULT_JAVA_VERSION, platform, PlatformUtils.getArchitecture(), "windows".equals(platform) ? "zip" : "tar.gz"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/github/ayltai/gradle/plugin/internal/VersionNumberComparatorTests.java: -------------------------------------------------------------------------------- 1 | package com.github.ayltai.gradle.plugin.internal; 2 | 3 | import com.github.ayltai.gradle.plugin.UnitTests; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public final class VersionNumberComparatorTests extends UnitTests { 9 | @Test 10 | public void testCompare() { 11 | Assertions.assertTrue(VersionNumberComparator.getInstance().compare("20.2.0", "20.3.0") < 0); 12 | Assertions.assertEquals(0, VersionNumberComparator.getInstance().compare("20.1.0", "20.1.0")); 13 | Assertions.assertTrue(VersionNumberComparator.getInstance().compare("19.2.0-dev-b01", "19.1.1") > 0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/spring-boot-0.0.1-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayltai/spring-graalvm-native-plugin/266e4c629fa6643cd2009097f71c79bf13a71709/src/test/resources/spring-boot-0.0.1-SNAPSHOT.jar --------------------------------------------------------------------------------