2

This is my first post but I have been using StackOverflow for years. Thanks for helping me every time.

I am writing a script in CMake that is supposed to rewrite a portion of a .cfg file (it is the resources.cfg file from Ogre 3D engine) so that:

  • when running config with cmake, absolute paths are set according to the system
  • when installing, all files are copied into a folder and relative paths are set instead

I will focus only on the first part, since they are both similar. This is the resource file I am working on:

# Resources required by the sample browser and most samples.
[Essential]
Zip=stufftochange/media/packs/SdkTrays.zip

# Resource locations to be added to the default path
[General]
FileSystem=../media #local
FileSystem=stufftochange/media/materials/scripts
FileSystem=stufftochange/media/materials/textures
FileSystem=stufftochange/media/models
FileSystem=stufftochange/media/materials/programs/HLSL_Cg 
FileSystem=stufftochange/media/materials/programs/GLSL

My current strategy is that only lines without the #local identifier should be affected by the REGEX REPLACE statement. My current code is:

file(READ ${CMAKE_SOURCE_DIR}/cfg/resources.cfg RESOURCES_FILE)
string(REGEX REPLACE
    "/media"
    "${OGRE_HOME_BACKSLASHES}/media"
    RESOURCES_FILE_MODIFIED ${RESOURCES_FILE})
file(WRITE ${CMAKE_SOURCE_DIR}/cfg/resources.cfg ${RESOURCES_FILE_MODIFIED})

which basically replaces all /media occurrences. I need to replace the stufftochange (that can be ANYTHING that is before string media) only if #local is not at the end of the line. I tried to modify the matching expression in lots of ways but, when I do, only the first and last line are replaced properly. I suspect that it has to do with the line endings.

These are some of the expressions I tried, without luck:

([^ #]*)=[^ ]*/media([^#\n$]*)
=[^ ]*/media([^#\n$]*)
/media([^# ]*)
/media([^#\n ]*)

Of course I used \\1 and \\2 to save the parts of the string that I want to keep.

I did a few searches on google and stackoverflow but couldn't find a proper solution or guide for using CMake's regex replace (and documentation is very basic). Any idea what my holy grail match expression would be?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
ARrigo
  • 83
  • 2
  • 8
  • Not exactly sure if this is what you want since there was no example output, but does this work for you? Match: `=.+/media(?!.+#local)` Replace: `=${newstuff}/media` – CAustin May 14 '15 at 00:05
  • the example output can be any result with "stufftochange" changed to anything, my goal is to go back and forth from any result to any result (`#local` identifier must be preserved) I tried then to add \\1 after your replace suggestion: cmake fails to compile regex. I think cmake does not support !? the way it does in other languages – ARrigo May 14 '15 at 13:44

1 Answers1

9

CMake's regex syntax and documentation are pretty limited. I'd favour turning the file's contents into a list of strings, each string being a line in the file. Iterating these makes the regex much simpler:

set(SourceFile "${CMAKE_SOURCE_DIR}/cfg/resources.cfg")
file(READ ${SourceFile} Contents)

# Set the variable "Esc" to the ASCII value 27 - basically something
# which is unlikely to conflict with anything in the file contents.
string(ASCII 27 Esc)

# Turn the contents into a list of strings, each ending with an Esc.
# This allows us to preserve blank lines in the file since CMake
# automatically prunes empty list items during a foreach loop.
string(REGEX REPLACE "\n" "${Esc};" ContentsAsList "${Contents}")

unset(ModifiedContents)
foreach(Line ${ContentsAsList})
  # Don't modify the line if it contains #local at the end.
  if(NOT "${Line}" MATCHES "#local${Esc}$")
    string(REGEX REPLACE "=.*/media" "=${OGRE_HOME_BACKSLASHES}/media" Line ${Line})
  endif()
  # Swap the appended Esc character back out in favour of a line feed
  string(REGEX REPLACE "${Esc}" "\n" Line ${Line})
  set(ModifiedContents "${ModifiedContents}${Line}")
endforeach()
file(WRITE ${SourceFile} ${ModifiedContents})

If you don't care about preserving blank lines, you can use file(STRINGS ...) to read in the file, which makes life a bit simpler:

set(SourceFile "${CMAKE_SOURCE_DIR}/cfg/resources.cfg")
file(STRINGS ${SourceFile} Contents)

unset(ModifiedContents)
foreach(Line ${Contents})
  # Don't modify the line if it contains #local at the end.
  if(NOT "${Line}" MATCHES "#local$")
    string(REGEX REPLACE
        "=.*/media"
        "=${OGRE_HOME_BACKSLASHES}/media"
        Line ${Line})
  endif()
  set(ModifiedContents "${ModifiedContents}${Line}\n")
endforeach()
file(WRITE ${SourceFile} ${ModifiedContents})

Probably the best description of CMake's regex syntax is found in the docs for string.

Fraser
  • 74,704
  • 20
  • 238
  • 215
  • FORMATTED: It works. I used a similar approach with `REGEX MATCHALL` but it was easier since cmake formatted the list for me. Let's say I wouldn't care about preserving empty lines: could I use simply `MATCHES "#local\n$"` ? Final `$` means end of the line, right? I assume `.+` means any character one or more times. Could I enhance it using `.*` instead so that it works even if between `=` and `/media` there are no characters? – ARrigo May 14 '15 at 14:15
  • Yes - you can simplify things if you don't care about the preservation of blank lines. I've updated my answer to show this. – Fraser May 14 '15 at 17:50
  • You can simply use `foreach(line IN LISTS content)` to include empty list elements without making any replacements. – Rafael Kitover Feb 20 '20 at 23:17