ENH: Allow separate installation of shared libs and their links.
- Add NAMELINK_ONLY and NAMELINK_SKIP to INSTALL command - Options select a \"namelink\" mode - cmInstallTargetGenerator selects files/link based on mode - See bug #4419
This commit is contained in:
parent
852f6018bb
commit
8401c5ba06
@ -283,6 +283,57 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce argument rules too complex to specify for the
|
||||||
|
// general-purpose parser.
|
||||||
|
if(archiveArgs.GetNamelinkOnly() ||
|
||||||
|
runtimeArgs.GetNamelinkOnly() ||
|
||||||
|
frameworkArgs.GetNamelinkOnly() ||
|
||||||
|
bundleArgs.GetNamelinkOnly() ||
|
||||||
|
privateHeaderArgs.GetNamelinkOnly() ||
|
||||||
|
publicHeaderArgs.GetNamelinkOnly() ||
|
||||||
|
resourceArgs.GetNamelinkOnly())
|
||||||
|
{
|
||||||
|
this->SetError(
|
||||||
|
"TARGETS given NAMELINK_ONLY option not in LIBRARY group. "
|
||||||
|
"The NAMELINK_ONLY option may be specified only following LIBRARY."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(archiveArgs.GetNamelinkSkip() ||
|
||||||
|
runtimeArgs.GetNamelinkSkip() ||
|
||||||
|
frameworkArgs.GetNamelinkSkip() ||
|
||||||
|
bundleArgs.GetNamelinkSkip() ||
|
||||||
|
privateHeaderArgs.GetNamelinkSkip() ||
|
||||||
|
publicHeaderArgs.GetNamelinkSkip() ||
|
||||||
|
resourceArgs.GetNamelinkSkip())
|
||||||
|
{
|
||||||
|
this->SetError(
|
||||||
|
"TARGETS given NAMELINK_SKIP option not in LIBRARY group. "
|
||||||
|
"The NAMELINK_SKIP option may be specified only following LIBRARY."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(libraryArgs.GetNamelinkOnly() && libraryArgs.GetNamelinkSkip())
|
||||||
|
{
|
||||||
|
this->SetError(
|
||||||
|
"TARGETS given NAMELINK_ONLY and NAMELINK_SKIP. "
|
||||||
|
"At most one of these two options may be specified."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the mode for installing symlinks to versioned shared libraries.
|
||||||
|
cmInstallTargetGenerator::NamelinkModeType
|
||||||
|
namelinkMode = cmInstallTargetGenerator::NamelinkModeNone;
|
||||||
|
if(libraryArgs.GetNamelinkOnly())
|
||||||
|
{
|
||||||
|
namelinkMode = cmInstallTargetGenerator::NamelinkModeOnly;
|
||||||
|
}
|
||||||
|
else if(libraryArgs.GetNamelinkSkip())
|
||||||
|
{
|
||||||
|
namelinkMode = cmInstallTargetGenerator::NamelinkModeSkip;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there is something to do.
|
// Check if there is something to do.
|
||||||
if(targetList.GetVector().empty())
|
if(targetList.GetVector().empty())
|
||||||
{
|
{
|
||||||
@ -352,6 +403,12 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
|
|||||||
// cygwin. Currently no other platform is a DLL platform.
|
// cygwin. Currently no other platform is a DLL platform.
|
||||||
if(dll_platform)
|
if(dll_platform)
|
||||||
{
|
{
|
||||||
|
// When in namelink only mode skip all libraries on Windows.
|
||||||
|
if(namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// This is a DLL platform.
|
// This is a DLL platform.
|
||||||
if(!archiveArgs.GetDestination().empty())
|
if(!archiveArgs.GetDestination().empty())
|
||||||
{
|
{
|
||||||
@ -378,6 +435,12 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
|
|||||||
// INSTALL properties. Otherwise, use the LIBRARY properties.
|
// INSTALL properties. Otherwise, use the LIBRARY properties.
|
||||||
if(target.IsFrameworkOnApple())
|
if(target.IsFrameworkOnApple())
|
||||||
{
|
{
|
||||||
|
// When in namelink only mode skip frameworks.
|
||||||
|
if(namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Use the FRAMEWORK properties.
|
// Use the FRAMEWORK properties.
|
||||||
if (!frameworkArgs.GetDestination().empty())
|
if (!frameworkArgs.GetDestination().empty())
|
||||||
{
|
{
|
||||||
@ -400,6 +463,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
|
|||||||
{
|
{
|
||||||
libraryGenerator = CreateInstallTargetGenerator(target,
|
libraryGenerator = CreateInstallTargetGenerator(target,
|
||||||
libraryArgs, false);
|
libraryArgs, false);
|
||||||
|
libraryGenerator->SetNamelinkMode(namelinkMode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -438,6 +502,7 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args)
|
|||||||
{
|
{
|
||||||
libraryGenerator = CreateInstallTargetGenerator(target, libraryArgs,
|
libraryGenerator = CreateInstallTargetGenerator(target, libraryArgs,
|
||||||
false);
|
false);
|
||||||
|
libraryGenerator->SetNamelinkMode(namelinkMode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -105,7 +105,7 @@ public:
|
|||||||
" [PERMISSIONS permissions...]\n"
|
" [PERMISSIONS permissions...]\n"
|
||||||
" [CONFIGURATIONS [Debug|Release|...]]\n"
|
" [CONFIGURATIONS [Debug|Release|...]]\n"
|
||||||
" [COMPONENT <component>]\n"
|
" [COMPONENT <component>]\n"
|
||||||
" [OPTIONAL]\n"
|
" [OPTIONAL] [NAMELINK_ONLY|NAMELINK_SKIP]\n"
|
||||||
" ] [...])\n"
|
" ] [...])\n"
|
||||||
"The TARGETS form specifies rules for installing targets from a "
|
"The TARGETS form specifies rules for installing targets from a "
|
||||||
"project. There are five kinds of target files that may be "
|
"project. There are five kinds of target files that may be "
|
||||||
@ -140,6 +140,25 @@ public:
|
|||||||
"See documentation of the PRIVATE_HEADER, PUBLIC_HEADER, and RESOURCE "
|
"See documentation of the PRIVATE_HEADER, PUBLIC_HEADER, and RESOURCE "
|
||||||
"target properties for details."
|
"target properties for details."
|
||||||
"\n"
|
"\n"
|
||||||
|
"Either NAMELINK_ONLY or NAMELINK_SKIP may be specified as a LIBRARY "
|
||||||
|
"option. "
|
||||||
|
"On some platforms a versioned shared library has a symbolic link "
|
||||||
|
"such as\n"
|
||||||
|
" lib<name>.so -> lib<name>.so.1\n"
|
||||||
|
"where \"lib<name>.so.1\" is the soname of the library and "
|
||||||
|
"\"lib<name>.so\" is a \"namelink\" allowing linkers to find the "
|
||||||
|
"library when given \"-l<name>\". "
|
||||||
|
"The NAMELINK_ONLY option causes installation of only the namelink "
|
||||||
|
"when a library target is installed. "
|
||||||
|
"The NAMELINK_SKIP option causes installation of library files other "
|
||||||
|
"than the namelink when a library target is installed. "
|
||||||
|
"When neither option is given both portions are installed. "
|
||||||
|
"On platforms where versioned shared libraries do not have namelinks "
|
||||||
|
"or when a library is not versioned the NAMELINK_SKIP option installs "
|
||||||
|
"the library and the NAMELINK_ONLY option installs nothing. "
|
||||||
|
"See the VERSION and SOVERSION target properties for details on "
|
||||||
|
"creating versioned shared libraries."
|
||||||
|
"\n"
|
||||||
"One or more groups of properties may be specified in a single call "
|
"One or more groups of properties may be specified in a single call "
|
||||||
"to the TARGETS form of this command. A target may be installed more "
|
"to the TARGETS form of this command. A target may be installed more "
|
||||||
"than once to different locations. Consider hypothetical "
|
"than once to different locations. Consider hypothetical "
|
||||||
|
@ -37,6 +37,8 @@ cmInstallCommandArguments::cmInstallCommandArguments()
|
|||||||
,Permissions (&Parser, "PERMISSIONS" , &ArgumentGroup)
|
,Permissions (&Parser, "PERMISSIONS" , &ArgumentGroup)
|
||||||
,Configurations(&Parser, "CONFIGURATIONS", &ArgumentGroup)
|
,Configurations(&Parser, "CONFIGURATIONS", &ArgumentGroup)
|
||||||
,Optional (&Parser, "OPTIONAL" , &ArgumentGroup)
|
,Optional (&Parser, "OPTIONAL" , &ArgumentGroup)
|
||||||
|
,NamelinkOnly (&Parser, "NAMELINK_ONLY" , &ArgumentGroup)
|
||||||
|
,NamelinkSkip (&Parser, "NAMELINK_SKIP" , &ArgumentGroup)
|
||||||
,GenericArguments(0)
|
,GenericArguments(0)
|
||||||
{
|
{
|
||||||
this->Component.SetDefaultString("Unspecified");
|
this->Component.SetDefaultString("Unspecified");
|
||||||
@ -107,6 +109,32 @@ bool cmInstallCommandArguments::GetOptional() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cmInstallCommandArguments::GetNamelinkOnly() const
|
||||||
|
{
|
||||||
|
if (this->NamelinkOnly.IsEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this->GenericArguments!=0)
|
||||||
|
{
|
||||||
|
return this->GenericArguments->GetNamelinkOnly();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmInstallCommandArguments::GetNamelinkSkip() const
|
||||||
|
{
|
||||||
|
if (this->NamelinkSkip.IsEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this->GenericArguments!=0)
|
||||||
|
{
|
||||||
|
return this->GenericArguments->GetNamelinkSkip();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<std::string>&
|
const std::vector<std::string>&
|
||||||
cmInstallCommandArguments::GetConfigurations() const
|
cmInstallCommandArguments::GetConfigurations() const
|
||||||
{
|
{
|
||||||
|
@ -39,6 +39,8 @@ class cmInstallCommandArguments
|
|||||||
const std::string& GetPermissions() const;
|
const std::string& GetPermissions() const;
|
||||||
const std::vector<std::string>& GetConfigurations() const;
|
const std::vector<std::string>& GetConfigurations() const;
|
||||||
bool GetOptional() const;
|
bool GetOptional() const;
|
||||||
|
bool GetNamelinkOnly() const;
|
||||||
|
bool GetNamelinkSkip() const;
|
||||||
|
|
||||||
// once HandleDirectoryMode() is also switched to using
|
// once HandleDirectoryMode() is also switched to using
|
||||||
// cmInstallCommandArguments then these two functions can become non-static
|
// cmInstallCommandArguments then these two functions can become non-static
|
||||||
@ -54,6 +56,8 @@ class cmInstallCommandArguments
|
|||||||
cmCAStringVector Permissions;
|
cmCAStringVector Permissions;
|
||||||
cmCAStringVector Configurations;
|
cmCAStringVector Configurations;
|
||||||
cmCAEnabler Optional;
|
cmCAEnabler Optional;
|
||||||
|
cmCAEnabler NamelinkOnly;
|
||||||
|
cmCAEnabler NamelinkSkip;
|
||||||
|
|
||||||
std::string DestinationString;
|
std::string DestinationString;
|
||||||
std::string PermissionsString;
|
std::string PermissionsString;
|
||||||
|
@ -22,9 +22,6 @@
|
|||||||
#include "cmMakefile.h"
|
#include "cmMakefile.h"
|
||||||
#include "cmake.h"
|
#include "cmake.h"
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Skip IF(EXISTS) checks if nothing is done with the installed file
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
cmInstallTargetGenerator
|
cmInstallTargetGenerator
|
||||||
::cmInstallTargetGenerator(cmTarget& t, const char* dest, bool implib,
|
::cmInstallTargetGenerator(cmTarget& t, const char* dest, bool implib,
|
||||||
@ -34,6 +31,7 @@ cmInstallTargetGenerator
|
|||||||
cmInstallGenerator(dest, configurations, component), Target(&t),
|
cmInstallGenerator(dest, configurations, component), Target(&t),
|
||||||
ImportLibrary(implib), FilePermissions(file_permissions), Optional(optional)
|
ImportLibrary(implib), FilePermissions(file_permissions), Optional(optional)
|
||||||
{
|
{
|
||||||
|
this->NamelinkMode = NamelinkModeNone;
|
||||||
this->Target->SetHaveInstallRule(true);
|
this->Target->SetHaveInstallRule(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,12 +147,19 @@ cmInstallTargetGenerator
|
|||||||
toInstallPath += this->GetInstallFilename(this->Target, config,
|
toInstallPath += this->GetInstallFilename(this->Target, config,
|
||||||
this->ImportLibrary, false);
|
this->ImportLibrary, false);
|
||||||
|
|
||||||
|
// Track whether post-install operations should be added to the
|
||||||
|
// script.
|
||||||
|
bool tweakInstalledFile = true;
|
||||||
|
|
||||||
// Compute the list of files to install for this target.
|
// Compute the list of files to install for this target.
|
||||||
std::vector<std::string> files;
|
std::vector<std::string> files;
|
||||||
std::string literal_args;
|
std::string literal_args;
|
||||||
cmTarget::TargetType type = this->Target->GetType();
|
cmTarget::TargetType type = this->Target->GetType();
|
||||||
if(type == cmTarget::EXECUTABLE)
|
if(type == cmTarget::EXECUTABLE)
|
||||||
{
|
{
|
||||||
|
// There is a bug in cmInstallCommand if this fails.
|
||||||
|
assert(this->NamelinkMode == NamelinkModeNone);
|
||||||
|
|
||||||
std::string targetName;
|
std::string targetName;
|
||||||
std::string targetNameReal;
|
std::string targetNameReal;
|
||||||
std::string targetNameImport;
|
std::string targetNameImport;
|
||||||
@ -215,6 +220,9 @@ cmInstallTargetGenerator
|
|||||||
config);
|
config);
|
||||||
if(this->ImportLibrary)
|
if(this->ImportLibrary)
|
||||||
{
|
{
|
||||||
|
// There is a bug in cmInstallCommand if this fails.
|
||||||
|
assert(this->NamelinkMode == NamelinkModeNone);
|
||||||
|
|
||||||
std::string from1 = fromDirConfig;
|
std::string from1 = fromDirConfig;
|
||||||
from1 += targetNameImport;
|
from1 += targetNameImport;
|
||||||
files.push_back(from1);
|
files.push_back(from1);
|
||||||
@ -224,6 +232,9 @@ cmInstallTargetGenerator
|
|||||||
}
|
}
|
||||||
else if(this->Target->IsFrameworkOnApple())
|
else if(this->Target->IsFrameworkOnApple())
|
||||||
{
|
{
|
||||||
|
// There is a bug in cmInstallCommand if this fails.
|
||||||
|
assert(this->NamelinkMode == NamelinkModeNone);
|
||||||
|
|
||||||
// Compute the build tree location of the framework directory
|
// Compute the build tree location of the framework directory
|
||||||
std::string from1 = fromDirConfig;
|
std::string from1 = fromDirConfig;
|
||||||
// Remove trailing slashes... so that from1 ends with ".framework":
|
// Remove trailing slashes... so that from1 ends with ".framework":
|
||||||
@ -243,25 +254,82 @@ cmInstallTargetGenerator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string from1 = fromDirConfig;
|
// Operations done at install time on the installed file should
|
||||||
from1 += targetName;
|
// be done on the real file and not any of the symlinks.
|
||||||
files.push_back(from1);
|
toInstallPath = this->GetInstallDestination();
|
||||||
|
toInstallPath += "/";
|
||||||
|
toInstallPath += targetNameReal;
|
||||||
|
|
||||||
|
// Construct the list of file names to install for this library.
|
||||||
|
bool haveNamelink = false;
|
||||||
|
std::string fromName;
|
||||||
|
std::string fromSOName;
|
||||||
|
std::string fromRealName;
|
||||||
|
fromName = fromDirConfig;
|
||||||
|
fromName += targetName;
|
||||||
if(targetNameSO != targetName)
|
if(targetNameSO != targetName)
|
||||||
{
|
{
|
||||||
std::string from2 = fromDirConfig;
|
haveNamelink = true;
|
||||||
from2 += targetNameSO;
|
fromSOName = fromDirConfig;
|
||||||
files.push_back(from2);
|
fromSOName += targetNameSO;
|
||||||
}
|
}
|
||||||
if(targetNameReal != targetName &&
|
if(targetNameReal != targetName &&
|
||||||
targetNameReal != targetNameSO)
|
targetNameReal != targetNameSO)
|
||||||
{
|
{
|
||||||
std::string from3 = fromDirConfig;
|
haveNamelink = true;
|
||||||
from3 += targetNameReal;
|
fromRealName = fromDirConfig;
|
||||||
files.push_back(from3);
|
fromRealName += targetNameReal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the names based on the current namelink mode.
|
||||||
|
if(haveNamelink)
|
||||||
|
{
|
||||||
|
// With a namelink we need to check the mode.
|
||||||
|
if(this->NamelinkMode == NamelinkModeOnly)
|
||||||
|
{
|
||||||
|
// Install the namelink only.
|
||||||
|
files.push_back(fromName);
|
||||||
|
tweakInstalledFile = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Install the real file if it has its own name.
|
||||||
|
if(!fromRealName.empty())
|
||||||
|
{
|
||||||
|
files.push_back(fromRealName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the soname link if it has its own name.
|
||||||
|
if(!fromSOName.empty())
|
||||||
|
{
|
||||||
|
files.push_back(fromSOName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the namelink if it is not to be skipped.
|
||||||
|
if(this->NamelinkMode != NamelinkModeSkip)
|
||||||
|
{
|
||||||
|
files.push_back(fromName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Without a namelink there will be only one file. Install it
|
||||||
|
// if this is not a namelink-only rule.
|
||||||
|
if(this->NamelinkMode != NamelinkModeOnly)
|
||||||
|
{
|
||||||
|
files.push_back(fromName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip this rule if no files are to be installed for the target.
|
||||||
|
if(files.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Write code to install the target file.
|
// Write code to install the target file.
|
||||||
const char* no_dir_permissions = 0;
|
const char* no_dir_permissions = 0;
|
||||||
const char* no_rename = 0;
|
const char* no_rename = 0;
|
||||||
@ -273,19 +341,26 @@ cmInstallTargetGenerator
|
|||||||
no_rename, literal_args.c_str(),
|
no_rename, literal_args.c_str(),
|
||||||
indent);
|
indent);
|
||||||
|
|
||||||
|
// Construct the path of the file on disk after installation on
|
||||||
|
// which tweaks may be performed.
|
||||||
std::string toDestDirPath = "$ENV{DESTDIR}";
|
std::string toDestDirPath = "$ENV{DESTDIR}";
|
||||||
if(toInstallPath[0] != '/')
|
if(toInstallPath[0] != '/' && toInstallPath[0] != '$')
|
||||||
{
|
{
|
||||||
toDestDirPath += "/";
|
toDestDirPath += "/";
|
||||||
}
|
}
|
||||||
toDestDirPath += toInstallPath;
|
toDestDirPath += toInstallPath;
|
||||||
|
|
||||||
os << indent << "IF(EXISTS \"" << toDestDirPath << "\")\n";
|
// TODO:
|
||||||
this->AddInstallNamePatchRule(os, indent.Next(), config, toDestDirPath);
|
// - Skip IF(EXISTS) checks if nothing is done with the installed file
|
||||||
this->AddChrpathPatchRule(os, indent.Next(), config, toDestDirPath);
|
if(tweakInstalledFile)
|
||||||
this->AddRanlibRule(os, indent.Next(), type, toDestDirPath);
|
{
|
||||||
this->AddStripRule(os, indent.Next(), type, toDestDirPath);
|
os << indent << "IF(EXISTS \"" << toDestDirPath << "\")\n";
|
||||||
os << indent << "ENDIF(EXISTS \"" << toDestDirPath << "\")\n";
|
this->AddInstallNamePatchRule(os, indent.Next(), config, toDestDirPath);
|
||||||
|
this->AddChrpathPatchRule(os, indent.Next(), config, toDestDirPath);
|
||||||
|
this->AddRanlibRule(os, indent.Next(), type, toDestDirPath);
|
||||||
|
this->AddStripRule(os, indent.Next(), type, toDestDirPath);
|
||||||
|
os << indent << "ENDIF(EXISTS \"" << toDestDirPath << "\")\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
|
@ -36,6 +36,16 @@ public:
|
|||||||
);
|
);
|
||||||
virtual ~cmInstallTargetGenerator();
|
virtual ~cmInstallTargetGenerator();
|
||||||
|
|
||||||
|
/** Select the policy for installing shared library linkable name
|
||||||
|
symlinks. */
|
||||||
|
enum NamelinkModeType
|
||||||
|
{
|
||||||
|
NamelinkModeNone,
|
||||||
|
NamelinkModeOnly,
|
||||||
|
NamelinkModeSkip
|
||||||
|
};
|
||||||
|
void SetNamelinkMode(NamelinkModeType mode) { this->NamelinkMode = mode; }
|
||||||
|
|
||||||
std::string GetInstallFilename(const char* config) const;
|
std::string GetInstallFilename(const char* config) const;
|
||||||
static std::string GetInstallFilename(cmTarget*target, const char* config,
|
static std::string GetInstallFilename(cmTarget*target, const char* config,
|
||||||
bool implib, bool useSOName);
|
bool implib, bool useSOName);
|
||||||
@ -72,6 +82,7 @@ protected:
|
|||||||
bool ImportLibrary;
|
bool ImportLibrary;
|
||||||
std::string FilePermissions;
|
std::string FilePermissions;
|
||||||
bool Optional;
|
bool Optional;
|
||||||
|
NamelinkModeType NamelinkMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user