String REG REPLACE in CMAKE - cmake

I am searching header files in a directory
Replacing LONG string in all header files to SVN_LONG.
I don't wan't to replace LONG,LONG,LONG to SVN_LONG.
When I run below code it's replacing SLONG to SSVN_LONG, LONG to SVN_LONG etc..
So how to avoid SLONG, LONGINT not to replace, only LONG string have to replace SVN_LONG?
file ( GLOB headerfiles "../common/include/tar/*.h")
if ("${grep_word}" STREQUAL "")
SET (searchreg "LONG")
foreach( eachheaderfile ${headerfiles} )
MESSAGE(STATUS " INFILES= ${eachheaderfile}\n")
FILE(READ ${eachheaderfile} file_content)
#MESSAGE(STATUS " FILES_content= ${file_content}")
string(REGEX REPLACE "${searchreg}" "SVN_LONG" modified_file_content "${file_content}" )
FILE(WRITE ${eachheaderfile} ${modified_file_content})
FILE(READ ${eachheaderfile} file_content1)
MESSAGE(STATUS " FILES_content= ${file_content1}")
endforeach(eachheaderfile)
#MESSAGE(STATUS " outFILES= ${headerfiles}\n")
endif()

As usual with regular expressions, you need to match symbols before replaced string and restore them in replacement string. Command string(REGEX REPLACE) supports backslashed references for that purpose:
string(REGEX REPLACE "([^a-zA-Z])LONG|^LONG" "\\1SVN_LONG"
modified_file_content ${file_content})
Given command matches single symbol, which should be different from the letter, before word LONG and restore this symbol via backslash reference \1 (symbol "\" need to be doubled in the cmake command, because it is parsed by CMake itself before going to the command).
Alternation started with ^ is needed for match LONG at the beginning of the string.

Related

Start reading file from a specific line using CMAKE

I want to start reading a file from a specific line. Cmake official docs suggest using file() with offset but I am not sure about its usage.
The file that I want to read is test.mak:
# -----------------------------------------------------------------------------
## TEST
# -----------------------------------------------------------------------------
TEST_COMPONENTS ?= ABC DEF GHI
# SYMBOLS
SYMBOLS_PROJ ?= A002
SYMBOLS_LABEL ?= TEST_A002_FINAL
I have a cmake file (the function is from internet and it works with my use case) where i want to read the test.mak file starting from "#SYMBOLS" so that the macros defined before this line are ignored/skipped, and then i want to set the macros in my current cmake:
function(Fileread MKFile)
file(READ "${MKFile}" FileContents [OFFSET "# SYMBOLS"])
string(REPLACE "?" "" FileContents ${FileContents})
string(REPLACE "\\\n" "" FileContents ${FileContents})
string(REPLACE "\n" ";" FileLines ${FileContents})
list(REMOVE_ITEM FileLines "")
foreach(line ${FileLines})
string(REPLACE "=" ";" line_split ${line})
list(LENGTH line_split count)
if (count LESS 2)
message(STATUS "Skipping ${line}")
continue()
endif()
list(GET line_split -1 value)
string(STRIP "${value}" value)
separate_arguments(value)
list(REMOVE_AT line_split -1)
foreach(var_name ${line_split})
string(STRIP ${var_name} var_name)
set(${var_name} ${value} PARENT_SCOPE)
endforeach()
endforeach()
endfunction()
Fileread("test.mak")
The offset setting is not working as a result of which i am also getting the macro TEST_COMPONENTS which i don't need. NOTE: TEST_COMPONENTS is just an example, there are multiple lines of macro definitions before "# SYMBOLS" that i would like to skip. Thanks for any suggestions to solve this in advance.
Use file(STRINGS) to read the lines of the text file into a list variable. You could then use list(POP_FRONT) until you encounter a matching line.
# Line 1
# Line 2
# Line 3
# Line 4
file(STRINGS ${CMAKE_CURRENT_LIST_FILE} THIS_FILE)
set(REDUCTION_SUCCESS False)
while(THIS_FILE)
list(POP_FRONT THIS_FILE LINE)
if (LINE MATCHES "^# SYMBOLS.*")
set(REDUCED_FILE ${LINE} ${THIS_FILE})
set(REDUCTION_SUCCESS True)
break()
endif()
endwhile()
if (REDUCTION_SUCCESS)
# use the contents of the REDUCED_FILE variable
# (print all remaining lines for the purpose of demonstation)
foreach(_LINE IN LISTS REDUCED_FILE)
message(STATUS "${_LINE}")
endforeach()
else()
message(FATAL_ERROR "No line containing '# SYMBOLS' found")
endif()
Replace one of the # Line 1 comments with # SYMBOLS to get a successful outcome. For simplicity this is just a cmake script that can be run with cmake -P script.cmake. The script file parses itself.
If the number of lines to skip is known, you could simplify the logic after the file(STRINGS) command to a single list(SUBLIST) call.

Variable reference in string does not get evaluated by "set"

I want the same as in
How to load variables in a "bar=foo" syntax in CMake?
with the difference that my "value" entries contain references to the "keys" (prospective variables).
Eg the BASEPATH variable:
BASEPATH:=/home/SomePath
LIB_BASE?=$(BASEPATH)/lib/$(LIBREV)
ACCELMOD_BASE=$(BASEPATH)/SomeOtherPath
I have modified my CMakeLists.txt script to extract the [Key/value] pair from each line to two variables(see code below) and end up with pairs like (note that the value still contains a reference to some variable's name which is usually defined at the beginning of the file):
[BASEPATH, /home/SomePath],
[LIB_BASE, ${BASEPATH}/lib/${LIBREV}],
[ACCELMOD_BASE, ${BASEPATH}/SomeOtherPath],
The code I have written is the following:
# Part2
file(STRINGS revisions.mk ConfigContents)
foreach(NameAndValue ${ConfigContents})
# Strip leading spaces
string(REGEX REPLACE "^[ ]+" "" NameAndValue ${NameAndValue})
#remove commented lines
string(FIND ${NameAndValue} "#" commentIndex)
if(${commentIndex} EQUAL 0)
continue()
endif()
# Find variable name
string(REGEX MATCH "^[^\\?:=]+" Name ${NameAndValue})
# Find the value
string(REGEX REPLACE "^(.+)=(.+)$" "\\2" Value ${NameAndValue})
# Replace () with {} to denote a cmake's variable reference
string(REGEX REPLACE "\\(([^)]+)\\)" "{\\1}" Value ${Value})
# Set the variable
set(${Name} ${Value})
message("> Value of " ${Name} " : " ${${Name}})
endforeach()
I expect that when I define a Key (Name) as a variable (using the set command) and set its value to the Value counterpart of the Key, the reference inside the string will be replaced with the current value of the referenced variable.
However this is not the case.
e.g. for the give input, the message command before the end of the loop returns:
>Value of BASEPATH: /home/SomePath
>Value of LIB_BASE : ${BASEPATH}/lib/${LIBREV}
>Value of ACCELMOD_BASE: $(BASEPATH)/SomeOtherPath
even though BASEPATH has already been defined.
To verify my expectation, I wrote the following simple code to simulate the behaviour in the loop:
set(BASE 123)
set(RIGHT '${BASE}/SomePath/AA')
set(LEFT_STR "LEFT")
set(${LEFT_STR} ${RIGHT})
message(">" ${LEFT} "<>" ${${LEFT_STR}})
and in this case the ${BASE} reference gets correctly replaced and
'123/SomePath/AA'<>'123/SomePath/AA'
is returned as expected.
What could I been getting wrong?
Since CMake 3.18 there is eval in CMake - cmake_language(EVAL CODE. See https://cmake.org/cmake/help/latest/command/cmake_language.html?highlight=eval .
cmake_langauge(EVAL CODE "set(${Name} ${Value})")
There is no eval in cmake. The popular and error-prone way around it is to create a script and then include it:
file(STRINGS revisions.mk ConfigContents)
set(varlist "")
foreach(NameAndValue ${ConfigContents})
# Strip leading spaces
string(REGEX REPLACE "^[ ]+" "" NameAndValue ${NameAndValue})
#remove commented lines
string(FIND ${NameAndValue} "#" commentIndex)
if(${commentIndex} EQUAL 0)
continue()
endif()
# Find variable name
string(REGEX MATCH "^[^\\?:=]+" Name ${NameAndValue})
# Find the value
string(REGEX REPLACE "^(.+)=(.+)$" "\\2" Value ${NameAndValue})
# Replace () with {} to denote a cmake's variable reference
string(REGEX REPLACE "\\(([^)]+)\\)" "{\\1}" Value ${Value})
# Set the variable
set(${Name} ${Value})
list(APPEND varlist ${Name})
endforeach()
set(script "")
foreach(i IN LISTS varlist)
string(APPEND script "set(${i} \"${${i}}\")\n")
endforeach()
file(WRITE script2.cmake ${script})
include(script2.cmake)
foreach(i IN LISTS varlist)
message(STATUS ${i}=${${i}})
endforeach()
This creates the script script2.cmake with the content:
set(BASEPATH "/home/SomePath")
set(LIB_BASE "${BASEPATH}/lib/${LIBREV}")
set(ACCELMOD_BASE "${BASEPATH}/SomeOtherPath")
and then includes it. Including such script would re-evaulate expressions thus resolve references.

CMake: Get version from multiline text file

I have a file version.txt
VERSION_MAJOR 1
VERSION_MINOR 1
VERSION_PATCH 3
I want to use cmake to add a definition for major, minor and patch.
I've tries using
file(STRING "version.txt" myvar)
but this just puts the whole file in myvar.
How do I get the numbers?
Your use of file is incorrect, you want to use READ in order to read the contents of the file into a variable.
file(READ "version.txt" ver)
Once you have the file's contents into a variable, you can then use REGEX MATCH with a capture group, and access the capture group using CMAKE_MATCH_N
REGEX MATCH:
Create a regular expression with a capture group which will capture the numbers following "VERSION_MAJOR":
string(REGEX MATCH "VERSION_MAJOR ([0-9]*)" _ ${ver})
Note that if the regex matches the input variable, the entire match will be stored in the output variable. However, we don't want the entire match (as that includes the string "VERSION_MAJOR"), so I've just used a variable name _ as the output variable, which, by convention, tells the user I am not interested in this variable
CMAKE_MATCH_N:
If the match is successful, then the capture groups are available in CMAKE_MATCH_N. In this instance there is only one capture group, so we want to use CMAKE_MATCH_1
set(ver_major ${CMAKE_MATCH_1})
At this point ver_major contains just the major version no.
You can then repeat this for the other version components.
Full example below:
cmake_minimum_required(VERSION 3.5)
file(READ "version.txt" ver)
string(REGEX MATCH "VERSION_MAJOR ([0-9]*)" _ ${ver})
set(ver_major ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_MINOR ([0-9]*)" _ ${ver})
set(ver_minor ${CMAKE_MATCH_1})
string(REGEX MATCH "VERSION_PATCH ([0-9]*)" _ ${ver})
set(ver_patch ${CMAKE_MATCH_1})
message("version: ${ver_major}.${ver_minor}.${ver_patch}")
Output:
$ cmake .
version: 1.1.3
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp
For production code you would obviously want to make the cmake script more robust by checking whether the match was successful, and emitting an error if not.

Excluding directory somewhere in file structure from cmake sourcefile list

In any subdirectory of my project it shall be possible to create anywhere a directory called 'hide' and put some code files (.c, .cpp, .h, ...) inside. CMake shall ignore those files. How can I achieve this?
Any proposals for a simple approach? Searched the net but could not find a solution.
What I tried:
file & glob
file(GLOB_RECURSE SOURCE_FILES "*.cpp" "*.c")
file(GLOB_RECURSE REMOVE_SOURCES
"*/hide/*"
"${PROJECT_SOURCE_DIR}/CMakeFiles/*"
"*main.c")
file(GLOB_RECURSE REMOVE_SOURCES "*/hide/*")
list(REMOVE_ITEM SOURCE_FILES ${REMOVE_SOURCES})
The 2rd line works fine if the directory path is known (as for the 'CMakeFiles' directory) or if the file is known (as for the 'main.c' file). But it does not work if there are two '*' for a string. I can not find a simple solution for a directory which is located somewhere.
Regex
Then I tried with REGEX.
file(GLOB_RECURSE SOURCE_FILES "*.cpp" "*.c")
string(REGEX REPLACE ";.*/hide/.*;" ";" FOO ${SOURCE_FILES})
Because the source file list is separated by semicolon, the line above shall remove the string from one semicolon to the next in case it contained the string 'hide'.
The expression seems to have a problem with the semicolon. Having any semicolon makes the command to find nothing.
foreach loop
I tried with some foreach loops, but could not achieve to get a list of all my 'hide'-directories.
One of the ways, I suppose, would be just like this:
set (EXCLUDE_DIR "/hide/")
file (GLOB_RECURSE SOURCE_FILES "*.cpp" "*.c")
foreach (TMP_PATH ${SOURCE_FILES})
string (FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND)
if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1)
list (REMOVE_ITEM SOURCE_FILES ${TMP_PATH})
endif ()
endforeach(TMP_PATH)
Simply searching for /hide/ substring in strings from the list. If the substring found --- remove whole string from the list.
Beware, this might be only solution for Linux. And might not work on older versions of CMake (<2.8.5, according to this).
In mentioned case You can do something like:
# try to replace substring with empty string, compare to original:
string (REPLACE ${EXCLUDE_DIR} "" REPLACED_PATH ${TMP_PATH})
string (COMPARE EQUAL ${TMP_PATH} ${REPLACED_PATH} EXCLUDE_DIR_FOUND)
NOTE: GLOB_RECURSE is considered to be root of all evil.
NOTE2: another thing to be aware of - CMake bug in matching empty variable to string.
NOTE3: took into account gg99's comment
Found a solution that works for me. Added a function to make it easy to exclude another directory.
# Function: EXCLUDE_FILES_FROM_DIR_IN_LIST
# Description: Exclude all files from a list under a specific directory.
# Param _InFileList: Input and returned List
# Param _excludeDirName: Name of the directory, which shall be ignored.
# Param _verbose: Print the names of the files handled
FUNCTION (EXCLUDE_FILES_FROM_DIR_IN_LIST _InFileList _excludeDirName _verbose)
foreach (ITR ${_InFileList})
if ("${_verbose}")
message(STATUS "ITR=${ITR}")
endif ("${_verbose}")
if ("${ITR}" MATCHES "(.*)${_excludeDirName}(.*)") # Check if the item matches the directory name in _excludeDirName
message(STATUS "Remove Item from List:${ITR}")
list (REMOVE_ITEM _InFileList ${ITR}) # Remove the item from the list
endif ("${ITR}" MATCHES "(.*)${_excludeDirName}(.*)")
endforeach(ITR)
set(SOURCE_FILES ${_InFileList} PARENT_SCOPE) # Return the SOURCE_FILES variable to the calling parent
ENDFUNCTION (EXCLUDE_FILES_FROM_DIR_IN_LIST)
EXCLUDE_FILES_FROM_DIR_IN_LIST("${SOURCE_FILES}" "/hide/" FALSE)
Generalizing the function solution mentioned above you get something like:
# Remove strings matching given regular expression from a list.
# #param(in,out) aItems Reference of a list variable to filter.
# #param aRegEx Value of regular expression to match.
function (filter_items aItems aRegEx)
# For each item in our list
foreach (item ${${aItems}})
# Check if our items matches our regular expression
if ("${item}" MATCHES ${aRegEx})
# Remove current item from our list
list (REMOVE_ITEM ${aItems} ${item})
endif ("${item}" MATCHES ${aRegEx})
endforeach(item)
# Provide output parameter
set(${aItems} ${${aItems}} PARENT_SCOPE)
endfunction (filter_items)
You can then use it thus:
file(GLOB_RECURSE MyFunFiles "*.fun")
filter_items(MyFunFiles ".*NotCool.*")

Cmake and regex dont work as expected

I have two variables in an text file that I would like to exhange. [V1] and [V2]. When I run my program it only changes one of them. Why?
#Read up into variable
file (STRINGS "myfile.txt" v1)
#Store text to replace in variable (we need to escape the '[' and ']' since we will use a regexp later on)
set(v1_placeholder "\\[v1\\]")
set(v2_placeholder "\\[v2\\]")
#New reading of documents to process
set(doc_files [v1]_Hello_Documentation.txt myfile.txt)
#Lets iterate over each documentation file
foreach(doc_file ${doc_files})
message(STATUS "Proccessing document file: " ${doc_file})
#Read up content of documentation file
file(READ ${doc_file} FILE_CONTENT)
#Remove occurences of [v2] with nothing
string(REGEX REPLACE "${v2_placeholder}" "" MODIFIED_FILE_CONTENT "${FILE_CONTENT}")
#Replace occurences of [v1] with the real variable in the content
string(REGEX REPLACE "${v1_placeholder}" "${V1}" MODIFIED_FILE_CONTENT "${FILE_CONTENT}")
#Replace occurences of [v1] with the real variable in the file name
string(REGEX REPLACE "${v1_placeholder}" "${V1}" MODIFIED_FILE_NAME "${doc_file}")
#Write modified content back into modifed file name
file(WRITE ${MODIFIED_FILE_NAME} "${MODIFIED_FILE_CONTENT}")
#Add the files to the package in dir /doc
install (FILES ${MODIFIED_FILE_NAME} DESTINATION doc)
endforeach(doc_file)
after I have run this my file will look liket this: (it never deletes the v2.)
The application is currently r.
Main features
When running Hello r the user is presented with the text "Hello World" on stdout.
History
[v2]
r
a
b
c
d
You probably need to pass MODIFIED_FILE_CONTENT instead of FILE_CONTENT to the second call to string(REGEX REPLACE, i.e.:
#Remove occurences of [v2] with nothing
string(REGEX REPLACE "${v2_placeholder}" "" MODIFIED_FILE_CONTENT "${FILE_CONTENT}")
#Replace occurences of [v1] with the real variable in the content
string(REGEX REPLACE "${v1_placeholder}" "${V1}" MODIFIED_FILE_CONTENT "${MODIFIED_FILE_CONTENT}")