3

I want to add a CMake target which, when made, will trigger the following:

rm $(find "${CMAKE_SOURCE_DIR}" -name "*.rej" -or -name "*.orig")

I tried this:

add_custom_target(pclean
    COMMAND bash -c "rm $(find \"${CMAKE_SOURCE_DIR}\" -name \"*.rej\" -or -name \"*.orig\")")

and this:

 add_custom_target(pclean
     COMMAND bash -c "find "${CMAKE_SOURCE_DIR}" -name \"*.rej\" -or -name \"*.orig\" | xargs rm")

but neither works. How should I do this right? Am I supposed to use something like add_custom_command?

Note: The issue here is not the quotes. Thus if I use:

 add_custom_target(pclean
     COMMAND bash -c "find "${CMAKE_SOURCE_DIR}" -name \"*.rej\" -or -name \"*.orig\"")

I get the list of *.orig and *.rej files.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • @Florian: The quotes are not the problem. – einpoklum Oct 20 '17 at 10:52
  • Can you use the `ADDITIONAL_MAKE_CLEAN_FILES` property? e.g. `SET(addfiles foo bar) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${addfiles}")` For getting a list of files to delete you could use `file(GLOB ...)` command. – vre Oct 20 '17 at 14:57
  • @vre: I did not know about that... will look into it. ... oh, wait, won't that cause `make clean` to remove these files? I don't want that to happen. – einpoklum Oct 20 '17 at 19:53
  • I guess `$(find)` construction is evaluated by `make` before shell even see it. In any case, using complex expressions with *COMMAND* isn't recommended. In case of `find` command, you can pass `rm {} ;` as its `-exec` argument; this would effectively remove every file found by `find`. – Tsyvarev Oct 20 '17 at 20:41
  • Why not use the `-delete` flag of `find`? – nega Oct 23 '17 at 16:40
  • @nega: That would help for this specific example, but sometimes you want to use subshells for other stuff. – einpoklum Oct 23 '17 at 17:20

2 Answers2

3

Turning my comment into an answer

I could reproduce your problem and adding the VERBATIM keyword to your add_custom_target() did fix it.

The following did work:

cmake_minimum_required(VERSION 2.6)

project(SubShell NONE)

add_custom_target(
    pclean
    COMMAND bash -c "rm $(find \"${CMAKE_SOURCE_DIR}\" -name \"*.rej\" -or -name \"*.orig\")"
    VERBATIM
)

If you start "escaping" things in your custom command, it's a hint you should use VERBATIM:

All arguments to the commands will be escaped properly for the build tool so that the invoked command receives each argument unchanged. Note that one level of escapes is still used by the CMake language processor before add_custom_target even sees the arguments.

An extract of the generated makefile without VERBATIM:

CMakeFiles/pclean:
    bash -c rm\ $(find\ "/mnt/c/temp/StackOverflow/SubShell"\ -name\ "*.rej"\ -or\ -name\ "*.orig")

and with VERBATIM:

CMakeFiles/pclean:
    bash -c "rm \$$(find \"/mnt/c/temp/StackOverflow/SubShell\" -name \"*.rej\" -or -name \"*.orig\")"

References

Florian
  • 39,996
  • 9
  • 133
  • 149
  • `VERBATIM` mode does not actually write out the commands verbatim, CMake still interprets a single backslash as an escape, but a pair of backslashes is emitted two backslashes. The verbatim concept itself is broken. In any case, the combination of VERBATIM _and_ wrapping with `bash -c "..."` seems to allow anything, including `sed` commands containing backslashes, e.g. `COMMAND bash -c "echo 'the(king)rat' | sed 's,[^(]*(\\([^)]*\\)).*,-->\\1<--,g'"` (which correctly outputs `-->king<--`). The trick being that the `bash -c "..."` wrapper is a necessary additional level of interpretation. – neuralmer Feb 07 '23 at 19:04
2

This is an incomplete answer.

It seems the issue we're facing is that the arguments after COMMAND are interpreted thrice:

  • first by CMake,
  • then by GNU Make,
  • then finally by the shell.

When it's just the last one - you only need to escape the glob character *:

find some/where -name \*.rej -or -name \*.orig | xargs rm

when it's in a Makefile, you need something like:

bash -c "find some/where -name \\*.rej -or -name \\*.orig | xargs rm"

and finally, in a CMake command, you need another level of escaping, so you do:

add_custom_target(pclean 
    COMMAND bash -c "find \"${CMAKE_SOURCE_DIR}\" -name \\\\*.rej -or -name \\\\*.orig \\| xargs rm")

However, I have not managed to get commands to work which also set and then use shell variables - that always get messed up somehow. So no luck with the second variant in my question, or with | while read f; do rm "$f"; done after the find command.

PS: As commenters have noted, it's apparently also possible to have the find command itself delete the files (with -exec or -delete).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • That escaping and backslash part sounds like you just need to add `VERBATIM` to your `add_custom_target()` call. – Florian Oct 23 '17 at 18:40
  • @Florian: Can you develop that into an answer please? – einpoklum Oct 23 '17 at 20:11
  • For reference, using CMake 3.25.0 and targeting the Xcode generator and targeting Xcodes new build system, the commands in `COMMAND` are put into shell scripts directly and skip the Makefile interpretation. It is still problematic. – neuralmer Feb 07 '23 at 14:41