State of building Cinder apps with CMake on Windows

Hey Embers,

Just checking in to see if anyone else has managed to build Cinder with CMake on windows. I found this thread:

in which the author is experiencing the exact issue I’m currently seeing, except whether I use win32 or x64, I keep getting the same error. I couldn’t find the sample CMake file that this page:

https://libcinder.org/docs/guides/cmake/cmake.html

mentions, so instead I cobbled mine together from the objLoader Cinder sample:

# For cmake_path
cmake_minimum_required( VERSION 3.20 FATAL_ERROR )
set( CMAKE_VERBOSE_MAKEFILE ON )

project( opengl-ObjLoader )

cmake_path(SET CINDER_PATH "D:/SDKS/cinder_0.9.2_vc2015")
cmake_path(SET APP_PATH "${CMAKE_CURRENT_SOURCE_DIR}")

include( "${CINDER_PATH}/proj/cmake/modules/cinderMakeApp.cmake" )

ci_make_app(
	APP_NAME    "ObjLoader"
	CINDER_PATH ${CINDER_PATH}
	SOURCES     ${APP_PATH}/src/main.cpp
	INCLUDES    ${APP_PATH}/include
)

But I’m pretty clueless as to why it isn’t working, or rather, why I keep seeing this error:

CMake Error at D:/SDKS/cinder_0.9.2_vc2015/proj/cmake/modules/cinderMakeApp.cmake:78 (find_package):
  Could not find a package configuration file provided by "cinder" with any
  of the following names:

    cinderConfig.cmake
    cinder-config.cmake

  Add the installation prefix of "cinder" to CMAKE_PREFIX_PATH or set
  "cinder_DIR" to a directory containing one of the above files.  If "cinder"
  provides a separate development package or SDK, be sure it has been
  installed.
Call Stack (most recent call first):
  CMakeLists.txt:11 (ci_make_app)

Any thoughts are most welcome!

Cheers,
Gazoo

I was dragged kicking and screaming to cmake and while I despise the language, I don’t think I’d go back now. Once you get it all running properly it really is a much better way to handle things.

That said, the issue you’re seeing i vaguely remember running into myself, and it was caused by not committing to cmake all the way. My understanding is that some of the cinder cmake config files are generated by building cinder itself with cmake in the first place. If you build cinder using the visual studio solution and then try and make a project using cmake, it won’t work.

Try something like:

$ cd D:/SDKS/cinder_0.9.2_vc2015/
$ mkdir build
$ cd build
$ cmake ..

If you open the resultant .sln and build cinder in VS as normal, with any luck your cmake borrowed from BasicApp should work now too

1 Like

Ah gotcha - Well then I’ll try building Cinder with Cmake tomorrow. Much obliged @lithium .

For the record I also despised CMake initially. I continue to dispise CMake now, but I also agree that it really is probably for the best in the long run. The language is just so damn… well… hard to interpret as a beginner.

You were right @lithium . Building Cinder itself using CMake ended up generating the missing and necessary files to allow me to start building my project in CMake. I’m taking things one step at a time, and generally making a fair bit of progress.

One thing I’m not entirely sure of, is if I’m using ci_make_app() correctly. In the Visual Studio project created by the Cinder TinderBox, the Resources.rc file is added under a separate Resource Files filter in the solution generated for Visual studio. With the CMake code below, all of it unceremoniously just appears to be added under the Source Files Filter, despite being passed in as a resource?

# cmake_path command requires CMake 3.2 or newer
cmake_minimum_required( VERSION 3.20 FATAL_ERROR )
set( CMAKE_VERBOSE_MAKEFILE ON )

project( PlanMixPlay )

# If cinder's CMake script complains about missing 'cinderConfig.cmake', it's
# likely due to building CinderLib *without* CMake:
# https://discourse.libcinder.org/t/state-of-building-cinder-apps-with-cmake-on-windows/1858
cmake_path(SET CINDER_PATH "D:/SDKS/cinder_0.9.2dev_cmake")
cmake_path(SET APP_PATH "${CMAKE_CURRENT_SOURCE_DIR}")

include( "${CINDER_PATH}/proj/cmake/modules/cinderMakeApp.cmake" )

# Why not working?
# set(RESOURCE_FILES
	# "${APP_PATH}/resources/wit.json"
# )

ci_make_app(
	APP_NAME    "PlanMixPlay"
	CINDER_PATH ${CINDER_PATH}
	SOURCES     ${APP_PATH}/src/main.cpp ${APP_PATH}/include/Resources.h
	INCLUDES    ${APP_PATH}/include
	RESOURCES   ${APP_PATH}/resources/wit.json ${APP_PATH}/resources/Resources.rc
)

# Target created in 'ci_make_app'...?
# C++17 standard required, otherwise ci:fs (filesystem) throws errors.
set_property(TARGET PlanMixPlay PROPERTY CXX_STANDARD 17)

# LibCinder (by default I believe) compiles with statically linked MultiThreaded execution.
# Your application *must* link with the same static MultiThreaded execution.
set_property(TARGET PlanMixPlay PROPERTY
  MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

# Set StartUp project to be the actual project (as opposed to ALL_BUILD). No clue as to why this
# property has to apply to DIRECTORY, rather than TARGET. Perhaps because DIRECTORY contains all
# TARGET's ...?
#set_property(TARGET PlanMixPlay PROPERTY VS_STARTUP_PROJECT PlanMixPlay)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT PlanMixPlay)
  

Any thoughts…?

I believe that has more to do with the way that cmake generates the project files moreso than anything cinder specific. I think the particular incantation you need to cast is called source_group

set ( SOURCES ${APP_PATH}/src/main.cpp ${APP_PATH}/include/Resources.h )
set ( RESOURCES ${APP_PATH}/resources/wit.json ${APP_PATH}/resources/Resources.rc )

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
source_group(TREE ${APP_PATH}/src PREFIX "Source Files/" FILES ${SOURCES})
source_group(TREE ${APP_PATH}/include PREFIX "Header Files/" FILES ${SOURCES})
source_group(TREE ${APP_PATH}/resources PREFIX "Resources/" FILES ${RESOURCES})

ci_make_app ( ... )

I didn’t test this but it’s something like that, you may need to tweak some of the paths for your specific project but I’ve definitely got it to work in the past. This sort of thing was one of my initial problems with cmake, since I was used to anal-retentively maintaining my project file hygiene, but over time I start to think of the project as being much more ephemeral, since it can just be completely rebuilt with a single call to cmake so i’ve started to relax on that a bit.

Having things at least in the right filters and maintaining their folder structure as on the filesystem was a minimum though :wink:

1 Like

Much obliged - I’ve started using source_group for my source, but I was unaware I could start setting these behaviors before calling ci_make_app().

For anyone else following along at home, calling source_group() after ci_make_app() has another nasty side-effect, which is that Visual Studio will detect changes made to CMakeLists.txt after you run CMake again, but NOT actually change anything in the solution.

source_group() must be called before ci_make_app() AND you MUST CREATE A NEW build folder. If you simply move source_group() before ci_make_app() and run CMake generation (via the CMakegui or whatever) it won’t reflect the changes properly.

That’s one of the things I really dislike about CMake. It can be very obscure as to what beginner traps there are for caching values and how you can sometimes get away with just generating the solution again, and when you must start from a totally clean directory.

Just deleting CmakeCache.txt and rerunning cmake is good enough in most cases.

That’s an action I would prefer to avoid. I know it sounds like a small thing, but I really hate the extra step of manually deleting a file. Tailoring the CMake file to properly generate (and lead to the proper changes) is very attractive to me.

Totally, i just meant if you were deleting your whole build folder to start clean.

1 Like

Also, having moved source_group() before ci_make_app() doesn’t apparently guarantee proper updating. Seems like sometimes it works, sometimes it doesn’t. Don’t care anymore. Got enough other stuff to fix before the CMake step is complete, so I’ll worry about that later.

Technically, this would be a question for a CMake forum, but since we’re on a roll here I thought I’d inquire regardless to hear your thoughts @lithium . In my attempt to polish the CMake, I’ve been able to put together this FindLibrary script as a CMake module to include external libraries that don’t have any CMake support:

# This is a FindModule, intended to ease the finding of non-CMake compliant libaries
# https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html#find-modules

# It attempts to follow the naming convention of CamelCase for the library inclusion.
# E.g. Xxx_LIBRARIES and Xxx_INCLUDE_DIRS

# The code generally follows this incredibly helpful tutorial:
# https://dominikberner.ch/cmake-find-library/
#
# To allow an exterior path to be passed in we rely on along these lines:
# https://stackoverflow.com/questions/35546440/cmake-set-start-path-for-find-package

# To work on
# Copy the DLL at the end
# https://stackoverflow.com/questions/10671916/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake

# Externally working implementation
#find_path(BASS_INCLUDE_PATH NAMES bass.h PATHS "C:/SDKS/bass/2.4.16/c" REQUIRED)
#find_library(BASS_LIBRARY NAMES bass PATHS "C:/SDKS/bass/2.4.16/c" REQUIRED)

find_path(
	Bass_INCLUDE_DIRS
	NAMES bass.h
	PATHS ${Bass_ROOT_DIR}
)

find_library(
	Bass_LIBRARIES NAMES
	NAMES bass
	PATHS ${Bass_ROOT_DIR}
)

include(FindPackageHandleStandardArgs)

find_package_handle_standard_args(Bass DEFAULT_MSG
                                  Bass_LIBRARIES
                                  Bass_INCLUDE_DIRS)

# Hides these parameters from the CMakeGUI (or other guis?)
mark_as_advanced(Bass_LIBRARIES Bass_INCLUDE_DIRS)

if(BASS_FOUND AND NOT TARGET Bass::Bass)
  add_library(Bass::Bass SHARED IMPORTED)
  set_target_properties(
    Bass::Bass
    PROPERTIES
      INTERFACE_INCLUDE_DIRECTORIES "${Bass_INCLUDE_DIRS}"
      IMPORTED_LOCATION ${Bass_LIBRARIES})
endif()

One thing you might note is that it relies on Bass_ROOT_DIR to be defined prior to use the module. As best as I can tell, that appears to be the standard approach for passing an external path into the module, although I’d prefer a parameter.

My question is about automatically copying the library’s DLL file. This can be accomplished successfully with:

add_custom_command(TARGET SandBoxProject POST_BUILD		# Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different		# which executes "cmake - E copy_if_different..."
        "C:/SDKS/bass/2.4.16/bass.dll"			# <--this is in-file
        $<TARGET_FILE_DIR:SandBoxProject>)				# <--this is out-file path

… However, it must be added after ci_make_app and the find_package must be called before. So far I’ve not really managed to figure out a way to easily and cleanly enable the auto-dll-copy if the package/library is added, but not copy the dll if the package/library is not added.

Right now, my best simple approach appears to be to have these two separate code pieces exist on either side of ci_make_app which feels stinky.

Have you dealt with any of this?

We were already tapping the limits of my cmake knowledge, I’ve learnt just enough to squeak by, we were just lucky that I’d happened to run into the same issues you were seeing :wink:

That said, this definitely seems like it should be in the purview of cmake, are you able to query the runtime dependencies for the project and initiate a copy for each file returned, assuming it returns nothing in the case where you don’t want the dll copied?

FILE(GET_RUNTIME_DEPENDENCIES)

TARGET_RUNTIME_DLLS looks promising too, depending on how new your cmake is.

I appreciate the continued discussion and suggestions.

From what I found FILE(GET_RUNTIME_DEPENDENCIES) is apparently recommended to be used during the install phase of a project, as opposed to the configuration phase. That being said, I get the impression that is the most reliably (and slightly dirty) approach to get things chugging along on Windows. Very unsure of that statement though.

TARGET_RUNTIME_DLLS looks really promising and I gave it a quick stab via the default example provided in the CMake docs. The resulted in, as far as I could tell, an empty generated statement.

I’ve been wrangling CMake for 3 days now and I still have so much left to convert I feel, so I will opt for a functional but not ideal approach. The find_package() function I noted earlier will set a series of values, one of them being Xxx_FOUND, where Xxx is the CamelCase library name, in my case BASS. Hence I will simply branch on that post ci_make_app with code I know works:

add_custom_command(TARGET SandBoxProject POST_BUILD		# Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different		# which executes "cmake - E copy_if_different..."
    "C:/SDKS/bass/2.4.16/bass.dll"						# <--this is in-file
    $<TARGET_FILE_DIR:SandBoxProject>)					# <--this is out-file path

Not pretty, but does solve the problem of the two separate pieces of code elegantly working together…

Now I just gotta work on building a find_package that can tolerate different dll’s depending on whether the configuration mode is Debug or Release… Sigh… CMake is not my favorite tool.

Having finally overcome most of the CMake issues, I’ve run into a new issue I’m confused about. When I link with cinder.lib built via CMake, I get 14 different linker errors all seemingly related to CaptureImplDirectShow:

Severity Code Description Project File Line Suppression State
Error LNK2019 unresolved external symbol public: bool __thiscall videoInput::isDeviceSetup(int) (?isDeviceSetup@videoInput@@QAE_NH@Z) referenced in function public: void __thiscall cinder::CaptureImplDirectShow::start(void) (?start@CaptureImplDirectShow@cinder@@QAEXXZ) PlanMixPlay C:\Code\pmpCMakeTest\build3\cinder.lib(CaptureImplDirectShow.obj) 1

I replaced my cinder.lib built via CMake and visual studio with one I originally built with the included visual studio .proj file (without CMake) and then the linker errors vanish. I’ve investigated the code and as far as I can tell the important bit is that CINDER_MSW is defined which… as far as I can see by the visual studio .proj that CMake creates is defined…

So… I’m super puzzled… Anyone got any ideas…?

@Gazoo I’m not sure what combo of events has led to your linker issues, but I suggest starting clean (git clean -dfx will do the trick from within a git versioned folder) and doing it all from cmake.

I’ve actually been adjusting how I configure cmake projects to simplify the number of commands and folder traversing, below is what I’ve lately been using:

cd %cinder dir%
cmake -S . -B ./build -G "Visual Studio 16 2019"
cmake --build build --config Release
cmake --build build --config Debug

cd %some sample folder%
cmake -S proj/cmake/ -B ./build -G "Visual Studio 16 2019"
start build/SampleProject.sln

I think you can also leave the `-G “Visual Studio 16 2019” thing off if all you have is one copy of VS installed on your machine, it will just use that. Ages ago it would default to nmake but things are bit more streamlined to use MSBuild tools now.

Cheers,
Rich