Quellcode durchsuchen

cmcmd: add end of options delimiter to cmake -E commands

Implements a -- delimiter, that indicates the end of options (starting
with a dash -) of a command and separates them from the subsequent
operands (positional arguments).

The following commands are affected:
- env: Implemented the -- delimiter.
- cat: The -- delimiter was already kind of considered, but its
  occurence did not stop the options parsing.
- rm: Here the command already implemented the -- delimiter as
  specified, but it was not documented.

Fixes #22970
Peter Würth vor 3 Jahren
Ursprung
Commit
b10930040d

+ 16 - 5
Help/manual/cmake.1.rst

@@ -646,11 +646,17 @@ Available commands are:
     ``true`` if cmake supports server-mode and ``false`` otherwise.
     Always false since CMake 3.20.
 
-``cat <files>...``
+``cat [--] <files>...``
   .. versionadded:: 3.18
 
   Concatenate files and print on the standard output.
 
+  .. versionadded:: 3.24
+    Added support for the double dash argument ``--``. This basic implementation
+    of ``cat`` does not support any options, so using a option starting with
+    ``-`` will result in an error. Use ``--`` to indicate the end of options, in
+    case a file starts with ``-``.
+
 ``chdir <dir> <cmd> [<arg>...]``
   Change the current working directory and run a command.
 
@@ -719,11 +725,16 @@ Available commands are:
 ``echo_append [<string>...]``
   Displays arguments as text but no new line.
 
-``env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...``
+``env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]``
   .. versionadded:: 3.1
 
   Run command in a modified environment.
 
+  .. versionadded:: 3.24
+    Added support for the double dash argument ``--``. Use ``--`` to stop
+    interpreting options/environment variables and treat the next argument as
+    the command, even if it start with ``-`` or contains a ``=``.
+
 ``environment``
   Display the current environment variables.
 
@@ -816,16 +827,16 @@ Available commands are:
   Rename a file or directory (on one volume). If file with the ``<newname>`` name
   already exists, then it will be silently replaced.
 
-``rm [-rRf] <file> <dir>...``
+``rm [-rRf] [--] <file|dir>...``
   .. versionadded:: 3.17
 
   Remove the files ``<file>`` or directories ``<dir>``.
-
   Use ``-r`` or ``-R`` to remove directories and their contents recursively.
   If any of the listed files/directories do not exist, the command returns a
   non-zero exit code, but no message is logged. The ``-f`` option changes
   the behavior to return a zero exit code (i.e. success) in such
-  situations instead.
+  situations instead. Use ``--`` to stop interpreting options and treat all
+  remaining arguments as paths, even if they start with ``-``.
 
 ``server``
   Launch :manual:`cmake-server(7)` mode.

+ 7 - 0
Help/release/dev/cmcmd-end-of-options-delimiter.rst

@@ -0,0 +1,7 @@
+cmcmd-end-of-options-delimiter
+------------------------------
+
+* The :manual:`cmake(1)` ``-E`` commands ``cat`` and ``env`` learned to respect
+  a double dash (``--``) argument that acts as a delimiter indicating the end of
+  options. Any following arguments are treated as operands/positional arguments,
+  even if they begin with a dash ``-`` character.

+ 17 - 6
Source/cmcmd.cxx

@@ -97,7 +97,8 @@ void CMakeCommandUsage(std::string const& program)
     << "Available commands: \n"
     << "  capabilities              - Report capabilities built into cmake "
        "in JSON format\n"
-    << "  cat <files>...            - concat the files and print them to the standard output\n"
+    << "  cat [--] <files>...       - concat the files and print them to the "
+       "standard output\n"
     << "  chdir dir cmd [args...]   - run command in a given directory\n"
     << "  compare_files [--ignore-eol] file1 file2\n"
     << "                              - check if file1 is same as file2\n"
@@ -110,7 +111,7 @@ void CMakeCommandUsage(std::string const& program)
     << "  echo [<string>...]        - displays arguments as text\n"
     << "  echo_append [<string>...] - displays arguments as text but no new "
        "line\n"
-    << "  env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...\n"
+    << "  env [--unset=NAME ...] [NAME=VALUE ...] [--] <command> [<arg>...]\n"
     << "                            - run command in a modified environment\n"
     << "  environment               - display the current environment\n"
     << "  make_directory <dir>...   - create parent and <dir> directories\n"
@@ -125,8 +126,9 @@ void CMakeCommandUsage(std::string const& program)
     << "  remove_directory <dir>... - remove directories and their contents (deprecated: use rm instead)\n"
     << "  rename oldname newname    - rename a file or directory "
        "(on one volume)\n"
-    << "  rm [-rRf] <file/dir>...    - remove files or directories, use -f to "
-       "force it, r or R to remove directories and their contents recursively\n"
+    << "  rm [-rRf] [--] <file/dir>... - remove files or directories, use -f "
+       "to force it, r or R to remove directories and their contents "
+       "recursively\n"
     << "  sleep <number>...         - sleep for given number of seconds\n"
     << "  tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n"
     << "                            - create or extract a tar or zip archive\n"
@@ -793,6 +795,12 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
       auto ae = args.cend();
       for (; ai != ae; ++ai) {
         std::string const& a = *ai;
+        if (a == "--") {
+          // Stop parsing options/environment variables; the next argument
+          // should be the command.
+          ++ai;
+          break;
+        }
         if (cmHasLiteralPrefix(a, "--unset=")) {
           // Unset environment variable.
           cmSystemTools::UnPutEnv(a.substr(8));
@@ -1051,9 +1059,12 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
     // Command to concat files into one
     if (args[1] == "cat" && args.size() >= 3) {
       int return_value = 0;
+      bool doing_options = true;
       for (auto const& arg : cmMakeRange(args).advance(2)) {
-        if (cmHasLiteralPrefix(arg, "-")) {
-          if (arg != "--") {
+        if (doing_options && cmHasLiteralPrefix(arg, "-")) {
+          if (arg == "--") {
+            doing_options = false;
+          } else {
             cmSystemTools::Error(arg + ": option not handled");
             return_value = 1;
           }

+ 2 - 1
Tests/RunCMake/CMakeLists.txt

@@ -703,7 +703,8 @@ endif()
 
 add_executable(pseudo_llvm-rc pseudo_llvm-rc.c)
 add_RunCMake_test(CommandLine -DLLVM_RC=$<TARGET_FILE:pseudo_llvm-rc> -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-                  -DCYGWIN=${CYGWIN} -DMSYS=${MSYS} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE})
+                  -DCYGWIN=${CYGWIN} -DMSYS=${MSYS} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
+                  -DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>)
 add_RunCMake_test(CommandLineTar)
 
 if(CMAKE_PLATFORM_NO_VERSIONED_SONAME OR (NOT CMAKE_SHARED_LIBRARY_SONAME_FLAG AND NOT CMAKE_SHARED_LIBRARY_SONAME_C_FLAG))

+ 1 - 0
Tests/RunCMake/CommandLine/E_cat-with-double-dash-stdout.txt

@@ -0,0 +1 @@
+file starting with dash, not an option

+ 1 - 0
Tests/RunCMake/CommandLine/E_cat-without-double-dash-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CommandLine/E_cat-without-double-dash-stderr.txt

@@ -0,0 +1 @@
+^CMake Error: -file-starting-with-dash.txt: option not handled$

+ 1 - 0
Tests/RunCMake/CommandLine/E_env-with-double-dash-result.txt

@@ -0,0 +1 @@
+0

+ 1 - 0
Tests/RunCMake/CommandLine/E_env-without-double-dash-result.txt

@@ -0,0 +1 @@
+1

+ 1 - 0
Tests/RunCMake/CommandLine/E_env-without-double-dash-stderr.txt

@@ -0,0 +1 @@
+.*

+ 17 - 1
Tests/RunCMake/CommandLine/RunCMakeTest.cmake

@@ -676,17 +676,33 @@ file(WRITE "${out}/empty_file.txt" "")
 file(WRITE "${out}/unicode_file.txt" "àéùç - 한국어") # Korean in Korean
 run_cmake_command(E_cat_good_cat
   ${CMAKE_COMMAND} -E cat "${out}/first_file.txt" "${out}/second_file.txt" "${out}/empty_file.txt" "${out}/unicode_file.txt")
-unset(out)
 
 run_cmake_command(E_cat_good_binary_cat
   ${CMAKE_COMMAND} -E cat "${RunCMake_SOURCE_DIR}/E_cat_binary_files/binary.obj" "${RunCMake_SOURCE_DIR}/E_cat_binary_files/binary.obj")
 
+# To test whether the double dash (--) works, we need to control the working directory
+# in order to be able to pass a relative path that starts with a dash.
+file(WRITE "${out}/-file-starting-with-dash.txt" "file starting with dash, not an option\n")
+set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${out}")
+run_cmake_command(E_cat-with-double-dash ${CMAKE_COMMAND} -E cat -- "-file-starting-with-dash.txt")
+run_cmake_command(E_cat-without-double-dash ${CMAKE_COMMAND} -E cat "-file-starting-with-dash.txt")
+unset(RunCMake_TEST_COMMAND_WORKING_DIRECTORY)
+unset(out)
+
 run_cmake_command(E_env-no-command0 ${CMAKE_COMMAND} -E env)
 run_cmake_command(E_env-no-command1 ${CMAKE_COMMAND} -E env TEST_ENV=1)
 run_cmake_command(E_env-bad-arg1 ${CMAKE_COMMAND} -E env -bad-arg1)
 run_cmake_command(E_env-set   ${CMAKE_COMMAND} -E env TEST_ENV=1 ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-set.cmake)
 run_cmake_command(E_env-unset ${CMAKE_COMMAND} -E env TEST_ENV=1 ${CMAKE_COMMAND} -E env --unset=TEST_ENV ${CMAKE_COMMAND} -P ${RunCMake_SOURCE_DIR}/E_env-unset.cmake)
 
+# To test whether the double dash (--) works for the env command, we need a command that e.g. contains an equals sign (=)
+# and would normally be interpreted as an NAME=VALUE environment variable.
+# Ensuring such a command is done by simply copying the trivial exit_code executable with a different name.
+cmake_path(GET EXIT_CODE_EXE FILENAME exit_code)
+file(COPY_FILE "${EXIT_CODE_EXE}" "${RunCMake_BINARY_DIR}/env=${exit_code}")
+run_cmake_command(E_env-with-double-dash ${CMAKE_COMMAND} -E env TEST_ENV=1 -- "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit)
+run_cmake_command(E_env-without-double-dash ${CMAKE_COMMAND} -E env TEST_ENV=1 "${RunCMake_BINARY_DIR}/env=${exit_code}" zero_exit)
+
 run_cmake_command(E_md5sum-dir ${CMAKE_COMMAND} -E md5sum .)
 run_cmake_command(E_sha1sum-dir ${CMAKE_COMMAND} -E sha1sum .)
 run_cmake_command(E_sha224sum-dir ${CMAKE_COMMAND} -E sha224sum .)