├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
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 | }
--------------------------------------------------------------------------------