carb/FindPlugins.h

File members: carb/FindPlugins.h

// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "Framework.h"
#include "extras/Library.h"
#include "filesystem/IFileSystem.h"
#include "filesystem/FindFiles.h"
#include "logging/Log.h"

#include "../omni/str/Wildcard.h"

#include <cstring>

namespace carb
{

using FindPluginsOnMatchedFn = void(const char* canonical, bool reloadable, void* context);

struct FindPluginsArgs
{
    const char* const* searchPaths;
    size_t searchPathCount;

    bool searchRecursive;

    const char* const* loadedFileWildcards;
    size_t loadedFileWildcardCount;

    const char* const* reloadableFileWildcards;
    size_t reloadableFileWildcardCount;

    const char* const* excludedFileWildcards;
    size_t excludedFileWildcardCount;

    FindPluginsOnMatchedFn* onMatched;
    void* onMatchedContext;

    filesystem::FindFilesOnExcludedFn* onExcluded;
    void* onExcludedContext;

    filesystem::FindFilesOnSkippedFn* onSkipped;
    void* onSkippedContext;

    filesystem::FindFilesOnSearchPathFn* onSearchPath;
    void* onSearchPathContext;

    filesystem::IFileSystem* fs;
};

#ifndef DOXYGEN_BUILD
namespace detail
{

inline bool caseInsensitiveEndsWith(const char* str, const char* tail)
{
    const size_t strLen = std::strlen(str);
    const size_t tailLen = std::strlen(tail);

    // String should be at least as long as tail
    if (strLen < tailLen)
    {
        return false;
    }

    // Compare with tail, character by character
    for (size_t i = 0; i < tailLen; ++i)
    {
        // Tail is assumed to already be lowercase
        if (tail[tailLen - i - 1] != std::tolower(str[strLen - i - 1]))
        {
            return false;
        }
    }
    return true;
}

} // namespace detail
#endif

inline bool findPlugins(const FindPluginsArgs& inArgs) noexcept
{
    filesystem::FindFilesArgs args{};

    args.searchPaths = inArgs.searchPaths;
    args.searchPathsCount = uint32_t(inArgs.searchPathCount);

    PluginLoadingDesc defaultPluginDesc = PluginLoadingDesc::getDefault();
    if (!args.searchPaths || (0 == args.searchPathsCount))
    {
        // If search path count it not specified, fall back to the default desc search paths
        args.searchPaths = defaultPluginDesc.searchPaths;
        args.searchPathsCount = uint32_t(defaultPluginDesc.searchPathCount);
    }

    args.matchWildcards = inArgs.loadedFileWildcards;
    args.matchWildcardsCount = uint32_t(inArgs.loadedFileWildcardCount);

    args.excludeWildcards = inArgs.excludedFileWildcards;
    args.excludeWildcardsCount = uint32_t(inArgs.excludedFileWildcardCount);

#if CARB_PLATFORM_LINUX || CARB_PLATFORM_MACOS
    constexpr const char* const kIgnorePrefixes[] = { "lib" };
    constexpr uint32_t kIgnorePrefixesCount = 1;
#elif CARB_PLATFORM_WINDOWS
    constexpr const char* const* kIgnorePrefixes = nullptr;
    constexpr uint32_t kIgnorePrefixesCount = 0;
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif

    args.ignorePrefixes = kIgnorePrefixes;
    args.ignorePrefixesCount = kIgnorePrefixesCount;

    args.fs = inArgs.fs;

    // to avoid the expensive filename canonicalization and pattern matching, we do a quick check to make sure the
    // extension is for a plugin
    args.onFilterNonCanonical = [](const char* path, void*) {
        if (detail::caseInsensitiveEndsWith(path, carb::extras::getDefaultLibraryExtension()))
        {
            return filesystem::WalkAction::eContinue; // could be a plugin (i.e. correct .ext)
        }
        else
        {
            return filesystem::WalkAction::eSkip; // not a plug .ext.  skip
        }
    };

    args.onMatched = [](const char* canonical, void* context) {
        auto inArgs = static_cast<FindPluginsArgs*>(context);

        bool reloadable = false;
        if (inArgs->reloadableFileWildcards && inArgs->reloadableFileWildcardCount)
        {
            extras::Path path(canonical);
            auto stemBuffer = path.getStem();
            const char* stem = stemBuffer.getStringBuffer();

            reloadable = omni::str::matchWildcards(
                stem, inArgs->reloadableFileWildcards, uint32_t(inArgs->reloadableFileWildcardCount));
#if CARB_PLATFORM_LINUX || CARB_PLATFORM_MACOS
            if (!reloadable)
            {
                if (extras::startsWith(stem, "lib"))
                {
                    stem += 3;
                    reloadable = omni::str::matchWildcards(
                        stem, inArgs->reloadableFileWildcards, uint32_t(inArgs->reloadableFileWildcardCount));
                }
            }
#endif
        }

        inArgs->onMatched(canonical, reloadable, inArgs->onMatchedContext);
    };
    args.onMatchedContext = const_cast<FindPluginsArgs*>(&inArgs);

    args.onExcluded = inArgs.onExcluded;
    args.onExcludedContext = inArgs.onExcludedContext;
    if (!args.onExcluded)
    {
        args.onExcluded = [](const char* canonical, void*) {
            CARB_LOG_VERBOSE("Excluding potential plugin file: %s.", canonical);
        };
    }

    args.onSkipped = inArgs.onSkipped;
    args.onSkippedContext = inArgs.onSkippedContext;

    args.onSearchPath = inArgs.onSearchPath;
    args.onSearchPathContext = inArgs.onSearchPathContext;
    if (!args.onSearchPath)
    {
        args.onSearchPath = [](const char* path, void* context) {
            auto inArgs = static_cast<FindPluginsArgs*>(context);
            CARB_LOG_VERBOSE("Searching plugins %sin folder: %s", (inArgs->searchRecursive ? "recursively " : ""), path);
        };
        args.onSearchPathContext = const_cast<FindPluginsArgs*>(&inArgs);
    }

    args.flags = (filesystem::kFindFilesFlagMatchStem | filesystem::kFindFilesFlagReplaceEnvironmentVariables);
    if (inArgs.searchRecursive)
    {
        args.flags |= filesystem::kFindFilesFlagRecursive;
    }

    return filesystem::findFiles(args);
}

} // namespace carb