cls, String jarPathPrefix, String name, String platformDir) {
46 | final String sep = jarPathPrefix.endsWith("/") ? "" : "/";
47 | String pathInJar = jarPathPrefix + sep;
48 | if (platformDir != null) {
49 | pathInJar += platformDir + "/";
50 | }
51 | pathInJar += PlatformConventions.LIB_PREFIX + name + PlatformConventions.LIB_SUFFIX;
52 | final InputStream is = cls.getResourceAsStream(pathInJar);
53 | if (is == null) {
54 | throw new LoadException("Internal error: cannot find " + pathInJar + ", broken package?");
55 | }
56 |
57 | try {
58 | File tempLib = null;
59 | try {
60 | final int dot = pathInJar.indexOf('.');
61 | tempLib = File.createTempFile(pathInJar.substring(0, dot), pathInJar.substring(dot));
62 | // copy to tempLib
63 | try (FileOutputStream out = new FileOutputStream(tempLib)) {
64 | StreamTransfer.copyToStream(is, out);
65 | } finally {
66 | tempLib.deleteOnExit();
67 | }
68 | System.load(tempLib.getAbsolutePath());
69 | } catch (IOException e) {
70 | throw new LoadException("Internal error: cannot unpack " + tempLib, e);
71 | }
72 | } finally {
73 | try {
74 | is.close();
75 | } catch (IOException e) {
76 | // ignore
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * Loads a native library from a JAR file.
83 | *
84 | * The library is loaded from the platform-specific subdirectory of the jarPathPrefix.
85 | *
86 | * The platform-specific subdirectory derived from the current platform and
87 | * the architecture of the JVM as determined by {@link Platform#RESOURCE_PREFIX}.
88 | *
89 | * For example if executing, JarJniLoader.loadLib(MyClass.class, "/native", "mylib");
90 | * on a 64-bit x86 Linux system, the library will be loaded from "/native/linux-amd64/libmylib.so"
91 | * from the same JAR file that contains MyClass.class.
92 | * If executing on an Apple Silicon Macbook, the library will be loaded from
93 | * "/native/mac_os_x-arm64/libmylib.dylib".
94 | * From Windows 11, the library will be loaded from "/native/windows-amd64/mylib.dll" (note, no "lib" prefix).
95 | *
96 | * @param cls The class to use for loading the library.
97 | * @param jarPathPrefix The path prefix to the library in the JAR file.
98 | * @param name The name of the library, sans "lib" prefix and ".so|.dll|.dylib" suffix.
99 | */
100 | static void loadLib(Class cls, String jarPathPrefix, String name) {
101 | loadLib(cls, jarPathPrefix, name, Platform.RESOURCE_PREFIX);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java/io/questdb/jar/jni/LoadException.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jar.jni;
26 |
27 | /**
28 | * Exception thrown when a native library cannot be loaded.
29 | */
30 | public class LoadException extends RuntimeException {
31 | public LoadException(String message) {
32 | super(message);
33 | }
34 |
35 | public LoadException(String message, Throwable cause) {
36 | super(message, cause);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java/io/questdb/jar/jni/Native.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jar.jni;
26 |
27 | /**
28 | * A re-implementation of the JNA Native class that only implements the `POINTER_SIZE` constant.
29 | * This is to satisfy the compile requirements of `Platform.java` as extracted from the JNA project.
30 | */
31 | public final class Native {
32 | public static final int POINTER_SIZE;
33 |
34 | static {
35 | // Raise an issue or a PR if this fails to detect the correct pointer size.
36 | final boolean is64Bit = System.getProperty("os.arch").contains("64");
37 | POINTER_SIZE = is64Bit ? 8 : 4;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java/io/questdb/jar/jni/Platform.java:
--------------------------------------------------------------------------------
1 | // Taken from https://github.com/java-native-access/jna/blob/e96f30192e9455e7cc4117cce06fc3fa80bead55/src/com/sun/jna/Platform.java
2 | // Renamed the package to io.questdb.jar.jni to avoid conflicts with the original class.
3 | // This is done as suggested by the JNA team and permitted by the Apache License 2.0 license.
4 | // See: https://github.com/java-native-access/jna/issues/1515
5 |
6 | /*
7 | * The contents of this file is licensed under the Apache License 2.0.
8 | *
9 | * You may obtain a copy of the Apache License at:
10 | *
11 | * http://www.apache.org/licenses/
12 | *
13 | * A copy is also included in the downloadable source code package
14 | * containing JNA, in file "AL2.0".
15 | */
16 | package io.questdb.jar.jni;
17 |
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.util.logging.Level;
21 | import java.util.logging.Logger;
22 |
23 | /** Provide simplified platform information. */
24 | public final class Platform {
25 | public static final int UNSPECIFIED = -1;
26 | public static final int MAC = 0;
27 | public static final int LINUX = 1;
28 | public static final int WINDOWS = 2;
29 | public static final int SOLARIS = 3;
30 | public static final int FREEBSD = 4;
31 | public static final int OPENBSD = 5;
32 | public static final int WINDOWSCE = 6;
33 | public static final int AIX = 7;
34 | public static final int ANDROID = 8;
35 | public static final int GNU = 9;
36 | public static final int KFREEBSD = 10;
37 | public static final int NETBSD = 11;
38 |
39 | /** Whether read-only (final) fields within Structures are supported. */
40 | public static final boolean RO_FIELDS;
41 | /** Whether this platform provides NIO Buffers. */
42 | public static final boolean HAS_BUFFERS;
43 | /** Whether this platform provides the AWT Component class; also false if
44 | * running headless.
45 | */
46 | public static final boolean HAS_AWT;
47 | /** Whether this platform supports the JAWT library. */
48 | public static final boolean HAS_JAWT;
49 | /** Canonical name of this platform's math library. */
50 | public static final String MATH_LIBRARY_NAME;
51 | /** Canonical name of this platform's C runtime library. */
52 | public static final String C_LIBRARY_NAME;
53 | /** Whether in-DLL callbacks are supported. */
54 | public static final boolean HAS_DLL_CALLBACKS;
55 | /** Canonical resource prefix for the current platform. This value is
56 | * used to load bundled native libraries from the class path.
57 | */
58 | public static final String RESOURCE_PREFIX;
59 |
60 | private static final int osType;
61 | /** Current platform architecture. */
62 | public static final String ARCH;
63 |
64 | static {
65 | String osName = System.getProperty("os.name");
66 | if (osName.startsWith("Linux")) {
67 | if ("dalvik".equals(System.getProperty("java.vm.name").toLowerCase())) {
68 | osType = ANDROID;
69 | // Native libraries on android must be bundled with the APK
70 | System.setProperty("jna.nounpack", "true");
71 | }
72 | else {
73 | osType = LINUX;
74 | }
75 | }
76 | else if (osName.startsWith("AIX")) {
77 | osType = AIX;
78 | }
79 | else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) {
80 | osType = MAC;
81 | }
82 | else if (osName.startsWith("Windows CE")) {
83 | osType = WINDOWSCE;
84 | }
85 | else if (osName.startsWith("Windows")) {
86 | osType = WINDOWS;
87 | }
88 | else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) {
89 | osType = SOLARIS;
90 | }
91 | else if (osName.startsWith("FreeBSD")) {
92 | osType = FREEBSD;
93 | }
94 | else if (osName.startsWith("OpenBSD")) {
95 | osType = OPENBSD;
96 | }
97 | else if (osName.equalsIgnoreCase("gnu")) {
98 | osType = GNU;
99 | }
100 | else if (osName.equalsIgnoreCase("gnu/kfreebsd")) {
101 | osType = KFREEBSD;
102 | }
103 | else if (osName.equalsIgnoreCase("netbsd")) {
104 | osType = NETBSD;
105 | }
106 | else {
107 | osType = UNSPECIFIED;
108 | }
109 | boolean hasBuffers = false;
110 | try {
111 | Class.forName("java.nio.Buffer");
112 | hasBuffers = true;
113 | }
114 | catch(ClassNotFoundException e) {
115 | }
116 | // NOTE: we used to do Class.forName("java.awt.Component"), but that
117 | // has the unintended side effect of actually loading AWT native libs,
118 | // which can be problematic
119 | HAS_AWT = osType != WINDOWSCE && osType != ANDROID && osType != AIX;
120 | HAS_JAWT = HAS_AWT && osType != MAC;
121 | HAS_BUFFERS = hasBuffers;
122 | RO_FIELDS = osType != WINDOWSCE;
123 | C_LIBRARY_NAME = osType == WINDOWS ? "msvcrt" : osType == WINDOWSCE ? "coredll" : "c";
124 | MATH_LIBRARY_NAME = osType == WINDOWS ? "msvcrt" : osType == WINDOWSCE ? "coredll" : "m";
125 | ARCH = getCanonicalArchitecture(System.getProperty("os.arch"), osType);
126 | // Windows aarch64 callbacks disabled via ASMFN_OFF (no mingw support)
127 | HAS_DLL_CALLBACKS = osType == WINDOWS && !ARCH.startsWith("aarch");
128 | RESOURCE_PREFIX = getNativeLibraryResourcePrefix();
129 | }
130 | private Platform() { }
131 | public static final int getOSType() {
132 | return osType;
133 | }
134 | public static final boolean isMac() {
135 | return osType == MAC;
136 | }
137 | public static final boolean isAndroid() {
138 | return osType == ANDROID;
139 | }
140 | public static final boolean isLinux() {
141 | return osType == LINUX;
142 | }
143 | public static final boolean isAIX() {
144 | return osType == AIX;
145 | }
146 | public static final boolean isWindowsCE() {
147 | return osType == WINDOWSCE;
148 | }
149 | /** Returns true for any windows variant. */
150 | public static final boolean isWindows() {
151 | return osType == WINDOWS || osType == WINDOWSCE;
152 | }
153 | public static final boolean isSolaris() {
154 | return osType == SOLARIS;
155 | }
156 | public static final boolean isFreeBSD() {
157 | return osType == FREEBSD;
158 | }
159 | public static final boolean isOpenBSD() {
160 | return osType == OPENBSD;
161 | }
162 | public static final boolean isNetBSD() {
163 | return osType == NETBSD;
164 | }
165 | public static final boolean isGNU() {
166 | return osType == GNU;
167 | }
168 | public static final boolean iskFreeBSD() {
169 | return osType == KFREEBSD;
170 | }
171 | public static final boolean isX11() {
172 | // TODO: check filesystem for /usr/X11 or some other X11-specific test
173 | return !Platform.isWindows() && !Platform.isMac();
174 | }
175 | public static final boolean hasRuntimeExec() {
176 | if (isWindowsCE() && "J9".equals(System.getProperty("java.vm.name")))
177 | return false;
178 | return true;
179 | }
180 | public static final boolean is64Bit() {
181 | String model = System.getProperty("sun.arch.data.model",
182 | System.getProperty("com.ibm.vm.bitmode"));
183 | if (model != null) {
184 | return "64".equals(model);
185 | }
186 | if ("x86-64".equals(ARCH)
187 | || "ia64".equals(ARCH)
188 | || "ppc64".equals(ARCH) || "ppc64le".equals(ARCH)
189 | || "sparcv9".equals(ARCH)
190 | || "mips64".equals(ARCH) || "mips64el".equals(ARCH)
191 | || "loongarch64".equals(ARCH)
192 | || "amd64".equals(ARCH)
193 | || "aarch64".equals(ARCH)) {
194 | return true;
195 | }
196 | return Native.POINTER_SIZE == 8;
197 | }
198 |
199 | public static final boolean isIntel() {
200 | if (ARCH.startsWith("x86")) {
201 | return true;
202 | }
203 | return false;
204 | }
205 |
206 | public static final boolean isPPC() {
207 | if (ARCH.startsWith("ppc")) {
208 | return true;
209 | }
210 | return false;
211 | }
212 |
213 | public static final boolean isARM() {
214 | return ARCH.startsWith("arm") || ARCH.startsWith("aarch");
215 | }
216 |
217 | public static final boolean isSPARC() {
218 | return ARCH.startsWith("sparc");
219 | }
220 |
221 | public static final boolean isMIPS() {
222 | if (ARCH.equals("mips")
223 | || ARCH.equals("mips64")
224 | || ARCH.equals("mipsel")
225 | || ARCH.equals("mips64el")) {
226 | return true;
227 | }
228 | return false;
229 | }
230 |
231 | public static final boolean isLoongArch() {
232 | return ARCH.startsWith("loongarch");
233 | }
234 |
235 | static String getCanonicalArchitecture(String arch, int platform) {
236 | arch = arch.toLowerCase().trim();
237 | if ("powerpc".equals(arch)) {
238 | arch = "ppc";
239 | }
240 | else if ("powerpc64".equals(arch)) {
241 | arch = "ppc64";
242 | }
243 | else if ("i386".equals(arch) || "i686".equals(arch)) {
244 | arch = "x86";
245 | }
246 | else if ("x86_64".equals(arch) || "amd64".equals(arch)) {
247 | arch = "x86-64";
248 | }
249 | else if ("zarch_64".equals(arch)) {
250 | arch = "s390x";
251 | }
252 | // Work around OpenJDK mis-reporting os.arch
253 | // https://bugs.openjdk.java.net/browse/JDK-8073139
254 | if ("ppc64".equals(arch) && "little".equals(System.getProperty("sun.cpu.endian"))) {
255 | arch = "ppc64le";
256 | }
257 | // Map arm to armel if the binary is running as softfloat build
258 | if("arm".equals(arch) && platform == Platform.LINUX && isSoftFloat()) {
259 | arch = "armel";
260 | }
261 |
262 | return arch;
263 | }
264 |
265 | static boolean isSoftFloat() {
266 | try {
267 | File self = new File("/proc/self/exe");
268 | if (self.exists()) {
269 | ELFAnalyser ahfd = ELFAnalyser.analyse(self.getCanonicalPath());
270 | return ! ahfd.isArmHardFloat();
271 | }
272 | } catch (IOException ex) {
273 | // asume hardfloat
274 | Logger.getLogger(Platform.class.getName()).log(Level.INFO, "Failed to read '/proc/self/exe' or the target binary.", ex);
275 | } catch (SecurityException ex) {
276 | // asume hardfloat
277 | Logger.getLogger(Platform.class.getName()).log(Level.INFO, "SecurityException while analysing '/proc/self/exe' or the target binary.", ex);
278 | }
279 | return false;
280 | }
281 |
282 | /** Generate a canonical String prefix based on the current OS
283 | type/arch/name.
284 | */
285 | static String getNativeLibraryResourcePrefix() {
286 | String prefix = System.getProperty("jna.prefix");
287 | if(prefix != null) {
288 | return prefix;
289 | } else {
290 | return getNativeLibraryResourcePrefix(getOSType(), System.getProperty("os.arch"), System.getProperty("os.name"));
291 | }
292 | }
293 |
294 | /** Generate a canonical String prefix based on the given OS
295 | type/arch/name.
296 | @param osType from {@link #getOSType()}
297 | @param arch from os.arch
System property
298 | @param name from os.name
System property
299 | */
300 | static String getNativeLibraryResourcePrefix(int osType, String arch, String name) {
301 | String osPrefix;
302 | arch = getCanonicalArchitecture(arch, osType);
303 | switch(osType) {
304 | case Platform.ANDROID:
305 | if (arch.startsWith("arm")) {
306 | arch = "arm";
307 | }
308 | osPrefix = "android-" + arch;
309 | break;
310 | case Platform.WINDOWS:
311 | osPrefix = "win32-" + arch;
312 | break;
313 | case Platform.WINDOWSCE:
314 | osPrefix = "w32ce-" + arch;
315 | break;
316 | case Platform.MAC:
317 | osPrefix = "darwin-" + arch;
318 | break;
319 | case Platform.LINUX:
320 | osPrefix = "linux-" + arch;
321 | break;
322 | case Platform.SOLARIS:
323 | osPrefix = "sunos-" + arch;
324 | break;
325 | case Platform.FREEBSD:
326 | osPrefix = "freebsd-" + arch;
327 | break;
328 | case Platform.OPENBSD:
329 | osPrefix = "openbsd-" + arch;
330 | break;
331 | case Platform.NETBSD:
332 | osPrefix = "netbsd-" + arch;
333 | break;
334 | case Platform.KFREEBSD:
335 | osPrefix = "kfreebsd-" + arch;
336 | break;
337 | default:
338 | osPrefix = name.toLowerCase();
339 | int space = osPrefix.indexOf(" ");
340 | if (space != -1) {
341 | osPrefix = osPrefix.substring(0, space);
342 | }
343 | osPrefix += "-" + arch;
344 | break;
345 | }
346 | return osPrefix;
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java/io/questdb/jar/jni/PlatformConventions.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jar.jni;
26 |
27 | public class PlatformConventions {
28 | public static final String LIB_PREFIX;
29 | public static final String LIB_SUFFIX;
30 | public static final String EXE_SUFFIX = isWindows() ? ".exe" : "";
31 |
32 | private static boolean isWindows() {
33 | switch (Platform.getOSType()) {
34 | case Platform.WINDOWS:
35 | case Platform.WINDOWSCE:
36 | return true;
37 | default:
38 | return false;
39 | }
40 | }
41 |
42 | static {
43 | switch (Platform.getOSType()) {
44 | case Platform.LINUX:
45 | case Platform.SOLARIS:
46 | case Platform.FREEBSD:
47 | case Platform.OPENBSD:
48 | case Platform.ANDROID:
49 | case Platform.GNU:
50 | case Platform.KFREEBSD:
51 | case Platform.NETBSD:
52 | LIB_PREFIX = "lib";
53 | LIB_SUFFIX = ".so";
54 | break;
55 |
56 | case Platform.MAC:
57 | LIB_PREFIX = "lib";
58 | LIB_SUFFIX = ".dylib";
59 | break;
60 |
61 | case Platform.WINDOWS:
62 | case Platform.WINDOWSCE:
63 | LIB_PREFIX = "";
64 | LIB_SUFFIX = ".dll";
65 | break;
66 |
67 | case Platform.AIX:
68 | LIB_PREFIX = "lib";
69 | LIB_SUFFIX = ".a";
70 | break;
71 |
72 | default:
73 | throw new IllegalStateException("Unsupported platform: " + Platform.getOSType());
74 |
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java/io/questdb/jar/jni/StreamTransfer.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jar.jni;
26 |
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.OutputStream;
30 |
31 | public interface StreamTransfer {
32 | static void copyToStream(InputStream is, OutputStream out) throws IOException {
33 | byte[] buf = new byte[4096];
34 | while (true) {
35 | int read = is.read(buf);
36 | if (read == -1) {
37 | break;
38 | }
39 | out.write(buf, 0, read);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java9/io/questdb/jar/jni/StreamTransfer.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jar.jni;
26 |
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.OutputStream;
30 |
31 | public interface StreamTransfer {
32 | static void copyToStream(InputStream is, OutputStream out) throws IOException {
33 | is.transferTo(out);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/jar-jni/src/main/java9/module-info.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | open module io.questdb.jar.jni {
26 | requires java.base;
27 | exports io.questdb.jar.jni;
28 | }
29 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | 4.0.0
27 |
28 | 1.2.1-SNAPSHOT
29 | org.questdb
30 | rust-maven
31 | pom
32 | Rust Maven
33 |
34 |
35 | rust-maven-plugin
36 | rust-maven-jni-example
37 | rust-maven-jna-example
38 | jar-jni
39 |
40 |
41 |
42 | true
43 | true
44 |
45 |
46 |
47 |
48 | QuestDB Team
49 | hello@questdb.io
50 |
51 |
52 |
53 |
54 | https://github.com/questdb/rust-maven-plugin
55 | scm:git:https://github.com/questdb/rust-maven-plugin.git
56 | scm:git:https://github.com/questdb/rust-maven-plugin.git
57 | HEAD
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-release-plugin
66 | 2.5.3
67 |
68 | @{project.version}
69 | -Dmaven.test.skipTests=true -Dmaven.test.skip=true
70 | maven-central-release
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/pom.xml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | 4.0.0
27 |
28 | org.questdb
29 | rust-maven-jna-example
30 | 1.2.1-SNAPSHOT
31 | jar
32 |
33 | Rust Maven Plugin JNA Usage Example
34 | A Java command line tool with a string reverse function written in Rust bridged over JNA.
35 |
36 |
37 | 1.8
38 | 1.8
39 | UTF-8
40 | UTF-8
41 | true
42 | true
43 |
44 |
45 |
46 | https://github.com/questdb/rust-maven-plugin
47 | scm:git:https://github.com/questdb/rust-maven-plugin.git
48 | scm:git:https://github.com/questdb/rust-maven-plugin.git
49 | HEAD
50 |
51 |
52 |
53 |
54 | net.java.dev.jna
55 | jna
56 | 5.13.0
57 |
58 |
59 | junit
60 | junit
61 | 4.13.2
62 | test
63 |
64 |
65 |
66 |
67 |
68 |
74 |
75 | org.questdb
76 | rust-maven-plugin
77 | ${project.version}
78 |
79 |
80 | double-number
81 |
82 | build
83 |
84 |
85 |
90 |
91 |
95 |
96 |
97 |
100 | src/main/rust/double-number
101 |
102 |
109 | true
110 |
111 |
117 | ${project.build.directory}/classes
118 |
119 |
123 | true
124 |
125 |
126 |
127 | double-number-test
128 |
129 |
133 |
134 | test
135 |
136 |
137 |
138 |
139 | src/main/rust/double-number
140 |
141 |
142 | false
143 |
144 | -v
145 |
146 |
147 | Testing prefix
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/main/java/io/questdb/jna/example/rust/DoubleNumber.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jna.example.rust;
26 |
27 |
28 | import com.sun.jna.Library;
29 | import com.sun.jna.Native;
30 |
31 | public interface DoubleNumber extends Library {
32 | DoubleNumber INSTANCE = (DoubleNumber) Native.load("double_number", DoubleNumber.class);
33 |
34 | int doubleNumber(int n);
35 |
36 | static void main(String[] args) {
37 | System.out.println(DoubleNumber.INSTANCE.doubleNumber(21));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/main/rust/double-number/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # /target/rust-maven-plugin/double-number
3 | target-dir = "../../../../target/rust-maven-plugin/double-number"
4 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/main/rust/double-number/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "double-number"
7 | version = "0.1.0"
8 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/main/rust/double-number/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "double-number"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/main/rust/double-number/src/lib.rs:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | #[no_mangle]
26 | #[allow(non_snake_case)]
27 | pub extern fn doubleNumber(n: i32) -> i32 {
28 | n * 2
29 | }
--------------------------------------------------------------------------------
/rust-maven-jna-example/src/test/java/io/questdb/jna/example/rust/LibTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jna.example.rust;
26 |
27 | import org.junit.Assert;
28 | import org.junit.Test;
29 |
30 | public class LibTest {
31 | @Test
32 | public void testLibrary() {
33 | Assert.assertEquals(42, DoubleNumber.INSTANCE.doubleNumber(21));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/pom.xml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | 4.0.0
27 |
28 | org.questdb
29 | rust-maven-jni-example
30 | 1.2.1-SNAPSHOT
31 | jar
32 |
33 | Rust Maven Plugin Usage Example
34 | A Java command line tool with a string reverse function written in Rust bridged over JNI.
35 |
36 |
37 | 1.8
38 | 1.8
39 | UTF-8
40 | UTF-8
41 | true
42 | true
43 |
44 |
45 |
46 | https://github.com/questdb/rust-maven-plugin
47 | scm:git:https://github.com/questdb/rust-maven-plugin.git
48 | scm:git:https://github.com/questdb/rust-maven-plugin.git
49 | HEAD
50 |
51 |
52 |
53 |
54 | org.questdb
55 | jar-jni
56 | ${project.version}
57 |
58 |
59 | junit
60 | junit
61 | 4.13.2
62 | test
63 |
64 |
65 |
66 |
67 |
68 |
74 |
75 | org.questdb
76 | rust-maven-plugin
77 | ${project.version}
78 |
79 |
80 | str-reverse
81 |
82 | build
83 |
84 |
85 |
90 |
91 |
95 |
96 |
97 |
100 | src/main/rust/str-reverse
101 |
102 |
109 | true
110 |
111 |
115 | ${project.build.directory}/classes/io/questdb/jni/example/rust/libs
116 |
117 |
121 | true
122 |
123 |
126 |
127 | Great Scott, A reversed string!
128 |
129 |
130 |
131 |
132 |
133 | str-reverse-binary
134 |
135 | build
136 |
137 |
138 | src/main/rust/str-reverse-binary
139 | true
140 | ${project.build.directory}/bin
141 |
142 | header
143 | footer
144 |
145 |
146 |
147 |
148 | str-reverse-test
149 |
150 |
154 |
155 | test
156 |
157 |
158 |
159 |
160 | src/main/rust/str-reverse
161 |
162 |
163 | false
164 |
165 | -v
166 |
167 |
168 | Testing prefix
169 |
170 |
171 |
172 |
173 |
177 | dummy
178 |
179 | build
180 |
181 |
182 | true
183 | required, but not evaluated if skipped
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/java/io/questdb/jni/example/rust/Main.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jni.example.rust;
26 |
27 | import io.questdb.jar.jni.JarJniLoader;
28 |
29 | public class Main {
30 | static {
31 | JarJniLoader.loadLib(
32 | Main.class,
33 |
34 | // A platform-specific path is automatically suffixed to path below.
35 | "/io/questdb/jni/example/rust/libs",
36 |
37 | // The "lib" prefix and ".so|.dynlib|.dll" suffix are added automatically as needed.
38 | "str_reverse");
39 | }
40 |
41 | public static native String reversedString(String str);
42 |
43 | public static void main(String[] args) {
44 | System.out.println(reversedString("Hello World!"));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse-binary/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # /target/rust-maven-plugin/str-reverse-binary
3 | target-dir = "../../../../target/rust-maven-plugin/str-reverse-binary"
4 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse-binary/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "str-reverse-binary"
7 | version = "0.1.0"
8 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse-binary/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "str-reverse-binary"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [features]
7 | header = []
8 | footer = []
9 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse-binary/src/main.rs:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | use std::env;
26 | use std::process::ExitCode;
27 |
28 | fn main() -> ExitCode {
29 | let args: Vec = env::args().collect();
30 |
31 | match args.len() {
32 | 2 => {
33 | let reversed: String = args[1].chars().rev().collect();
34 |
35 | #[cfg(feature = "header")]
36 | println!(">>>>>>>>>>>>>>>>>>>>>>>>>");
37 |
38 | println!("{}", reversed);
39 |
40 | #[cfg(feature = "footer")]
41 | println!("<<<<<<<<<<<<<<<<<<<<<<<<<<");
42 |
43 | ExitCode::SUCCESS
44 | }
45 | _ => {
46 | eprintln!("Error: Must provide a single string argument.");
47 | ExitCode::FAILURE
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # /target/rust-maven-plugin/str-reverse
3 | target-dir = "../../../../target/rust-maven-plugin/str-reverse"
4 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "bytes"
7 | version = "1.3.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
10 |
11 | [[package]]
12 | name = "cesu8"
13 | version = "1.1.0"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
16 |
17 | [[package]]
18 | name = "cfg-if"
19 | version = "1.0.0"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
22 |
23 | [[package]]
24 | name = "combine"
25 | version = "4.6.6"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
28 | dependencies = [
29 | "bytes",
30 | "memchr",
31 | ]
32 |
33 | [[package]]
34 | name = "jni"
35 | version = "0.21.1"
36 | source = "registry+https://github.com/rust-lang/crates.io-index"
37 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
38 | dependencies = [
39 | "cesu8",
40 | "cfg-if",
41 | "combine",
42 | "jni-sys",
43 | "log",
44 | "thiserror",
45 | "walkdir",
46 | "windows-sys",
47 | ]
48 |
49 | [[package]]
50 | name = "jni-sys"
51 | version = "0.3.0"
52 | source = "registry+https://github.com/rust-lang/crates.io-index"
53 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
54 |
55 | [[package]]
56 | name = "log"
57 | version = "0.4.17"
58 | source = "registry+https://github.com/rust-lang/crates.io-index"
59 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
60 | dependencies = [
61 | "cfg-if",
62 | ]
63 |
64 | [[package]]
65 | name = "memchr"
66 | version = "2.5.0"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
69 |
70 | [[package]]
71 | name = "proc-macro2"
72 | version = "1.0.49"
73 | source = "registry+https://github.com/rust-lang/crates.io-index"
74 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
75 | dependencies = [
76 | "unicode-ident",
77 | ]
78 |
79 | [[package]]
80 | name = "quote"
81 | version = "1.0.23"
82 | source = "registry+https://github.com/rust-lang/crates.io-index"
83 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
84 | dependencies = [
85 | "proc-macro2",
86 | ]
87 |
88 | [[package]]
89 | name = "same-file"
90 | version = "1.0.6"
91 | source = "registry+https://github.com/rust-lang/crates.io-index"
92 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
93 | dependencies = [
94 | "winapi-util",
95 | ]
96 |
97 | [[package]]
98 | name = "str-reverse"
99 | version = "0.1.0"
100 | dependencies = [
101 | "jni",
102 | ]
103 |
104 | [[package]]
105 | name = "syn"
106 | version = "1.0.107"
107 | source = "registry+https://github.com/rust-lang/crates.io-index"
108 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
109 | dependencies = [
110 | "proc-macro2",
111 | "quote",
112 | "unicode-ident",
113 | ]
114 |
115 | [[package]]
116 | name = "thiserror"
117 | version = "1.0.38"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
120 | dependencies = [
121 | "thiserror-impl",
122 | ]
123 |
124 | [[package]]
125 | name = "thiserror-impl"
126 | version = "1.0.38"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
129 | dependencies = [
130 | "proc-macro2",
131 | "quote",
132 | "syn",
133 | ]
134 |
135 | [[package]]
136 | name = "unicode-ident"
137 | version = "1.0.6"
138 | source = "registry+https://github.com/rust-lang/crates.io-index"
139 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
140 |
141 | [[package]]
142 | name = "walkdir"
143 | version = "2.3.2"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
146 | dependencies = [
147 | "same-file",
148 | "winapi",
149 | "winapi-util",
150 | ]
151 |
152 | [[package]]
153 | name = "winapi"
154 | version = "0.3.9"
155 | source = "registry+https://github.com/rust-lang/crates.io-index"
156 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
157 | dependencies = [
158 | "winapi-i686-pc-windows-gnu",
159 | "winapi-x86_64-pc-windows-gnu",
160 | ]
161 |
162 | [[package]]
163 | name = "winapi-i686-pc-windows-gnu"
164 | version = "0.4.0"
165 | source = "registry+https://github.com/rust-lang/crates.io-index"
166 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
167 |
168 | [[package]]
169 | name = "winapi-util"
170 | version = "0.1.5"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
173 | dependencies = [
174 | "winapi",
175 | ]
176 |
177 | [[package]]
178 | name = "winapi-x86_64-pc-windows-gnu"
179 | version = "0.4.0"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
182 |
183 | [[package]]
184 | name = "windows-sys"
185 | version = "0.45.0"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
188 | dependencies = [
189 | "windows-targets",
190 | ]
191 |
192 | [[package]]
193 | name = "windows-targets"
194 | version = "0.42.2"
195 | source = "registry+https://github.com/rust-lang/crates.io-index"
196 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
197 | dependencies = [
198 | "windows_aarch64_gnullvm",
199 | "windows_aarch64_msvc",
200 | "windows_i686_gnu",
201 | "windows_i686_msvc",
202 | "windows_x86_64_gnu",
203 | "windows_x86_64_gnullvm",
204 | "windows_x86_64_msvc",
205 | ]
206 |
207 | [[package]]
208 | name = "windows_aarch64_gnullvm"
209 | version = "0.42.2"
210 | source = "registry+https://github.com/rust-lang/crates.io-index"
211 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
212 |
213 | [[package]]
214 | name = "windows_aarch64_msvc"
215 | version = "0.42.2"
216 | source = "registry+https://github.com/rust-lang/crates.io-index"
217 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
218 |
219 | [[package]]
220 | name = "windows_i686_gnu"
221 | version = "0.42.2"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
224 |
225 | [[package]]
226 | name = "windows_i686_msvc"
227 | version = "0.42.2"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
230 |
231 | [[package]]
232 | name = "windows_x86_64_gnu"
233 | version = "0.42.2"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
236 |
237 | [[package]]
238 | name = "windows_x86_64_gnullvm"
239 | version = "0.42.2"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
242 |
243 | [[package]]
244 | name = "windows_x86_64_msvc"
245 | version = "0.42.2"
246 | source = "registry+https://github.com/rust-lang/crates.io-index"
247 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
248 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "str-reverse"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
9 | [dependencies]
10 | jni = "0.21.1"
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/main/rust/str-reverse/src/lib.rs:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | use jni::JNIEnv;
26 | use jni::objects::{JClass, JString};
27 | use jni::sys::jstring;
28 |
29 | #[no_mangle]
30 | pub extern "system" fn Java_io_questdb_jni_example_rust_Main_reversedString(
31 | mut env: JNIEnv,
32 | _class: JClass,
33 | input: JString) -> jstring {
34 | let input: String =
35 | env.get_string(&input).expect("Couldn't get java string!").into();
36 | let reversed: String = input.chars().rev().collect();
37 | let reversed = format!("{}: {}", std::env!("REVERSED_STR_PREFIX"), reversed);
38 | let output = env.new_string(reversed)
39 | .expect("Couldn't create java string!");
40 | output.into_raw()
41 | }
42 |
43 | #[cfg(test)]
44 | mod tests {
45 | #[test]
46 | fn test_rubber_duck() {
47 | assert_ne!("rubber", "duck");
48 | }
49 | }
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/test/java/io/questdb/jni/example/rust/BinaryTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jni.example.rust;
26 |
27 | import org.junit.Test;
28 |
29 | import java.io.BufferedReader;
30 | import java.io.File;
31 | import java.io.InputStreamReader;
32 | import java.util.ArrayList;
33 | import java.util.Arrays;
34 | import java.util.Collections;
35 | import java.util.List;
36 | import java.util.stream.Collectors;
37 |
38 | import static org.junit.Assert.assertEquals;
39 |
40 | public class BinaryTest {
41 |
42 | @Test
43 | public void testBinary() throws Exception {
44 | File binaryFile = new File("target/bin/str-reverse-binary");
45 |
46 | Process process = new ProcessBuilder(
47 | Arrays.asList(
48 | binaryFile.getAbsolutePath(),
49 | "Hello World!"))
50 | .start();
51 |
52 | List output = new BufferedReader(new InputStreamReader(process.getInputStream()))
53 | .lines()
54 | .collect(Collectors.toList());
55 |
56 | assertEquals(0, process.waitFor());
57 | final List exp = new ArrayList<>();
58 | Collections.addAll(exp,
59 | ">>>>>>>>>>>>>>>>>>>>>>>>>",
60 | "!dlroW olleH",
61 | "<<<<<<<<<<<<<<<<<<<<<<<<<<");
62 | assertEquals(exp, output);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/rust-maven-jni-example/src/test/java/io/questdb/jni/example/rust/LibTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.jni.example.rust;
26 |
27 | import io.questdb.jni.example.rust.Main;
28 | import org.junit.Test;
29 |
30 | import static org.junit.Assert.assertEquals;
31 |
32 | public class LibTest {
33 |
34 | @Test
35 | public void testLibrary() {
36 | assertEquals("Great Scott, A reversed string!: !dlroW olleH", Main.reversedString("Hello World!"));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/rust-maven-plugin/pom.xml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | 4.0.0
27 |
28 | org.questdb
29 | rust-maven-plugin
30 | 1.2.1-SNAPSHOT
31 | maven-plugin
32 |
33 | Rust Maven Plugin
34 | Embed Rust Cargo projects in Java
35 | https://github.com/questdb/rust-maven-plugin
36 |
37 |
38 | The Apache Software License, Version 2.0
39 | http://www.apache.org/licenses/LICENSE-2.0.txt
40 | repo
41 |
42 |
43 |
44 |
45 | QuestDB Team
46 | hello@questdb.io
47 | QuestDB
48 | https://questdb.io/
49 |
50 |
51 |
52 | https://github.com/questdb/rust-maven-plugin
53 | scm:git:https://github.com/questdb/rust-maven-plugin.git
54 | scm:git:https://github.com/questdb/rust-maven-plugin.git
55 | HEAD
56 |
57 |
58 |
59 | 1.8
60 | 1.8
61 | UTF-8
62 | UTF-8
63 |
64 |
65 |
66 |
67 | maven-central-release
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-javadoc-plugin
73 | 3.5.0
74 |
75 |
76 | attach-javadocs
77 |
78 | jar
79 |
80 |
81 |
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-source-plugin
86 | 3.0.1
87 |
88 |
89 | attach-sources
90 |
91 | jar
92 |
93 |
94 |
95 |
96 |
97 | org.apache.maven.plugins
98 | maven-gpg-plugin
99 | 1.6
100 |
101 |
102 | sign-artifacts
103 | verify
104 |
105 | sign
106 |
107 |
108 |
109 |
110 | gpg
111 |
112 |
113 |
114 | org.sonatype.plugins
115 | nexus-staging-maven-plugin
116 | 1.6.3
117 |
118 |
119 | default-deploy
120 | deploy
121 |
122 | deploy
123 |
124 |
125 |
126 |
127 | central
128 | https://oss.sonatype.org
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | org.questdb
139 | jar-jni
140 | ${project.version}
141 |
142 |
143 | org.apache.maven
144 | maven-plugin-api
145 | 3.8.7
146 | provided
147 |
148 |
149 | org.apache.maven
150 | maven-core
151 | 3.8.7
152 | provided
153 |
154 |
155 | org.apache.maven.plugin-tools
156 | maven-plugin-annotations
157 | 3.7.0
158 | provided
159 |
160 |
161 | org.tomlj
162 | tomlj
163 | 1.1.0
164 |
165 |
166 | junit
167 | junit
168 | 4.13.2
169 | test
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | org.apache.maven.plugins
178 | maven-plugin-plugin
179 | 3.7.0
180 |
181 |
182 | org.apache.maven.plugins
183 | maven-site-plugin
184 | 3.12.1
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | central
193 | https://oss.sonatype.org/content/repositories/snapshots
194 |
195 |
196 | central
197 | https://oss.sonatype.org/service/local/staging/deploy/maven2
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoBuildMojo.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import org.apache.maven.plugin.MojoExecutionException;
28 | import org.apache.maven.plugin.MojoFailureException;
29 | import org.apache.maven.plugins.annotations.LifecyclePhase;
30 | import org.apache.maven.plugins.annotations.Mojo;
31 | import org.apache.maven.plugins.annotations.Parameter;
32 |
33 | import java.nio.file.Path;
34 | import java.nio.file.Paths;
35 |
36 | /**
37 | * An example of a Maven plugin.
38 | */
39 | @Mojo(name = "build", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true)
40 | public class CargoBuildMojo extends CargoMojoBase {
41 | /**
42 | * Location to copy the built Rust binaries to.
43 | * If unset, the binaries are not copied and remain in the target directory.
44 | *
45 | * See also `copyWithPlatformDir`.
46 | */
47 | @Parameter(property = "copyTo")
48 | private String copyTo;
49 |
50 | /**
51 | * Further nest copy into a child directory named through the target's platform.
52 | * The computed name matches that of the `io.questdb.jar.jni.OsInfo.platform()` method.
53 | *
54 | * See also `copyTo`.
55 | */
56 | @Parameter(property = "copyWithPlatformDir")
57 | private boolean copyWithPlatformDir;
58 |
59 | @Override
60 | public void execute() throws MojoExecutionException, MojoFailureException {
61 | if (skip) {
62 | getLog().info("Skipping build");
63 | return;
64 | }
65 | final Crate crate = new Crate(
66 | getCrateRoot(),
67 | getTargetRootDir(),
68 | extractCrateParams());
69 | crate.setLog(getLog());
70 | crate.build();
71 | crate.copyArtifacts();
72 | }
73 |
74 | private Crate.Params extractCrateParams() throws MojoExecutionException {
75 | final Crate.Params params = getCommonCrateParams();
76 | if (copyTo != null) {
77 | Path copyToDir = Paths.get(copyTo);
78 | if (!copyToDir.isAbsolute()) {
79 | copyToDir = project.getBasedir().toPath()
80 | .resolve(copyToDir);
81 | }
82 | params.copyToDir = copyToDir;
83 | }
84 | params.copyWithPlatformDir = copyWithPlatformDir;
85 | return params;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoInstalledChecker.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import org.apache.maven.plugin.MojoExecutionException;
28 |
29 | import java.io.File;
30 | import java.util.HashMap;
31 |
32 | public final class CargoInstalledChecker {
33 |
34 | public static final CargoInstalledChecker INSTANCE =
35 | new CargoInstalledChecker();
36 |
37 | private final HashMap cache = new HashMap<>();
38 |
39 | private CargoInstalledChecker() {
40 | }
41 |
42 | public synchronized void check(String cargoPath)
43 | throws MojoExecutionException {
44 | InstalledState cached = cache.getOrDefault(
45 | cargoPath, InstalledState.UNKNOWN);
46 |
47 | if (cached == InstalledState.UNKNOWN) {
48 | try {
49 | final ProcessBuilder pb = new ProcessBuilder(
50 | cargoPath, "--version");
51 | final Process p = pb.start();
52 | final int exitCode = p.waitFor();
53 | cached = exitCode == 0
54 | ? InstalledState.INSTALLED
55 | : InstalledState.BROKEN;
56 | cache.put(cargoPath, cached);
57 | } catch (Exception e) {
58 | cached = InstalledState.NOT_INSTALLED;
59 | cache.put(cargoPath, cached);
60 | }
61 | }
62 |
63 | if (cached == InstalledState.INSTALLED) {
64 | return;
65 | }
66 |
67 | final StringBuilder error = new StringBuilder();
68 |
69 | if (cached == InstalledState.BROKEN) {
70 | if (cargoPath.equals("cargo")) {
71 | error.append("Rust's `cargo` ");
72 | } else {
73 | error
74 | .append("Rust's `cargo` at ")
75 | .append(Shlex.quote(cargoPath));
76 | }
77 | error.append(
78 | " is a broken install: Running `cargo --version` " +
79 | "returned non-zero exit code");
80 | } else { // cached == InstalledState.NOT_INSTALLED
81 | if (cargoPath.equals("cargo")) {
82 | error
83 | .append("Rust's `cargo` not found in PATH=")
84 | .append(Shlex.quote(String.join(File.pathSeparator, System.getenv("PATH"))));
85 | } else {
86 | error
87 | .append("Rust's `cargo` not found at ")
88 | .append(Shlex.quote(cargoPath));
89 | }
90 | }
91 | error.append(".\n\nSee https://www.rust-lang.org/tools/install");
92 | throw new MojoExecutionException(error.toString());
93 | }
94 |
95 | private enum InstalledState {
96 | UNKNOWN,
97 | NOT_INSTALLED,
98 | INSTALLED,
99 | BROKEN
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoMojoBase.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import org.apache.maven.plugin.AbstractMojo;
28 | import org.apache.maven.plugin.MojoExecutionException;
29 | import org.apache.maven.plugins.annotations.Parameter;
30 | import org.apache.maven.project.MavenProject;
31 |
32 | import java.nio.file.Path;
33 | import java.nio.file.Paths;
34 | import java.util.HashMap;
35 |
36 |
37 | public abstract class CargoMojoBase extends AbstractMojo {
38 | @Parameter(property = "project", readonly = true)
39 | protected MavenProject project;
40 |
41 | /**
42 | * Skip this configured execution.
43 | */
44 | @Parameter(property = "skip", defaultValue = "false")
45 | protected boolean skip;
46 |
47 | /**
48 | * Additional environment variables set when running `cargo`.
49 | */
50 | @Parameter(property = "environmentVariables")
51 | private HashMap environmentVariables;
52 |
53 | /**
54 | * Path to the `cargo` command. If unset or set to "cargo", uses $PATH.
55 | */
56 | @Parameter(property = "cargoPath", defaultValue = "cargo")
57 | private String cargoPath;
58 |
59 | /**
60 | * Path to the Rust crate to build.
61 | */
62 | @Parameter(property = "path", required = true)
63 | private String path;
64 |
65 | /**
66 | * Build artifacts in release mode, with optimizations.
67 | * Defaults to "false" and creates a debug build.
68 | * Equivalent to Cargo's `--release` option.
69 | */
70 | @Parameter(property = "release", defaultValue = "false")
71 | private boolean release;
72 |
73 | /**
74 | * List of features to activate.
75 | * If not specified, default features are activated.
76 | * Equivalent to Cargo's `--features` option.
77 | */
78 | @Parameter(property = "features")
79 | private String[] features;
80 |
81 | /**
82 | * Activate all available features.
83 | * Defaults to "false".
84 | * Equivalent to Cargo's `--all-features` option.
85 | */
86 | @Parameter(property = "all-features", defaultValue = "false")
87 | private boolean allFeatures;
88 |
89 | /**
90 | * Do not activate the `default` feature.
91 | * Defaults to "false".
92 | * Equivalent to Cargo's `--no-default-features` option.
93 | */
94 | @Parameter(property = "no-default-features", defaultValue = "false")
95 | private boolean noDefaultFeatures;
96 |
97 | /**
98 | * Set the verbosity level, forwarded to Cargo.
99 | * Valid values are "", "-q", "-v", "-vv".
100 | */
101 | @Parameter(property = "verbosity")
102 | private String verbosity;
103 |
104 | /**
105 | * Additional args to pass to cargo.
106 | */
107 | @Parameter(property = "extra-args")
108 | private String[] extraArgs;
109 |
110 | protected String getVerbosity() throws MojoExecutionException {
111 | if (verbosity == null) {
112 | return null;
113 | }
114 | switch (verbosity) {
115 | case "":
116 | return null;
117 | case "-q":
118 | case "-v":
119 | case "-vv":
120 | return verbosity;
121 | default:
122 | throw new MojoExecutionException("Invalid verbosity: " + verbosity);
123 | }
124 | }
125 |
126 | protected Path getCrateRoot() {
127 | Path crateRoot = Paths.get(path);
128 | if (!crateRoot.isAbsolute()) {
129 | crateRoot = project.getBasedir().toPath().resolve(path);
130 | }
131 | return crateRoot;
132 | }
133 |
134 | protected Path getTargetRootDir() {
135 | return Paths.get(
136 | project.getBuild().getDirectory(),
137 | "rust-maven-plugin");
138 | }
139 |
140 | protected Crate.Params getCommonCrateParams() throws MojoExecutionException {
141 | final Crate.Params params = new Crate.Params();
142 | params.verbosity = getVerbosity();
143 | params.environmentVariables = environmentVariables;
144 | params.cargoPath = cargoPath;
145 | params.release = release;
146 | params.features = features;
147 | params.allFeatures = allFeatures;
148 | params.noDefaultFeatures = noDefaultFeatures;
149 | params.extraArgs = extraArgs;
150 | return params;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoTestMojo.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import org.apache.maven.plugin.MojoExecutionException;
28 | import org.apache.maven.plugin.MojoFailureException;
29 | import org.apache.maven.plugins.annotations.LifecyclePhase;
30 | import org.apache.maven.plugins.annotations.Mojo;
31 | import org.apache.maven.plugins.annotations.Parameter;
32 |
33 | @Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true)
34 | public class CargoTestMojo extends CargoMojoBase {
35 | /**
36 | * Skips running tests when building with `mvn package -DskipTests=true`.
37 | */
38 | @Parameter(property = "skipTests", defaultValue = "false")
39 | private boolean skipTests;
40 |
41 | @Override
42 | public void execute() throws MojoExecutionException, MojoFailureException {
43 | if (skip || skipTests) {
44 | getLog().info("Skipping tests");
45 | return;
46 | }
47 | final Crate crate = new Crate(
48 | getCrateRoot(),
49 | getTargetRootDir(),
50 | getCommonCrateParams());
51 | crate.setLog(getLog());
52 | crate.test();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/Crate.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import io.questdb.jar.jni.Platform;
28 | import io.questdb.jar.jni.PlatformConventions;
29 | import org.apache.maven.plugin.MojoExecutionException;
30 | import org.apache.maven.plugin.MojoFailureException;
31 | import org.apache.maven.plugin.logging.Log;
32 | import org.tomlj.Toml;
33 | import org.tomlj.TomlArray;
34 | import org.tomlj.TomlInvalidTypeException;
35 | import org.tomlj.TomlTable;
36 |
37 | import java.io.BufferedReader;
38 | import java.io.IOException;
39 | import java.io.InputStreamReader;
40 | import java.nio.file.Files;
41 | import java.nio.file.LinkOption;
42 | import java.nio.file.Path;
43 | import java.nio.file.StandardCopyOption;
44 | import java.util.ArrayList;
45 | import java.util.Collections;
46 | import java.util.HashMap;
47 | import java.util.List;
48 | import java.util.concurrent.Executors;
49 |
50 | /**
51 | * Controls running tasks on a Rust crate.
52 | */
53 | public class Crate {
54 | private final Path crateRoot;
55 | private final Path targetDir;
56 | private final Params params;
57 | private final TomlTable cargoToml;
58 | private final String packageName;
59 | private Log log;
60 |
61 | public Crate(
62 | Path crateRoot,
63 | Path targetRootDir,
64 | Params params) throws MojoExecutionException {
65 | this.log = nullLog();
66 | this.crateRoot = crateRoot;
67 | this.targetDir = targetRootDir.resolve(getDirName());
68 | this.params = params;
69 |
70 | final Path tomlPath = crateRoot.resolve("Cargo.toml");
71 | if (!Files.exists(tomlPath, LinkOption.NOFOLLOW_LINKS)) {
72 | throw new MojoExecutionException(
73 | "Cargo.toml file expected under: " + crateRoot);
74 | }
75 | try {
76 | this.cargoToml = Toml.parse(tomlPath);
77 | } catch (IOException e) {
78 | throw new MojoExecutionException(
79 | "Failed to parse Cargo.toml file: " + e.getMessage());
80 | }
81 |
82 | try {
83 | packageName = cargoToml.getString("package.name");
84 | if (packageName == null) {
85 | throw new MojoExecutionException(
86 | "Missing required `package.name` from Cargo.toml file");
87 | }
88 | } catch (TomlInvalidTypeException e) {
89 | throw new MojoExecutionException(
90 | "Failed to extract `package.name` from Cargo.toml file: " +
91 | e.getMessage());
92 | }
93 | }
94 |
95 | public static String pinLibName(String name) {
96 | return PlatformConventions.LIB_PREFIX +
97 | name.replace('-', '_') +
98 | PlatformConventions.LIB_SUFFIX;
99 | }
100 |
101 | public static String pinBinName(String name) {
102 | return name + PlatformConventions.EXE_SUFFIX;
103 | }
104 |
105 | public static Log nullLog() {
106 | return new Log() {
107 | @Override
108 | public void debug(CharSequence content) {
109 | }
110 |
111 | @Override
112 | public void debug(CharSequence content, Throwable error) {
113 | }
114 |
115 | @Override
116 | public void debug(Throwable error) {
117 | }
118 |
119 | @Override
120 | public void error(CharSequence content) {
121 | }
122 |
123 | @Override
124 | public void error(CharSequence content, Throwable error) {
125 | }
126 |
127 | @Override
128 | public void error(Throwable error) {
129 | }
130 |
131 | @Override
132 | public void info(CharSequence content) {
133 | }
134 |
135 | @Override
136 | public void info(CharSequence content, Throwable error) {
137 | }
138 |
139 | @Override
140 | public void info(Throwable error) {
141 | }
142 |
143 | @Override
144 | public boolean isDebugEnabled() {
145 | return false;
146 | }
147 |
148 | @Override
149 | public boolean isErrorEnabled() {
150 | return false;
151 | }
152 |
153 | @Override
154 | public boolean isInfoEnabled() {
155 | return false;
156 | }
157 |
158 | @Override
159 | public boolean isWarnEnabled() {
160 | return false;
161 | }
162 |
163 | @Override
164 | public void warn(CharSequence content) {
165 | }
166 |
167 | @Override
168 | public void warn(CharSequence content, Throwable error) {
169 | }
170 |
171 | @Override
172 | public void warn(Throwable error) {
173 | }
174 | };
175 | }
176 |
177 | public void setLog(Log log) {
178 | this.log = log;
179 | }
180 |
181 | private String getDirName() {
182 | return crateRoot.getFileName().toString();
183 | }
184 |
185 | private String getProfile() {
186 | return params.release ? "release" : "debug";
187 | }
188 |
189 | public boolean hasCdylib() {
190 | try {
191 | TomlArray crateTypes = getCrateTypes();
192 | if (crateTypes == null) {
193 | return false;
194 | }
195 |
196 | for (int index = 0; index < crateTypes.size(); index++) {
197 | String crateType = crateTypes.getString(index);
198 | if ((crateType != null) && crateType.equals("cdylib")) {
199 | return true;
200 | }
201 | }
202 |
203 | return false;
204 | } catch (TomlInvalidTypeException e) {
205 | return false;
206 | }
207 | }
208 |
209 | private TomlArray getCrateTypes() {
210 | TomlArray crateTypes = cargoToml.getArray("lib.crate-type");
211 | if (crateTypes == null) {
212 | String crateTypeLegacyKey = "lib.crate_type";
213 | return cargoToml.getArray(crateTypeLegacyKey);
214 | }
215 | return crateTypes;
216 | }
217 |
218 | private String getCdylibName() throws MojoExecutionException {
219 | String name;
220 | try {
221 | name = cargoToml.getString("lib.name");
222 | } catch (TomlInvalidTypeException e) {
223 | throw new MojoExecutionException(
224 | "Failed to extract `lib.name` from Cargo.toml file: " +
225 | e.getMessage());
226 | }
227 |
228 | // The name might be missing, but the lib section might be present.
229 | if ((name == null) && hasCdylib()) {
230 | name = packageName;
231 | }
232 |
233 | return name;
234 | }
235 |
236 | private List getBinNames() throws MojoExecutionException {
237 | final List binNames = new java.util.ArrayList<>();
238 |
239 | String defaultBin = null;
240 | if (Files.exists(crateRoot.resolve("src").resolve("main.rs"))) {
241 | // Expecting default bin, given that there's no lib.
242 | defaultBin = packageName;
243 | binNames.add(defaultBin);
244 | }
245 |
246 | TomlArray bins;
247 | try {
248 | bins = cargoToml.getArray("bin");
249 | } catch (TomlInvalidTypeException e) {
250 | throw new MojoExecutionException(
251 | "Failed to extract `bin`s from Cargo.toml file: " +
252 | e.getMessage());
253 | }
254 |
255 | if (bins == null) {
256 | return binNames;
257 | }
258 |
259 | for (int index = 0; index < bins.size(); ++index) {
260 | final TomlTable bin = bins.getTable(index);
261 | if (bin == null) {
262 | throw new MojoExecutionException(
263 | "Failed to extract `bin`s from Cargo.toml file: " +
264 | "expected a `bin` table at index " + index);
265 | }
266 |
267 | String name;
268 | try {
269 | name = bin.getString("name");
270 | } catch (TomlInvalidTypeException e) {
271 | throw new MojoExecutionException(
272 | "Failed to extract `bin`s from Cargo.toml file: " +
273 | "expected a string at index " + index + " `name` key");
274 | }
275 |
276 | if (name == null) {
277 | throw new MojoExecutionException(
278 | "Failed to extract `bin`s from Cargo.toml file: " +
279 | "missing `name` key at `bin` with index " + index);
280 | }
281 |
282 | String path;
283 | try {
284 | path = bin.getString("path");
285 | } catch (TomlInvalidTypeException e) {
286 | throw new MojoExecutionException(
287 | "Failed to extract `bin`s from Cargo.toml file: " +
288 | "expected a string at index " + index + " `path` key");
289 | }
290 |
291 | // Handle special case where the default bin is renamed.
292 | if ((path != null) && path.equals("src/main.rs")) {
293 | defaultBin = name;
294 | binNames.remove(0);
295 | binNames.add(0, defaultBin);
296 | }
297 |
298 | // This `[[bin]]` entry just configures the default bin.
299 | // It's already been added.
300 | if (!name.equals(defaultBin)) {
301 | binNames.add(name);
302 | }
303 | }
304 |
305 | return binNames;
306 | }
307 |
308 | public List getArtifactPaths() throws MojoExecutionException {
309 | List paths = new ArrayList<>();
310 | final String profile = getProfile();
311 |
312 | final String libName = getCdylibName();
313 | if (libName != null) {
314 | final Path libPath = targetDir
315 | .resolve(profile)
316 | .resolve(pinLibName(libName));
317 | paths.add(libPath);
318 | }
319 |
320 | for (String binName : getBinNames()) {
321 | final Path binPath = targetDir
322 | .resolve(profile)
323 | .resolve(pinBinName(binName));
324 | paths.add(binPath);
325 | }
326 |
327 | return paths;
328 | }
329 |
330 | private String getCargoPath() {
331 | String path = params.cargoPath;
332 |
333 | final boolean isWindows = System.getProperty("os.name")
334 | .toLowerCase().startsWith("windows");
335 |
336 | // Expand "~" to user's home directory.
337 | // This works around a limitation of ProcessBuilder.
338 | if (!isWindows && path.startsWith("~/")) {
339 | path = System.getProperty("user.home") + path.substring(1);
340 | }
341 |
342 | return path;
343 | }
344 |
345 | private void runCommand(List args)
346 | throws IOException, InterruptedException, MojoExecutionException {
347 | final ProcessBuilder processBuilder = new ProcessBuilder(args);
348 | processBuilder.redirectErrorStream(true);
349 | processBuilder.environment().putAll(params.environmentVariables);
350 |
351 | // Set the current working directory for the cargo command.
352 | processBuilder.directory(crateRoot.toFile());
353 | final Process process = processBuilder.start();
354 | Executors.newSingleThreadExecutor().submit(() ->
355 | new BufferedReader(new InputStreamReader(process.getInputStream()))
356 | .lines()
357 | .forEach(log::info));
358 |
359 | final int exitCode = process.waitFor();
360 | if (exitCode != 0) {
361 | throw new MojoExecutionException(
362 | "Cargo command failed with exit code " + exitCode);
363 | }
364 | }
365 |
366 | private void cargo(List args) throws MojoExecutionException, MojoFailureException {
367 | String cargoPath = getCargoPath();
368 | final List cmd = new ArrayList<>();
369 | cmd.add(cargoPath);
370 | cmd.addAll(args);
371 | log.info("Working directory: " + crateRoot);
372 | if (!params.environmentVariables.isEmpty()) {
373 | log.info("Environment variables:");
374 | for (String key : params.environmentVariables.keySet()) {
375 | log.info(" " + key + "=" + Shlex.quote(
376 | params.environmentVariables.get(key)));
377 | }
378 | }
379 | log.info("Running: " + Shlex.quote(cmd));
380 | try {
381 | runCommand(cmd);
382 | } catch (IOException | InterruptedException e) {
383 | CargoInstalledChecker.INSTANCE.check(cargoPath);
384 | throw new MojoFailureException("Failed to invoke cargo", e);
385 | }
386 | }
387 |
388 | private void addCargoArgs(List args) {
389 | if (params.verbosity != null) {
390 | args.add(params.verbosity);
391 | }
392 |
393 | args.add("--target-dir");
394 | args.add(targetDir.toAbsolutePath().toString());
395 |
396 | if (params.release) {
397 | args.add("--release");
398 | }
399 |
400 | if (params.allFeatures) {
401 | args.add("--all-features");
402 | }
403 |
404 | if (params.noDefaultFeatures) {
405 | args.add("--no-default-features");
406 | }
407 |
408 | final String[] cleanedFeatures = params.cleanedFeatures();
409 | if (cleanedFeatures.length > 0) {
410 | args.add("--features");
411 | args.add(String.join(",", cleanedFeatures));
412 | }
413 |
414 | if (params.tests) {
415 | args.add("--tests");
416 | }
417 |
418 | if (params.extraArgs != null) {
419 | Collections.addAll(args, params.extraArgs);
420 | }
421 | }
422 |
423 | public void build() throws MojoExecutionException, MojoFailureException {
424 | List args = new ArrayList<>();
425 | args.add("build");
426 | addCargoArgs(args);
427 | cargo(args);
428 | }
429 |
430 | public void test() throws MojoExecutionException, MojoFailureException {
431 | List args = new ArrayList<>();
432 | args.add("test");
433 | addCargoArgs(args);
434 | cargo(args);
435 | }
436 |
437 | private Path resolveCopyToDir() throws MojoExecutionException {
438 |
439 | Path copyToDir = params.copyToDir;
440 |
441 | if (copyToDir == null) {
442 | return null;
443 | }
444 |
445 | if (params.copyWithPlatformDir) {
446 | copyToDir = copyToDir.resolve(Platform.RESOURCE_PREFIX);
447 | }
448 |
449 | if (!Files.exists(copyToDir, LinkOption.NOFOLLOW_LINKS)) {
450 | try {
451 | Files.createDirectories(copyToDir);
452 | } catch (IOException e) {
453 | throw new MojoExecutionException(
454 | "Failed to create directory " + copyToDir +
455 | ": " + e.getMessage(), e);
456 | }
457 | }
458 |
459 | if (!Files.isDirectory(copyToDir)) {
460 | throw new MojoExecutionException(copyToDir + " is not a directory");
461 | }
462 | return copyToDir;
463 | }
464 |
465 | public void copyArtifacts() throws MojoExecutionException {
466 | // Cargo nightly has support for `--out-dir`
467 | // which allows us to copy the artifacts directly to the desired path.
468 | // Once the feature is stabilized, copy the artifacts directly via:
469 | // args.add("--out-dir")
470 | // args.add(resolveCopyToDir());
471 | final Path copyToDir = resolveCopyToDir();
472 | if (copyToDir == null) {
473 | return;
474 | }
475 | final List artifactPaths = getArtifactPaths();
476 | log.info(
477 | "Copying " + getDirName() +
478 | "'s artifacts to " + Shlex.quote(
479 | copyToDir.toAbsolutePath().toString()));
480 |
481 | for (Path artifactPath : artifactPaths) {
482 | final Path fileName = artifactPath.getFileName();
483 | final Path destPath = copyToDir.resolve(fileName);
484 | try {
485 | Files.copy(
486 | artifactPath,
487 | destPath,
488 | StandardCopyOption.REPLACE_EXISTING);
489 | } catch (IOException e) {
490 | throw new MojoExecutionException(
491 | "Failed to copy " + artifactPath +
492 | " to " + copyToDir + ":" + e.getMessage());
493 | }
494 | log.info("Copied " + Shlex.quote(fileName.toString()));
495 | }
496 | }
497 |
498 | public static class Params {
499 | public String verbosity;
500 | public HashMap environmentVariables;
501 | public String cargoPath;
502 | public boolean release;
503 | public String[] features;
504 | public boolean allFeatures;
505 | public boolean noDefaultFeatures;
506 | public boolean tests;
507 | public String[] extraArgs;
508 | public Path copyToDir;
509 | public boolean copyWithPlatformDir;
510 |
511 | /**
512 | * Returns the features array with empty and null elements removed.
513 | */
514 | public String[] cleanedFeatures() {
515 | if ((features == null) || (features.length == 0)) {
516 | return new String[0];
517 | }
518 | List cleanedFeatures = new ArrayList<>();
519 | for (String feature : features) {
520 | if (feature != null) {
521 | feature = feature.trim();
522 | if (!feature.isEmpty()) {
523 | cleanedFeatures.add(feature);
524 | }
525 | }
526 | }
527 | return cleanedFeatures.toArray(new String[0]);
528 | }
529 | }
530 | }
531 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/main/java/io/questdb/maven/rust/Shlex.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import java.util.List;
28 | import java.util.regex.Pattern;
29 |
30 | public interface Shlex {
31 |
32 | /**
33 | * Escape a string for use in a shell command.
34 | *
35 | * @param s The string to escape.
36 | * @return The escaped string.
37 | * @see shlex.quote
38 | */
39 | static String quote(String s) {
40 | if (s.isEmpty())
41 | return "''";
42 | Pattern unsafe = Pattern.compile("[^\\w@%+=:,./-]");
43 | if (unsafe.matcher(s).find())
44 | return "'" + s.replace("'", "'\"'\"'") + "'";
45 | else
46 | return s;
47 | }
48 |
49 | static String quote(List args) {
50 | StringBuilder sb = new StringBuilder();
51 | for (String arg : args) {
52 | if (sb.length() > 0)
53 | sb.append(' ');
54 | sb.append(quote(arg));
55 | }
56 | return sb.toString();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/test/java/io/questdb/maven/rust/CrateTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * ___ _ ____ ____
3 | * / _ \ _ _ ___ ___| |_| _ \| __ )
4 | * | | | | | | |/ _ \/ __| __| | | | _ \
5 | * | |_| | |_| | __/\__ \ |_| |_| | |_) |
6 | * \__\_\\__,_|\___||___/\__|____/|____/
7 | *
8 | * Copyright (c) 2014-2019 Appsicle
9 | * Copyright (c) 2019-2023 QuestDB
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License");
12 | * you may not use this file except in compliance with the License.
13 | * You may obtain a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | * See the License for the specific language governing permissions and
21 | * limitations under the License.
22 | *
23 | ******************************************************************************/
24 |
25 | package io.questdb.maven.rust;
26 |
27 | import io.questdb.jar.jni.Platform;
28 | import org.apache.maven.plugin.MojoExecutionException;
29 | import org.junit.After;
30 | import org.junit.Before;
31 | import org.junit.Rule;
32 | import org.junit.Test;
33 | import org.junit.rules.TemporaryFolder;
34 |
35 | import java.io.IOException;
36 | import java.io.PrintWriter;
37 | import java.nio.file.Files;
38 | import java.nio.file.Path;
39 | import java.util.HashMap;
40 | import java.util.List;
41 |
42 | import static org.junit.Assert.*;
43 |
44 |
45 | public class CrateTest {
46 |
47 | @Rule
48 | public TemporaryFolder tmpDir = new TemporaryFolder();
49 | private Path targetRootDir;
50 |
51 | public static boolean isWindows() {
52 | return System.getProperty("os.name").toLowerCase().contains("win");
53 | }
54 |
55 | public static boolean isMac() {
56 | return System.getProperty("os.name").toLowerCase().contains("mac");
57 | }
58 |
59 | @Before
60 | public void before() throws IOException {
61 | tmpDir = new TemporaryFolder();
62 | tmpDir.create();
63 | targetRootDir = tmpDir.newFolder(
64 | "target",
65 | "rust-maven-plugin").toPath();
66 | }
67 |
68 | @After
69 | public void after() {
70 | tmpDir.delete();
71 | }
72 |
73 | private void doTestDefaultBin(
74 | boolean release,
75 | boolean copyTo,
76 | boolean copyWithPlatformDir) throws Exception {
77 | // Setting up mock Rust project directory.
78 | final MockCrate mock = new MockCrate(
79 | "test-bin-1",
80 | release ? "release" : "debug");
81 | mock.writeCargoToml(
82 | "[package]\n" +
83 | "name = \"test-bin\"\n" +
84 | "version = \"0.1.0\"\n" +
85 | "edition = \"2021\"\n");
86 | mock.touchSrc("main.rs");
87 | final Path mockBinPath = mock.touchBin("test-bin");
88 | assertTrue(Files.exists(mockBinPath));
89 |
90 | // Configuring the build job.
91 | final Crate.Params params = new Crate.Params();
92 | params.release = release;
93 | if (copyTo) {
94 | params.copyToDir = tmpDir.newFolder("bin_dest_dir").toPath();
95 | }
96 | params.copyWithPlatformDir = copyWithPlatformDir;
97 |
98 | final Crate crate = new Crate(
99 | mock.crateRoot,
100 | targetRootDir,
101 | params);
102 |
103 | // Checking the expected paths it generates.
104 | final List artifacts = crate.getArtifactPaths();
105 | assertEquals(1, artifacts.size());
106 | assertEquals(mockBinPath, artifacts.get(0));
107 |
108 | if (!copyTo) {
109 | return;
110 | }
111 |
112 | crate.copyArtifacts();
113 |
114 | Path expectedBinPath = params.copyToDir;
115 | if (copyWithPlatformDir) {
116 | expectedBinPath = expectedBinPath.resolve(
117 | Platform.RESOURCE_PREFIX);
118 | }
119 | expectedBinPath = expectedBinPath.resolve(mockBinPath.getFileName());
120 |
121 | assertTrue(Files.exists(expectedBinPath));
122 | }
123 |
124 | @Test
125 | public void testDefaultBinDebugNoCopyTo() throws Exception {
126 | doTestDefaultBin(false, false, false);
127 | }
128 |
129 | @Test
130 | public void testDefaultBinReleaseNoCopyTo() throws Exception {
131 | // Last arg to `true` should be ignored.
132 | doTestDefaultBin(true, false, true);
133 | }
134 |
135 | @Test
136 | public void testDefaultBinDebugCopyTo() throws Exception {
137 | doTestDefaultBin(false, true, false);
138 | }
139 |
140 | @Test
141 | public void testDefaultBinReleaseCopyTo() throws Exception {
142 | doTestDefaultBin(true, true, false);
143 | }
144 |
145 | @Test
146 | public void testDefaultBinDebugCopyToPlatformDir() throws Exception {
147 | doTestDefaultBin(false, true, true);
148 | }
149 |
150 | @Test
151 | public void testDefaultBinReleaseCopyToPlatformDir() throws Exception {
152 | doTestDefaultBin(true, true, true);
153 | }
154 |
155 | private void doTestCdylib(boolean release, boolean copyTo, boolean copyWithPlatformDir) throws Exception {
156 | // Setting up mock Rust project directory.
157 | final MockCrate mock = new MockCrate(
158 | "test-lib-1",
159 | release ? "release" : "debug");
160 | mock.writeCargoToml(
161 | "[package]\n" +
162 | "name = \"test-lib\"\n" +
163 | "version = \"0.1.0\"\n" +
164 | "edition = \"2021\"\n" +
165 | "\n" +
166 | "[lib]\n" +
167 | "crate-type = [\"cdylib\"]\n");
168 | mock.touchSrc("lib.rs");
169 | final Path cdylibPath = mock.touchLib("test-lib");
170 |
171 | // Configuring the build job.
172 | final Crate.Params params = new Crate.Params();
173 | params.release = release;
174 | if (copyTo) {
175 | params.copyToDir = tmpDir.newFolder("lib_dest_dir").toPath();
176 | }
177 | params.copyWithPlatformDir = copyWithPlatformDir;
178 |
179 | final Crate crate = new Crate(
180 | mock.crateRoot,
181 | targetRootDir,
182 | params);
183 |
184 | // Checking the expected paths it generates.
185 | final List artifacts = crate.getArtifactPaths();
186 | assertEquals(1, artifacts.size());
187 | assertEquals(cdylibPath, artifacts.get(0));
188 |
189 | if (!copyTo) {
190 | return;
191 | }
192 |
193 | crate.copyArtifacts();
194 |
195 | Path expectedLibPath = params.copyToDir;
196 | if (copyWithPlatformDir) {
197 | expectedLibPath = expectedLibPath.resolve(Platform.RESOURCE_PREFIX);
198 | }
199 | expectedLibPath = expectedLibPath.resolve(cdylibPath.getFileName());
200 |
201 | assertTrue(Files.exists(expectedLibPath));
202 | }
203 |
204 | @Test
205 | public void testCdylibDebug() throws Exception {
206 | // Last arg to `true` should be ignored.
207 | doTestCdylib(false, false, true);
208 | }
209 |
210 | @Test
211 | public void testCdylibDebugCopyTo() throws Exception {
212 | doTestCdylib(false, true, false);
213 | }
214 |
215 | @Test
216 | public void testCdylibReleaseCopyTo() throws Exception {
217 | doTestCdylib(true, true, false);
218 | }
219 |
220 | @Test
221 | public void testCdylibDebugCopyToPlatformDir() throws Exception {
222 | doTestCdylib(false, true, true);
223 | }
224 |
225 | @Test
226 | public void testCdylibReleaseCopyToPlatformDir() throws Exception {
227 | doTestCdylib(true, true, true);
228 | }
229 |
230 | @Test
231 | public void testCustomCdylibName() throws Exception {
232 | // Setting up mock Rust project directory.
233 | final MockCrate mock = new MockCrate(
234 | "test-lib-1",
235 | "debug");
236 | mock.writeCargoToml(
237 | "[package]\n" +
238 | "name = \"test-lib\"\n" +
239 | "version = \"0.1.0\"\n" +
240 | "edition = \"2021\"\n" +
241 | "\n" +
242 | "[lib]\n" +
243 | "name = \"mylib\"\n" +
244 | "crate-type = [\"cdylib\"]\n");
245 | mock.touchSrc("lib.rs");
246 | final Path cdylibPath = mock.touchLib("mylib");
247 |
248 | // Configuring the build job.
249 | final Crate.Params params = new Crate.Params();
250 | params.release = false;
251 | params.copyToDir = tmpDir.newFolder("lib_dest_dir").toPath();
252 | params.copyWithPlatformDir = true;
253 |
254 | final Crate crate = new Crate(
255 | mock.crateRoot,
256 | targetRootDir,
257 | params);
258 |
259 | // Checking the expected paths it generates.
260 | final List artifacts = crate.getArtifactPaths();
261 | assertEquals(1, artifacts.size());
262 | assertEquals(cdylibPath, artifacts.get(0));
263 |
264 | crate.copyArtifacts();
265 |
266 | Path expectedLibPath = params.copyToDir
267 | .resolve(Platform.RESOURCE_PREFIX)
268 | .resolve(cdylibPath.getFileName());
269 |
270 | assertTrue(Files.exists(expectedLibPath));
271 | }
272 |
273 | @Test
274 | public void testCdylibLegacyKeyCrateType() throws Exception {
275 | // Setting up mock Rust project directory.
276 | final MockCrate mock = new MockCrate(
277 | "test-lib-1",
278 | "debug");
279 | mock.writeCargoToml(
280 | "[package]\n" +
281 | "name = \"test-lib\"\n" +
282 | "version = \"0.1.0\"\n" +
283 | "edition = \"2021\"\n" +
284 | "\n" +
285 | "[lib]\n" +
286 | "crate_type = [\"cdylib\"]\n");
287 | mock.touchSrc("lib.rs");
288 | final Path cdylibPath = mock.touchLib("test-lib");
289 |
290 | // Configuring the build job.
291 |
292 | final Crate crate = new Crate(
293 | mock.crateRoot,
294 | targetRootDir,
295 | new Crate.Params());
296 |
297 | // Checking the expected paths it generates.
298 | final List artifacts = crate.getArtifactPaths();
299 |
300 | assertEquals(1, artifacts.size());
301 | assertEquals(cdylibPath, artifacts.get(0));
302 | }
303 |
304 | @Test
305 | public void testDefaultBinAndCdylib() throws Exception {
306 | // Setting up mock Rust project directory.
307 | final MockCrate mock = new MockCrate(
308 | "test42",
309 | "debug");
310 | mock.writeCargoToml(
311 | "[package]\n" +
312 | "name = \"test42\"\n" +
313 | "version = \"0.1.0\"\n" +
314 | "edition = \"2021\"\n" +
315 | "\n" +
316 | "[lib]\n" +
317 | "crate-type = [\"cdylib\"]\n");
318 | mock.touchSrc("lib.rs");
319 | mock.touchSrc("main.rs");
320 | final Path cdylibPath = mock.touchLib("test42");
321 | final Path binPath = mock.touchBin("test42");
322 |
323 | // Configuring the build job.
324 | final Crate.Params params = new Crate.Params();
325 | params.release = false;
326 | params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
327 |
328 | final Crate crate = new Crate(
329 | mock.crateRoot,
330 | targetRootDir,
331 | params);
332 |
333 | // Checking the expected paths it generates.
334 | final List artifacts = crate.getArtifactPaths();
335 | assertEquals(2, artifacts.size());
336 | assertEquals(cdylibPath, artifacts.get(0));
337 | assertEquals(binPath, artifacts.get(1));
338 |
339 | crate.copyArtifacts();
340 |
341 | Path expectedLibPath = params.copyToDir
342 | .resolve(cdylibPath.getFileName());
343 | Path expectedBinPath = params.copyToDir
344 | .resolve(binPath.getFileName());
345 |
346 | assertTrue(Files.exists(expectedLibPath));
347 | assertTrue(Files.exists(expectedBinPath));
348 | }
349 |
350 | @Test
351 | public void testRenamedDefaultBin() throws Exception {
352 | // Setting up mock Rust project directory.
353 | final MockCrate mock = new MockCrate(
354 | "test-custom-name-bin",
355 | "debug");
356 | mock.writeCargoToml(
357 | "[package]\n" +
358 | "name = \"test-custom-name-bin\"\n" +
359 | "version = \"0.1.0\"\n" +
360 | "edition = \"2021\"\n" +
361 | "\n" +
362 | "[[bin]]\n" +
363 | "name = \"test43\"\n" +
364 | "path = \"src/main.rs\"\n");
365 | mock.touchSrc("main.rs");
366 | final Path binPath = mock.touchBin("test43");
367 |
368 | // Configuring the build job.
369 | final Crate.Params params = new Crate.Params();
370 | params.release = false;
371 | params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
372 |
373 | final Crate crate = new Crate(
374 | mock.crateRoot,
375 | targetRootDir,
376 | params);
377 |
378 | // Checking the expected paths it generates.
379 | final List artifacts = crate.getArtifactPaths();
380 | assertEquals(1, artifacts.size());
381 | assertEquals(binPath, artifacts.get(0));
382 |
383 | crate.copyArtifacts();
384 |
385 | Path expectedBinPath = params.copyToDir
386 | .resolve(binPath.getFileName());
387 |
388 | assertTrue(Files.exists(expectedBinPath));
389 | }
390 |
391 | @Test
392 | public void testConfiguredDefaultBin() throws Exception {
393 | // Setting up mock Rust project directory.
394 | final MockCrate mock = new MockCrate(
395 | "test-configured-bin",
396 | "debug");
397 | mock.writeCargoToml(
398 | "[package]\n" +
399 | "name = \"test-configured-bin\"\n" +
400 | "version = \"0.1.0\"\n" +
401 | "edition = \"2021\"\n" +
402 | "\n" +
403 | "[[bin]]\n" +
404 | "name = \"test-configured-bin\"\n" +
405 | "path = \"src/main.rs\"\n");
406 | mock.touchSrc("main.rs");
407 | final Path binPath = mock.touchBin("test-configured-bin");
408 |
409 | // Configuring the build job.
410 | final Crate.Params params = new Crate.Params();
411 | params.release = false;
412 | params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
413 |
414 | final Crate crate = new Crate(
415 | mock.crateRoot,
416 | targetRootDir,
417 | params);
418 |
419 | // Checking the expected paths it generates.
420 | final List artifacts = crate.getArtifactPaths();
421 | assertEquals(1, artifacts.size());
422 | assertEquals(binPath, artifacts.get(0));
423 |
424 | crate.copyArtifacts();
425 |
426 | Path expectedBinPath = params.copyToDir
427 | .resolve(binPath.getFileName());
428 |
429 | assertTrue(Files.exists(expectedBinPath));
430 | }
431 |
432 | @Test
433 | public void testCdylibDefaultBinAndExplicitBin() throws Exception {
434 | // Setting up mock Rust project directory.
435 | final MockCrate mock = new MockCrate("mixed", "release");
436 | mock.writeCargoToml(
437 | "[package]\n" +
438 | "name = \"mixed\"\n" +
439 | "version = \"0.1.0\"\n" +
440 | "edition = \"2021\"\n" +
441 | "\n" +
442 | "[lib]\n" +
443 | "crate-type = [\"cdylib\"]\n" +
444 | "\n" +
445 | "[[bin]]\n" +
446 | "name = \"extra-bin\"\n" +
447 | "path = \"src/extra-bin/main.rs\"\n");
448 | mock.touchSrc("lib.rs");
449 | mock.touchSrc("main.rs");
450 | mock.touchSrc("extra-bin", "main.rs");
451 |
452 | final Path cdylibPath = mock.touchLib("mixed");
453 | final Path binPath = mock.touchBin("mixed");
454 | final Path extraBinPath = mock.touchBin("extra-bin");
455 |
456 | // Configuring the build job.
457 | final Crate.Params params = new Crate.Params();
458 | params.release = true;
459 | params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
460 |
461 | final Crate crate = new Crate(
462 | mock.crateRoot,
463 | targetRootDir,
464 | params);
465 |
466 | // Checking the expected paths it generates.
467 | final List artifacts = crate.getArtifactPaths();
468 | assertEquals(3, artifacts.size());
469 | assertEquals(cdylibPath, artifacts.get(0));
470 | assertEquals(binPath, artifacts.get(1));
471 | assertEquals(extraBinPath, artifacts.get(2));
472 |
473 | crate.copyArtifacts();
474 |
475 | Path expectedLibPath = params.copyToDir
476 | .resolve(cdylibPath.getFileName());
477 | Path expectedBinPath = params.copyToDir
478 | .resolve(binPath.getFileName());
479 | Path expectedExtraBinPath = params.copyToDir
480 | .resolve(extraBinPath.getFileName());
481 |
482 | assertTrue(Files.exists(expectedLibPath));
483 | assertTrue(Files.exists(expectedBinPath));
484 | assertTrue(Files.exists(expectedExtraBinPath));
485 | }
486 |
487 | public static Crate.Params defaultParams() {
488 | final Crate.Params params = new Crate.Params();
489 | params.release = false;
490 | params.features = new String[0];
491 | params.cargoPath = "cargo";
492 | params.environmentVariables = new HashMap<>();
493 | return params;
494 | }
495 |
496 | @Test
497 | public void testTwoFeatures() throws Exception {
498 | final String crateName = "two-features";
499 | final MockCrate mock = new MockCrate(crateName, "release");
500 | mock.writeCargoToml(
501 | "[package]\n" +
502 | "name = \"" + crateName + "\"\n" +
503 | "version = \"0.1.0\"\n" +
504 | "edition = \"2021\"\n" +
505 | "\n" +
506 | "[features]\n" +
507 | "feature1 = []\n" +
508 | "feature2 = []\n");
509 | final Path libSrc = mock.touchSrc("lib.rs");
510 | writeFile(libSrc, "pub fn foo() -> i32 { 42 }");
511 |
512 | final Crate.Params params = defaultParams();
513 | params.features = new String[]{"feature1", "feature2"};
514 |
515 | final Crate crate = new Crate(
516 | mock.crateRoot,
517 | targetRootDir,
518 | params);
519 | crate.setLog(TestLog.INSTANCE);
520 | crate.build();
521 | }
522 |
523 | @Test
524 | public void testEmptyFeatures() throws Exception {
525 | final String crateName = "two-features";
526 | final MockCrate mock = new MockCrate(crateName, "release");
527 | mock.writeCargoToml(
528 | "[package]\n" +
529 | "name = \"" + crateName + "\"\n" +
530 | "version = \"0.1.0\"\n" +
531 | "edition = \"2021\"\n" +
532 | "\n" +
533 | "[features]\n" +
534 | "feature1 = []\n" +
535 | "feature2 = []\n");
536 | final Path libSrc = mock.touchSrc("lib.rs");
537 | writeFile(libSrc, "pub fn foo() -> i32 { 42 }");
538 |
539 | final Crate.Params params = defaultParams();
540 | params.features = new String[]{"", ""};
541 | params.release = true;
542 |
543 | final Crate crate = new Crate(
544 | mock.crateRoot,
545 | targetRootDir,
546 | params);
547 | crate.setLog(TestLog.INSTANCE);
548 | crate.build();
549 | }
550 |
551 | @Test
552 | public void testBadCargoToml() throws Exception {
553 | // Setting up mock Rust project directory.
554 | final MockCrate mock = new MockCrate("bad-toml", "release");
555 | mock.writeCargoToml(
556 | // "[package]\n" + MISSING!
557 | "name = \"bad-toml\"\n" +
558 | "version = \"0.1.0\"\n" +
559 | "edition = \"2021\"\n");
560 | mock.touchSrc("main.rs");
561 | mock.touchBin("bad-toml");
562 |
563 | // Configuring the build job.
564 | final Crate.Params params = new Crate.Params();
565 | params.release = true;
566 | params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
567 |
568 | assertThrows(
569 | MojoExecutionException.class,
570 | () -> new Crate(mock.crateRoot, targetRootDir, params));
571 | }
572 |
573 | private static void writeFile(Path dest, String contents) throws IOException {
574 | try (PrintWriter w = new PrintWriter(dest.toFile(), "UTF-8")) {
575 | w.write(contents);
576 | }
577 | }
578 |
579 | class MockCrate {
580 | private final String name;
581 | private final String profile;
582 | private final Path crateRoot;
583 |
584 | public MockCrate(String name, String profile) throws IOException {
585 | this.name = name;
586 | this.profile = profile;
587 | this.crateRoot = tmpDir.newFolder(name).toPath();
588 | }
589 |
590 | public void writeCargoToml(String contents) throws IOException {
591 | Path cargoToml = crateRoot.resolve("Cargo.toml");
592 | writeFile(cargoToml, contents);
593 | }
594 |
595 | public Path touchBin(String name) throws IOException {
596 | final Path mockBinPath = targetRootDir
597 | .resolve(this.name)
598 | .resolve(profile)
599 | .resolve(name + (isWindows() ? ".exe" : ""));
600 | if (!Files.exists(mockBinPath.getParent())) {
601 | Files.createDirectories(mockBinPath.getParent());
602 | }
603 | Files.createFile(mockBinPath);
604 | return mockBinPath;
605 | }
606 |
607 | public Path touchSrc(String... pathComponents) throws IOException {
608 | Path srcPath = crateRoot.resolve("src");
609 | for (String pathComponent : pathComponents) {
610 | srcPath = srcPath.resolve(pathComponent);
611 | }
612 | if (!Files.exists(srcPath.getParent())) {
613 | Files.createDirectories(srcPath.getParent());
614 | }
615 | Files.createFile(srcPath);
616 | return srcPath;
617 | }
618 |
619 | public Path touchLib(String name) throws IOException {
620 | final String prefix = isWindows() ? "" : "lib";
621 | final String suffix =
622 | isWindows() ? ".dll" :
623 | isMac() ? ".dylib"
624 | : ".so";
625 | final String libName = prefix + name.replace('-', '_') + suffix;
626 | final Path libPath = targetRootDir
627 | .resolve(this.name)
628 | .resolve(profile)
629 | .resolve(libName);
630 | if (!Files.exists(libPath.getParent())) {
631 | Files.createDirectories(libPath.getParent());
632 | }
633 | Files.createFile(libPath);
634 | return libPath;
635 | }
636 | }
637 | }
638 |
--------------------------------------------------------------------------------
/rust-maven-plugin/src/test/java/io/questdb/maven/rust/TestLog.java:
--------------------------------------------------------------------------------
1 | package io.questdb.maven.rust;
2 |
3 | import org.apache.maven.plugin.logging.Log;
4 |
5 | public class TestLog implements Log {
6 | public static final TestLog INSTANCE = new TestLog();
7 |
8 | @Override
9 | public boolean isDebugEnabled() {
10 | return true;
11 | }
12 |
13 | @Override
14 | public void debug(CharSequence charSequence) {
15 | System.out.println("[DEBUG] " + charSequence);
16 | }
17 |
18 | @Override
19 | public void debug(CharSequence charSequence, Throwable throwable) {
20 | System.out.println("[DEBUG] msg={" + charSequence + "}, throwable={" + throwable + "}");
21 | }
22 |
23 | @Override
24 | public void debug(Throwable throwable) {
25 | System.out.println("[DEBUG] throwable={" + throwable + "}");
26 | }
27 |
28 | @Override
29 | public boolean isInfoEnabled() {
30 | return true;
31 | }
32 |
33 | @Override
34 | public void info(CharSequence charSequence) {
35 | System.out.println("[INFO] " + charSequence);
36 | }
37 |
38 | @Override
39 | public void info(CharSequence charSequence, Throwable throwable) {
40 | System.out.println("[INFO] msg={" + charSequence + "}, throwable={" + throwable + "}");
41 | }
42 |
43 | @Override
44 | public void info(Throwable throwable) {
45 | System.out.println("[INFO] throwable={" + throwable + "}");
46 | }
47 |
48 | @Override
49 | public boolean isWarnEnabled() {
50 | return true;
51 | }
52 |
53 | @Override
54 | public void warn(CharSequence charSequence) {
55 | System.out.println("[WARN] " + charSequence);
56 | }
57 |
58 | @Override
59 | public void warn(CharSequence charSequence, Throwable throwable) {
60 | System.out.println("[WARN] msg={" + charSequence + "}, throwable={" + throwable + "}");
61 | }
62 |
63 | @Override
64 | public void warn(Throwable throwable) {
65 | System.out.println("[WARN] throwable={" + throwable + "}");
66 | }
67 |
68 | @Override
69 | public boolean isErrorEnabled() {
70 | return true;
71 | }
72 |
73 | @Override
74 | public void error(CharSequence charSequence) {
75 | System.out.println("[ERROR] " + charSequence);
76 | }
77 |
78 | @Override
79 | public void error(CharSequence charSequence, Throwable throwable) {
80 | System.out.println("[ERROR] msg={" + charSequence + "}, throwable={" + throwable + "}");
81 | }
82 |
83 | @Override
84 | public void error(Throwable throwable) {
85 | System.out.println("[ERROR] throwable={" + throwable + "}");
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/update_doc_version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | import pathlib
5 | import difflib
6 | import re
7 |
8 |
9 | # Table of file names to strings to match and replace.
10 | # The special $$VERSION$$ string will be replaced with the version number
11 | # during both matching and replacing.
12 | # All paths are relative to the root of the repository.
13 | MATCHERS = {
14 | 'README.md':
15 | ["rust-maven-plugin:$$VERSION$$:build",
16 | "$$VERSION$$"],
17 | }
18 |
19 |
20 | def parse_args():
21 | parser = argparse.ArgumentParser()
22 | parser.add_argument('version', help='New version number')
23 | parser.add_argument('-p', '--preview', action='store_true',
24 | help='Preview changes without making them')
25 | parser.add_argument('-n', '--num-lines', type=int, default=3,
26 | help='Number of lines of context to show in diff')
27 | parser.add_argument('--no-color', action='store_true',
28 | help='Disable color-coded diff output')
29 | return parser.parse_args()
30 |
31 |
32 | def get_old_vers():
33 | with open('README.md', 'r', encoding='utf-8') as f:
34 | contents = f.read()
35 | pat = re.compile(r'.*rust-maven-plugin:(.*?):build.*')
36 | for line in contents.splitlines():
37 | match = pat.search(line)
38 | if match:
39 | return match.group(1)
40 | raise RuntimeError('Could not find old version in README.md')
41 |
42 |
43 | def main():
44 | args = parse_args()
45 | old_vers = get_old_vers()
46 | new_vers = args.version
47 | old_contents = {}
48 | new_contents = {}
49 | for filename, matchers in MATCHERS.items():
50 | file_path = pathlib.Path(filename)
51 | with open(file_path, 'r', encoding='utf-8') as f:
52 | contents = f.read()
53 | old_contents[file_path] = contents
54 |
55 | for matcher in matchers:
56 | old_version_text = matcher.replace('$$VERSION$$', old_vers)
57 | new_version_text = matcher.replace('$$VERSION$$', new_vers)
58 | if old_version_text not in contents:
59 | raise RuntimeError(
60 | 'Could not find "{}" in file "{}"'.format(
61 | old_version_text, file_path))
62 | contents = contents.replace(old_version_text, new_version_text)
63 |
64 | new_contents[file_path] = contents
65 |
66 | for file_path, new_text in new_contents.items():
67 | if args.preview:
68 | old_text = old_contents[file_path]
69 | filename = str(file_path)
70 | colors = not args.no_color
71 | if colors:
72 | green = '\x1b[38;5;16;48;5;2m'
73 | red = '\x1b[38;5;16;48;5;1m'
74 | end = '\x1b[0m'
75 | else:
76 | green = ''
77 | red = ''
78 | end = ''
79 | diff = difflib.unified_diff(
80 | old_text.splitlines(keepends=True),
81 | new_text.splitlines(keepends=True),
82 | fromfile=filename,
83 | tofile=filename,
84 | lineterm='',
85 | n=args.num_lines)
86 | if colors:
87 | for line in diff:
88 | if line.startswith('+') and not line.startswith('+++'):
89 | print(green + line + end, end='')
90 | elif line.startswith('-') and not line.startswith('---'):
91 | print(red + line + end, end='')
92 | else:
93 | print(line, end='')
94 | else:
95 | for line in diff:
96 | print(line, end='')
97 | else:
98 | with open(file_path, 'w', encoding='utf-8') as f:
99 | f.write(new_text)
100 |
101 |
102 | if __name__ == '__main__':
103 | main()
104 |
105 |
--------------------------------------------------------------------------------