I wrote this .cmake script when I needed to make smallest possible executables. It makes CMake prefer static libraries, and adds custom command to strip and UPX the end result. I used it only with MinGW on Windows with MSYS2.
My questions:
- Can this be made more short and readable?
- Are my checks portable enough?
- Any way to handle MSVC?
example main.c
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(!glfwInit())
return EXIT_FAILURE;
GLFWwindow *window = glfwCreateWindow(1024, 768, "Hello World", NULL, NULL);
if(!window)
{
glfwTerminate();
return EXIT_FAILURE;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
glewExperimental = GL_TRUE;
GLenum code = glewInit();
if(code != GLEW_OK)
{
fprintf(stderr, "[GLEW Error](%d): %s\n", code, glewGetErrorString(code));
return EXIT_FAILURE;
}
}
example CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(staticExe LANGUAGES C)
include(${PROJECT_SOURCE_DIR}/cmake/ReallySmall.cmake)
prefer_static_libs()
find_package(GLEW REQUIRED)
find_package(GLFW3 NAMES glfw glfw3 REQUIRED)
restore_preferred_libs()
find_package(OpenGL REQUIRED)
set(INCLUDE_DIRS ${OPENGL_INCLUDE_DIR} ${GLFW3_INCLUDE_DIR})
set(LIBS GLEW::GLEW ${GLFW3_LIBRARY} ${OPENGL_gl_LIBRARY})
add_executable(${PROJECT_NAME} WIN32 main.c)
make_small_executable(${PROJECT_NAME})
add_static_definitions(${PROJECT_NAME} "GLEW_STATIC")
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${LIBS})
ReallySmall.cmake
##
# How to use:
# include(ReallySmall.cmake)
#
# prefer_static_libs()
# find_package(STATIC_LIBS)
# restore_preferred_libs()
# find_package(SHARED_LIBS)
#
# add_executable(MY_EXE)
# make_small_executable(MY_EXE)
# target_link_libraries(...)
##
function(add_static_definitions TARGET_NAME)
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(IS_MARKED -1)
list(FIND _SMALL_EXECUTABLES ${TARGET_NAME} IS_MARKED)
if(NOT (IS_MARKED EQUAL -1))
list(REMOVE_AT ARGV 0)
foreach(DEFINITION IN LISTS ARGV)
target_compile_definitions(${TARGET_NAME} PUBLIC ${DEFINITION})
endforeach()
endif()
endif()
endfunction()
function(make_small_executable)
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
foreach(TARGET_NAME IN LISTS ARGV)
list(APPEND _SMALL_EXECUTABLES ${TARGET_NAME})
set(_SMALL_EXECUTABLES ${_SMALL_EXECUTABLES} PARENT_SCOPE)
# We will run UPX only if we have strip
if(CMAKE_STRIP)
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_STRIP} ${STRIP_FLAGS} $<TARGET_FILE:${TARGET_NAME}>)
if(SELF_PACKER_FOR_EXECUTABLE)
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${SELF_PACKER_FOR_EXECUTABLE} ${SELF_PACKER_FOR_EXECUTABLE_FLAGS} $<TARGET_FILE:${TARGET_NAME}>)
endif()
endif()
# Now add some linker flags
get_target_property(TARGET_LANG ${TARGET_NAME} LINKER_LANGUAGE)
if(TARGET_LANG STREQUAL "C")
if(CMAKE_COMPILER_IS_GNUCC)
target_link_libraries(${TARGET_NAME} "-static-libgcc")
target_link_libraries(${TARGET_NAME} "-static-libasan" "-static-libtsan" "-static-liblsan" "-static-libubsan" "-static-libmpx" "-static-libmpxwrappers")
endif()
elseif(TARGET_LANG STREQUAL "CXX")
if(CMAKE_COMPILER_IS_GNUCXX)
target_link_libraries(${TARGET_NAME} "-static-libgcc" "-static-libstdc++")
target_link_libraries(${TARGET_NAME} "-static-libasan" "-static-libtsan" "-static-liblsan" "-static-libubsan" "-static-libmpx" "-static-libmpxwrappers")
endif()
endif()
endforeach()
endif()
endfunction(make_small_executable)
function(prefer_static_libs)
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(_OLD_FIND_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES} PARENT_SCOPE)
if(MINGW)
list(REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
list(REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ".dll.a")
elseif(UNIX AND NOT APPLE)
list(REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ".so")
elseif(APPLE)
list(REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ".dylib")
endif()
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES} PARENT_SCOPE)
endif()
endfunction(prefer_static_libs)
function(restore_preferred_libs)
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_OLD_FIND_SUFFIXES} PARENT_SCOPE)
endif()
endfunction(restore_preferred_libs)
if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
# Find needed utilities
set(STRIP_FLAGS "-s" CACHE STRING "Remove all symbols")
set(SELF_PACKER_FOR_EXECUTABLE_FLAGS "-9q" CACHE STRING "Quiet max compression")
mark_as_advanced(STRIP_FLAGS)
mark_as_advanced(SELF_PACKER_FOR_EXECUTABLE_FLAGS)
find_package(SelfPackers) # Find UPX, it should be somewhere in PATH
endif()
1 Answer 1
Here are some ideas about how to reduce the complexity and thereby increase the readability of your module's code:
You could merge
prefer_static_libs()
,find_package()
andrestore_preferred_libs()
into a single dedicated macro likemacro(rs_find_package) set(_OLD_FIND_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".a" ".so" ".sl" ".dylib" ".dll.a") find_package(${ARGN}) set(CMAKE_FIND_LIBRARY_SUFFIXES ${_OLD_FIND_SUFFIXES}) unset(_OLD_FIND_SUFFIXES) endmacro(rs_find_package)
- The simplified version
CMAKE_FIND_LIBRARY_SUFFIXES
preferring static libraries on all platforms is taken fromTest/OutDir/OutDir.cmake
- In your use case I would recommend to prefer static libraries for all configurations to simplify the usage in multi-configuration environments like Visual Studio (otherwise we have to run
find_package()
twice)
- The simplified version
You could move the post build steps into its own CMake script like
cmake/ReallySmallPostBuild.cmake
if(EXECUTE_POST_BUILD) if (CMAKE_STRIP) execute_process(COMMAND ${CMAKE_STRIP} -s ${TARGET_FILE}) endif() if(SELF_PACKER_FOR_EXECUTABLE) execute_process(COMMAND ${SELF_PACKER_FOR_EXECUTABLE} -9q ${TARGET_FILE}) endif() endif()
cmake/ReallySmall.cmake
... add_custom_command( TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -D EXECUTE_POST_BUILD=$<CONFIG:MinSizeRel> -D TARGET_FILE="$<TARGET_FILE:${TARGET_NAME}>" -D CMAKE_STRIP="${CMAKE_STRIP}" -D SELF_PACKER_FOR_EXECUTABLE="${SELF_PACKER_FOR_EXECUTABLE}" -P ${CMAKE_SOURCE_DIR}/cmake/ReallySmallPostBuild.cmake ) ...
- This works also for multi-configuration environments, since the check for "Should I execute post build steps?" is inside the external script
- I've moved your
STRIP_FLAGS
andSELF_PACKER_FOR_EXECUTABLE_FLAGS
a fixed parameters directly into the external script
You could simplify your
add_static_definitions()
with generator expressions:cmake/ReallySmall.cmake
... function(add_static_definitions TARGET_NAME) target_compile_definitions(${TARGET_NAME} PUBLIC $<$<CONFIG:MinSizeRel>:${ARGN}>) endfunction() ...
Or if you take my recommendation from the top to always prefer static libs, I think you should directly put those definition declarations to
CMakeLists.txt
... target_compile_definitions(${PROJECT_NAME} PUBLIC "GLEW_STATIC") ...
- I did not see the necessity for the
_SMALL_EXECUTABLES
crosschecks, so they are removed here
- I did not see the necessity for the