25

I'm trying to use cmake to simplify distributing my OpenCL program. I have a kernel file which includes several headers and other source files, and I want to have a single self contained executable.

My plan is to have cmake run the C preprocessor on the kernel source, turning the cl file and its includes into a single unit which is easier to work with.

I can use add_custom_command to do it by calling gcc/clang with -E, but then I don't get the flags to include the right directories to find the various header files in the command, and I don't see an easy way to find all current include directories to use in the custom call to the compiler.

Is there a way to run only the C preprocessor on a file with the current cmake environment?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
arsenm
  • 2,903
  • 1
  • 23
  • 23
  • this types of things should usually be contained in CPPFLAGS variable. try to added to your gcc command. cmake might have special variable for that, tryr CMAKE_CPP_FLAGS – Anycorn Aug 25 '10 at 02:43
  • If there's no such variable defined in your build configuration then you can replace the complier with a script that adds the necessary flags. – torak Aug 25 '10 at 03:36
  • If I understand correctly, how about `cpp`? – Foo Bah Aug 10 '11 at 22:13

4 Answers4

24

CMake automatically generates make targets for preprocessing files. For each foo.c in your project there's a foo.i make target that will run only the preprocessor (with all the relevant -D and -I flags etc.). Run make help to see all other potentially useful targets that CMake generates in your makefiles.

BTW, I can't see how this "single unit" will be easier to work with for you.

Tadeusz A. Kadłubowski
  • 8,047
  • 1
  • 30
  • 37
  • 2
    This answer is the one most people will be looking for even though it doesn't precisely answer the OPs question. – Alexander Oh Jan 14 '14 at 14:31
  • A preprocessed single unit is easier to work with for embedding into the compiled binary to be JIT compiled later, but a make target will only help if there is another target that depends on it that takes the resulting file and post-processes it to (e.g.) embed the file in a string in a source file compiled at build time. That string can be JIT compiled later. – mabraham Dec 22 '14 at 18:04
  • 8
    Also, not all generators that you can use with CMake will generate the .i targets - the Ninja generator does not. – mabraham Dec 22 '14 at 18:04
  • 1
    What @mabraham said. See http://public.kitware.com/Bug/view.php?id=13838 (it's been in the backlog since late 2013). – Kyle Strand Jun 18 '15 at 19:30
  • Is this still the case? I ran `make help` under CMake 3.5.1 and I see no .o or .i targets, only the target names specified in CMakeLists.txt – Gillespie Sep 01 '20 at 14:01
  • 1
    Worth noting that for a complex CMake project, the ".i" target for the file you are interested in is likely to be in one of the nested Makefiles - first is `CMakeFiles/Makefile2`, then for sub-projects it calls out to things like `build.make` buried quite deep in the paths for the libraries – Michael Firth Jan 12 '21 at 12:21
  • Worth noting that `make help` shows a Target name of the form `Class.i`, but the actual file is `Class.cpp.i` or similar. Beware when trying to `find` the output. – oarfish Jun 29 '22 at 10:14
5

This worked ok so far:

function(add_c_preprocessor_command)
    # Add custom command to run C preprocessor.
    #
    # Arguments
    #   OUTPUT          output file
    #   SOURCE          input file
    #   TARGET          CMake target to inherit compile definitions, include directories, and compile options
    #   EXTRA_C_FLAGS   extra compiler flags added after all flags inherited from the TARGET

    set(one_value_args TARGET SOURCE OUTPUT)
    set(multi_value_args EXTRA_C_FLAGS)
    cmake_parse_arguments(CPP "" "${one_value_args}" "${multi_value_args}" ${ARGN})

    string(TOUPPER ${CMAKE_BUILD_TYPE} build_type)
    string(REPLACE " " ";" c_flags "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${build_type}}")

    add_custom_command(
        OUTPUT ${CPP_OUTPUT}
        COMMAND ${CMAKE_C_COMPILER}
            "-D$<JOIN:$<TARGET_PROPERTY:${CPP_TARGET},COMPILE_DEFINITIONS>,;-D>"
            "-I$<JOIN:$<TARGET_PROPERTY:${CPP_TARGET},INCLUDE_DIRECTORIES>,;-I>"
            ${c_flags}
            $<TARGET_PROPERTY:${CPP_TARGET},COMPILE_OPTIONS>
            ${CPP_EXTRA_C_FLAGS}
            -E ${CPP_SOURCE} -o ${CPP_OUTPUT}
        COMMAND_EXPAND_LISTS VERBATIM
        IMPLICIT_DEPENDS C ${CPP_SOURCE}
        DEPENDS ${CPP_SOURCE})
endfunction()
klimkin
  • 573
  • 5
  • 10
  • I tried this: `add_c_preprocessor_command(OUTPUT "./build/test-processed.c" SOURCE "./test.c" TARGET ${PROJECT_NAME} EXTRA_C_FLAGS "")`, but it doesn't work. – Amin Ya May 20 '21 at 08:53
  • @Amin, the command runs in the `CMAKE_CURRENT_BINARY_DIR`, so make sure to set the `OUTPUT` correctly. If `OUTPUT` is `build/test-processed.` and `build` directory doesn't exist, the command will naturally fail. – klimkin Jun 24 '21 at 04:24
  • This solution is very nice, but I think it may be missing an important detail: The custom command is only executed if the generated file `${CPP_OUTPUT}` is consumed by some other target (that is actually built). So either add the generated pre-processed file to some other target, or consider another version of `add_custom_command()` that is automatically added to a target: Replace `OUTPUT ${CPP_OUTPUT}` with `TARGET ${CPP_TARGET} PRE_BUILD` and the preprocessed file is automatically generated whenever `TARGET` is built. – emmenlau Sep 19 '22 at 07:49
2

This will be a crude hack, but you can abuse add_definition for that, as "This command can be used to add any flags, but it was originally intended to add preprocessor definitions."

Alternatively you could just set the COMPILE_FLAGS property of the target to "-E", which will achieve the same effect but be local to that target.

0

I would suggest a different approach.

  1. Build your kernel into a binary (example for ATI Stream: http://developer.amd.com/support/KnowledgeBase/Lists/KnowledgeBase/DispForm.aspx?ID=115 )

  2. Compile this binary data into your program (as a char[] blob) and load it when your program starts.

With cmake and custom targets this should be quite simple.

tauran
  • 7,986
  • 6
  • 41
  • 48
  • There is a small problem with that approach: since opencl binaries are implementation (and device specific for AMD cpu and gpu) you are locking yourself to the configuration you compiled this for (possibly even to the environment you built it with, since there is no guarantee the format won't change over time). So you are loosing the ability to run your compiled program on different harwareconfigurations (unless you include a binary for each possible device). – Grizzly Oct 01 '10 at 19:49
  • As OpenCL is for highly optimized calculations and the implementations and drivers (AMD/nVidia) continually improve as do the gpu devices, you would have to think about an update mechanism any way. Additionally OpenCL (AMD) provides the possibility to build the kernel for all known devices. – tauran Oct 02 '10 at 08:31