diff --git a/.editorconfig b/.editorconfig index 1f494b0c..fec076ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,6 @@ end_of_line = lf [*.java] indent_size = 4 max_line_length = 120 + +[*.rs] +indent_size = 4 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 323a962e..e0d3254a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,6 +32,9 @@ jobs: java-version: ${{ matrix.jdk }} distribution: 'temurin' cache: 'maven' + #- uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: ./update-rust-jni-libs.sh -r + if: ${{ matrix.os.image == 'macos-latest' }} - name: test run: mvn -B clean test "-Dwatch.mac.backend=${{ matrix.os.mac-backend }}" diff --git a/.gitignore b/.gitignore index 5ef0a829..f0df1881 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ replay_pid* # release plugin state files /pom.xml.releaseBackup -/release.properties \ No newline at end of file +/release.properties diff --git a/pom.xml b/pom.xml index 53c096f7..fe7fc25e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,13 +74,20 @@ 3.49.5 5.13.4 2.25.1 - 5.17.0 11 11 fsevents + + + src/main/resources + + src/main/rust/**/*.* + + + org.apache.maven.plugins @@ -147,6 +154,9 @@ + + DOUBLESLASH_STYLE + @@ -170,6 +180,11 @@ + + + src/main/resources/** + + org.apache.maven.plugins @@ -229,16 +244,6 @@ ${log4j.version} test - - net.java.dev.jna - jna - ${jna.version} - - - net.java.dev.jna - jna-platform - ${jna.version} - diff --git a/src/main/java/engineering/swat/watch/impl/jdk/JDKPoller.java b/src/main/java/engineering/swat/watch/impl/jdk/JDKPoller.java index 22f947bb..0a1c4a03 100644 --- a/src/main/java/engineering/swat/watch/impl/jdk/JDKPoller.java +++ b/src/main/java/engineering/swat/watch/impl/jdk/JDKPoller.java @@ -56,6 +56,7 @@ import engineering.swat.watch.DaemonThreadPool; import engineering.swat.watch.impl.mac.MacWatchService; +import engineering.swat.watch.impl.mac.NativeLibrary; import engineering.swat.watch.impl.util.SubscriptionKey; /** @@ -189,7 +190,7 @@ public Watchable newWatchable(Path path) { static final Platform CURRENT = current(); // Assumption: the platform doesn't change private static Platform current() { - if (com.sun.jna.Platform.isMac()) { + if (NativeLibrary.isMac()) { var key = "engineering.swat.java-watch.mac"; var val = System.getProperty(key); if (val != null) { diff --git a/src/main/java/engineering/swat/watch/impl/mac/NativeEventHandler.java b/src/main/java/engineering/swat/watch/impl/mac/NativeEventHandler.java index bf3e88f4..5fe5297a 100644 --- a/src/main/java/engineering/swat/watch/impl/mac/NativeEventHandler.java +++ b/src/main/java/engineering/swat/watch/impl/mac/NativeEventHandler.java @@ -26,8 +26,13 @@ */ package engineering.swat.watch.impl.mac; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; + +import java.nio.file.Path; import java.nio.file.WatchEvent; -import java.nio.file.WatchEvent.Kind; import org.checkerframework.checker.nullness.qual.Nullable; @@ -51,5 +56,36 @@ */ @FunctionalInterface interface NativeEventHandler { - void handle(Kind kind, @Nullable T context); + void handle(java.nio.file.WatchEvent.Kind kind, @Nullable T context); + + default void handle(int kindOrdinal, String rootPath, String relativePath) { + if (kindOrdinal < Kind.values().length) { + var kind = Kind.values()[kindOrdinal]; + switch (kind) { + case CREATE: + handle(ENTRY_CREATE, toContext(rootPath, relativePath)); + break; + case MODIFY: + handle(ENTRY_MODIFY, toContext(rootPath, relativePath)); + break; + case DELETE: + handle(ENTRY_DELETE, toContext(rootPath, relativePath)); + break; + case OVERFLOW: + handle(OVERFLOW, null); + break; + } + } + } + + static Path toContext(String rootPath, String relativePath) { + return Path.of(rootPath).relativize(Path.of(relativePath)); + } +} + +enum Kind { // Order of values needs to be consistent with enum `Kind` in Rust + OVERFLOW, + CREATE, + DELETE, + MODIFY; } diff --git a/src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java b/src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java index 58856731..25b6071d 100644 --- a/src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java +++ b/src/main/java/engineering/swat/watch/impl/mac/NativeEventStream.java @@ -26,45 +26,13 @@ */ package engineering.swat.watch.impl.mac; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.FILE_EVENTS; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.NO_DEFER; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.WATCH_ROOT; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_CREATED; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_INODE_META_MOD; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_MODIFIED; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_REMOVED; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.ITEM_RENAMED; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag.MUST_SCAN_SUB_DIRS; -import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; -import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; -import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; -import static java.nio.file.StandardWatchEventKinds.OVERFLOW; - import java.io.Closeable; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; - -import org.checkerframework.checker.nullness.qual.Nullable; - -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.mac.CoreFoundation; -import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef; -import com.sun.jna.platform.mac.CoreFoundation.CFIndex; -import com.sun.jna.platform.mac.CoreFoundation.CFStringRef; - -import engineering.swat.watch.impl.mac.apis.DispatchObjects; -import engineering.swat.watch.impl.mac.apis.DispatchQueue; -import engineering.swat.watch.impl.mac.apis.FileSystemEvents; -import engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCallback; -// Note: This file is designed to be the only place in this package where JNA is -// used and/or the native APIs are invoked. If the need to do so arises outside -// this file, consider extending this file to offer the required services -// without exposing JNA and/or the native APIs. +// Note: This file is designed to be the only place in this package where native +// APIs are invoked. If the need to do so arises outside this file, consider +// extending this file to offer the required services without the native APIs. /** *

@@ -79,23 +47,14 @@ *

*/ class NativeEventStream implements Closeable { - - // Native APIs - private static final CoreFoundation CF = CoreFoundation.INSTANCE; - private static final DispatchObjects DO = DispatchObjects.INSTANCE; - private static final DispatchQueue DQ = DispatchQueue.INSTANCE; - private static final FileSystemEvents FSE = FileSystemEvents.INSTANCE; - - // Native memory - private @Nullable FSEventStreamCallback callback; // Keep reference to avoid premature GC'ing - private @Nullable Pointer stream; - private @Nullable Pointer queue; - // Note: These fields aren't volatile, as all reads/write from/to them are - // inside synchronized blocks. Be careful to not break this invariant. + static { + NativeLibrary.load(); + } private final Path path; private final NativeEventHandler handler; private volatile boolean closed; + private volatile long nativeWatch; public NativeEventStream(Path path, NativeEventHandler handler) throws IOException { this.path = path.toRealPath(); // Resolve symbolic links @@ -110,88 +69,7 @@ public synchronized void open() { closed = false; } - // Allocate native memory - callback = createCallback(); - stream = createFSEventStream(callback); - queue = createDispatchQueue(); - - // Start the stream - var streamNonNull = stream; - if (streamNonNull != null) { - FSE.FSEventStreamSetDispatchQueue(streamNonNull, queue); - FSE.FSEventStreamStart(streamNonNull); - } - } - - private FSEventStreamCallback createCallback() { - return new FSEventStreamCallback() { - @Override - public void callback(Pointer streamRef, Pointer clientCallBackInfo, - long numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds) { - // This function is called each time native events are issued by - // macOS. The purpose of this function is to perform the minimal - // amount of processing to hide the native APIs from downstream - // consumers, who are offered native events via `handler`. - - var paths = eventPaths.getStringArray(0, (int) numEvents); - var flags = eventFlags.getIntArray(0, (int) numEvents); - - for (var i = 0; i < numEvents; i++) { - var context = path.relativize(Path.of(paths[i])); - - // Note: Multiple "physical" native events might be - // coalesced into a single "logical" native event, so the - // following series of checks should be if-statements - // (instead of if/else-statements). - if (any(flags[i], ITEM_CREATED.mask)) { - handler.handle(ENTRY_CREATE, context); - } - if (any(flags[i], ITEM_REMOVED.mask)) { - handler.handle(ENTRY_DELETE, context); - } - if (any(flags[i], ITEM_MODIFIED.mask | ITEM_INODE_META_MOD.mask)) { - handler.handle(ENTRY_MODIFY, context); - } - if (any(flags[i], MUST_SCAN_SUB_DIRS.mask)) { - handler.handle(OVERFLOW, null); - } - if (any(flags[i], ITEM_RENAMED.mask)) { - // For now, check if the file exists to determine if the - // event pertains to the target of the rename (if it - // exists) or to the source (else). This is an - // approximation. It might be more accurate to maintain - // an internal index (but getting the concurrency right - // requires care). - if (Files.exists(Path.of(paths[i]))) { - handler.handle(ENTRY_CREATE, context); - } else { - handler.handle(ENTRY_DELETE, context); - } - } - } - } - - private boolean any(int bits, int mask) { - return (bits & mask) != 0; - } - }; - } - - private Pointer createFSEventStream(FSEventStreamCallback callback) { - try (var pathsToWatch = new Strings(path.toString())) { - var allocator = CF.CFAllocatorGetDefault(); - var context = Pointer.NULL; - var sinceWhen = FSE.FSEventsGetCurrentEventId(); - var latency = 0.15; - var flags = NO_DEFER.mask | WATCH_ROOT.mask | FILE_EVENTS.mask; - return FSE.FSEventStreamCreate(allocator, callback, context, pathsToWatch.toCFArray(), sinceWhen, latency, flags); - } - } - - private Pointer createDispatchQueue() { - var label = "engineering.swat.watch"; - var attr = Pointer.NULL; - return DQ.dispatch_queue_create(label, attr); + nativeWatch = NativeLibrary.start(path.toString(), handler); } // -- Closeable -- @@ -204,97 +82,6 @@ public synchronized void close() { closed = true; } - var streamNonNull = stream; - var queueNonNull = queue; - if (streamNonNull != null && queueNonNull != null) { - - // Stop the stream - FSE.FSEventStreamStop(streamNonNull); - FSE.FSEventStreamSetDispatchQueue(streamNonNull, Pointer.NULL); - FSE.FSEventStreamInvalidate(streamNonNull); - - // Deallocate native memory - DO.dispatch_release(queueNonNull); - FSE.FSEventStreamRelease(streamNonNull); - queue = null; - stream = null; - callback = null; - } - } -} - -/** - * Array of strings in native memory, needed to create a new native event stream - * (i.e., the {@code pathsToWatch} argument of {@code FSEventStreamCreate} is an - * array of strings). - */ -class Strings implements AutoCloseable { - - // Native APIs - private static final CoreFoundation CF = CoreFoundation.INSTANCE; - - // Native memory - private final CFStringRef[] strings; - private final CFArrayRef array; - - private volatile boolean closed = false; - - public Strings(String... strings) { - // Allocate native memory - this.strings = createCFStrings(strings); - this.array = createCFArray(this.strings); - } - - public CFArrayRef toCFArray() { - if (closed) { - throw new IllegalStateException("Strings are already deallocated"); - } else { - return array; - } - } - - private static CFStringRef[] createCFStrings(String[] pathsToWatch) { - return Arrays.stream(pathsToWatch) - .map(CFStringRef::createCFString) - .toArray(CFStringRef[]::new); - } - - private static CFArrayRef createCFArray(CFStringRef[] strings) { - var n = strings.length; - var size = Native.getNativeSize(CFStringRef.class); - - // Create a temporary array of pointers to the strings (automatically - // freed when `values` goes out of scope) - var values = new Memory(n * size); - for (int i = 0; i < n; i++) { - values.setPointer(i * size, strings[i].getPointer()); - } - - // Create a permanent array based on the temporary array - var alloc = CF.CFAllocatorGetDefault(); - var numValues = new CFIndex(n); - var callBacks = Pointer.NULL; - return CF.CFArrayCreate(alloc, values, numValues, callBacks); - } - - // -- AutoCloseable -- - - @Override - public void close() { - if (closed) { - throw new IllegalStateException("Strings are already deallocated"); - } else { - closed = true; - } - - // Deallocate native memory - for (var s : strings) { - if (s != null) { - s.release(); - } - } - if (array != null) { - array.release(); - } + NativeLibrary.stop(nativeWatch); } } diff --git a/src/main/java/engineering/swat/watch/impl/mac/NativeLibrary.java b/src/main/java/engineering/swat/watch/impl/mac/NativeLibrary.java new file mode 100644 index 00000000..7b8eeed3 --- /dev/null +++ b/src/main/java/engineering/swat/watch/impl/mac/NativeLibrary.java @@ -0,0 +1,103 @@ +/* + * BSD 2-Clause License + * + * Copyright (c) 2023, Swat.engineering + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package engineering.swat.watch.impl.mac; + +import static java.nio.file.attribute.PosixFilePermission.*; + +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; + +public class NativeLibrary { + + public static native long start(String path, NativeEventHandler handler); + public static native void stop(long watchId); + + public static boolean isMac() { + var os = System.getProperty("os.name"); + return os != null && (os.toLowerCase().contains("mac") || os.toLowerCase().contains("darwin")); + } + + private static boolean isAarch64() { + var arch = System.getProperty("os.arch"); + return arch != null && arch.toLowerCase().equals("aarch64"); + } + + private static volatile boolean loaded = false; + public static void load() { + if (loaded) { + return; + } + try { + if (!isMac()) { + throw new IllegalStateException("We should not be loading FileSystemEvents api on non mac machines"); + } + String path = "/engineering/swat/watch/jni/"; + if (isAarch64()) { + path += "macos-aarch64/"; + } + else { + path += "macos-x64/"; + } + path += "librust_fsevents_jni.dylib"; + + loadLibrary(path); + } finally { + loaded = true; + } + } + + private static FileAttribute> PRIVATE_FILE = PosixFilePermissions.asFileAttribute(Set.of(OWNER_READ, OWNER_WRITE , OWNER_EXECUTE)); + + private static void loadLibrary(String path) { + try { + var localFile = NativeLibrary.class.getResource(path); + if (localFile != null && localFile.getProtocol().equals("file")) { + System.load(localFile.getPath()); + return; + } + // in most cases the file is inside of a jar + // so we have to copy it out and load that file instead + var localCopy = Files.createTempFile("watch", ".dylib", PRIVATE_FILE); + localCopy.toFile().deleteOnExit(); + try (var libStream = NativeLibrary.class.getResourceAsStream(path)) { + if (libStream != null) { + try (var writer = Files.newOutputStream(localCopy, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { + libStream.transferTo(writer); + } + System.load(localCopy.toString()); + } + } + } + catch (Throwable e) { + throw new IllegalStateException("We could not load: " + path, e); + } + } +} diff --git a/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchObjects.java b/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchObjects.java deleted file mode 100644 index 41bb5ee6..00000000 --- a/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchObjects.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * BSD 2-Clause License - * - * Copyright (c) 2023, Swat.engineering - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package engineering.swat.watch.impl.mac.apis; - -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; - -/** - * Interface for the "Dispatch Objects" API collection of the "Dispatch" - * framework. - * - * @see https://developer.apple.com/documentation/dispatch/dispatch_objects?language=objc - */ -public interface DispatchObjects extends Library { - DispatchObjects INSTANCE = Native.load("c", DispatchObjects.class); - - /** - * @param object {@code dispatch_object_t} - * @see https://developer.apple.com/documentation/dispatch/1496328-dispatch_release?language=objc - */ - void dispatch_release(Pointer object); -} diff --git a/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchQueue.java b/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchQueue.java deleted file mode 100644 index 747d87a5..00000000 --- a/src/main/java/engineering/swat/watch/impl/mac/apis/DispatchQueue.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * BSD 2-Clause License - * - * Copyright (c) 2023, Swat.engineering - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package engineering.swat.watch.impl.mac.apis; - -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; - -/** - * Interface for the "Dispatch Queue" API collection of the "Dispatch" - * framework. - * - * @see https://developer.apple.com/documentation/dispatch/dispatch_queue?language=objc - */ -public interface DispatchQueue extends Library { - DispatchQueue INSTANCE = Native.load("c", DispatchQueue.class); - - /** - * @param label {@code dispatch_queue_t} - * @param attr {@code const char*} - * @return {@code dispatch_queue_t} - * @see https://developer.apple.com/documentation/dispatch/1453030-dispatch_queue_create?language=objc - */ - Pointer dispatch_queue_create(String label, Pointer attr); -} diff --git a/src/main/java/engineering/swat/watch/impl/mac/apis/FileSystemEvents.java b/src/main/java/engineering/swat/watch/impl/mac/apis/FileSystemEvents.java deleted file mode 100644 index d81a0447..00000000 --- a/src/main/java/engineering/swat/watch/impl/mac/apis/FileSystemEvents.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * BSD 2-Clause License - * - * Copyright (c) 2023, Swat.engineering - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package engineering.swat.watch.impl.mac.apis; - -import com.sun.jna.Callback; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.mac.CoreFoundation.CFAllocatorRef; -import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef; -import com.sun.jna.platform.mac.CoreFoundation.CFStringRef; - -/** - * Interface for the "File System Events" API collection of the "Core Services" - * framework. - * - * https://developer.apple.com/documentation/coreservices/file_system_events?language=objc - */ -public interface FileSystemEvents extends Library { - FileSystemEvents INSTANCE = Native.load("CoreServices", FileSystemEvents.class); - - // -- Functions -- - - /** - * @param allocator {@code CFAllocator} - * @param callback {@code FSEventStreamCallback} - * @param context {@code FSEventStreamContext} - * @param pathsToWatch {@code CFArray} - * @param sinceWhen {@code FSEventStreamEventId} - * @param latency {@code CFTimeInterval} - * @param flags {@code FSEventStreamCreateFlags} - * @return {@code FSEventStreamRef} - * @see https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate?language=objc - */ - Pointer FSEventStreamCreate(CFAllocatorRef allocator, FSEventStreamCallback callback, - Pointer context, CFArrayRef pathsToWatch, long sinceWhen, double latency, int flags); - - /** - * @param streamRef {@code FSEventStreamRef} - * @see https://developer.apple.com/documentation/coreservices/1446990-fseventstreaminvalidate?language=objc - */ - void FSEventStreamInvalidate(Pointer streamRef); - - /** - * @param streamRef {@code FSEventStreamRef} - * @see https://developer.apple.com/documentation/coreservices/1445989-fseventstreamrelease?language=objc - */ - void FSEventStreamRelease(Pointer streamRef); - - /** - * @param streamRef {@code FSEventStreamRef} - * @param q {@code dispatch_queue_t} - * @see https://developer.apple.com/documentation/coreservices/1444164-fseventstreamsetdispatchqueue?language=objc - */ - void FSEventStreamSetDispatchQueue(Pointer streamRef, Pointer q); - - /** - * @param streamRef {@code FSEventStreamRef} - * @see https://developer.apple.com/documentation/coreservices/1444302-fseventstreamshow?language=objc - */ - boolean FSEventStreamShow(Pointer streamRef); - - /** - * @param streamRef {@code FSEventStreamRef} - * @return {@code Boolean} - * @see https://developer.apple.com/documentation/coreservices/1448000-fseventstreamstart?language=objc - */ - boolean FSEventStreamStart(Pointer streamRef); - - /** - * @param streamRef {@code FSEventStreamRef} - * @see https://developer.apple.com/documentation/coreservices/1447673-fseventstreamstop?language=objc - */ - void FSEventStreamStop(Pointer streamRef); - - /** - * @return {@code FSEventStreamEventId} - * @see https://developer.apple.com/documentation/coreservices/1442917-fseventsgetcurrenteventid?language=objc - */ - long FSEventsGetCurrentEventId(); - - // -- Enumerations -- - - /** - * @see https://developer.apple.com/documentation/coreservices/1455376-fseventstreamcreateflags?language=objc - */ - static enum FSEventStreamCreateFlag { - NONE (0x00000000), - USE_CF_TYPES (0x00000001), - NO_DEFER (0x00000002), - WATCH_ROOT (0x00000004), - IGNORE_SELF (0x00000008), - FILE_EVENTS (0x00000010), - MARK_SELF (0x00000020), - FULL_HISTORY (0x00000080), - USE_EXTENDED_DATA(0x00000040), - WITH_DOC_ID (0x00000100); - - public final int mask; - - private FSEventStreamCreateFlag(int mask) { - this.mask = mask; - } - } - - /** - * @see https://developer.apple.com/documentation/coreservices/1455361-fseventstreameventflags?language=objc - */ - static enum FSEventStreamEventFlag { - NONE (0x00000000), - MUST_SCAN_SUB_DIRS (0x00000001), - USER_DROPPED (0x00000002), - KERNEL_DROPPED (0x00000004), - EVENT_IDS_WRAPPED (0x00000008), - HISTORY_DONE (0x00000010), - ROOT_CHANGED (0x00000020), - MOUNT (0x00000040), - UNMOUNT (0x00000080), - ITEM_CHANGE_OWNER (0x00004000), - ITEM_CREATED (0x00000100), - ITEM_FINDER_INFO_MOD (0x00002000), - ITEM_INODE_META_MOD (0x00000400), - ITEM_IS_DIR (0x00020000), - ITEM_IS_FILE (0x00010000), - ITEM_IS_HARD_LINK (0x00100000), - ITEM_IS_LAST_HARD_LINK(0x00200000), - ITEM_IS_SYMLINK (0x00040000), - ITEM_MODIFIED (0x00001000), - ITEM_REMOVED (0x00000200), - ITEM_RENAMED (0x00000800), - ITEM_XATTR_MOD (0x00008000), - OWN_EVENT (0x00080000), - ITEM_CLONED (0x00400000); - - public final int mask; - - private FSEventStreamEventFlag(int mask) { - this.mask = mask; - } - } - - // -- Data types -- - - static interface FSEventStreamCallback extends Callback { - /** - * @param streamRef {@code ConstFSEventStreamRef} - * @param clientCallBackInfo {@code void*} - * @param numEvents {@code size_t} - * @param eventPaths {@code void*} - * @param eventFlags {@code FSEventStreamEventFlags*} - * @param eventIds {@code FSEventStreamEventIds*} - * @see https://developer.apple.com/documentation/coreservices/fseventstreamcallback?language=objc - */ - void callback(Pointer streamRef, Pointer clientCallBackInfo, - long numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds); - } - - // -- Constants -- - - /** - * @see https://developer.apple.com/documentation/coreservices/kfseventstreameventextendeddatapathkey?language=objc - */ - static final CFStringRef kFSEventStreamEventExtendedDataPathKey = CFStringRef.createCFString("path"); - - /** - * @see https://developer.apple.com/documentation/coreservices/kfseventstreameventextendedfileidkey?language=objc - */ - static final CFStringRef kFSEventStreamEventExtendedFileIDKey = CFStringRef.createCFString("fileID"); -} diff --git a/src/main/resources/.gitignore b/src/main/resources/.gitignore new file mode 100644 index 00000000..e5d04671 --- /dev/null +++ b/src/main/resources/.gitignore @@ -0,0 +1 @@ +*.dylib diff --git a/src/main/rust/.gitignore b/src/main/rust/.gitignore new file mode 100644 index 00000000..9552259c --- /dev/null +++ b/src/main/rust/.gitignore @@ -0,0 +1,7 @@ +/target +/debug +**/*.rs.bk +*.pdb +*.log +*.d +*.dylib diff --git a/src/main/rust/Cargo.lock b/src/main/rust/Cargo.lock new file mode 100644 index 00000000..043a44e3 --- /dev/null +++ b/src/main/rust/Cargo.lock @@ -0,0 +1,353 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "fsevent-sys" +version = "5.0.0" +source = "git+https://github.com/octplane/fsevent-rust.git?rev=refs%2Fpull%2F44%2Fhead#93167444410b3576d6c49ae773becde1d6b45b37" +dependencies = [ + "core-foundation 0.9.4", + "dispatch2", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "proc-macro2" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust-fsevents-jni" +version = "0.1.0" +dependencies = [ + "core-foundation 0.10.1", + "dispatch2", + "fsevent-sys", + "jni", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/src/main/rust/Cargo.toml b/src/main/rust/Cargo.toml new file mode 100644 index 00000000..96c7d670 --- /dev/null +++ b/src/main/rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rust-fsevents-jni" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +strip = true +lto = true +codegen-units = 1 + +[dependencies] +# we need this PR as it contains an updated version that targets core-foundation +fsevent-sys = { git = "https://github.com/octplane/fsevent-rust.git", rev= "refs/pull/44/head"} +core-foundation = "0.10.1" +# we need this specific version of dispatch2, that still exposes the native type +dispatch2 = { version = "0.2.0", default-features = false, features = ["alloc"] } +jni = "0.21.1" diff --git a/src/main/rust/src/fs_monitor.rs b/src/main/rust/src/fs_monitor.rs new file mode 100644 index 00000000..a41b270e --- /dev/null +++ b/src/main/rust/src/fs_monitor.rs @@ -0,0 +1,182 @@ +// +// BSD 2-Clause License +// +// Copyright (c) 2023, Swat.engineering +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#![cfg(target_os = "macos")] +#![deny( + trivial_numeric_casts, + unstable_features, + unused_import_braces, + unused_qualifications +)] + +use std::{ + ffi::CStr, + ptr, + fs, + os::raw::{c_char, c_void}, + sync::atomic::{AtomicBool, Ordering}, +}; +use dispatch2::ffi::{dispatch_object_t, dispatch_queue_t, DISPATCH_QUEUE_SERIAL, dispatch_queue_create}; +use core_foundation::{ + array::CFArray, + base::{kCFAllocatorDefault, TCFType}, + string::CFString, +}; +use fsevent_sys::{self as fse}; + +pub enum Kind { // Ordinals need to be consistent with enum `Kind` in Java + OVERFLOW=0, + CREATE=1, + DELETE=2, + MODIFY=3, +} + +pub struct NativeEventStream { + since_when: fse::FSEventStreamEventId, + closed: AtomicBool, + path : CFArray, + queue: dispatch_queue_t, + stream: Option, + info: *mut ContextInfo, +} + +impl NativeEventStream { + pub fn new(path: String, handler: impl Fn(Kind, &String) + 'static) -> Self { + Self { + since_when: unsafe { fse::FSEventsGetCurrentEventId() }, + closed: AtomicBool::new(false), + path: CFArray::from_CFTypes(&[CFString::new(&path)]), + queue: unsafe { dispatch_queue_create(ptr::null(), DISPATCH_QUEUE_SERIAL) }, + stream: None, + info: Box::into_raw(Box::new(ContextInfo{ handler: Box::new(handler) })), + } + } + + pub fn start(&mut self) { + unsafe { + let stream = fse::FSEventStreamCreate( + kCFAllocatorDefault, + callback, + &fse::FSEventStreamContext { + version: 0, + info: self.info as *mut _, + retain: None, + release: Some(release_context), + copy_description: None + }, + self.path.as_concrete_TypeRef(), + self.since_when, + 0.15, + FLAGS); + + self.stream = Some(stream); + + fse::FSEventStreamSetDispatchQueue(stream, self.queue); + fse::FSEventStreamStart(stream); + }; + } + + pub fn stop(&self) { + if self.closed.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() { + return; // The stream has already been closed + } + match self.stream { + Some(stream) => unsafe{ + fse::FSEventStreamStop(stream); + fse::FSEventStreamSetDispatchQueue(stream, ptr::null_mut()); + fse::FSEventStreamInvalidate(stream); + dispatch2::ffi::dispatch_release(self.queue as dispatch_object_t); + fse::FSEventStreamRelease(stream); + } + None => unsafe { + dispatch2::ffi::dispatch_release(self.queue as dispatch_object_t); + } + }; + } +} + +struct ContextInfo { + handler: Box, +} + +const FLAGS : fse::FSEventStreamCreateFlags + = fse::kFSEventStreamCreateFlagNoDefer + | fse::kFSEventStreamCreateFlagWatchRoot + | fse::kFSEventStreamCreateFlagFileEvents; + +extern "C" fn release_context(info: *mut c_void) { + let ctx_ptr = info as *mut ContextInfo; + unsafe{ drop(Box::from_raw( ctx_ptr)); } +} + +extern "C" fn callback( + _stream_ref: fse::FSEventStreamRef, + info: *mut c_void, + num_events: usize, + event_paths: *mut c_void, + event_flags: *const fse::FSEventStreamEventFlags, + _event_ids: *const fse::FSEventStreamEventId, +) { + let info = unsafe{ &mut *(info as *mut ContextInfo) }; + let handler = info.handler.as_ref(); + + let event_paths = event_paths as *const *const c_char; + for i in 0..num_events { + // TODO: We're currently going from C strings to Rust strings to JNI + // strings. If possible, go directly from C strings to JNI strings. + let path = unsafe { CStr::from_ptr(*event_paths.add(i)).to_str().unwrap().to_string() }; + let flags: fse::FSEventStreamEventFlags = unsafe { *event_flags.add(i) }; + + // Note: Multiple "physical" native events might be coalesced into a + // single "logical" native event, so the following series of checks + // should be if-statements (instead of if/else-statements). + if flags & fse::kFSEventStreamEventFlagItemCreated != 0 { + handler(Kind::CREATE, &path); + } + if flags & fse::kFSEventStreamEventFlagItemRemoved != 0 { + handler(Kind::DELETE, &path); + } + if flags & (fse::kFSEventStreamEventFlagItemModified | fse::kFSEventStreamEventFlagItemInodeMetaMod) != 0 { + handler(Kind::MODIFY, &path); + } + if flags & fse::kFSEventStreamEventFlagMustScanSubDirs != 0 { + handler(Kind::OVERFLOW, &path); + } + if flags & fse::kFSEventStreamEventFlagItemRenamed != 0 { + // For now, check if the file exists to determine if the event + // pertains to the target of the rename (if it exists) or to the + // source (else). This is an approximation. It might be more + // accurate to maintain an internal index (but getting the + // concurrency right requires care). + if fs::exists(&path).unwrap_or(false) { + handler(Kind::CREATE, &path); + } else { + handler(Kind::DELETE, &path); + } + } + } +} diff --git a/src/main/rust/src/lib.rs b/src/main/rust/src/lib.rs new file mode 100644 index 00000000..b7acbd3d --- /dev/null +++ b/src/main/rust/src/lib.rs @@ -0,0 +1,104 @@ +// +// BSD 2-Clause License +// +// Copyright (c) 2023, Swat.engineering +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +mod fs_monitor; + +use jni::{Executor, JNIEnv}; +use jni::objects::{GlobalRef, JClass, JMethodID, JObject, JString, JValue}; +use jni::sys::{jint, jlong}; + +use crate::fs_monitor::{Kind, NativeEventStream}; + +#[allow(dead_code)] +struct HandlerExecutor { + executor: Executor, + obj: GlobalRef, + method: JMethodID, + path: String, + class: GlobalRef, // Ensure the reference to the class (of `obj`) isn't lost +} + +impl HandlerExecutor { + pub fn new<'local>(env: &mut JNIEnv<'local>, path: String, obj: JObject<'local>) -> Result { + let executor = Executor::new(Into::into(env.get_java_vm()?)); + let obj = env.new_global_ref(obj)?; + let class = env.new_global_ref(env.get_object_class(&obj)?)?; + let method = env.get_method_id( + &class, "handle", "(ILjava/lang/String;Ljava/lang/String;)V")?; + + Ok(Self { executor, obj, method, path, class }) + } + + pub fn execute<'local>(&self, kind: Kind, path: &String) { + self.executor.with_attached(|env: &mut JNIEnv<'_>| -> Result<(), jni::errors::Error> { + unsafe { + env.call_method_unchecked( + self.obj.as_obj(), + self.method, + jni::signature::ReturnType::Primitive(jni::signature::Primitive::Void), + &[ + JValue::from(kind as jint).as_jni(), + JValue::from(&env.new_string(&self.path).unwrap()).as_jni(), + JValue::from(&env.new_string(&path).unwrap()).as_jni(), + ] + )?; + } + Ok(()) + }).unwrap(); + } +} + +#[unsafe(no_mangle)] +#[allow(unused_variables)] +pub extern "system" fn Java_engineering_swat_watch_impl_mac_NativeLibrary_start<'local>( + mut env: JNIEnv<'local>, + class: JClass<'local>, + path: JString<'local>, + handler: JObject<'local>, +) -> jlong +{ + let path: String = env.get_string(&path).expect("Should not fail to get string").into(); + let handler_executor = HandlerExecutor::new(&mut env, path.clone(), handler).unwrap(); + let handler = move |kind: Kind, path: &String| handler_executor.execute(kind, path); + let mut mon = NativeEventStream::new(path, handler); + mon.start(); + Box::into_raw(Box::new(mon)) as jlong +} + +#[unsafe(no_mangle)] +#[allow(unused_variables)] +pub extern "system" fn Java_engineering_swat_watch_impl_mac_NativeLibrary_stop<'local>( + env: JNIEnv<'local>, + class: JClass<'local>, + stream: jlong, +) +{ + let mon_ptr = stream as *mut NativeEventStream; + let mon = unsafe { Box::from_raw(mon_ptr) }; + mon.stop(); + // After this, the mon will be released, as it has been taken out of the box +} diff --git a/src/test/java/engineering/swat/watch/impl/mac/APIs.java b/src/test/java/engineering/swat/watch/impl/mac/APIs.java deleted file mode 100644 index 4b408b1a..00000000 --- a/src/test/java/engineering/swat/watch/impl/mac/APIs.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * BSD 2-Clause License - * - * Copyright (c) 2023, Swat.engineering - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package engineering.swat.watch.impl.mac; - -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.FILE_EVENTS; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.NO_DEFER; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.USE_CF_TYPES; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.USE_EXTENDED_DATA; -import static engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamCreateFlag.WATCH_ROOT; -import static org.awaitility.Awaitility.await; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.mac.CoreFoundation; -import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef; -import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef; -import com.sun.jna.platform.mac.CoreFoundation.CFIndex; -import com.sun.jna.platform.mac.CoreFoundation.CFNumberRef; -import com.sun.jna.platform.mac.CoreFoundation.CFStringRef; - -import engineering.swat.watch.TestDirectory; -import engineering.swat.watch.impl.mac.apis.DispatchObjects; -import engineering.swat.watch.impl.mac.apis.DispatchQueue; -import engineering.swat.watch.impl.mac.apis.FileSystemEvents; -import engineering.swat.watch.impl.mac.apis.FileSystemEvents.FSEventStreamEventFlag; - -@EnabledOnOs({OS.MAC}) -class APIs { - private static final Logger LOGGER = LogManager.getLogger(); - - // Native APIs - private static final CoreFoundation CF = CoreFoundation.INSTANCE; - private static final DispatchObjects DO = DispatchObjects.INSTANCE; - private static final DispatchQueue DQ = DispatchQueue.INSTANCE; - private static final FileSystemEvents FSE = FileSystemEvents.INSTANCE; - - @Test - void smokeTest() throws IOException { - try (var test = new TestDirectory()) { - var ready = new AtomicBoolean(false); - var paths = ConcurrentHashMap. newKeySet(); - - var s = test.getTestDirectory().toString(); - var handler = (MinimalWorkingExample.EventHandler) (path, inode, flags, id) -> { - synchronized (ready) { - while (!ready.get()) { - try { - ready.wait(); - } catch (InterruptedException e) { - LOGGER.error("Unexpected interrupt. Test likely to fail. Event ignored ({}).", prettyPrint(path, inode, flags, id)); - Thread.currentThread().interrupt(); - return; - } - } - } - paths.remove(path); - }; - - try (var mwe = new MinimalWorkingExample(s, handler, true)) { - var dir = test.getTestDirectory().toRealPath(); - paths.add(Files.writeString(dir.resolve("a.txt"), "foo").toString()); - paths.add(Files.writeString(dir.resolve("b.txt"), "bar").toString()); - paths.add(Files.createFile(dir.resolve("d.txt")).toString()); - - synchronized (ready) { - ready.set(true); - ready.notifyAll(); - } - - await("The event handler has been called").until(paths::isEmpty); - } - } - } - - public static void main(String[] args) throws IOException { - var s = args[0]; - var handler = (MinimalWorkingExample.EventHandler) (path, inode, flags, id) -> { - LOGGER.info(prettyPrint(path, inode, flags, id)); - }; - var useExtendedData = args.length >= 2 && Boolean.parseBoolean(args[1]); - - try (var mwe = new MinimalWorkingExample(s, handler, useExtendedData)) { - // Block the program from terminating until `ENTER` is pressed - new BufferedReader(new InputStreamReader(System.in)).readLine(); - } - } - - private static String prettyPrint(String path, long inode, int flags, long id) { - var flagsPrettyPrinted = Stream - .of(FSEventStreamEventFlag.values()) - .filter(f -> (f.mask & flags) == f.mask) - .map(Object::toString) - .collect(Collectors.joining(", ")); - - var format = "path: \"%s\", inode: %s, flags: [%s], id: %s"; - return String.format(format, path, inode, flagsPrettyPrinted, id); - } - - private static class MinimalWorkingExample implements Closeable { - private FileSystemEvents.FSEventStreamCallback callback; - private Pointer stream; - private Pointer queue; - - public MinimalWorkingExample(String s, EventHandler handler, boolean useExtendedData) { - - // Allocate singleton array of paths - CFStringRef pathToWatch = CFStringRef.createCFString(s); - CFArrayRef pathsToWatch = null; - { - var values = new Memory(Native.getNativeSize(CFStringRef.class)); - values.setPointer(0, pathToWatch.getPointer()); - pathsToWatch = CF.CFArrayCreate( - CF.CFAllocatorGetDefault(), - values, - new CFIndex(1), - null); - } // Automatically free `values` when it goes out of scope - - // Allocate callback - this.callback = (x1, x2, x3, x4, x5, x6) -> { - var paths = x4.getStringArray(0, (int) x3); - var inodes = new long[(int) x3]; - var flags = x5.getIntArray(0, (int) x3); - var ids = x6.getLongArray(0, (int) x3); - - if (useExtendedData) { - var extendedData = new CFArrayRef(x4); - for (int i = 0; i < x3; i++) { - var dictionary = new CFDictionaryRef(extendedData.getValueAtIndex(i)); - var dictionaryPath = dictionary.getValue(FileSystemEvents.kFSEventStreamEventExtendedDataPathKey); - var dictionaryInode = dictionary.getValue(FileSystemEvents.kFSEventStreamEventExtendedFileIDKey); - paths[i] = dictionaryPath == null ? null : new CFStringRef(dictionaryPath).stringValue(); - inodes[i] = dictionaryInode == null ? 0 : new CFNumberRef(dictionaryInode).longValue(); - } - } - - for (int i = 0; i < x3; i++) { - handler.handle(paths[i], inodes[i], flags[i], ids[i]); - } - }; - - // Allocate stream - this.stream = FSE.FSEventStreamCreate( - CF.CFAllocatorGetDefault(), - callback, - Pointer.NULL, - pathsToWatch, - FSE.FSEventsGetCurrentEventId(), - 0.15, - NO_DEFER.mask | WATCH_ROOT.mask | FILE_EVENTS.mask | - (useExtendedData ? USE_EXTENDED_DATA.mask | USE_CF_TYPES.mask : 0)); - - // Deallocate array of paths - pathsToWatch.release(); - pathToWatch.release(); - - // Allocate queue - this.queue = DQ.dispatch_queue_create("q", null); - - // Start stream - FSE.FSEventStreamSetDispatchQueue(stream, queue); - FSE.FSEventStreamStart(stream); - FSE.FSEventStreamShow(stream); - } - - @Override - public void close() throws IOException { - - // Stop stream - FSE.FSEventStreamStop(stream); - FSE.FSEventStreamSetDispatchQueue(stream, Pointer.NULL); - FSE.FSEventStreamInvalidate(stream); - - // Deallocate queue, stream, and callback - DO.dispatch_release(queue); - FSE.FSEventStreamRelease(stream); - queue = null; - stream = null; - callback = null; - } - - @FunctionalInterface - private static interface EventHandler { - void handle(String path, long inode, int flags, long id); - } - } -} diff --git a/src/test/java/engineering/swat/watch/impl/mac/NativeEventStreamTests.java b/src/test/java/engineering/swat/watch/impl/mac/NativeEventStreamTests.java new file mode 100644 index 00000000..174ac5fb --- /dev/null +++ b/src/test/java/engineering/swat/watch/impl/mac/NativeEventStreamTests.java @@ -0,0 +1,86 @@ +/* + * BSD 2-Clause License + * + * Copyright (c) 2023, Swat.engineering + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package engineering.swat.watch.impl.mac; + +import static org.awaitility.Awaitility.await; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.WatchEvent.Kind; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.awaitility.Awaitility; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import engineering.swat.watch.TestDirectory; +import engineering.swat.watch.TestHelper; + +@EnabledOnOs({OS.MAC}) +public class NativeEventStreamTests { + + private TestDirectory testDir; + + @BeforeEach + void setup() throws IOException { + testDir = new TestDirectory(); + } + + @AfterEach + void cleanup() { + if (testDir != null) { + testDir.close(); + } + } + + @BeforeAll + static void setupEverything() { + Awaitility.setDefaultTimeout(TestHelper.NORMAL_WAIT); + } + + @Test + void signalsAreSent() throws IOException, InterruptedException { + var signaled = new AtomicBoolean(false); + try (var stream = new NativeEventStream(testDir.getTestDirectory(), + new NativeEventHandler() { + @Override + public void handle(Kind kind, @Nullable T context) { + signaled.set(true); + } + } + )) { + stream.open(); + Files.write(testDir.getTestFiles().get(0), "Hello".getBytes()); + await("Signal received").untilTrue(signaled); + } + } +} diff --git a/update-rust-jni-libs.sh b/update-rust-jni-libs.sh new file mode 100755 index 00000000..cb0ee6b5 --- /dev/null +++ b/update-rust-jni-libs.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# BSD 2-Clause License +# +# Copyright (c) 2023, Swat.engineering +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +set -euxo pipefail + +# Run Cargo in debug mode by default +CARGO_OPTIONS="" +TARGET_SUBDIR="debug" + +while getopts ":r" OPTION; do + case $OPTION in + r) # Run Cargo in release mode + CARGO_OPTIONS="--release" + TARGET_SUBDIR="release" + esac +done + +RESOURCES="../resources/engineering/swat/watch/jni/" + +cd src/main/rust + +function build() { + rustup target add $1 + cargo build --target $1 $CARGO_OPTIONS + mkdir -p "$RESOURCES/$2/" + cp "target/$1/$TARGET_SUBDIR/librust_fsevents_jni.dylib" "$RESOURCES/$2/" +} + +build "x86_64-apple-darwin" "macos-x64" +build "aarch64-apple-darwin" "macos-aarch64"