FindPlugins.h#

Fully qualified name: carb/FindPlugins.h

File members: carb/FindPlugins.h

// SPDX-FileCopyrightText: Copyright (c) 2020-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

#pragma once

#include "Framework.h"
#include "cpp/ZStringView.h"
#include "cpp/Span.h"
#include "extras/Library.h"
#include "extras/StringUtils.h"
#include "filesystem/FileSystemUtils.h"
#include "filesystem/FindFiles.h"
#include "logging/Log.h"

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

#include <cstring>

namespace carb
{

#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
using FindPluginsOnMatchedFn = void(cpp::zstring_view canonical, bool reloadable, void* context);
#else
using FindPluginsOnMatchedFn = void(const char* canonical, bool reloadable, void* context);
#endif

struct FindPluginsArgs
{
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
    cpp::span<const cpp::string_view> searchPaths;

    bool searchRecursive;

    cpp::span<const cpp::string_view> loadedFileWildcards;

    cpp::span<const cpp::string_view> reloadableFileWildcards;

    cpp::span<const cpp::string_view> excludedFileWildcards;
#else
    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;
#endif

    FindPluginsOnMatchedFn* onMatched;
    void* onMatchedContext;

    filesystem::FindFilesOnExcludedFn* onExcluded;
    void* onExcludedContext;

    filesystem::FindFilesOnSkippedFn* onSkipped;
    void* onSkippedContext;

    filesystem::FindFilesOnSearchPathFn* onSearchPath;
    void* onSearchPathContext;

    filesystem::IFileSystem* fs;
};

namespace detail
{
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
using StrType = cpp::zstring_view;
using SVType = cpp::string_view;
#    define CARBLOCAL_ASSIGN_SPAN(first, second, afirst, asecond) (first) = (afirst)
#    define CARBLOCAL_SPAN_SIZE(first, second) ((first).size())
inline filesystem::WalkAction filterNonCanonical(cpp::zstring_view path, void*)
{
    return extras::caseInsensitiveEndsWith(path, CARB_LIBRARY_EXTENSION) ? filesystem::WalkAction::eContinue :
                                                                           filesystem::WalkAction::eSkip;
}
inline void excludeLog(cpp::zstring_view path, void*)
{
    CARB_LOG_VERBOSE(CARB_LOG_DEFAULT_CHANNEL, "Excluding potential plugin file: %s", path.c_str());
}
inline void logSearchPaths(cpp::zstring_view path, void* context)
{
    auto args = static_cast<FindPluginsArgs*>(context);
    CARB_LOG_VERBOSE(CARB_LOG_DEFAULT_CHANNEL, "Searching plugins %sin folder: %s",
                     args->searchRecursive ? "recursively " : "", path.c_str());
}
#else
using StrType = const char*;
using SVType = cpp::zstring_view;
#    define CARBLOCAL_ASSIGN_SPAN(first, second, afirst, asecond) (first) = (afirst), (second) = uint32_t(asecond)
#    define CARBLOCAL_SPAN_SIZE(first, second) (second)
inline filesystem::WalkAction filterNonCanonical(const char* path, void*)
{
    return extras::caseInsensitiveEndsWith(cpp::unsafe_length, path, CARB_LIBRARY_EXTENSION) ?
               filesystem::WalkAction::eContinue :
               filesystem::WalkAction::eSkip;
}
inline void excludeLog(const char* path, void*)
{
    CARB_LOG_VERBOSE("Excluding potential plugin file: %s", path);
}
inline void logSearchPaths(const char* path, void* context)
{
    auto args = static_cast<FindPluginsArgs*>(context);
    CARB_LOG_VERBOSE("Searching plugins %sin folder: %s", args->searchRecursive ? "recursively " : "", path);
}
#endif
} // namespace detail

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

    CARBLOCAL_ASSIGN_SPAN(args.searchPaths, args.searchPathsCount, inArgs.searchPaths, inArgs.searchPathCount);

    PluginLoadingDesc defaultPluginDesc = PluginLoadingDesc::getDefault();
    if (CARBLOCAL_SPAN_SIZE(args.searchPaths, args.searchPathsCount) == 0)
    {
        // If search path count it not specified, fall back to the default desc search paths
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
        auto stack = CARB_STACK_ALLOC(cpp::string_view, defaultPluginDesc.searchPathCount);
        for (size_t i = 0; i != defaultPluginDesc.searchPathCount; ++i)
            stack[i] = cpp::string_view(cpp::unsafe_length, defaultPluginDesc.searchPaths[i]);
        args.searchPaths = { stack, defaultPluginDesc.searchPathCount };
#else
        args.searchPaths = defaultPluginDesc.searchPaths;
        args.searchPathsCount = uint32_t(defaultPluginDesc.searchPathCount);
#endif
    }

    CARBLOCAL_ASSIGN_SPAN(
        args.matchWildcards, args.matchWildcardsCount, inArgs.loadedFileWildcards, inArgs.loadedFileWildcardCount);

    CARBLOCAL_ASSIGN_SPAN(args.excludeWildcards, args.excludeWildcardsCount, inArgs.excludedFileWildcards,
                          inArgs.excludedFileWildcardCount);

#if CARB_PLATFORM_LINUX
    constexpr static std::array<detail::SVType, 1> kIgnorePrefixes = { detail::SVType("lib") };
#elif CARB_PLATFORM_WINDOWS
    constexpr static std::array<detail::SVType, 0> kIgnorePrefixes;
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif

#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
    args.ignorePrefixes = kIgnorePrefixes;
#else
    {
        auto stack = CARB_STACK_ALLOC(const char*, kIgnorePrefixes.size());
        for (size_t i = 0; i != kIgnorePrefixes.size(); ++i)
            stack[i] = kIgnorePrefixes[i].c_str();

        args.ignorePrefixes = stack;
        args.ignorePrefixesCount = uint32_t(kIgnorePrefixes.size());
    }
#endif

    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 = detail::filterNonCanonical;

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

        bool reloadable = false;
        if (CARBLOCAL_SPAN_SIZE(inArgs->reloadableFileWildcards, inArgs->reloadableFileWildcardCount) != 0)
        {
            extras::Path path(canonical);
            auto stemBuffer = path.getStem();
            carb::cpp::zstring_view stem = stemBuffer.getZStringView();
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
            auto reloadableFileWildcards = inArgs->reloadableFileWildcards;
#else
            auto stack = CARB_STACK_ALLOC(cpp::string_view, inArgs->reloadableFileWildcardCount);
            for (size_t i = 0; i != inArgs->reloadableFileWildcardCount; ++i)
                stack[i] = cpp::string_view(cpp::unsafe_length, inArgs->reloadableFileWildcards[i]);
            auto reloadableFileWildcards = cpp::span<cpp::string_view>{ stack, inArgs->reloadableFileWildcardCount };
#endif

            reloadable = omni::str::matchWildcards(stem, reloadableFileWildcards).has_value();
#if CARB_PLATFORM_LINUX
            if (!reloadable)
            {
                if (extras::startsWith(stem, "lib"))
                {
                    stem.remove_prefix(3);
                    reloadable = omni::str::matchWildcards(stem, reloadableFileWildcards).has_value();
                }
            }
#endif
        }

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

    args.onExcluded = inArgs.onExcluded;
    args.onExcludedContext = inArgs.onExcludedContext;
    if (!args.onExcluded && g_carbLogLevel <= carb::logging::kLevelVerbose)
    {
        args.onExcluded = detail::excludeLog;
    }

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

    args.onSearchPath = inArgs.onSearchPath;
    args.onSearchPathContext = inArgs.onSearchPathContext;
    if (!args.onSearchPath && g_carbLogLevel <= carb::logging::kLevelVerbose)
    {
        args.onSearchPath = detail::logSearchPaths;
        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

#undef CARBLOCAL_ASSIGN_SPAN
#undef CARBLOCAL_SPAN_SIZE