Embedding icon into GUI executables for MacOS / Windows via cmake
CMake while being arcane and quirky in its own way, is currently the standard cross-platform compilation platform for C++/C build systems. I think the power of CMake comes from all the community contributions and the knowledge base built up over the years.
I recently worked on a project to migrate it from an archaic Perl build system into the land of CMake. Following modern CMake practices really made dependency management a breeze (i.e. target based compilation flag specification, target based linking, transient header propagation via INTERFACE/PUBLIC/PRIVATE, generator expressions to optionally specify different compiler warning / optimization levels)
One of the things that took a little while for me to figure out is how to embed a GUI based application an application icon that is in a cross-platform friendly method. I googled around and really didn’t come across a simple enough solution, so I decided to roll my own.
I wrote a .cmake
module that defines a CMake function AddIconToBinary()
that allows you to embed the application icon in a cross-platform way. The usage example is as follows:
# USAGE:
set(SOURCE_FILES source/main.cpp
source/helpers/utils.cpp)
set(HEADERS source/helpers/utils.h)
# Add icons
include(${CMAKE_SOURCE_DIR}/cmake/AddIconToBinary.cmake)
AddIconToBinary(SOURCE_FILES ICONS ${CMAKE_SOURCE_DIR}/infra/kin.ico ${CMAKE_SOURCE_DIR}/infra/kin.icns)
if (MSVC)
add_executable(${APP_TARGET} WIN32 ${SOURCE_FILES} ${HEADERS})
elseif(APPLE)
add_executable(${APP_TARGET} MACOSX_BUNDLE ${SOURCE_FILES} ${HEADERS})
else()
message(FATAL_ERROR "Unsupported platform, currently we are only supporting MacOS / Windows")
endif()
icns
file is the MacOS icon file archive format that contains a wide variety of different sizes. ico
is the Windows icon format. You can find online tools that take your SVG/PNG icons into these formats.
AddIconToBinary.cmake
include(CMakeParseArguments)
function(AddIconToBinary appsources)
set(options)
set(oneValueArgs OUTFILE_BASENAME)
set(multiValueArgs ICONS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (NOT ARG_ICONS)
message(FATAL_ERROR "No ICONS argument given to AddIconToBinary")
endif()
if (ARG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unexpected arguments to ecm_add_app_icon: ${ARG_UNPARSED_ARGUMENTS}")
endif()
foreach (icon ${ARG_ICONS})
get_filename_component(icon_full ${icon} ABSOLUTE)
get_filename_component(icon_type ${icon_full} EXT)
get_filename_component(icon_name ${icon_full} NAME_WE)
if (APPLE)
if (${icon_type} STREQUAL ".icns")
set(icon_full_output ${CMAKE_CURRENT_BINARY_DIR}/${icon_name}.icns)
configure_file(${icon_full} ${icon_full_output} COPYONLY)
set(MACOSX_BUNDLE_ICON_FILE ${icon_name}.icns PARENT_SCOPE)
set(${appsources} "${${appsources}};${icon_full_output}" PARENT_SCOPE)
set_source_files_properties(${icon_full_output} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
return()
endif()
endif()
if (MSVC)
if (${icon_type} STREQUAL ".ico")
set(icon_full_output ${CMAKE_CURRENT_BINARY_DIR}/${icon_name}.ico)
configure_file(${icon_full} ${icon_full_output} COPYONLY)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${icon_name}.rc.in" "IDI_ICON1 ICON DISCARDABLE \"${icon_name}.ico\"\n")
add_custom_command(
OUTPUT "${icon_name}.rc"
COMMAND ${CMAKE_COMMAND}
ARGS -E copy "${icon_name}.rc.in" "${icon_name}.rc"
DEPENDS "${icon_name}.ico"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
set(${appsources} "${${appsources}};${icon_name}.rc" PARENT_SCOPE)
return()
endif()
endif()
endforeach()
return()
endfunction()