Commit c3385dd8 authored by Asit Dhal's avatar Asit Dhal
Browse files

file(GENERATE): Support options to manipulate file permissions

Fixes: #15653
parent c89514a6
Pipeline #205925 passed with stages
in 125 minutes and 39 seconds
......@@ -479,7 +479,9 @@ modified.
file(GENERATE OUTPUT output-file
<INPUT input-file|CONTENT content>
[CONDITION expression] [TARGET target])
[CONDITION expression] [TARGET target]
[FILE_PERMISSIONS <permissions>...]
[NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS])
Generate an output file for each build configuration supported by the current
:manual:`CMake Generator <cmake-generators(7)>`. Evaluate
......@@ -520,6 +522,17 @@ from the input content to produce the output content. The options are:
require a target for evaluation (e.g. ``$<COMPILE_FEATURES:...>``,
``$<TARGET_PROPERTY:prop>``).
``FILE_PERMISSIONS <permissions>...``
Use user provided permissions for the generated file.
``NO_SOURCE_PERMISSIONS``
The generated file permissions default to the standard 644 value
(-rw-r--r--).
``USE_SOURCE_PERMISSIONS``
Transfer the file permissions of the original file to the generated file.
This option expects INPUT option.
Exactly one ``CONTENT`` or ``INPUT`` option must be given. A specific
``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
Generated files are modified and their timestamp updated on subsequent cmake
......
file-generate-permissions
-------------------------
* The :command:`file(GENERATE)` command gained ``NO_SOURCE_PERMISSIONS``,
``USE_SOURCE_PERMISSIONS``, and ``FILE_PERMISSIONS`` options to support
permissions of the generated file.
......@@ -2291,7 +2291,7 @@ void AddEvaluationFile(const std::string& inputName,
const std::string& targetName,
const std::string& outputExpr,
const std::string& condition, bool inputIsContent,
cmExecutionStatus& status)
mode_t permissions, cmExecutionStatus& status)
{
cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
......@@ -2305,7 +2305,7 @@ void AddEvaluationFile(const std::string& inputName,
status.GetMakefile().AddEvaluationFile(
inputName, targetName, std::move(outputCge), std::move(conditionCge),
inputIsContent);
permissions, inputIsContent);
}
bool HandleGenerateCommand(std::vector<std::string> const& args,
......@@ -2323,14 +2323,21 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
std::string Content;
std::string Condition;
std::string Target;
bool NoSourcePermissions = false;
bool UseSourcePermissions = false;
std::vector<std::string> FilePermissions;
};
static auto const parser = cmArgumentParser<Arguments>{}
.Bind("OUTPUT"_s, &Arguments::Output)
.Bind("INPUT"_s, &Arguments::Input)
.Bind("CONTENT"_s, &Arguments::Content)
.Bind("CONDITION"_s, &Arguments::Condition)
.Bind("TARGET"_s, &Arguments::Target);
static auto const parser =
cmArgumentParser<Arguments>{}
.Bind("OUTPUT"_s, &Arguments::Output)
.Bind("INPUT"_s, &Arguments::Input)
.Bind("CONTENT"_s, &Arguments::Content)
.Bind("CONDITION"_s, &Arguments::Condition)
.Bind("TARGET"_s, &Arguments::Target)
.Bind("NO_SOURCE_PERMISSIONS"_s, &Arguments::NoSourcePermissions)
.Bind("USE_SOURCE_PERMISSIONS"_s, &Arguments::UseSourcePermissions)
.Bind("FILE_PERMISSIONS"_s, &Arguments::FilePermissions);
std::vector<std::string> unparsedArguments;
std::vector<std::string> keywordsMissingValues;
......@@ -2399,8 +2406,65 @@ bool HandleGenerateCommand(std::vector<std::string> const& args,
input = arguments.Content;
}
if (arguments.NoSourcePermissions && arguments.UseSourcePermissions) {
status.SetError("given both NO_SOURCE_PERMISSIONS and "
"USE_SOURCE_PERMISSIONS. Only one option allowed.");
return false;
}
if (!arguments.FilePermissions.empty()) {
if (arguments.NoSourcePermissions) {
status.SetError("given both NO_SOURCE_PERMISSIONS and "
"FILE_PERMISSIONS. Only one option allowed.");
return false;
}
if (arguments.UseSourcePermissions) {
status.SetError("given both USE_SOURCE_PERMISSIONS and "
"FILE_PERMISSIONS. Only one option allowed.");
return false;
}
}
if (arguments.UseSourcePermissions) {
if (inputIsContent) {
status.SetError("given USE_SOURCE_PERMISSIONS without a file INPUT.");
return false;
}
}
mode_t permisiions = 0;
if (arguments.NoSourcePermissions) {
permisiions |= cmFSPermissions::mode_owner_read;
permisiions |= cmFSPermissions::mode_owner_write;
permisiions |= cmFSPermissions::mode_group_read;
permisiions |= cmFSPermissions::mode_world_read;
}
if (!arguments.FilePermissions.empty()) {
std::vector<std::string> invalidOptions;
for (auto const& e : arguments.FilePermissions) {
if (!cmFSPermissions::stringToModeT(e, permisiions)) {
invalidOptions.push_back(e);
}
}
if (!invalidOptions.empty()) {
std::ostringstream oss;
oss << "given invalid permission ";
for (auto i = 0u; i < invalidOptions.size(); i++) {
if (i == 0u) {
oss << "\"" << invalidOptions[i] << "\"";
} else {
oss << ",\"" << invalidOptions[i] << "\"";
}
}
oss << ".";
status.SetError(oss.str());
return false;
}
}
AddEvaluationFile(input, arguments.Target, arguments.Output,
arguments.Condition, inputIsContent, status);
arguments.Condition, inputIsContent, permisiions, status);
return true;
}
......
......@@ -21,13 +21,15 @@ cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
std::string input, std::string target,
std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
std::unique_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070)
bool inputIsContent, mode_t permissions,
cmPolicies::PolicyStatus policyStatusCMP0070)
: Input(std::move(input))
, Target(std::move(target))
, OutputFileExpr(std::move(outputFileExpr))
, Condition(std::move(condition))
, InputIsContent(inputIsContent)
, PolicyStatusCMP0070(policyStatusCMP0070)
, Permissions(permissions)
{
}
......@@ -111,14 +113,15 @@ void cmGeneratorExpressionEvaluationFile::CreateOutputFile(
void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
{
mode_t perm = 0;
std::string inputContent;
if (this->InputIsContent) {
inputContent = this->Input;
} else {
const std::string inputFileName = this->GetInputFileName(lg);
lg->GetMakefile()->AddCMakeDependFile(inputFileName);
cmSystemTools::GetPermissions(inputFileName.c_str(), perm);
if (!this->Permissions) {
cmSystemTools::GetPermissions(inputFileName.c_str(), this->Permissions);
}
cmsys::ifstream fin(inputFileName.c_str());
if (!fin) {
std::ostringstream e;
......@@ -152,7 +155,8 @@ void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
for (std::string const& le : enabledLanguages) {
for (std::string const& li : allConfigs) {
this->Generate(lg, li, le, inputExpression.get(), outputFiles, perm);
this->Generate(lg, li, le, inputExpression.get(), outputFiles,
this->Permissions);
if (cmSystemTools::GetFatalErrorOccured()) {
return;
}
......
......@@ -24,7 +24,8 @@ public:
std::string input, std::string target,
std::unique_ptr<cmCompiledGeneratorExpression> outputFileExpr,
std::unique_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent, cmPolicies::PolicyStatus policyStatusCMP0070);
bool inputIsContent, mode_t permissions,
cmPolicies::PolicyStatus policyStatusCMP0070);
void Generate(cmLocalGenerator* lg);
......@@ -59,4 +60,5 @@ private:
std::vector<std::string> Files;
const bool InputIsContent;
cmPolicies::PolicyStatus PolicyStatusCMP0070;
mode_t Permissions;
};
......@@ -865,13 +865,14 @@ void cmMakefile::EnforceDirectoryLevelRules() const
void cmMakefile::AddEvaluationFile(
const std::string& inputFile, const std::string& targetName,
std::unique_ptr<cmCompiledGeneratorExpression> outputName,
std::unique_ptr<cmCompiledGeneratorExpression> condition,
std::unique_ptr<cmCompiledGeneratorExpression> condition, mode_t permissions,
bool inputIsContent)
{
this->EvaluationFiles.push_back(
cm::make_unique<cmGeneratorExpressionEvaluationFile>(
inputFile, targetName, std::move(outputName), std::move(condition),
inputIsContent, this->GetPolicyStatus(cmPolicies::CMP0070)));
inputIsContent, permissions,
this->GetPolicyStatus(cmPolicies::CMP0070)));
}
const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>&
......
......@@ -899,7 +899,7 @@ public:
const std::string& inputFile, const std::string& targetName,
std::unique_ptr<cmCompiledGeneratorExpression> outputName,
std::unique_ptr<cmCompiledGeneratorExpression> condition,
bool inputIsContent);
mode_t permissions, bool inputIsContent);
const std::vector<std::unique_ptr<cmGeneratorExpressionEvaluationFile>>&
GetEvaluationFiles() const;
......
file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/customfilepermissions.txt")
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/customfilepermissions.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
FILE_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_EXECUTE
WORLD_EXECUTE
)
add_custom_target(checkCustomFilePermissions ALL
COMMAND ${CMAKE_COMMAND}
-DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/customfilepermissions.txt
-P "${CMAKE_CURRENT_SOURCE_DIR}/CustomFilePermissionsVerify.cmake"
)
if(NOT EXISTS "${generatedFile}")
message(SEND_ERROR "Missing file:\n ${generatedFile}")
endif()
if (UNIX)
find_program(STAT_EXECUTABLE NAMES stat)
if(NOT STAT_EXECUTABLE)
return()
endif()
if (CMAKE_HOST_SYSTEM_NAME MATCHES "FreeBSD")
execute_process(COMMAND "${STAT_EXECUTABLE}" -f %Lp "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
elseif (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin")
execute_process(COMMAND "${STAT_EXECUTABLE}" -f %A "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
else()
execute_process(COMMAND "${STAT_EXECUTABLE}" -c %a "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
endif()
if (NOT output EQUAL "711")
message(SEND_ERROR "file generate has different permissions source "
"permissions: \"${output}\" desired permissions: \"711\"")
endif()
endif()
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/nosourcepermissions.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
NO_SOURCE_PERMISSIONS
)
add_custom_target(checkNoSourcePermission ALL
COMMAND ${CMAKE_COMMAND}
-DgeneratedFile=${CMAKE_CURRENT_BINARY_DIR}/$<LOWER_CASE:$<CONFIG>>/nosourcepermissions.txt
-P "${CMAKE_CURRENT_SOURCE_DIR}/NoSourcePermissionsVerify.cmake"
)
if(NOT EXISTS "${generatedFile}")
message(SEND_ERROR "Missing generated file:\n ${generatedFile}")
endif()
if (UNIX)
find_program(STAT_EXECUTABLE NAMES stat)
if(NOT STAT_EXECUTABLE)
return()
endif()
if (CMAKE_HOST_SYSTEM_NAME MATCHES "FreeBSD")
execute_process(COMMAND "${STAT_EXECUTABLE}" -f %Lp "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin")
execute_process(COMMAND "${STAT_EXECUTABLE}" -f %A "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
else()
execute_process(COMMAND "${STAT_EXECUTABLE}" -c %a "${generatedFile}"
OUTPUT_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
endif()
if (NOT output EQUAL "644")
message(SEND_ERROR "generated file has different permissions than "
"desired, generated permissions: \"${output}\"")
endif()
endif()
......@@ -123,3 +123,24 @@ set(RunCMake_TEST_NO_CLEAN 1)
run_cmake_command(AdjacentInOut-nowork ${CMAKE_COMMAND} --build .)
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
run_cmake(SourcePermissions1)
run_cmake(SourcePermissions2)
run_cmake(SourcePermissions3)
run_cmake(SourcePermissions4)
run_cmake(SourcePermissions5)
function(run_cmake_and_verify_after_build case)
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${case}-build")
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake(${case})
run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
unset(RunCMake_TEST_NO_CLEAN)
unset(RunCMake_TEST_BINARY_DIR)
endfunction()
run_cmake_and_verify_after_build(NoSourcePermissions)
run_cmake_and_verify_after_build(UseSourcePermissions)
run_cmake_and_verify_after_build(CustomFilePermissions)
CMake Error at SourcePermissions1.cmake:[0-9]+ \(file\):
file given both NO_SOURCE_PERMISSIONS and USE_SOURCE_PERMISSIONS. Only one
option allowed.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output-sourcepermission1.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
NO_SOURCE_PERMISSIONS
USE_SOURCE_PERMISSIONS
)
CMake Error at SourcePermissions2.cmake:[0-9]+ \(file\):
file given both NO_SOURCE_PERMISSIONS and FILE_PERMISSIONS. Only one
option allowed.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/output-sourcepermission2.txt"
INPUT "${CMAKE_CURRENT_SOURCE_DIR}/input.txt"
NO_SOURCE_PERMISSIONS
FILE_PERMISSIONS OWNER_READ
)
CMake Error at SourcePermissions3.cmake:[0-9]+ \(file\):
file given both USE_SOURCE_PERMISSIONS and FILE_PERMISSIONS. Only one
option allowed.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment