3

I have a file that contains a bunch of data. I want to turn it into a C++ string literal, because I need to compile this data into the binary - I cannot read it from disk.

One way of doing this is to just generate a C++ source file that declares a string literal with a known name. The CMake code to do this is straightforward, if somewhat awful:

function(make_literal_from_file dest_file source_file literal_name)              
    add_custom_command(                                                          
        OUTPUT ${dest_file}                                                      
        COMMAND printf \'char const* ${literal_name} = R\"\#\(\' > ${dest_file}  
        COMMAND cat ${source_file} >> ${dest_file}                               
        COMMAND printf \'\)\#\"\;\' >> ${dest_file}                              
        DEPENDS ${source_file})                                                  
endfunction()

This works and does what I want (printf is necessary to avoid a new line after the raw string introducer). However, the amount of escaping going on here makes it very difficult to see what's going on. Is there a way to write this function such that it's actually readable?


Note that I cannot use a file(READ ...)/configure_file(...) combo here because source_file could be something that is generated by CMake at build time and so may not be present at configuration time.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Sorry to be picky, but could you make the title more descriptive? Can't imagine this is very searchable... – Dan Mašek Aug 23 '18 at 15:14
  • @DanMašek Better? – Barry Aug 23 '18 at 15:17
  • I think so. Thanks :) – Dan Mašek Aug 23 '18 at 15:18
  • Hmm, could you perhaps use `CONFIGURE_FILE`, along with `FILE(READ ...)` to load the contents of source_file to a variable? – Dan Mašek Aug 23 '18 at 15:25
  • @DanMašek Ah, meant to add that to the question and forgot. Added now. Not an option unfortunately. – Barry Aug 23 '18 at 15:27
  • 1
    Right. A possible way to work around it is to put the file/configure_file into a separate .cmake script that you invoke from a custom command. I use similar setup to run various code generators at compile time. Although, I admit, it makes the implementation more complex that what you have right now. – Dan Mašek Aug 23 '18 at 15:32

1 Answers1

2

I would recommend writing a script to do this. You could write it in CMake, but I personally prefer a better language such as Python:

# Untested, just to show roughly how to do it
import sys

dest_file, source_file, literal_name = sys.argv[1:]

with open(dest_file) as dest, open(source_file) as source:
    literal_contents = source.read()
    dest.write(f'char const* {literal_name} = R"({literal_contents})";\n')

Corresponding CMake code:

# String interpolation came in Python 3.6, thus the requirement on 3.6.
# If using CMake < 3.12, use find_package(PythonInterp) instead.
find_package(Python3 3.6 COMPONENTS Interpreter)

# Make sure this resolves correctly. ${CMAKE_CURRENT_LIST_DIR} is helpful;
# it's the directory containing the current file (this cmake file)
set(make_literal_from_file_script "path/to/make_literal_from_file.py")

function(make_literal_from_file dest_file source_file literal_name)              
    add_custom_command(                                                          
        OUTPUT "${dest_file}"
        COMMAND 
            "${Python3_EXECUTABLE}" "${make_literal_from_file_script}"
            "${dest_file}"
            "${source_file}"
            "${literal_name}"
        DEPENDS "${source_file}")                                                  
endfunction()

If you don't want the dependency on Python, you could use C++ (only the CMake code shown):

add_executable(make_literal_from_file_exe
    path/to/cpp/file.cpp
)

function(make_literal_from_file dest_file source_file literal_name)              
    add_custom_command(                                                          
        OUTPUT "${dest_file}"
        COMMAND 
            make_literal_from_file_exe
            "${dest_file}"
            "${source_file}"
            "${literal_name}"
        DEPENDS "${source_file}")
endfunction()
Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    This works (+1)... however, it's quite a bit longer and I think I'd prefer the three really ugly CMake lines to having another external thing. I would [strongly] prefer a solution that just made it easier to deal with the escaping. – Barry Aug 23 '18 at 18:38
  • @Barry That's reasonable. Do note that the original code isn't cross platform AFAIK (`printf` isn't on Windows IIRC). – Justin Aug 23 '18 at 18:43
  • @Barry Have you tried using [`VERBATIM`](https://cmake.org/cmake/help/latest/command/add_custom_command.html?highlight=VERBATIM)? I think `COMMAND printf 'char const* ${literal_name} = R\"#(' > ${dest_file}` would work if you did so, maybe requiring wrapping in quotes (something like `printf "'char const* ${literal_name} = R\"#('"` or maybe `printf "char const* ${literal_name} = R\"#("`). – Justin Aug 23 '18 at 18:45
  • Don't care about cross platform. I could not come up with a permutation of VERBATIM that worked (which does not mean that there isn't one that works - I just coudn't figure it out). – Barry Aug 23 '18 at 18:50
  • 1
    @Barry I've run into similar problems every time I've tried to have CMake execute commands that begin to have any complexity at all, unless I break it out into a separate script. I doubt I'd be able to figure it out either... – Justin Aug 23 '18 at 18:54