carb/StartupUtils.h
File members: carb/StartupUtils.h
// Copyright (c) 2018-2024, 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 "crashreporter/CrashReporterUtils.h"
#include "dictionary/DictionaryUtils.h"
#include "extras/AppConfig.h"
#include "extras/CmdLineParser.h"
#include "extras/EnvironmentVariableParser.h"
#include "extras/EnvironmentVariableUtils.h"
#include "extras/Path.h"
#include "extras/VariableSetup.h"
#include "filesystem/IFileSystem.h"
#include "l10n/L10nUtils.h"
#include "logging/Log.h"
#include "logging/LoggingSettingsUtils.h"
#include "logging/StandardLogger.h"
#include "profiler/Profile.h"
#include "settings/ISettings.h"
#include "tokens/ITokens.h"
#include "tokens/TokensUtils.h"
#include "../omni/structuredlog/StructuredLogSettingsUtils.h"
#include <array>
#include <map>
#include <string>
#include <vector>
namespace carb
{
struct StartupFrameworkDesc
{
const char* configString; // Path to a config file or string with configuration data
char** argv;
int argc;
const char* const* initialPluginsSearchPaths;
size_t initialPluginsSearchPathCount;
const char* cmdLineParamPrefix;
const char* envVarsParamPrefix;
const char* configFormat;
const char* appNameOverride;
const char* appPathOverride;
bool disableCrashReporter;
PluginLoadingDesc pluginLoadingDesc;
static StartupFrameworkDesc getDefault()
{
static constexpr const char* kDefaultCmdLineParamPrefix = "--/";
static constexpr const char* kDefaultEnvVarsParamPrefix = "OMNI_APPNAME_";
static constexpr const char* kDefaultConfigFormat = "toml";
StartupFrameworkDesc result{};
result.cmdLineParamPrefix = kDefaultCmdLineParamPrefix;
result.envVarsParamPrefix = kDefaultEnvVarsParamPrefix;
result.configFormat = kDefaultConfigFormat;
result.pluginLoadingDesc = carb::PluginLoadingDesc::getDefault();
return result;
}
};
inline void loadPluginsFromPatterns(const char* const* pluginNamePatterns,
size_t pluginNamePatternCount,
const char* const* searchPaths = nullptr,
size_t searchPathCount = 0)
{
Framework* f = getFramework();
PluginLoadingDesc desc = PluginLoadingDesc::getDefault();
desc.loadedFileWildcards = pluginNamePatterns;
desc.loadedFileWildcardCount = pluginNamePatternCount;
desc.searchPaths = searchPaths;
desc.searchPathCount = searchPathCount;
f->loadPlugins(desc);
}
inline void loadPluginsFromPattern(const char* pluginNamePattern,
const char* const* searchPaths = nullptr,
size_t searchPathCount = 0)
{
const char* plugins[] = { pluginNamePattern };
loadPluginsFromPatterns(plugins, countOf(plugins), searchPaths, searchPathCount);
}
namespace detail
{
inline void loadPluginsFromConfig(settings::ISettings* settings, const PluginLoadingDesc& pluginLoadingDesc)
{
if (settings == nullptr)
return;
Framework* f = getFramework();
// Initialize the plugin loading description to default configuration,
// and override parts of it to the config values, if present.
PluginLoadingDesc loadingDesc = pluginLoadingDesc;
// Check if plugin search paths are present in the config, and override if present
const char* kPluginSearchPathsKey = "/pluginSearchPaths";
std::vector<const char*> pluginSearchPaths(settings->getArrayLength(kPluginSearchPathsKey));
if (!pluginSearchPaths.empty())
{
settings->getStringBufferArray(kPluginSearchPathsKey, pluginSearchPaths.data(), pluginSearchPaths.size());
loadingDesc.searchPaths = pluginSearchPaths.data();
loadingDesc.searchPathCount = pluginSearchPaths.size();
}
const char* kPluginSearchRecursive = "/pluginSearchRecursive";
// Is search recursive?
if (settings->isAccessibleAs(carb::dictionary::ItemType::eBool, kPluginSearchRecursive))
{
loadingDesc.searchRecursive = settings->getAsBool(kPluginSearchRecursive);
}
// Check/override reloadable plugins if present
const char* kReloadablePluginsKey = "/reloadablePlugins";
std::vector<const char*> reloadablePluginFiles(settings->getArrayLength(kReloadablePluginsKey));
if (!reloadablePluginFiles.empty())
{
settings->getStringBufferArray(kReloadablePluginsKey, reloadablePluginFiles.data(), reloadablePluginFiles.size());
loadingDesc.reloadableFileWildcards = reloadablePluginFiles.data();
loadingDesc.reloadableFileWildcardCount = reloadablePluginFiles.size();
}
// Check/override plugins to load if present
const char* kPluginsLoadedKey = "/pluginsLoaded";
std::vector<const char*> pluginsLoaded;
if (settings->getItemType(kPluginsLoadedKey) == dictionary::ItemType::eDictionary)
{
pluginsLoaded.resize(settings->getArrayLength(kPluginsLoadedKey));
settings->getStringBufferArray(kPluginsLoadedKey, pluginsLoaded.data(), pluginsLoaded.size());
loadingDesc.loadedFileWildcards = pluginsLoaded.size() ? pluginsLoaded.data() : nullptr;
loadingDesc.loadedFileWildcardCount = pluginsLoaded.size();
}
const char* kPluginsExcludedKey = "/pluginsExcluded";
std::vector<const char*> pluginsExcluded;
if (settings->getItemType(kPluginsExcludedKey) == dictionary::ItemType::eDictionary)
{
pluginsExcluded.resize(settings->getArrayLength(kPluginsExcludedKey));
settings->getStringBufferArray(kPluginsExcludedKey, pluginsExcluded.data(), pluginsExcluded.size());
loadingDesc.excludedFileWildcards = pluginsExcluded.size() ? pluginsExcluded.data() : nullptr;
loadingDesc.excludedFileWildcardCount = pluginsExcluded.size();
}
// Load plugins based on the resulting desc
if (loadingDesc.loadedFileWildcardCount)
f->loadPlugins(loadingDesc);
}
inline void setDefaultPluginsFromConfig(settings::ISettings* settings)
{
if (settings == nullptr)
return;
Framework* f = getFramework();
// Default plugins
const char* kDefaultPluginsKey = "/defaultPlugins";
std::vector<const char*> defaultPlugins(settings->getArrayLength(kDefaultPluginsKey));
if (!defaultPlugins.empty())
{
settings->getStringBufferArray(kDefaultPluginsKey, defaultPlugins.data(), defaultPlugins.size());
for (const char* pluginName : defaultPlugins)
{
// Set plugin as default for all interfaces it provides
const PluginDesc& pluginDesc = f->getPluginDesc(pluginName);
for (size_t i = 0; i < pluginDesc.interfaceCount; i++)
{
f->setDefaultPluginEx(g_carbClientName, pluginDesc.interfaces[i], pluginName);
}
}
}
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
// If the dict item is a special raw string then it returns pointer to the buffer past the special raw string marker
// In all other cases it returns nullptr
inline const char* getRawStringFromItem(carb::dictionary::IDictionary* dictInterface, const carb::dictionary::Item* item)
{
if (!dictInterface || !item)
{
return nullptr;
}
if (dictInterface->getItemType(item) != dictionary::ItemType::eString)
{
return nullptr;
}
const char* stringBuffer = dictInterface->getStringBuffer(item);
if (!stringBuffer)
{
return nullptr;
}
constexpr char kSpecialRawStringMarker[] = "$raw:";
constexpr size_t kMarkerLen = carb::countOf(kSpecialRawStringMarker) - 1;
if (std::strncmp(stringBuffer, kSpecialRawStringMarker, kMarkerLen) != 0)
{
return nullptr;
}
return stringBuffer + kMarkerLen;
}
class LoadSettingsHelper
{
public:
struct SupportedConfigInfo
{
const char* configFormatName;
const char* serializerPluginName;
const char* configExt;
};
LoadSettingsHelper()
{
Framework* f = getFramework();
m_fs = f->acquireInterface<filesystem::IFileSystem>();
}
struct LoadSettingsDesc
{
std::string appDir; // Application directory
std::string appName; // Application name
const char* configStringOrPath; // Configuration string that can be null, string containing configuration data
// (in selected configFormat) or a path to a config file
const extras::ConfigLoadHelper::CmdLineOptionsMap* cmdLineOptionsMap; // Mapping of the command line options
const extras::ConfigLoadHelper::PathwiseEnvOverridesMap* pathwiseEnvOverridesMap; // Mapping of path-wise
// environment variables that
// will be mapped into
// corresponding settings
const extras::ConfigLoadHelper::EnvVariablesMap* envVariablesMap; // Mapping of common environment variables
const char* const* pluginSearchPaths; // Array of directories used by the system to search for plugins
size_t pluginSearchPathCount; // Number of elements in the pluginSearchPaths
const char* cmdLineConfigPath; // Path to a file containing config override (in selected configFormat), can be
// null
const char* configFormat; // Selected configuration format that is supported by the system
inline static LoadSettingsDesc getDefault() noexcept
{
LoadSettingsDesc result{};
Framework* f = getFramework();
filesystem::IFileSystem* fs = f->acquireInterface<filesystem::IFileSystem>();
extras::Path execPathStem(extras::getPathStem(fs->getExecutablePath()));
// Initialize application path and name to the executable path and name
result.appName = execPathStem.getFilename();
result.appDir = execPathStem.getParent();
result.configFormat = "toml";
return result;
}
inline void overwriteWithNonEmptyParams(const LoadSettingsDesc& other) noexcept
{
if (!other.appDir.empty())
{
appDir = other.appDir;
}
if (!other.appName.empty())
{
appName = other.appName;
}
if (other.configStringOrPath)
{
configStringOrPath = other.configStringOrPath;
}
if (other.cmdLineOptionsMap)
{
cmdLineOptionsMap = other.cmdLineOptionsMap;
}
if (other.pathwiseEnvOverridesMap)
{
pathwiseEnvOverridesMap = other.pathwiseEnvOverridesMap;
}
if (other.envVariablesMap)
{
envVariablesMap = other.envVariablesMap;
}
if (other.pluginSearchPaths)
{
pluginSearchPaths = other.pluginSearchPaths;
pluginSearchPathCount = other.pluginSearchPathCount;
}
if (other.cmdLineConfigPath)
{
cmdLineConfigPath = other.cmdLineConfigPath;
}
if (other.configFormat)
{
configFormat = other.configFormat;
}
}
};
void loadBaseSettingsPlugins(const char* const* pluginSearchPaths, size_t pluginSearchPathCount)
{
Framework* f = getFramework();
// clang-format off
const char* plugins[] = {
"carb.dictionary.plugin",
"carb.settings.plugin",
"carb.tokens.plugin",
m_selectedConfigInfo ? m_selectedConfigInfo->serializerPluginName : "carb.dictionary.serializer-toml.plugin"
};
// clang-format on
loadPluginsFromPatterns(plugins, countOf(plugins), pluginSearchPaths, pluginSearchPathCount);
m_idict = f->tryAcquireInterface<dictionary::IDictionary>();
if (m_idict == nullptr)
{
CARB_LOG_INFO("Couldn't acquire dictionary::IDictionary interface on startup to load the settings.");
return;
}
m_settings = f->tryAcquireInterface<settings::ISettings>();
if (m_settings == nullptr)
{
CARB_LOG_INFO("Couldn't acquire settings::ISettings interface on startup to load the settings.");
}
}
class ConfigStageLoader
{
public:
static constexpr const char* kConfigSuffix = ".config";
static constexpr const char* kOverrideSuffix = ".override";
ConfigStageLoader(filesystem::IFileSystem* fs,
dictionary::ISerializer* configSerializer,
LoadSettingsHelper* helper,
const SupportedConfigInfo* selectedConfigInfo,
const extras::ConfigLoadHelper::EnvVariablesMap* envVariablesMap)
: m_fs(fs),
m_configSerializer(configSerializer),
m_helper(helper),
m_selectedConfigInfo(selectedConfigInfo),
m_envVariablesMap(envVariablesMap)
{
m_possibleConfigPathsStorage.reserve(4);
}
dictionary::Item* loadAndMergeSharedUserSpaceConfig(const extras::Path& userFolder,
dictionary::Item* combinedConfig,
std::string* sharedUserSpaceFilepath)
{
if (!userFolder.isEmpty())
{
m_possibleConfigPathsStorage.clear();
m_possibleConfigPathsStorage.emplace_back(userFolder / "omni" + kConfigSuffix +
m_selectedConfigInfo->configExt);
return tryLoadAnySettingsAndMergeIntoTarget(m_configSerializer, combinedConfig,
m_possibleConfigPathsStorage, m_envVariablesMap,
sharedUserSpaceFilepath);
}
return combinedConfig;
}
dictionary::Item* loadAndMergeAppSpecificUserSpaceConfig(const extras::Path& userFolder,
const std::string& appName,
dictionary::Item* combinedConfig,
std::string* appSpecificUserSpaceFilepath)
{
if (!userFolder.isEmpty())
{
m_possibleConfigPathsStorage.clear();
m_possibleConfigPathsStorage.emplace_back(userFolder / appName + kConfigSuffix +
m_selectedConfigInfo->configExt);
return tryLoadAnySettingsAndMergeIntoTarget(m_configSerializer, combinedConfig,
m_possibleConfigPathsStorage, m_envVariablesMap,
appSpecificUserSpaceFilepath);
}
return combinedConfig;
}
dictionary::Item* loadAndMergeLocalSpaceConfig(const std::string& appDir,
const std::string& appName,
dictionary::Item* combinedConfig,
std::string* localSpaceConfigFilepath)
{
const extras::Path cwd(m_fs->getCurrentDirectoryPath());
const extras::Path appDirPath(appDir);
const extras::Path exePath(m_fs->getExecutableDirectoryPath());
const std::string appConfig = appName + kConfigSuffix + m_selectedConfigInfo->configExt;
m_possibleConfigPathsStorage.clear();
m_possibleConfigPathsStorage.emplace_back(cwd / appConfig);
if (!appDir.empty())
{
m_possibleConfigPathsStorage.emplace_back(appDirPath / appConfig);
}
if (appDirPath != exePath)
{
m_possibleConfigPathsStorage.emplace_back(exePath / appConfig);
}
return tryLoadAnySettingsAndMergeIntoTarget(m_configSerializer, combinedConfig, m_possibleConfigPathsStorage,
m_envVariablesMap, localSpaceConfigFilepath);
}
dictionary::Item* loadAndMergeSharedUserSpaceConfigOverride(dictionary::Item* combinedConfig,
const std::string& sharedUserSpaceFilepath)
{
if (!sharedUserSpaceFilepath.empty())
{
m_possibleConfigPathsStorage.clear();
addPossiblePathOverridesForSearch(
extras::getPathStem(sharedUserSpaceFilepath), m_selectedConfigInfo->configExt);
return tryLoadAnySettingsAndMergeIntoTarget(
m_configSerializer, combinedConfig, m_possibleConfigPathsStorage, m_envVariablesMap, nullptr);
}
return combinedConfig;
}
dictionary::Item* loadAndMergeAppSpecificUserSpaceConfigOverride(dictionary::Item* combinedConfig,
const std::string& appSpecificUserSpaceFilepath)
{
if (!appSpecificUserSpaceFilepath.empty())
{
m_possibleConfigPathsStorage.clear();
addPossiblePathOverridesForSearch(
extras::getPathStem(appSpecificUserSpaceFilepath), m_selectedConfigInfo->configExt);
return tryLoadAnySettingsAndMergeIntoTarget(
m_configSerializer, combinedConfig, m_possibleConfigPathsStorage, m_envVariablesMap, nullptr);
}
return combinedConfig;
}
dictionary::Item* loadAndMergeLocalSpaceConfigOverride(dictionary::Item* combinedConfig,
const std::string& localSpaceConfigFilepath)
{
if (!localSpaceConfigFilepath.empty())
{
m_possibleConfigPathsStorage.clear();
addPossiblePathOverridesForSearch(
extras::getPathStem(localSpaceConfigFilepath), m_selectedConfigInfo->configExt);
return tryLoadAnySettingsAndMergeIntoTarget(
m_configSerializer, combinedConfig, m_possibleConfigPathsStorage, m_envVariablesMap, nullptr);
}
return combinedConfig;
}
dictionary::Item* loadAndMergeCustomConfig(dictionary::Item* combinedConfig,
const char* filepath,
dictionary::ISerializer* customSerializer = nullptr)
{
m_possibleConfigPathsStorage.clear();
m_possibleConfigPathsStorage.emplace_back(filepath);
dictionary::ISerializer* configSerializer = customSerializer ? customSerializer : m_configSerializer;
return tryLoadAnySettingsAndMergeIntoTarget(
configSerializer, combinedConfig, m_possibleConfigPathsStorage, m_envVariablesMap, nullptr);
}
private:
void addPossiblePathOverridesForSearch(const std::string& pathStem, const char* extension)
{
m_possibleConfigPathsStorage.emplace_back(pathStem + kOverrideSuffix + extension);
m_possibleConfigPathsStorage.emplace_back(pathStem + extension + kOverrideSuffix);
}
dictionary::Item* tryLoadAnySettingsAndMergeIntoTarget(dictionary::ISerializer* configSerializer,
dictionary::Item* targetDict,
const std::vector<std::string>& possibleConfigPaths,
const extras::ConfigLoadHelper::EnvVariablesMap* envVariablesMap,
std::string* loadedDictPath)
{
if (loadedDictPath)
{
loadedDictPath->clear();
}
dictionary::Item* loadedDict = nullptr;
for (const auto& curConfigPath : possibleConfigPaths)
{
const char* dictFilename = curConfigPath.c_str();
if (!m_fs->exists(dictFilename))
{
continue;
}
loadedDict = dictionary::createDictionaryFromFile(configSerializer, dictFilename);
if (loadedDict)
{
if (loadedDictPath)
{
*loadedDictPath = dictFilename;
}
CARB_LOG_INFO("Found and loaded settings from: %s", dictFilename);
break;
}
else
{
CARB_LOG_ERROR("Couldn't load the '%s' config data from file '%s'",
m_selectedConfigInfo->configFormatName, dictFilename);
break;
}
}
dictionary::IDictionary* dictionaryInterface = m_helper->getDictionaryInterface();
return extras::ConfigLoadHelper::resolveAndMergeNewDictIntoTarget(
dictionaryInterface, targetDict, loadedDict, loadedDictPath ? loadedDictPath->c_str() : nullptr,
envVariablesMap);
}
std::vector<std::string> m_possibleConfigPathsStorage;
filesystem::IFileSystem* m_fs = nullptr;
dictionary::ISerializer* m_configSerializer = nullptr;
LoadSettingsHelper* m_helper = nullptr;
const SupportedConfigInfo* m_selectedConfigInfo = nullptr;
const extras::ConfigLoadHelper::EnvVariablesMap* m_envVariablesMap = nullptr;
};
inline dictionary::ISerializer* acquireOrLoadSerializerFromConfigInfo(const LoadSettingsDesc& params,
const SupportedConfigInfo* configInfo)
{
dictionary::ISerializer* configSerializer =
getFramework()->tryAcquireInterface<dictionary::ISerializer>(configInfo->serializerPluginName);
if (configSerializer)
return configSerializer;
return loadConfigSerializerPlugin(params.pluginSearchPaths, params.pluginSearchPathCount, configInfo);
}
inline dictionary::Item* readConfigStages(const LoadSettingsDesc& params,
std::string* localSpaceConfigFilepath,
std::string* customConfigFilepath,
std::string* cmdLineConfigFilepath)
{
if (!m_configSerializer)
{
return nullptr;
}
CARB_LOG_INFO("Using '%s' format for config files.", m_selectedConfigInfo->configFormatName);
dictionary::Item* combinedConfig = nullptr;
extras::Path userFolder = extras::ConfigLoadHelper::getConfigUserFolder(params.envVariablesMap);
std::string sharedUserSpaceFilepath;
std::string appSpecificUserSpaceFilepath;
ConfigStageLoader configStageLoader(m_fs, m_configSerializer, this, m_selectedConfigInfo, params.envVariablesMap);
// Base configs
combinedConfig =
configStageLoader.loadAndMergeSharedUserSpaceConfig(userFolder, combinedConfig, &sharedUserSpaceFilepath);
combinedConfig = configStageLoader.loadAndMergeAppSpecificUserSpaceConfig(
userFolder, params.appName, combinedConfig, &appSpecificUserSpaceFilepath);
combinedConfig = configStageLoader.loadAndMergeLocalSpaceConfig(
params.appDir, params.appName, combinedConfig, localSpaceConfigFilepath);
// Overrides
combinedConfig =
configStageLoader.loadAndMergeSharedUserSpaceConfigOverride(combinedConfig, sharedUserSpaceFilepath);
combinedConfig = configStageLoader.loadAndMergeAppSpecificUserSpaceConfigOverride(
combinedConfig, appSpecificUserSpaceFilepath);
combinedConfig =
configStageLoader.loadAndMergeLocalSpaceConfigOverride(combinedConfig, *localSpaceConfigFilepath);
tokens::ITokens* tokensInterface = carb::getFramework()->tryAcquireInterface<tokens::ITokens>();
// Loading text configuration override
if (params.configStringOrPath)
{
std::string configPath;
if (tokensInterface)
{
configPath = tokens::resolveString(tokensInterface, params.configStringOrPath);
}
else
{
configPath = params.configStringOrPath;
}
if (m_fs->exists(configPath.c_str()))
{
std::string configExt = extras::Path(configPath).getExtension();
const SupportedConfigInfo* configInfo = getConfigInfoFromExtension(configExt.c_str());
dictionary::ISerializer* customSerializer = acquireOrLoadSerializerFromConfigInfo(params, configInfo);
if (customConfigFilepath)
*customConfigFilepath = configPath;
combinedConfig =
configStageLoader.loadAndMergeCustomConfig(combinedConfig, configPath.c_str(), customSerializer);
}
else
{
dictionary::Item* textConfigurationOverride =
m_configSerializer->createDictionaryFromStringBuffer(params.configStringOrPath);
if (textConfigurationOverride)
{
CARB_LOG_INFO("Loaded text configuration override");
combinedConfig = extras::ConfigLoadHelper::resolveAndMergeNewDictIntoTarget(
m_idict, combinedConfig, textConfigurationOverride, "text configuration override",
params.envVariablesMap);
}
else
{
CARB_LOG_ERROR("Couldn't process provided config string as a '%s' config file or config data",
m_selectedConfigInfo->configFormatName);
}
}
}
// Loading custom file configuration override
if (params.cmdLineConfigPath)
{
std::string configPath;
if (tokensInterface)
{
configPath = tokens::resolveString(tokensInterface, params.cmdLineConfigPath);
}
else
{
configPath = params.cmdLineConfigPath;
}
if (m_fs->exists(configPath.c_str()))
{
std::string configExt = extras::Path(configPath).getExtension();
const SupportedConfigInfo* configInfo = getConfigInfoFromExtension(configExt.c_str());
dictionary::ISerializer* customSerializer = acquireOrLoadSerializerFromConfigInfo(params, configInfo);
if (cmdLineConfigFilepath)
*cmdLineConfigFilepath = params.cmdLineConfigPath;
combinedConfig =
configStageLoader.loadAndMergeCustomConfig(combinedConfig, configPath.c_str(), customSerializer);
}
else
{
CARB_LOG_ERROR("The config file '%s' provided via command line doesn't exist", params.cmdLineConfigPath);
}
}
combinedConfig = extras::ConfigLoadHelper::applyPathwiseEnvOverrides(
m_idict, combinedConfig, params.pathwiseEnvOverridesMap, params.envVariablesMap);
combinedConfig = extras::ConfigLoadHelper::applyCmdLineOverrides(
m_idict, combinedConfig, params.cmdLineOptionsMap, params.envVariablesMap);
return combinedConfig;
}
const auto& getSupportedConfigTypes()
{
static const std::array<SupportedConfigInfo, 2> kSupportedConfigTypes = {
{ { "toml", "carb.dictionary.serializer-toml.plugin", ".toml" },
{ "json", "carb.dictionary.serializer-json.plugin", ".json" } }
};
return kSupportedConfigTypes;
}
const SupportedConfigInfo* getConfigInfoFromExtension(const char* configExtension)
{
const std::string parmsConfigExt = configExtension;
for (const auto& curConfigInfo : getSupportedConfigTypes())
{
const char* curConfigExtEnd = curConfigInfo.configExt + std::strlen(curConfigInfo.configExt);
if (std::equal(curConfigInfo.configExt, curConfigExtEnd, parmsConfigExt.begin(), parmsConfigExt.end(),
[](char l, char r) { return std::tolower(l) == std::tolower(r); }))
{
return &curConfigInfo;
}
}
return nullptr;
}
const SupportedConfigInfo* getConfigInfoFromFormatName(const char* configFormat)
{
const std::string parmsConfigFormat = configFormat;
for (const auto& curConfigInfo : getSupportedConfigTypes())
{
const char* curConfigFormatEnd = curConfigInfo.configFormatName + std::strlen(curConfigInfo.configFormatName);
if (std::equal(curConfigInfo.configFormatName, curConfigFormatEnd, parmsConfigFormat.begin(),
parmsConfigFormat.end(), [](char l, char r) { return std::tolower(l) == std::tolower(r); }))
{
return &curConfigInfo;
}
}
return nullptr;
}
void selectConfigType(const char* configFormat)
{
m_selectedConfigInfo = getConfigInfoFromFormatName(configFormat);
if (!m_selectedConfigInfo)
{
CARB_LOG_ERROR("Unsupported configuration format: %s. Falling back to %s", configFormat,
getSupportedConfigTypes()[0].configFormatName);
m_selectedConfigInfo = &getSupportedConfigTypes()[0];
}
}
static dictionary::ISerializer* loadConfigSerializerPlugin(const char* const* pluginSearchPaths,
size_t pluginSearchPathCount,
const SupportedConfigInfo* configInfo)
{
if (!configInfo)
{
return nullptr;
}
dictionary::ISerializer* configSerializer =
getFramework()->tryAcquireInterface<dictionary::ISerializer>(configInfo->serializerPluginName);
if (!configSerializer)
{
loadPluginsFromPattern(configInfo->serializerPluginName, pluginSearchPaths, pluginSearchPathCount);
configSerializer =
getFramework()->tryAcquireInterface<dictionary::ISerializer>(configInfo->serializerPluginName);
}
if (!configSerializer)
{
CARB_LOG_ERROR("Couldn't acquire ISerializer interface on startup for parsing '%s' settings.",
configInfo->configFormatName);
}
return configSerializer;
}
void loadSelectedConfigSerializerPlugin(const char* const* pluginSearchPaths, size_t pluginSearchPathCount)
{
m_configSerializer = loadConfigSerializerPlugin(pluginSearchPaths, pluginSearchPathCount, m_selectedConfigInfo);
}
void fixRawStrings(dictionary::Item* combinedConfig)
{
// Fixing the special raw strings
auto rawStringsFixer = [&](dictionary::Item* item, uint32_t elementData, void* userData) {
CARB_UNUSED(elementData, userData);
const char* rawString = getRawStringFromItem(m_idict, item);
if (!rawString)
{
return 0;
}
// buffering the value to be implementation-safe
const std::string value(rawString);
m_idict->setString(item, value.c_str());
return 0;
};
const auto getChildByIndexMutable = [](dictionary::IDictionary* dict, dictionary::Item* item, size_t index) {
return dict->getItemChildByIndexMutable(item, index);
};
dictionary::walkDictionary(m_idict, dictionary::WalkerMode::eIncludeRoot, combinedConfig, 0, rawStringsFixer,
nullptr, getChildByIndexMutable);
}
dictionary::IDictionary* getDictionaryInterface() const
{
return m_idict;
}
dictionary::ISerializer* getConfigSerializerInterface() const
{
return m_configSerializer;
}
settings::ISettings* getSettingsInterface() const
{
return m_settings;
}
dictionary::Item* createEmptyDict(const char* name = "<config>")
{
dictionary::Item* item = m_idict->createItem(nullptr, name, dictionary::ItemType::eDictionary);
if (!item)
{
CARB_LOG_ERROR("Couldn't create empty configuration");
}
return item;
};
private:
filesystem::IFileSystem* m_fs = nullptr;
dictionary::IDictionary* m_idict = nullptr;
dictionary::ISerializer* m_configSerializer = nullptr;
settings::ISettings* m_settings = nullptr;
const SupportedConfigInfo* m_selectedConfigInfo = nullptr;
};
inline void loadSettings(const LoadSettingsHelper::LoadSettingsDesc& settingsDesc)
{
Framework* f = getFramework();
// Preparing settings parameters
LoadSettingsHelper::LoadSettingsDesc params = LoadSettingsHelper::LoadSettingsDesc::getDefault();
params.overwriteWithNonEmptyParams(settingsDesc);
LoadSettingsHelper loadSettingsHelper;
loadSettingsHelper.selectConfigType(params.configFormat);
loadSettingsHelper.loadBaseSettingsPlugins(params.pluginSearchPaths, params.pluginSearchPathCount);
filesystem::IFileSystem* fs = f->acquireInterface<filesystem::IFileSystem>();
tokens::ITokens* tokensInterface = f->tryAcquireInterface<tokens::ITokens>();
// Initializing tokens
if (tokensInterface)
{
const char* kExePathToken = "exe-path";
const char* kExeFilenameToken = "exe-filename";
carb::extras::Path exeFullPath = fs->getExecutablePath();
tokensInterface->setInitialValue(kExePathToken, exeFullPath.getParent().getStringBuffer());
tokensInterface->setInitialValue(kExeFilenameToken, exeFullPath.getFilename().getStringBuffer());
}
settings::ISettings* settings = loadSettingsHelper.getSettingsInterface();
std::string localSpaceConfigFilepath;
std::string customConfigFilepath;
std::string cmdLineConfigFilepath;
if (settings)
{
loadSettingsHelper.loadSelectedConfigSerializerPlugin(params.pluginSearchPaths, params.pluginSearchPathCount);
dictionary::Item* combinedConfig = nullptr;
combinedConfig = loadSettingsHelper.readConfigStages(
params, &localSpaceConfigFilepath, &customConfigFilepath, &cmdLineConfigFilepath);
if (!combinedConfig)
{
dictionary::IDictionary* dictionaryInterface = loadSettingsHelper.getDictionaryInterface();
CARB_LOG_INFO("Using empty configuration for settings as no other sources created it.");
combinedConfig = dictionaryInterface->createItem(nullptr, "<settings>", dictionary::ItemType::eDictionary);
}
if (!combinedConfig)
{
CARB_LOG_ERROR("Couldn't initialize settings because no configuration were created.");
}
else
{
loadSettingsHelper.fixRawStrings(combinedConfig);
// Making the settings from the result dictionary
settings->initializeFromDictionary(combinedConfig);
}
}
else
{
CARB_LOG_INFO("Couldn't acquire ISettings interface on startup to load settings.");
}
// Initializing tokens
if (tokensInterface)
{
const char* kLocalSpaceConfigPathToken = "local-config-path";
const char* kLocalSpaceConfigPathTokenStr = "${local-config-path}";
const char* kCustomConfigPathToken = "custom-config-path";
const char* kCmdLineConfigPathToken = "cli-config-path";
if (!localSpaceConfigFilepath.empty())
{
tokensInterface->setInitialValue(kLocalSpaceConfigPathToken, localSpaceConfigFilepath.c_str());
}
else
{
tokensInterface->setInitialValue(kLocalSpaceConfigPathToken, fs->getCurrentDirectoryPath());
}
if (!customConfigFilepath.empty())
{
tokensInterface->setInitialValue(kCustomConfigPathToken, customConfigFilepath.c_str());
}
else
{
tokensInterface->setInitialValue(kCustomConfigPathToken, kLocalSpaceConfigPathTokenStr);
}
if (!cmdLineConfigFilepath.empty())
{
tokensInterface->setInitialValue(kCmdLineConfigPathToken, cmdLineConfigFilepath.c_str());
}
else
{
tokensInterface->setInitialValue(kCmdLineConfigPathToken, kLocalSpaceConfigPathTokenStr);
}
}
else
{
CARB_LOG_INFO("Couldn't acquire tokens interface and initialize default tokens.");
}
}
#endif // DOXYGEN_SHOULD_SKIP_THIS
} // namespace detail
inline void loadFrameworkConfiguration(const StartupFrameworkDesc& params)
{
Framework* f = getFramework();
const StartupFrameworkDesc& defaultStartupFrameworkDesc = StartupFrameworkDesc::getDefault();
const char* cmdLineParamPrefix = params.cmdLineParamPrefix;
if (!cmdLineParamPrefix)
{
cmdLineParamPrefix = defaultStartupFrameworkDesc.cmdLineParamPrefix;
}
const char* envVarsParamPrefix = params.envVarsParamPrefix;
if (!envVarsParamPrefix)
{
envVarsParamPrefix = defaultStartupFrameworkDesc.envVarsParamPrefix;
}
const char* configFormat = params.configFormat;
if (!configFormat)
{
configFormat = defaultStartupFrameworkDesc.configFormat;
}
char** const argv = params.argv;
const int argc = params.argc;
extras::CmdLineParser cmdLineParser(cmdLineParamPrefix);
cmdLineParser.parse(argv, argc);
const extras::CmdLineParser::Options& args = cmdLineParser.getOptions();
const char* cmdLineConfigPath = nullptr;
bool verboseConfiguration = false;
int32_t startLogLevel = logging::getLogging()->getLevelThreshold();
if (argv && argc > 0)
{
auto findOptionIndex = [=](const char* option) {
for (int i = 0; i < argc; ++i)
{
const char* curArg = argv[i];
if (curArg && !strcmp(curArg, option))
{
return i;
}
}
return -1;
};
auto findOptionValue = [=](const char* option) -> const char* {
const int optionIndex = findOptionIndex(option);
if (optionIndex == -1)
{
return nullptr;
}
if (optionIndex >= argc - 1)
{
CARB_LOG_ERROR("Argument not present for the '%s' option", option);
}
return argv[optionIndex + 1];
};
// Parsing verbose configuration option
const char* const kVerboseConfigKey = "--verbose-config";
verboseConfiguration = findOptionIndex(kVerboseConfigKey) != -1;
if (verboseConfiguration)
{
logging::getLogging()->setLevelThreshold(logging::kLevelVerbose);
}
// Parsing cmd line for "--config-path" argument
const char* const kConfigPathKey = "--config-path";
cmdLineConfigPath = findOptionValue(kConfigPathKey);
if (cmdLineConfigPath)
{
CARB_LOG_INFO("Using '%s' as the value for '%s'", cmdLineConfigPath, kConfigPathKey);
}
// Parsing config format from the command line
const char* kConfigFormatKey = "--config-format";
const char* const configFormatValue = findOptionValue(kConfigFormatKey);
if (configFormatValue)
{
configFormat = configFormatValue;
}
}
carb::extras::EnvironmentVariableParser envVarsParser(envVarsParamPrefix);
envVarsParser.parse();
filesystem::IFileSystem* fs = f->acquireInterface<filesystem::IFileSystem>();
// Prepare application path and name, which will be used to initialize the IFileSystem default root folder,
// and also as one of the variants of configuration file name and location.
std::string appPath, appName;
extras::getAppPathAndName(args, appPath, appName);
// If explicitly specified - override this search logic. That means an application doesn't give a control over
// app path and/or app name through settings and env vars.
if (params.appNameOverride)
appName = params.appNameOverride;
if (params.appPathOverride)
appPath = params.appPathOverride;
CARB_LOG_INFO("App path: %s, name: %s", appPath.c_str(), appName.c_str());
// set the application path for the process. This will be one of the locations we search for
// the config file by default.
fs->setAppDirectoryPath(appPath.c_str());
// Loading settings from config and command line.
{
detail::LoadSettingsHelper::LoadSettingsDesc loadSettingsParams =
detail::LoadSettingsHelper::LoadSettingsDesc::getDefault();
loadSettingsParams.appDir = appPath;
loadSettingsParams.appName = appName;
loadSettingsParams.configStringOrPath = params.configString;
loadSettingsParams.cmdLineOptionsMap = &args;
loadSettingsParams.pathwiseEnvOverridesMap = &envVarsParser.getOptions();
loadSettingsParams.envVariablesMap = &envVarsParser.getEnvVariables();
loadSettingsParams.pluginSearchPaths = params.initialPluginsSearchPaths;
loadSettingsParams.pluginSearchPathCount = params.initialPluginsSearchPathCount;
loadSettingsParams.cmdLineConfigPath = cmdLineConfigPath;
loadSettingsParams.configFormat = configFormat;
detail::loadSettings(loadSettingsParams);
}
// restoring the starting log level
if (verboseConfiguration)
{
logging::getLogging()->setLevelThreshold(startLogLevel);
}
}
inline void configureFramework(const StartupFrameworkDesc& params)
{
Framework* f = getFramework();
if (!params.disableCrashReporter)
{
// Startup the crash reporter
loadPluginsFromPattern(
"carb.crashreporter-*", params.initialPluginsSearchPaths, params.initialPluginsSearchPathCount);
crashreporter::registerCrashReporterForClient();
}
auto settings = f->tryAcquireInterface<carb::settings::ISettings>();
// Configure logging plugin and its default logger
logging::configureLogging(settings);
logging::configureDefaultLogger(settings);
omni::structuredlog::configureStructuredLogging(settings);
// Uploading leftover dumps asynchronously
if (settings != nullptr)
{
if (!params.disableCrashReporter)
{
const char* const kStarupDumpsUploadKey = "/app/uploadDumpsOnStartup";
settings->setDefaultBool(kStarupDumpsUploadKey, true);
if (settings->getAsBool(kStarupDumpsUploadKey))
{
crashreporter::sendAndRemoveLeftOverDumpsAsync();
}
}
// specify the plugin search paths in settings so that loadPluginsFromConfig()
// will have the search paths to look through
const char* kPluginSearchPathsKey = "/pluginSearchPaths";
// only set this if nothing else has been manually set
settings->setDefaultStringArray(
kPluginSearchPathsKey, params.initialPluginsSearchPaths, params.initialPluginsSearchPathCount);
}
// Load plugins using supplied configuration
detail::loadPluginsFromConfig(settings, params.pluginLoadingDesc);
// Configure default plugins as present in the config
detail::setDefaultPluginsFromConfig(settings);
#if !CARB_PLATFORM_MACOS // CC-669: avoid registering this on Mac OS since it's unimplemented
// Starting up profiling
// This way of registering profiler allows to enable/disable profiling in the config file, by
// allowing/denying to load profiler plugin.
carb::profiler::registerProfilerForClient();
CARB_PROFILE_STARTUP();
#endif
carb::l10n::registerLocalizationForClient();
}
inline void startupFramework(const StartupFrameworkDesc& params)
{
loadFrameworkConfiguration(params);
configureFramework(params);
}
inline void shutdownFramework()
{
CARB_PROFILE_SHUTDOWN();
profiler::deregisterProfilerForClient();
crashreporter::deregisterCrashReporterForClient();
carb::l10n::deregisterLocalizationForClient();
}
} // namespace carb