├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── .gitignore ├── encodings.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── github │ └── coffeelibs │ └── maven │ └── jextract │ ├── LoggingOutputStream.java │ └── SourcesMojo.java └── test └── java └── io └── github └── coffeelibs └── maven └── jextract └── LoggingOutputStreamTest.java /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [overheadhunter] 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | [push] 4 | 5 | env: 6 | JAVA_VERSION: 21 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write # Required for the attestations step 14 | contents: write # Required for the release step 15 | attestations: write # Required for the attestations step 16 | packages: write # Required for the deploy to GitHub Packages step 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - uses: actions/setup-java@v4 22 | with: 23 | java-version: ${{ env.JAVA_VERSION }} 24 | distribution: temurin 25 | cache: 'maven' 26 | server-id: central 27 | server-username: MAVEN_CENTRAL_USERNAME 28 | server-password: MAVEN_CENTRAL_PASSWORD 29 | gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} 30 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 31 | 32 | - name: Build and Test 33 | run: mvn -B verify --no-transfer-progress 34 | 35 | - name: Deploy to Maven Central 36 | if: startsWith(github.ref, 'refs/tags/') 37 | run: mvn -B deploy -Psign,deploy-central -DskipTests --no-transfer-progress 38 | env: 39 | MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 40 | MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 41 | MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} 42 | MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} 43 | MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }} 44 | 45 | - uses: actions/setup-java@v4 46 | if: startsWith(github.ref, 'refs/tags/') 47 | with: 48 | java-version: ${{ env.JAVA_VERSION }} 49 | distribution: temurin 50 | cache: 'maven' 51 | 52 | - name: Deploy to GitHub Packages 53 | if: startsWith(github.ref, 'refs/tags/') 54 | run: mvn -B deploy -Psign,deploy-github -DskipTests --no-transfer-progress 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} 58 | MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} 59 | MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }} 60 | 61 | - name: Attest 62 | if: startsWith(github.ref, 'refs/tags/') 63 | uses: actions/attest-build-provenance@v2 64 | with: 65 | subject-path: | 66 | target/jextract-maven-plugin-*.jar 67 | target/jextract-maven-plugin-*.pom 68 | 69 | - name: Release 70 | if: startsWith(github.ref, 'refs/tags/') 71 | uses: softprops/action-gh-release@v2 72 | with: 73 | generate_release_notes: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # Eclipse Settings Files # 9 | .settings 10 | .project 11 | .classpath 12 | 13 | # Maven # 14 | target/ 15 | pom.xml.versionsBackup 16 | 17 | # IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) # 18 | .idea/**/workspace.xml 19 | .idea/**/tasks.xml 20 | .idea/dictionaries 21 | .idea/compiler.xml 22 | .idea/jarRepositories.xml 23 | .idea/**/libraries/ 24 | *.iml 25 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sebastian Stenzel 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 | # jextract-maven-plugin 2 | 3 | This is a Maven wrapper for the [`jextract`](https://github.com/openjdk/jextract) tool. 4 | 5 | ## Usage 6 | 7 | ```xml 8 | 9 | io.github.coffeelibs 10 | jextract-maven-plugin 11 | 0.1.0 12 | 13 | /path/to/jextract 14 | /usr/x86_64-linux-gnu/include/ 15 | 16 | 17 | 18 | example 19 | 20 | sources 21 | 22 | 23 | point.h 24 | 25 | /path/to/shared/library.so 26 | 27 | com.example.mypackage 28 | Point2d 29 | 30 | Point2d 31 | 32 | 33 | distance 34 | 35 | 36 | 37 | 38 | 39 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | io.github.coffeelibs 5 | jextract-maven-plugin 6 | maven-plugin 7 | 0.5.0-SNAPSHOT 8 | jextract Maven Plugin 9 | A Maven plugin to invokes jextract. 10 | 2022 11 | https://github.com/coffeelibs/jextract-maven-plugin 12 | 13 | 14 | 15 | MIT License 16 | http://www.opensource.org/licenses/mit-license.php 17 | repo 18 | 19 | 20 | 21 | 22 | 23 | Sebastian Stenzel 24 | sebastian.stenzel@gmail.com 25 | +1 26 | 27 | 28 | 29 | 30 | scm:git:git@github.com:coffeelibs/jextract-maven-plugin.git 31 | scm:git:git@github.com:coffeelibs/jextract-maven-plugin.git 32 | git@github.com:coffeelibs/jextract-maven-plugin.git 33 | 34 | 35 | 36 | UTF-8 37 | 38 | 39 | 40 | 41 | org.apache.maven 42 | maven-plugin-api 43 | 3.8.5 44 | provided 45 | 46 | 47 | org.apache.maven.plugin-tools 48 | maven-plugin-annotations 49 | 3.6.4 50 | provided 51 | 52 | 53 | org.apache.maven 54 | maven-project 55 | 2.2.1 56 | provided 57 | 58 | 59 | 60 | org.junit.jupiter 61 | junit-jupiter 62 | 5.8.2 63 | test 64 | 65 | 66 | org.mockito 67 | mockito-core 68 | 4.4.0 69 | test 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-compiler-plugin 79 | 3.10.1 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-resources-plugin 84 | 3.2.0 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-dependency-plugin 89 | 3.3.0 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-surefire-plugin 94 | 3.0.0-M5 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-plugin-plugin 99 | 3.6.4 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-compiler-plugin 107 | 108 | 17 109 | 110 | 111 | 112 | maven-source-plugin 113 | 3.2.1 114 | 115 | 116 | attach-sources 117 | 118 | jar-no-fork 119 | 120 | 121 | 122 | 123 | 124 | maven-javadoc-plugin 125 | 3.3.2 126 | 127 | 128 | attach-javadocs 129 | 130 | jar 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | sign 141 | 142 | 143 | 144 | maven-gpg-plugin 145 | 3.2.7 146 | 147 | 148 | sign-artifacts 149 | verify 150 | 151 | sign 152 | 153 | 154 | bc 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | deploy-central 165 | 166 | 167 | 168 | org.sonatype.central 169 | central-publishing-maven-plugin 170 | 0.7.0 171 | true 172 | 173 | central 174 | true 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | deploy-github 183 | 184 | 185 | github 186 | GitHub Packages 187 | https://maven.pkg.github.com/coffeelibs/jextract-maven-plugin 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/io/github/coffeelibs/maven/jextract/LoggingOutputStream.java: -------------------------------------------------------------------------------- 1 | package io.github.coffeelibs.maven.jextract; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.nio.charset.Charset; 7 | import java.util.function.Consumer; 8 | 9 | public class LoggingOutputStream extends OutputStream { 10 | 11 | private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 12 | private final Consumer logger; 13 | private final Charset charset; 14 | 15 | public LoggingOutputStream(Consumer logger, Charset charset) { 16 | this.logger = logger; 17 | this.charset = charset; 18 | } 19 | 20 | @Override 21 | public void write(byte[] b, int off, int len) { 22 | int begin = off; 23 | for (int i = off; i < off + len; i++) { 24 | if (b[i] == '\n') { 25 | buffer.write(b, begin, i - begin); 26 | flush(); 27 | begin = i + 1; 28 | } 29 | } 30 | // append remaining 31 | int delta = begin - off; 32 | buffer.write(b, begin, len - delta); 33 | } 34 | 35 | @Override 36 | public void write(int b) throws IOException { 37 | write(new byte[]{(byte) b}); 38 | } 39 | 40 | @Override 41 | public void flush() { 42 | var line = buffer.toString(charset); 43 | logger.accept(line); 44 | buffer.reset(); 45 | } 46 | 47 | @Override 48 | public void close() { 49 | if (buffer.size() > 0) { 50 | flush(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/coffeelibs/maven/jextract/SourcesMojo.java: -------------------------------------------------------------------------------- 1 | package io.github.coffeelibs.maven.jextract; 2 | 3 | import org.apache.maven.plugin.AbstractMojo; 4 | import org.apache.maven.plugin.MojoFailureException; 5 | import org.apache.maven.plugins.annotations.LifecyclePhase; 6 | import org.apache.maven.plugins.annotations.Mojo; 7 | import org.apache.maven.plugins.annotations.Parameter; 8 | import org.apache.maven.project.MavenProject; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.nio.file.Files; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | @SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"}) 19 | @Mojo(name = "sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true, requiresOnline = true) 20 | public class SourcesMojo extends AbstractMojo { 21 | 22 | @Parameter(defaultValue = "${project}") 23 | private MavenProject project; 24 | 25 | /** 26 | * Path to the jextract binary. 27 | */ 28 | @Parameter(property = "jextract.executable", required = true) 29 | private String executable; 30 | 31 | /** 32 | *
33 | *
--include-macro
34 | *
name of constant macro to include
35 | *
36 | */ 37 | @Parameter(property = "jextract.headerFile", required = true) 38 | private String headerFile; 39 | 40 | /** 41 | *
42 | *
--target-package
43 | *
target package for specified header files
44 | *
45 | */ 46 | @Parameter(property = "jextract.targetPackage", required = true) 47 | private String targetPackage; 48 | 49 | /** 50 | *
51 | *
--header-class-name
52 | *
name of the header class
53 | *
54 | */ 55 | @Parameter(property = "jextract.headerClassName", required = false) 56 | private String headerClassName; 57 | 58 | /** 59 | *
60 | *
-I
61 | *
specify include files path
62 | *
63 | */ 64 | @Parameter(property = "jextract.headerSearchPaths", required = false) 65 | private String[] headerSearchPaths; 66 | 67 | /** 68 | *
69 | *
-D
70 | *
C preprocessor macro
71 | *
72 | */ 73 | @Parameter(property = "jextract.cPreprocessorMacros", required = false) 74 | private String[] cPreprocessorMacros; 75 | 76 | /** 77 | *
78 | *
--include-function
79 | *
name of function to include
80 | *
81 | */ 82 | @Parameter(property = "jextract.includeFunctions", required = false) 83 | private String[] includeFunctions; 84 | 85 | /** 86 | *
87 | *
--include-constant
88 | *
name of macro or enum constant to include
89 | *
90 | */ 91 | @Parameter(property = "jextract.includeConstants", required = false) 92 | private String[] includeConstants; 93 | 94 | /** 95 | *
96 | *
--include-struct
97 | *
name of struct definition to include
98 | *
99 | */ 100 | @Parameter(property = "jextract.includeStructs", required = false) 101 | private String[] includeStructs; 102 | 103 | /** 104 | *
105 | *
--include-typedef
106 | *
name of type definition to include
107 | *
108 | */ 109 | @Parameter(property = "jextract.includeTypedefs", required = false) 110 | private String[] includeTypedefs; 111 | 112 | /** 113 | *
114 | *
--include-union
115 | *
name of union definition to include
116 | *
117 | */ 118 | @Parameter(property = "jextract.includeUnions", required = false) 119 | private String[] includeUnions; 120 | 121 | /** 122 | *
123 | *
--include-var
124 | *
name of global variable to include
125 | *
126 | */ 127 | @Parameter(property = "jextract.includeVars", required = false) 128 | private String[] includeVars; 129 | 130 | /** 131 | *
132 | *
--library
133 | *
path to shared libraries to load
134 | *
135 | */ 136 | @Parameter(property = "jextract.libraries", required = false) 137 | private String[] libraries; 138 | 139 | /** 140 | *
141 | *
--output
142 | *
specify the directory to place generated files
143 | *
144 | */ 145 | @Parameter(property = "jextract.outputDirectory", defaultValue = "${project.build.directory}/generated-sources/jextract", required = true) 146 | private File outputDirectory; 147 | 148 | /** 149 | * working directory, which might contain compile_flags.txt to specify additional clang compiler options. 150 | */ 151 | @Parameter(property = "jextract.workingDirectory", defaultValue = "${project.basedir}", required = true) 152 | private File workingDirectory; 153 | 154 | 155 | public void execute() throws MojoFailureException { 156 | try { 157 | getLog().debug("Create dir " + outputDirectory); 158 | Files.createDirectories(outputDirectory.toPath()); 159 | } catch (IOException e) { 160 | throw new MojoFailureException("Failed to create dir " + outputDirectory.getAbsolutePath(), e); 161 | } 162 | 163 | List args = new ArrayList<>(); 164 | args.add(executable); 165 | if (headerClassName != null) { 166 | args.add("--header-class-name"); 167 | args.add(headerClassName); 168 | } 169 | args.add("--output"); 170 | args.add(outputDirectory.getAbsolutePath()); 171 | args.add("--target-package"); 172 | args.add(targetPackage); 173 | Arrays.stream(libraries).forEach(str -> { 174 | args.add("--library"); 175 | args.add(str); 176 | }); 177 | Arrays.stream(headerSearchPaths).forEach(str -> { 178 | args.add("-I"); 179 | args.add(str); 180 | }); 181 | Arrays.stream(cPreprocessorMacros).forEach(str -> { 182 | args.add("-D"); 183 | args.add(str); 184 | }); 185 | Arrays.stream(includeFunctions).forEach(str -> { 186 | args.add("--include-function"); 187 | args.add(str); 188 | }); 189 | Arrays.stream(includeConstants).forEach(str -> { 190 | args.add("--include-constant"); 191 | args.add(str); 192 | }); 193 | Arrays.stream(includeStructs).forEach(str -> { 194 | args.add("--include-struct"); 195 | args.add(str); 196 | }); 197 | Arrays.stream(includeTypedefs).forEach(str -> { 198 | args.add("--include-typedef"); 199 | args.add(str); 200 | }); 201 | Arrays.stream(includeUnions).forEach(str -> { 202 | args.add("--include-union"); 203 | args.add(str); 204 | }); 205 | Arrays.stream(includeVars).forEach(str -> { 206 | args.add("--include-var"); 207 | args.add(str); 208 | }); 209 | args.add(headerFile); 210 | 211 | getLog().debug("Running: " + String.join(" ", args)); 212 | 213 | ProcessBuilder command = new ProcessBuilder(args); 214 | command.directory(workingDirectory); 215 | try (var stdout = new LoggingOutputStream(getLog()::info, StandardCharsets.UTF_8); 216 | var stderr = new LoggingOutputStream(getLog()::warn, StandardCharsets.UTF_8)) { 217 | Process process = command.start(); 218 | process.getInputStream().transferTo(stdout); 219 | process.getErrorStream().transferTo(stderr); 220 | int result = process.waitFor(); 221 | if (result != 0) { 222 | throw new MojoFailureException("jextract returned error code " + result); 223 | } 224 | } catch (IOException e) { 225 | throw new MojoFailureException("Invoking jextract failed", e); 226 | } catch (InterruptedException e) { 227 | throw new MojoFailureException("Interrupted while waiting for jextract", e); 228 | } 229 | 230 | project.addCompileSourceRoot(outputDirectory.toString()); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/test/java/io/github/coffeelibs/maven/jextract/LoggingOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | package io.github.coffeelibs.maven.jextract; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.mockito.Mockito; 7 | 8 | import java.io.IOException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.function.Consumer; 11 | 12 | public class LoggingOutputStreamTest { 13 | 14 | private Consumer logger; 15 | private LoggingOutputStream out; 16 | 17 | @BeforeEach 18 | @SuppressWarnings("unchecked") 19 | public void setup() { 20 | this.logger = Mockito.mock(Consumer.class); 21 | this.out = new LoggingOutputStream(logger, StandardCharsets.UTF_8); 22 | } 23 | 24 | @Test 25 | @DisplayName("don't log if nothing got written") 26 | public void testNoWriteNoLog() throws IOException { 27 | out.write(new byte[0]); 28 | out.close(); 29 | 30 | Mockito.verifyNoMoreInteractions(logger); 31 | } 32 | 33 | @Test 34 | @DisplayName("write just newlines") 35 | public void testJustNewlines() throws IOException { 36 | out.write('\n'); 37 | out.write('\n'); 38 | out.write('\n'); 39 | out.close(); 40 | 41 | Mockito.verify(logger, Mockito.times(3)).accept(""); 42 | } 43 | 44 | @Test 45 | @DisplayName("write with no newlines") 46 | public void testWriteNoNewlines() throws IOException { 47 | out.write("foo".getBytes(StandardCharsets.UTF_8)); 48 | 49 | Mockito.verifyNoInteractions(logger); 50 | } 51 | 52 | @Test 53 | @DisplayName("write with two newlines") 54 | public void testWriteTwoNewlines() throws IOException { 55 | out.write("foo\nbar\nbaz".getBytes(StandardCharsets.UTF_8)); 56 | 57 | Mockito.verify(logger).accept("foo"); 58 | Mockito.verify(logger).accept("bar"); 59 | Mockito.verifyNoMoreInteractions(logger); 60 | } 61 | 62 | @Test 63 | @DisplayName("multiple writes followed by newline") 64 | public void testMultipleWriteBeforeNewline() throws IOException { 65 | out.write('f'); 66 | out.write('o'); 67 | out.write('o'); 68 | out.write("bar".getBytes(StandardCharsets.UTF_8)); 69 | out.write("\nbaz".getBytes(StandardCharsets.UTF_8)); 70 | 71 | Mockito.verify(logger).accept("foobar"); 72 | Mockito.verifyNoMoreInteractions(logger); 73 | } 74 | 75 | @Test 76 | @DisplayName("write with consecutive newlines") 77 | public void testConsecutiveNewlines() throws IOException { 78 | out.write("foo\n\nbar\nbaz".getBytes(StandardCharsets.UTF_8)); 79 | 80 | Mockito.verify(logger).accept("foo"); 81 | Mockito.verify(logger).accept(""); 82 | Mockito.verify(logger).accept("bar"); 83 | Mockito.verifyNoMoreInteractions(logger); 84 | } 85 | 86 | @Test 87 | @DisplayName("flush on close") 88 | public void testFlushOnClose() throws IOException { 89 | out.write("foo\nbar\nbaz".getBytes(StandardCharsets.UTF_8)); 90 | out.close(); 91 | 92 | Mockito.verify(logger).accept("foo"); 93 | Mockito.verify(logger).accept("bar"); 94 | Mockito.verify(logger).accept("baz"); 95 | Mockito.verifyNoMoreInteractions(logger); 96 | } 97 | 98 | } --------------------------------------------------------------------------------