Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Easy Logging using a config file #747

Open
wants to merge 86 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
7ab80fb
Implement easy logging
sfc-gh-ext-simba-nl Oct 2, 2024
836a82a
Add missing function definition
sfc-gh-ext-simba-nl Oct 2, 2024
c1878f3
Fix linux build errors
sfc-gh-ext-simba-nl Oct 2, 2024
21673d4
Fix conversion issue on linux
sfc-gh-ext-simba-nl Oct 2, 2024
806fb9c
Fix mac build error and fix logging test failure
sfc-gh-ext-simba-nl Oct 2, 2024
4a5ad67
Fix mac build error in test case
sfc-gh-ext-simba-nl Oct 3, 2024
4e05bc7
Fix setting of global log path
sfc-gh-ext-simba-nl Oct 3, 2024
a79370d
Use sf_memcpy instead
sfc-gh-ext-simba-nl Oct 4, 2024
be865c1
Remove the use of boost algorithm string for compare
sfc-gh-ext-simba-nl Oct 7, 2024
f90848d
Fix test issue
sfc-gh-ext-simba-nl Oct 8, 2024
3823568
Fix build issue with test
sfc-gh-ext-simba-nl Oct 9, 2024
a863b05
Change ifstream to FILE due to 32bit debug read issue
sfc-gh-ext-simba-nl Oct 9, 2024
994228b
Fix linux build issue
sfc-gh-ext-simba-nl Oct 10, 2024
70bdc60
Fix test issues
sfc-gh-ext-simba-nl Oct 10, 2024
309d118
Rename ifndef for headers to be more consistent
sfc-gh-ext-simba-nl Oct 11, 2024
65e29f1
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-ext-simba-nl Oct 11, 2024
21a3b11
Replace boost with std::filesystem, fix possible NULL issues, add tests
sfc-gh-ext-simba-nl Oct 16, 2024
8d97f3a
Add client_config_parser to include dir
sfc-gh-ext-simba-nl Oct 16, 2024
577925a
Fix compilation error
sfc-gh-ext-simba-nl Oct 16, 2024
6a27fe5
Fix return value error
sfc-gh-ext-simba-nl Oct 17, 2024
fdb5187
Use experimental filesystem for non-windows
sfc-gh-ext-simba-nl Oct 17, 2024
2d66b20
Add stdc++fs to link libraries for unix builds
sfc-gh-ext-simba-nl Oct 17, 2024
e480074
Fix macos filesystem error
sfc-gh-ext-simba-nl Oct 17, 2024
39d1036
SNOW-692945: multiple statements support (#727)
SimbaGithub Oct 17, 2024
1ee5015
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Oct 17, 2024
f162232
Revert from using std::filesystem, add back check file exists due to …
sfc-gh-ext-simba-nl Oct 17, 2024
04993e8
Fix boost error
sfc-gh-ext-simba-nl Oct 18, 2024
0f494ee
Fix test cases
sfc-gh-ext-simba-nl Oct 18, 2024
0aaea59
Fix logger unit tests
sfc-gh-ext-simba-nl Oct 18, 2024
6bc1b92
SNOW-692945: refactoring for resultset C wrapper (#724)
SimbaGithub Oct 18, 2024
3c4f610
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-ext-simba-nl Oct 21, 2024
e3e1b88
SNOW-1524269: support put/get for GCP (#738)
sfc-gh-ext-simba-hx Oct 22, 2024
4b2cc16
SNOW-1524269 native put get support (#745)
sfc-gh-ext-simba-hx Oct 23, 2024
81bf6fb
Merge branch 'master' into master-2.0.0
sfc-gh-ext-simba-hx Oct 24, 2024
96b9b87
Implement easy logging
sfc-gh-ext-simba-nl Oct 2, 2024
8ce9da6
Add missing function definition
sfc-gh-ext-simba-nl Oct 2, 2024
9fcc790
Fix linux build errors
sfc-gh-ext-simba-nl Oct 2, 2024
1f778c0
Fix conversion issue on linux
sfc-gh-ext-simba-nl Oct 2, 2024
17e1d10
Fix mac build error and fix logging test failure
sfc-gh-ext-simba-nl Oct 2, 2024
5c90290
Fix mac build error in test case
sfc-gh-ext-simba-nl Oct 3, 2024
ac79374
Fix setting of global log path
sfc-gh-ext-simba-nl Oct 3, 2024
99daf14
Use sf_memcpy instead
sfc-gh-ext-simba-nl Oct 4, 2024
16fd3bf
Remove the use of boost algorithm string for compare
sfc-gh-ext-simba-nl Oct 7, 2024
2a80a5e
Fix test issue
sfc-gh-ext-simba-nl Oct 8, 2024
47334d0
Fix build issue with test
sfc-gh-ext-simba-nl Oct 9, 2024
3e80253
Change ifstream to FILE due to 32bit debug read issue
sfc-gh-ext-simba-nl Oct 9, 2024
0d642b2
Fix linux build issue
sfc-gh-ext-simba-nl Oct 10, 2024
c89424a
Fix test issues
sfc-gh-ext-simba-nl Oct 10, 2024
a742893
Rename ifndef for headers to be more consistent
sfc-gh-ext-simba-nl Oct 11, 2024
e16a67c
Replace boost with std::filesystem, fix possible NULL issues, add tests
sfc-gh-ext-simba-nl Oct 16, 2024
05331fb
Add client_config_parser to include dir
sfc-gh-ext-simba-nl Oct 16, 2024
f370f9b
Fix compilation error
sfc-gh-ext-simba-nl Oct 16, 2024
d0cbc0c
Fix return value error
sfc-gh-ext-simba-nl Oct 17, 2024
e020429
Use experimental filesystem for non-windows
sfc-gh-ext-simba-nl Oct 17, 2024
d351866
Add stdc++fs to link libraries for unix builds
sfc-gh-ext-simba-nl Oct 17, 2024
5cc39ae
Fix macos filesystem error
sfc-gh-ext-simba-nl Oct 17, 2024
75eb366
Revert from using std::filesystem, add back check file exists due to …
sfc-gh-ext-simba-nl Oct 17, 2024
8651788
SNOW-1744756 add picojson to libsnowflakeclient (#755)
sfc-gh-jszczerbinski Oct 28, 2024
48d562f
Fix boost error
sfc-gh-ext-simba-nl Oct 18, 2024
79128b4
Fix test cases
sfc-gh-ext-simba-nl Oct 18, 2024
98c4924
Fix logger unit tests
sfc-gh-ext-simba-nl Oct 18, 2024
9d54bab
Merge branch 'SNOW-981602-implement-easy-logging-config-file' of http…
sfc-gh-ext-simba-nl Oct 29, 2024
a5549aa
Update log level and path precedence logic, add SF_LOG_DEFAULT
sfc-gh-ext-simba-nl Oct 29, 2024
169063c
Remove accidentally added files
sfc-gh-ext-simba-nl Oct 29, 2024
2c285bf
Fix test case
sfc-gh-ext-simba-nl Oct 30, 2024
9ede47a
Merge master
sfc-gh-ext-simba-nl Nov 13, 2024
bc61cd7
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-ext-simba-nl Nov 14, 2024
9eaadd0
Use picojson instead of cjson, refactor client config parser
sfc-gh-ext-simba-nl Nov 15, 2024
6c3aa65
Add exception to keep track of errors
sfc-gh-ext-simba-nl Nov 16, 2024
c022028
Revert accidental inclusions from master-2.0.0 branch
sfc-gh-ext-simba-nl Nov 16, 2024
b03491a
Add back missing files, fix styling change
sfc-gh-ext-simba-nl Nov 18, 2024
0d2e977
Disable test case for windows 32-bit debug
sfc-gh-ext-simba-nl Nov 18, 2024
36faf07
Ignore client config loading on win32 debug
sfc-gh-ext-simba-nl Nov 19, 2024
be2a7b3
Add comment explaining the ifdef
sfc-gh-ext-simba-nl Nov 20, 2024
d6a00fe
Fix log message
sfc-gh-ext-simba-nl Nov 21, 2024
726b897
Remove unncessary else clauses and removed ifstream.close
sfc-gh-ext-simba-nl Nov 25, 2024
a6e6ca0
Remove exceptions, add test case, fix home env bug
sfc-gh-ext-simba-nl Nov 29, 2024
301d333
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-ext-simba-nl Dec 11, 2024
21f7b13
Add test cases, fix config parsing return when json is malformed
sfc-gh-ext-simba-nl Dec 11, 2024
31001f3
Fix code quality
sfc-gh-ext-simba-nl Dec 12, 2024
905bc6d
Remove client config logger tests for Windows 32-bit debug
sfc-gh-ext-simba-nl Dec 12, 2024
1d2512c
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-jszczerbinski Dec 12, 2024
358b310
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-jszczerbinski Dec 12, 2024
c220e29
Merge master branch
sfc-gh-ext-simba-nl Dec 19, 2024
44786f6
Merge branch 'master' into SNOW-981602-implement-easy-logging-config-…
sfc-gh-ext-simba-nl Dec 20, 2024
6a53849
Remove in_ and out_ from function parameters, update copyright date
sfc-gh-ext-simba-nl Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ set(SOURCE_FILES
include/snowflake/logger.h
include/snowflake/version.h
include/snowflake/platform.h
include/snowflake/client_config_parser.h
lib/client.c
lib/constants.h
lib/cJSON.h
Expand Down Expand Up @@ -213,6 +214,7 @@ set(SOURCE_FILES_CPP_WRAPPER
include/snowflake/SFURL.hpp
include/snowflake/CurlDesc.hpp
include/snowflake/CurlDescPool.hpp
include/snowflake/ClientConfigParser.hpp
cpp/lib/Exceptions.cpp
cpp/lib/Connection.cpp
cpp/lib/Statement.cpp
Expand All @@ -235,6 +237,7 @@ set(SOURCE_FILES_CPP_WRAPPER
cpp/lib/ResultSetJson.hpp
cpp/lib/Authenticator.cpp
cpp/lib/Authenticator.hpp
cpp/lib/ClientConfigParser.cpp
cpp/jwt/jwtWrapper.cpp
cpp/util/SnowflakeCommon.cpp
cpp/util/SFURL.cpp
Expand Down Expand Up @@ -406,6 +409,7 @@ if (LINUX)
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/uuid/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
endif()
Expand All @@ -421,6 +425,7 @@ if (APPLE)
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/aws/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
endif()
Expand All @@ -435,6 +440,7 @@ if (WIN32)
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/aws/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
Expand Down
292 changes: 292 additions & 0 deletions cpp/lib/ClientConfigParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/**
* Copyright (c) 2024 Snowflake Computing
*/

#include "snowflake/ClientConfigParser.hpp"
#include "snowflake/Exceptions.hpp"
#include "snowflake/client_config_parser.h"
#include "../logger/SFLogger.hpp"
#include "memory.h"

#include <fstream>
#include <sstream>
#include <set>

#undef snprintf
#include <boost/filesystem.hpp>

#ifndef _WIN32
#include <dlfcn.h>
#endif

using namespace Snowflake::Client;
using namespace picojson;

namespace
{
// constants
const std::string SF_CLIENT_CONFIG_FILE_NAME("sf_client_config.json");
const std::string SF_CLIENT_CONFIG_ENV_NAME("SF_CLIENT_CONFIG_FILE");
std::set<std::string> KnownCommonEntries = {"log_level", "log_path"};
}

////////////////////////////////////////////////////////////////////////////////
sf_bool load_client_config(
const char* in_configFilePath,
client_config* out_clientConfig)
{
// Disable easy logging for 32-bit windows debug build due to linking issues
// with _osfile causing hanging/assertions until dynamic linking is available
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it mean that with static library the EasyLogging feature is not available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but only with 32-bit windows debug build, all other configurations/platforms/bitness work fine

#if !defined(_WIN32) && !defined(_DEBUG)
try {
EasyLoggingConfigParser configParser;
configParser.loadClientConfig(in_configFilePath, *out_clientConfig);
} catch (std::exception e) {
CXX_LOG_ERROR("Error loading client configuration: %s", e.what());
return false;
}
#endif
return true;
}

////////////////////////////////////////////////////////////////////////////////
void free_client_config(client_config* clientConfig)
{
if (clientConfig->logLevel != NULL)
{
SF_FREE(clientConfig->logLevel);
}
if (clientConfig->logPath != NULL)
{
SF_FREE(clientConfig->logPath);
}
}

// Public ======================================================================
////////////////////////////////////////////////////////////////////////////////
EasyLoggingConfigParser::EasyLoggingConfigParser()
{
; // Do nothing.
}

////////////////////////////////////////////////////////////////////////////////
EasyLoggingConfigParser::~EasyLoggingConfigParser()
{
; // Do nothing.
}

////////////////////////////////////////////////////////////////////////////////
void EasyLoggingConfigParser::loadClientConfig(
const std::string& in_configFilePath,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need in_ out_ prefixes?

client_config& out_clientConfig)
{
std::string derivedConfigPath = resolveClientConfigPath(in_configFilePath);

if (!derivedConfigPath.empty())
{
parseConfigFile(derivedConfigPath, out_clientConfig);
}
}

////////////////////////////////////////////////////////////////////////////////
std::string EasyLoggingConfigParser::resolveClientConfigPath(
const std::string& in_configFilePath)
{
char envbuf[MAX_PATH + 1];
if (!in_configFilePath.empty())
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved Hide resolved
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved Hide resolved
{
// 1. Try config file if it was passed in
CXX_LOG_INFO("Using client configuration path from a connection string: %s", in_configFilePath.c_str());
return in_configFilePath;
}
else if (const char* clientConfigEnv = sf_getenv_s(SF_CLIENT_CONFIG_ENV_NAME.c_str(), envbuf, sizeof(envbuf)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Else no longer needed when we do an early return.

{
// 2. Try environment variable SF_CLIENT_CONFIG_ENV_NAME
CXX_LOG_INFO("Using client configuration path from an environment variable: %s", clientConfigEnv);
return clientConfigEnv;
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Else no longer needed when we do an early return.

{
// 3. Try DLL binary dir
std::string binaryDir = getBinaryPath();
std::string binaryDirFilePath = binaryDir + SF_CLIENT_CONFIG_FILE_NAME;
if (boost::filesystem::is_regular_file(binaryDirFilePath))
{
CXX_LOG_INFO("Using client configuration path from binary directory: %s", binaryDirFilePath.c_str());
return binaryDirFilePath;
}
else
{
// 4. Try user home dir
return resolveHomeDirConfigPath();
}
}
}

// Private =====================================================================
////////////////////////////////////////////////////////////////////////////////
std::string EasyLoggingConfigParser::resolveHomeDirConfigPath()
{
char envbuf[MAX_PATH + 1];
#if defined(WIN32) || defined(_WIN64)
std::string homeDirFilePath;
char* homeDir;
if ((homeDir = sf_getenv_s("USERPROFILE", envbuf, sizeof(envbuf))) && strlen(homeDir) != 0)
{
homeDirFilePath = homeDir + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME;
} else {
// USERPROFILE is empty, try HOMEDRIVE and HOMEPATH
char* homeDriveEnv = sf_getenv_s("HOMEDRIVE", envbuf, sizeof(envbuf));
char* homePathEnv = sf_getenv_s("HOMEPATH", envbuf, sizeof(envbuf));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we overwrite homePathEnv here. envbuf is used twice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for resolveHomeDirConfigPath

if (homeDriveEnv && strlen(homeDriveEnv) != 0 && homePathEnv && strlen(homePathEnv) != 0)
{
homeDirFilePath = std::string(homeDriveEnv) + homePathEnv + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME;
}
}
if (boost::filesystem::is_regular_file(homeDirFilePath))
{
CXX_LOG_INFO("Using client configuration path from home directory: %s", homeDirFilePath.c_str());
return homeDirFilePath;
}
#else
char* homeDir;
if ((homeDir = sf_getenv_s("HOME", envbuf, sizeof(envbuf))) && (strlen(homeDir) != 0))
{
std::string homeDirFilePath = std::string(homeDir) + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME;
if (boost::filesystem::is_regular_file(homeDirFilePath))
{
CXX_LOG_INFO("Using client configuration path from home directory: %s", homeDirFilePath.c_str());
return homeDirFilePath;
}
}
#endif
return "";
}

////////////////////////////////////////////////////////////////////////////////
void EasyLoggingConfigParser::parseConfigFile(
const std::string& in_filePath,
client_config& out_clientConfig)
{
value jsonConfig;
std::string err;
std::ifstream configFile;
try
{
configFile.open(in_filePath, std::fstream::in | std::ios::binary);
if (!configFile)
{
CXX_LOG_INFO("Could not open a file. The file may not exist: %s",
in_filePath.c_str());
std::string errMsg = "Error finding client configuration file: " + in_filePath;
throw ClientConfigException(errMsg.c_str());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
#if !defined(WIN32) && !defined(_WIN64)
checkIfValidPermissions(in_filePath);
#endif
err = parse(jsonConfig, configFile);

if (!err.empty())
{
CXX_LOG_ERROR("Error in parsing JSON: %s, err: %s", in_filePath.c_str(), err.c_str());
std::string errMsg = "Error parsing client configuration file: " + in_filePath;
throw ClientConfigException(errMsg.c_str());
}
}
catch (std::exception& e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to explicitly close the file. It will close automatically when destructor of ifstream is called:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we catch and rethrow the exception, it makes no sense.

{
configFile.close();
throw;
}

value commonProps = jsonConfig.get("common");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if "common" does not exist in object or top-level is not an object?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added checks before and after

checkUnknownEntries(commonProps);
if (commonProps.is<object>())
{
if (commonProps.contains("log_level") && commonProps.get("log_level").is<std::string>())
{
const char* logLevel = commonProps.get("log_level").get<std::string>().c_str();
size_t logLevelSize = strlen(logLevel) + 1;
out_clientConfig.logLevel = (char*)SF_CALLOC(1, logLevelSize);
sf_strcpy(out_clientConfig.logLevel, logLevelSize, logLevel);
}
if (commonProps.contains("log_path") && commonProps.get("log_path").is<std::string>())
{
const char* logPath = commonProps.get("log_path").get<std::string>().c_str();
size_t logPathSize = strlen(logPath) + 1;
out_clientConfig.logPath = (char*)SF_CALLOC(1, logPathSize);
sf_strcpy(out_clientConfig.logPath, logPathSize, logPath);
}
}
configFile.close();
}

////////////////////////////////////////////////////////////////////////////////
void EasyLoggingConfigParser::checkIfValidPermissions(const std::string& in_filePath)
{
boost::filesystem::file_status fileStatus = boost::filesystem::status(in_filePath);
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved Hide resolved
boost::filesystem::perms permissions = fileStatus.permissions();
if (permissions & boost::filesystem::group_write ||
permissions & boost::filesystem::others_write)
{
CXX_LOG_ERROR("Error due to other users having permission to modify the config file: %s",
in_filePath.c_str());
std::string errMsg = "Error due to other users having permission to modify the config file: " + in_filePath;
throw ClientConfigException(errMsg.c_str());
}
}

////////////////////////////////////////////////////////////////////////////////
void EasyLoggingConfigParser::checkUnknownEntries(value& in_config)
{
if (in_config.is<object>())
{
const value::object& configObj = in_config.get<object>();
for (value::object::const_iterator i = configObj.begin(); i != configObj.end(); ++i)
{
std::string key = i->first;
bool found = false;
for (std::string knownEntry : KnownCommonEntries)
{
if (sf_strncasecmp(key.c_str(), knownEntry.c_str(), knownEntry.length()) == 0)
{
found = true;
}
}
if (!found)
{
std::string warnMsg =
"Unknown configuration entry: " + key + " with value:" + i->second.to_str().c_str();
CXX_LOG_WARN(warnMsg.c_str());
}
}
}
}

////////////////////////////////////////////////////////////////////////////////
std::string EasyLoggingConfigParser::getBinaryPath()
{
std::string binaryFullPath;
#if defined(WIN32) || defined(_WIN64)
std::wstring path;
HMODULE hm = NULL;
wchar_t appName[256];
GetModuleFileNameW(hm, appName, 256);
path = appName;
binaryFullPath = std::string(path.begin(), path.end());
#else
Dl_info info;
int result = dladdr((void*)load_client_config, &info);
if (result)
{
binaryFullPath = std::string(info.dli_fname);
}
#endif
size_t pos = binaryFullPath.find_last_of(PATH_SEP);
if (pos == std::string::npos)
{
return "";
}
std::string binaryPath = binaryFullPath.substr(0, pos + 1);
return binaryPath;
}
Loading
Loading