1

I have a shell script that takes a JSON file in and outputs a .h file which one of my targets depends on. It would appear that CMake's add_custom_command is what I need to accomplish this, but I can't get the header file to generate. I have tried just about every combination I could think of using the information from this post and this post.

Below is the simplest I could create to reproduce the issues I am encountering.

My project structure is as follows:

.
├── CMakeLists.txt
├── main.c
└── res
    ├── generate.sh
    └── input.json

CMakeLists.txt


cmake_minimum_required(VERSION 2.8)
project(test)

set(TEST_DATA_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.h)
add_custom_command(
    OUTPUT ${TEST_DATA_OUTPUT}
    COMMAND res/generate.sh h res/input.json ${TEST_DATA_OUTPUT}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    COMMENT "Generates the header file containing the JSON data."
)

# add the binary tree to the search path for include files so we
# will fine the generated files
include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(SRCS main.c)
add_executable(test ${SRCS})

main.c


#include <stdio.h>

#include "test_data.h"

int main(int argc, char** argv)
{
    printf("%s\n", TEST_DATA);
    return 0;
}

res/generate.sh


#!/bin/sh
#
# Converts the JSON to a C header file to be used as a resource file.

print_usage()
{
cat << EOF
USAGE:
    $0 h INPUT

DESCRIPTION:
    Outputs JSON data to another format.

EOF
}

to_h()
{
cat << EOF
#ifndef TEST_DATA_H
#define TEST_DATA_H

static const char* TEST_DATA =
"$(cat "$1" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/"\n"/g')";

#endif // TEST_DATA_H
EOF
}

case "$1" in
h)
    if [ $# -eq 3 ] ; then
        to_h "$2" > "$3"
    elif [ $# -eq 2 ] ; then
        to_h "$2"
    else
        echo "no input file specified" 1>&2
    fi
    ;;
*)
    print_usage
    ;;
esac

exit 0

res/input.json


{
    "1": {
        "attr1": "value1",
        "attr2": "value2"
    },
    "2": {
        "attr1": "value1",
        "attr2": "value2"
    }
}
Community
  • 1
  • 1
E-rich
  • 9,243
  • 11
  • 48
  • 79
  • In out-of-source builds this command will fail and your first link provides a solution. Otherwise read and/or provide the build log to understand what does actually happen when the build process tries to run your custom command. – wRAR Feb 11 '13 at 02:20
  • What's the target that depends on `generated.h`? Could you please show us your target? `add_custom_command` will only execute if you are trying to build it, or something that depends on it. – congusbongus Feb 11 '13 at 03:00

2 Answers2

0

Here's an example CMakeLists.txt which uses Ragel to read an input file (nstrip.rl), generate an output file (nstrip.c), and compile/link the output file into a binary.

set(RAGEL_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nstrip.c)

add_executable(
  nstrip
    ${RAGEL_OUTPUT}
  )
set_target_properties(
  nstrip
  PROPERTIES
    COMPILE_FLAGS -std=c99
  )

find_program(RAGEL ragel)
add_custom_command(
  OUTPUT             ${RAGEL_OUTPUT}
  COMMAND            ${RAGEL} -G2 -o ${RAGEL_OUTPUT} nstrip.rl
  DEPENDS            nstrip.rl
  WORKING_DIRECTORY  ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT            "Ragel: Generating Statemachine"
  VERBATIM
  )

Notes:

  • with DEPENDS the command will only execute when a dependency actually changed.
  • This is prepared for out-of-source builds, because it places the generated file in CMAKE_CURRENT_BINARY_DIR (ie. the directory in the build tree corresponding to the location of the above CMakeLists.txt in the source tree), and uses that as input for the executable.
  • Likewise, it also executes the command with the WORKING_DIRECTORY set to where the above CMakeLists.txt resides, so you can refer to source files (here nstrip.rl) with relative paths (you could also do it the other way around).

For your case, you will need to tell CMake where to find the header:

# assuming you generate into the build tree
include_directories("${CMAKE_CURRENT_BINARY_DIR}")`

Also, if the generating executable is a script which lives in the source tree instead of on the $PATH, leave out the find_program and just add the script to DEPENDS. CMake will then automatically trigger a rebuild of the header whenever you change the inputfile (json) or the script itself.

unthought
  • 651
  • 1
  • 15
  • 24
0

Now that I've gained more experience with CMake, I came back to see if I could get this working. The following shows the 2 lines that needed to be changed.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6aa2ac6..5d80124 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,8 +2,7 @@ cmake_minimum_required(VERSION 2.8)
 project(test)

 set(TEST_DATA_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.h)
-add_custom_command(
-    OUTPUT ${TEST_DATA_OUTPUT}
+add_custom_target(generate
     COMMAND res/generate.sh h res/input.json ${TEST_DATA_OUTPUT}
     WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     COMMENT "Generates the header file containing the JSON data."
@@ -15,3 +14,4 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})

 set(SRCS main.c)
 add_executable(test ${SRCS})
+add_dependencies(test generate)
E-rich
  • 9,243
  • 11
  • 48
  • 79