HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

CMake module to make executable as small as possible

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
cmakeexecutablemodulemakepossiblesmall

Problem

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 
#include 
#include 
#include 

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_M

Solution

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() and restore_preferred_libs() into a single dedicated macro like

macro(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 from Test/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)



-
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=$
            -D TARGET_FILE="$"
            -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 and SELF_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 $:${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

Code Snippets

macro(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)
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()
...
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
)
...
...
function(add_static_definitions TARGET_NAME)
    target_compile_definitions(${TARGET_NAME} PUBLIC $<$<CONFIG:MinSizeRel>:${ARGN}>)
endfunction()
...
...
target_compile_definitions(${PROJECT_NAME} PUBLIC "GLEW_STATIC")
...

Context

StackExchange Code Review Q#103972, answer score: 3

Revisions (0)

No revisions yet.