// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include namespace { // A structure to pass around the FSEvents info and the list of paths. struct JNIEventsDiffAwareness { // FSEvents run loop (thread) CFRunLoopRef runLoop; // FSEvents stream reference (reference to the listened stream) FSEventStreamRef stream; // If true, fsevents dropped events so we don't know what changed exactly. bool everything_changed; // List of paths that have been changed since last polling. std::list paths; // Mutex to protect concurrent accesses to paths and everything_changed. pthread_mutex_t mutex; JNIEventsDiffAwareness() : everything_changed(false) { pthread_mutex_init(&mutex, nullptr); } ~JNIEventsDiffAwareness() { pthread_mutex_destroy(&mutex); } }; // Callback called when an event is reported by the FSEvents API void FsEventsDiffAwarenessCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) { char **paths = static_cast(eventPaths); JNIEventsDiffAwareness *info = static_cast(clientCallBackInfo); pthread_mutex_lock(&(info->mutex)); for (size_t i = 0; i < numEvents; i++) { if ((eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) != 0) { // Either we lost events or they were coalesced. Assume everything changed // and give up, which matches the fsevents documentation in that the // caller is expected to rescan the directory contents on its own. info->everything_changed = true; break; } else if ((eventFlags[i] & kFSEventStreamEventFlagItemIsDir) != 0 && (eventFlags[i] & kFSEventStreamEventFlagItemRenamed) != 0) { // A directory was renamed. When this happens, fsevents may or may not // give us individual events about which files changed underneath, which // means we have to rescan the directories in order to know what changed. // // The problem is that we cannot rescan the source of the move to discover // which files "disappeared"... so we have no choice but to rescan // everything. Well, in theory, we could try to track directory inodes and // using those to guess which files within them moved... but that'd be way // too much complexity for this rather-uncommon use case. info->everything_changed = true; break; } else { info->paths.push_back(std::string(paths[i])); } } pthread_mutex_unlock(&(info->mutex)); } extern "C" JNIEXPORT void JNICALL Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_create( JNIEnv *env, jobject fsEventsDiffAwareness, jobjectArray paths, jdouble latency) { // Create a FSEventStreamContext to pass around (env, fsEventsDiffAwareness) JNIEventsDiffAwareness *info = new JNIEventsDiffAwareness(); FSEventStreamContext context; context.version = 0; context.info = static_cast(info); context.retain = nullptr; context.release = nullptr; context.copyDescription = nullptr; // Create an CFArrayRef of CFStringRef from the Java array of String jsize length = env->GetArrayLength(paths); CFStringRef *pathsArray = new CFStringRef[length]; for (int i = 0; i < length; i++) { jstring path = (jstring)env->GetObjectArrayElement(paths, i); const char *pathCStr = env->GetStringUTFChars(path, nullptr); pathsArray[i] = CFStringCreateWithCString(nullptr, pathCStr, kCFStringEncodingUTF8); env->ReleaseStringUTFChars(path, pathCStr); } CFArrayRef pathsToWatch = CFArrayCreate(nullptr, (const void **)pathsArray, 1, nullptr); delete[] pathsArray; info->stream = FSEventStreamCreate( nullptr, &FsEventsDiffAwarenessCallback, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, static_cast(latency), kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); // Save the info pointer to FSEventsDiffAwareness#nativePointer jbyteArray array = env->NewByteArray(sizeof(info)); env->SetByteArrayRegion(array, 0, sizeof(info), reinterpret_cast(&info)); jclass clazz = env->GetObjectClass(fsEventsDiffAwareness); jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J"); env->SetLongField(fsEventsDiffAwareness, fid, reinterpret_cast(info)); } JNIEventsDiffAwareness *GetInfo(JNIEnv *env, jobject fsEventsDiffAwareness) { jclass clazz = env->GetObjectClass(fsEventsDiffAwareness); jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J"); jlong field = env->GetLongField(fsEventsDiffAwareness, fid); return reinterpret_cast(field); } } // namespace extern "C" JNIEXPORT void JNICALL Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_run( JNIEnv *env, jobject fsEventsDiffAwareness, jobject listening) { JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); info->runLoop = CFRunLoopGetCurrent(); FSEventStreamScheduleWithRunLoop(info->stream, info->runLoop, kCFRunLoopDefaultMode); FSEventStreamStart(info->stream); jclass countDownLatchClass = env->GetObjectClass(listening); jmethodID countDownMethod = env->GetMethodID(countDownLatchClass, "countDown", "()V"); env->CallVoidMethod(listening, countDownMethod); CFRunLoopRun(); } extern "C" JNIEXPORT jobjectArray JNICALL Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_poll( JNIEnv *env, jobject fsEventsDiffAwareness) { JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); pthread_mutex_lock(&(info->mutex)); jobjectArray result; if (info->everything_changed) { result = nullptr; } else { jclass classString = env->FindClass("java/lang/String"); result = env->NewObjectArray(info->paths.size(), classString, nullptr); int i = 0; for (auto it = info->paths.begin(); it != info->paths.end(); it++, i++) { env->SetObjectArrayElement(result, i, env->NewStringUTF(it->c_str())); } } info->everything_changed = false; info->paths.clear(); pthread_mutex_unlock(&(info->mutex)); return result; } extern "C" JNIEXPORT void JNICALL Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_doClose( JNIEnv *env, jobject fsEventsDiffAwareness) { JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); CFRunLoopStop(info->runLoop); FSEventStreamStop(info->stream); FSEventStreamUnscheduleFromRunLoop(info->stream, info->runLoop, kCFRunLoopDefaultMode); FSEventStreamInvalidate(info->stream); FSEventStreamRelease(info->stream); delete info; }