3
\$\begingroup\$

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:

  1. Can this be made more short and readable?
  2. Are my checks portable enough?
  3. 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()
\$\endgroup\$
3
\$\begingroup\$

Here are some ideas about how to reduce the complexity and thereby increase the readability of your module's code:

  1. 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)
  2. 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 and SELF_PACKER_FOR_EXECUTABLE_FLAGS a fixed parameters directly into the external script
  3. 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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.