From 337bc5662c35acce32630bfa26e87a42d472726f Mon Sep 17 00:00:00 2001 From: Marc Chevrier Date: Sat, 21 Oct 2023 17:16:13 +0200 Subject: if(): add operators IS_READABLE, IS_WRITABLE and IS_EXECUTABLE. Offers possibility to check for file or directory permissions. --- Help/command/if.rst | 41 +++++++ Help/release/dev/if-check-file-permissions.rst | 5 + Source/cmConditionEvaluator.cxx | 21 ++++ Tests/RunCMake/CMakeLists.txt | 2 +- Tests/RunCMake/if/FilePermissions.cmake | 153 +++++++++++++++++++++++++ Tests/RunCMake/if/RunCMakeTest.cmake | 17 +++ 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 Help/release/dev/if-check-file-permissions.rst create mode 100644 Tests/RunCMake/if/FilePermissions.cmake diff --git a/Help/command/if.rst b/Help/command/if.rst index 5d85a1f..1afbe04 100644 --- a/Help/command/if.rst +++ b/Help/command/if.rst @@ -178,6 +178,47 @@ File Operations False if the given path is an empty string. + .. warning:: + To check the readability of a file, use preferably ``if(IS_READABLE)`` + because this test will evolve to check file existence only in a future + release. + +.. signature:: if(IS_READABLE ) + + .. versionadded:: 3.29 + + True if the named file or directory is readable. Behavior + is well-defined only for explicit full paths (a leading ``~/`` is not + expanded as a home directory and is considered a relative path). + Resolves symbolic links, i.e. if the named file or directory is a + symbolic link, returns true if the target of the symbolic link is readable. + + False if the given path is an empty string. + +.. signature:: if(IS_WRITABLE ) + + .. versionadded:: 3.29 + + True if the named file or directory is writable. Behavior + is well-defined only for explicit full paths (a leading ``~/`` is not + expanded as a home directory and is considered a relative path). + Resolves symbolic links, i.e. if the named file or directory is a + symbolic link, returns true if the target of the symbolic link is writable. + + False if the given path is an empty string. + +.. signature:: if(IS_EXECUTABLE ) + + .. versionadded:: 3.29 + + True if the named file or directory is executable. Behavior + is well-defined only for explicit full paths (a leading ``~/`` is not + expanded as a home directory and is considered a relative path). + Resolves symbolic links, i.e. if the named file or directory is a + symbolic link, returns true if the target of the symbolic link is executable. + + False if the given path is an empty string. + .. signature:: if( IS_NEWER_THAN ) :target: IS_NEWER_THAN diff --git a/Help/release/dev/if-check-file-permissions.rst b/Help/release/dev/if-check-file-permissions.rst new file mode 100644 index 0000000..ec69b00 --- /dev/null +++ b/Help/release/dev/if-check-file-permissions.rst @@ -0,0 +1,5 @@ +if-check-file-permissions +------------------------- + +* The :command:`if` command gained new tests ``IS_READABLE``, ``IS_WRITABLE`` + and ``IS_EXECUTABLE`` to check file or directory permissions. diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx index 6f9f541..eba4c57 100644 --- a/Source/cmConditionEvaluator.cxx +++ b/Source/cmConditionEvaluator.cxx @@ -33,6 +33,9 @@ auto const keyCOMMAND = "COMMAND"_s; auto const keyDEFINED = "DEFINED"_s; auto const keyEQUAL = "EQUAL"_s; auto const keyEXISTS = "EXISTS"_s; +auto const keyIS_READABLE = "IS_READABLE"_s; +auto const keyIS_WRITABLE = "IS_WRITABLE"_s; +auto const keyIS_EXECUTABLE = "IS_EXECUTABLE"_s; auto const keyGREATER = "GREATER"_s; auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s; auto const keyIN_LIST = "IN_LIST"_s; @@ -568,6 +571,24 @@ bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&, newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()), args); } + // check if a file is readable + else if (this->IsKeyword(keyIS_READABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_READ), + args); + } + // check if a file is writable + else if (this->IsKeyword(keyIS_WRITABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_WRITE), + args); + } + // check if a file is executable + else if (this->IsKeyword(keyIS_EXECUTABLE, *args.current)) { + newArgs.ReduceOneArg(cmSystemTools::TestFileAccess( + args.next->GetValue(), cmsys::TEST_FILE_EXECUTE), + args); + } // does a directory with this name exist else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) { newArgs.ReduceOneArg( diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index ae878c9..1b1ecb5 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -535,7 +535,7 @@ add_RunCMake_test(function) add_RunCMake_test(block) add_RunCMake_test(get_filename_component) add_RunCMake_test(get_property) -add_RunCMake_test(if) +add_RunCMake_test(if -DMSYS=${MSYS}) add_RunCMake_test(include) add_RunCMake_test(include_directories) add_RunCMake_test(include_guard) diff --git a/Tests/RunCMake/if/FilePermissions.cmake b/Tests/RunCMake/if/FilePermissions.cmake new file mode 100644 index 0000000..7881b5f --- /dev/null +++ b/Tests/RunCMake/if/FilePermissions.cmake @@ -0,0 +1,153 @@ + +file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/executable.txt") + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" "foo") +file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" "foo") +file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" PERMISSIONS OWNER_WRITE GROUP_WRITE WORLD_WRITE) + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" "foo") +file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE) + +if(NOT WIN32) + file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/readable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/writable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/executable-dir") + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/readable-dir") + file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/readable-dir" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/writable-dir") + file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/writable-dir" PERMISSIONS OWNER_WRITE GROUP_WRITE WORLD_WRITE) + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/executable-dir") + file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/executable-dir" PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE) +endif() + +if(WIN32) + # files are always readable and executable + # directories are always, readable, writable and executable + if(NOT IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/readable.txt\" failed") + endif() + + if(NOT IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/executable.txt\" failed") + endif() +else() + if(NOT IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/readable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/readable.txt\" failed") + endif() + + if(NOT IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/writable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/writable.txt\" failed") + endif() + + if(NOT IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/executable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/executable.txt\" failed") + endif() + + + if(NOT IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/readable-dir" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/readable-dir" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/readable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/readable-dir\" failed") + endif() + + if(NOT IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/writable-dir" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/writable-dir" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/writable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/writable-dir\" failed") + endif() + + if(NOT IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/executable-dir" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/executable-dir" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/executable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/executable.txt\" failed") + endif() +endif() + +if(UNIX) + # + # Check that file permissions are on the real file, not the symbolic link + # + file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir") + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/readable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt" + SYMBOLIC) + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/writable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt" + SYMBOLIC) + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/executable.txt" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt" + SYMBOLIC) + + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/readable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir" + SYMBOLIC) + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/writable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir" + SYMBOLIC) + + file(CREATE_LINK "${CMAKE_CURRENT_BINARY_DIR}/executable-dir" + "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir" + SYMBOLIC) + + if(NOT IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-readable.txt\" failed") + endif() + + if(NOT IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-writable.txt\" failed") + endif() + + if(NOT IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-executable.txt\" failed") + endif() + + + if(NOT IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-readable-dir\" failed") + endif() + + if(NOT IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir" + OR IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-writable-dir\" failed") + endif() + + if(NOT IS_EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir" + OR IS_READABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir" + OR IS_WRITABLE "${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir") + message(FATAL_ERROR "checks on \"${CMAKE_CURRENT_BINARY_DIR}/link-to-executable-dir\" failed") + endif() +endif() diff --git a/Tests/RunCMake/if/RunCMakeTest.cmake b/Tests/RunCMake/if/RunCMakeTest.cmake index efee116..0bfff90 100644 --- a/Tests/RunCMake/if/RunCMakeTest.cmake +++ b/Tests/RunCMake/if/RunCMakeTest.cmake @@ -2,6 +2,23 @@ include(RunCMake) run_cmake(InvalidArgument1) run_cmake(exists) +if(NOT MSYS) + # permissions and symbolic links are broken on MSYS + unset(uid) + unset(status) + if(UNIX) + set(ID "id") + if (CMAKE_SYSTEM_NAME STREQUAL "SunOS" AND EXISTS "/usr/xpg4/bin/id") + set (ID "/usr/xpg4/bin/id") + endif() + # if real user is root, tests are irrelevant + execute_process(COMMAND ${ID} -u $ENV{USER} OUTPUT_VARIABLE uid ERROR_QUIET + RESULT_VARIABLE status OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + if(NOT status AND NOT uid STREQUAL "0") + run_cmake(FilePermissions) + endif() +endif() run_cmake(IsDirectory) run_cmake(IsDirectoryLong) run_cmake(duplicate-deep-else) -- cgit v0.12