Help with a baking one binary's hash into another

I have a situation where one binary’s hash needs to be baked into another as a define. So, something like this:

add_executable(app1 app1.c)

add_executable(app2 app2.c)
target_compile_definitions(app2 -DHASH="APP1_HASH")

I can’t figure it out in an elegant way. What I tried:

add_custom_command(
    OUTPUT app1-hash.txt
    COMMAND hash-command "$<TARGET_FILE:app1>" > app1-hash.txt
)

file(READ app1-hash.txt)

But file reads at configuration time, of course. Same with file(<HASH>.

execute_process allows capturing output to stdout, but doesn’t have dependencies.

What I’ve currently settled on does seem to work, but I’m not satisfied with it and was hoping there would be a more elegant solution:

add_executable(app1 app1.c)

add_custom_command(
    OUTPUT app1-hash.txt
    COMMAND "${CMAKE_COMMAND}" -E echo '-DHASH=\"' >  app1-hash.txt
    COMMAND "${CMAKE_COMMAND}" -E sha512sum "$<TARGET_FILE:app1>" | cut -d ' ' -f 1 >> app1-hash.txt
    COMMAND "${CMAKE_COMMAND}" -E echo '\"' >> app1-hash.txt
    VERBATIM
)
add_custom_target(app1-hash DEPENDS app1-hash.txt)

add_executable(app2 app2.c)
add_dependency(app2 app1-hash)
target_compile_options(app2 @app1-hash.txt)

Instead of trying to construct a -D... compiler command line option for compiling app2, consider writing a header file that provides the hash value. Then any code that needs the hash can include that generated header.

For an even better solution, write a non-generated header that defines a function prototype, then generate a .c or .cpp file that implements that function. The implementation is the only thing that needs to know the actual hash. The function prototype doesn’t, and the caller shouldn’t need the hash to be inlined. The advantage of this is that when the hash changes, the only thing in app2 that needs to be recompiled is the one generated source file. If you put the hash value in the header, then every file that directly or indirectly includes that generated header would have to be recompiled.

To sketch it out more fully, the (non-generated) header might be something as simple as this:

const char* get_app1_hash();

Then your generated C or C++ source file might look like this:

const char* get_app1_hash()
{
    return "<hash-value-goes-here>";
}

The add_custom_command() that you’ve constructed assumes shell behaviors like piping and redirection, but that isn’t guaranteed to be supported by add_custom_command(). You can use a CMake script instead to achieve this more cleanly and in a platform-independent way. For example:

set(script_file ${CMAKE_CURRENT_SOURCE_DIR}/write_hash.cmake)
set(output_file ${CMAKE_CURRENT_BINARY_DIR}/app1-hash.txt)
add_custom_command(
    OUTPUT ${output_file}
    COMMAND "${CMAKE_COMMAND}"
        -DFILE_TO_HASH=$<TARGET_FILE:app1>
        -DOUTPUT_FILE=${output_file}
        -P ${script_file}
    DEPENDS
        ${script_file}
        app1
)

Note the DEPENDS in the above to ensure the file gets regenerated if app1 changes, or if you modify the write_hash.cmake script. The contents of write_hash.cmake might look something like this:

file(SHA512 "${FILE_TO_HASH}" hash)
file(WRITE "${OUTPUT_FILE}"
"const char* get_app1_hash()
{
    return "${hash}";
}
")
2 Likes

Exactly what I was going to suggest :)

This is actually so much better, thanks!