Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmake build enhancements, mostly for windows, with some documentation #315

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions CMake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Building zlib with CMake #

## Prerequisites ##

CMake version 2.4.4 or later is required. [Download](https://cmake.org/download/) and install the latest CMake (recommended).

## Introduction ##

If you are not familiar with CMake, this project is a great way to learn how it works. Let's define a couple directories we'll refer to throughout the document:

- `BUILDDIR` is the directory where the build takes place
- `BUILDTYPE` is one of the CMake standard build types: `Debug`, `RelWithDebInfo`, or `Release`.
- `INSTDIR` is the directory where the build result is installed
- `SRCDIR` is the directory that contains the zlib project files

CMake takes cmake script files (typically called CMakeLists.txt) from `SRCDIR` and generates an entire platform-specific build environment in `BUILDDIR`. CMake supports output of many different build environments, such as make, ninja, and NMake to name a few. CMake also supports generation of IDE projects for Eclipse, Visual Studio, and others.

## Invocation ##

Generating an out-of-tree build directory with CMake is trivial:

1. Create the `BUILDDIR`.
2. cd into `BUILDDIR`.
3. cmake `SRCDIR` `-DCMAKE_INSTALL_PREFIX=INSTDIR` `<other-options>`

## Options ##

Options are defined when you generate the build environment. Options are defined by passing them with a `-D` prefix on the cmake command line. Here are the options that matter most when building zlib:

| Option | Origin | Default | Meaning |
| :----- | -----: | ------: | :------ |
| `ASM686` | zlib | `OFF` | Build with an i686 assembly implementation |
| `AMD64` | zlib | `OFF` | Build with an x86_64 assembly implementation |
| `BUILD_SHARED_LIBS` | cmake | `ON` | Build a shared library |
| `BUILD_STATIC_AND_SHARED` | zlib | `ON` | Build both a shared and a static library* |
| `CMAKE_BUILD_TYPE` | cmake | `Debug` | Defines the build type at generation time** |
| `CMAKE_INSTALL_PREFIX` | cmake | depends | Build prefix for installation, platform specific|
| `SKIP_INSTALL_ALL` | zlib | `OFF` | Skip installation of everything |
| `SKIP_INSTALL_FILES` | zlib | `OFF` | Skip installation of manual pages and pkgconfig files |
| `SKIP_INSTALL_HEADERS` | zlib | `OFF` | Skip installation of headers |
| `SKIP_INSTALL_LIRARIES` | zlib | `OFF` | Skip installation of binaries, libraries, and symbol files |

\* The CMake build for zlib produces static and shared libraries in the same build by default. This behavior is not always optimal for building on every platform, therefore a CMake option called `BUILD_STATIC_AND_SHARED` was introduced to allow the caller to disable this combined build and honor the more commonly used `BUILD_SHARED_LIBS` CMake option instead.

\** `CMAKE_BUILD_TYPE` is only used on Unix. For Windows, the build type is defined at build time, not at generation time.

A default build without options will create a shared library, debug build, and attempt to install it into a system-wide visible location (`/usr/local` on Unix, and `C:\Program Files` on Windows).

## Unix ##

Unix does not suffer from as many runtime library choices as Windows, and is therefore quite simple. Using a minimal set of options we end up with static and shared builds as output:

$ mkdir <BUILDDIR>
$ cd <BUILDDIR>
$ cmake <SRCDIR> -DCMAKE_INSTALL_PREFIX=<INSTDIR> -DCMAKE_BUILD_TYPE=<BUILDTYPE>
$ cmake --build . --target install

### Generating Eclipse Projects ###

CMake has the ability to generate Eclipse projects. When invoking cmake in `BUILDDIR`, specify the generator:

$ cmake `<SRCDIR>` -G"Eclipse CDT4 - Unix Makefiles"

This will produce an Eclipse solution targeting `make` builds. A full list of generators can be obtained by running this command and scrolling to the end of the output:

cmake --help

## Windows ##

Building on Windows, with the large number of library configuration options, is a little more complicated. For example one can build a statically-linked zlib that uses a dynamic runtime library, or one can build a statically-linked zlib that uses a static MSVC runtime library. The CMake install target will put only the files you need into `INSTDIR`. If the build contains debugging information, that will be included. Some examples for building on Windows are found below.

### DLL (shared) with MSVCRT DLL ###

C:\BUILDDIR> cmake C:\SRCDIR -DCMAKE_INSTALL_PREFIX=<INSTDIR> -DBUILD_STATIC_AND_SHARED=OFF
C:\BUILDDIR> cmake --build . --target install --config <BUILDTYPE>

### LIB (static) with MSVCRT LIB (statically linked runtime) ###

C:\BUILDDIR> cmake C:\SRCDIR -DCMAKE_INSTALL_PREFIX=<INSTDIR> -DBUILD_STATIC_AND_SHARED=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS_<BUILDTYPE>="/MT /Z7"
C:\BUILDDIR> cmake --build . --target install --config <BUILDTYPE>

Note the use of `CMAKE_C_FLAGS_<BUILDTYPE>` here, which is equivalent to using `CFLAGS` in a make build for a given `BUILDTYPE`. Options typically used here are:

- `/MT` to force a static release runtime link
- `/MTd` to force a static debug runtime link
- `/Z7` to put symbols in objects and libraries instead of a PDB file

### Generating Visual Studio Projects ###

CMake has the ability to generate MSVC projects. When invoking cmake in `BUILDDIR`, specify the generator:

C:\TEMP\zlib-build> cmake C:\SRC\zlib -G"Visual Studio 15 2017 Win64"

This will produce a Visual Studio 2017 solution. A full list of generators can be obtained by running this command and scrolling to the end of the output:

cmake --help

### Additional Resources ###

A full suite of example builds for Windows can be found in the `win32\cmake-matrix.bat` file.
174 changes: 124 additions & 50 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ set(VERSION "1.2.11")

option(ASM686 "Enable building i686 assembly implementation")
option(AMD64 "Enable building amd64 assembly implementation")
option(BUILD_SHARED_LIBS "Build shared (dynamic) libraries instead of static" ON)
option(BUILD_STATIC_AND_SHARED "Build both static and shared libraries in the same build" ON)
option(SKIP_INSTALL_ALL "Skip installation of everything")
option(SKIP_INSTALL_FILES "Skip installation of manual pages and pkgconfig files")
option(SKIP_INSTALL_HEADERS "Skip installation of headers")
option(SKIP_INSTALL_LIBRARIES "Skip installation of binaries, libraries, and symbol files")

if(BUILD_STATIC_AND_SHARED)
set(BUILD_SHARED_LIBS ON)
endif()

set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables")
set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries")
Expand Down Expand Up @@ -80,9 +90,9 @@ endif()

set(ZLIB_PC ${CMAKE_CURRENT_BINARY_DIR}/zlib.pc)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zlib.pc.cmakein
${ZLIB_PC} @ONLY)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein
${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY)
${ZLIB_PC} @ONLY)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein
${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY)
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR})


Expand Down Expand Up @@ -123,7 +133,7 @@ set(ZLIB_SRCS
zutil.c
)

if(NOT MINGW)
if(BUILD_SHARED_LIBS AND NOT MINGW)
set(ZLIB_DLL_SRCS
win32/zlib1.rc # If present will override custom build rule below.
)
Expand All @@ -136,38 +146,38 @@ if(CMAKE_COMPILER_IS_GNUCC)
set(ZLIB_ASMS contrib/amd64/amd64-match.S)
endif ()

if(ZLIB_ASMS)
add_definitions(-DASMV)
set_source_files_properties(${ZLIB_ASMS} PROPERTIES LANGUAGE C COMPILE_FLAGS -DNO_UNDERLINE)
endif()
if(ZLIB_ASMS)
add_definitions(-DASMV)
set_source_files_properties(${ZLIB_ASMS} PROPERTIES LANGUAGE C COMPILE_FLAGS -DNO_UNDERLINE)
endif()
endif()

if(MSVC)
if(ASM686)
ENABLE_LANGUAGE(ASM_MASM)
ENABLE_LANGUAGE(ASM_MASM)
set(ZLIB_ASMS
contrib/masmx86/inffas32.asm
contrib/masmx86/match686.asm
)
contrib/masmx86/inffas32.asm
contrib/masmx86/match686.asm
)
elseif (AMD64)
ENABLE_LANGUAGE(ASM_MASM)
ENABLE_LANGUAGE(ASM_MASM)
set(ZLIB_ASMS
contrib/masmx64/gvmat64.asm
contrib/masmx64/inffasx64.asm
)
contrib/masmx64/gvmat64.asm
contrib/masmx64/inffasx64.asm
)
endif()

if(ZLIB_ASMS)
add_definitions(-DASMV -DASMINF)
endif()
if(ZLIB_ASMS)
add_definitions(-DASMV -DASMINF)
endif()
endif()

# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents)
string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([-0-9A-Za-z.]+)\".*"
"\\1" ZLIB_FULL_VERSION ${_zlib_h_contents})

if(MINGW)
if(BUILD_SHARED_LIBS AND MINGW)
# This gets us DLL resource information when compiling on MinGW.
if(NOT CMAKE_RC_COMPILER)
set(CMAKE_RC_COMPILER windres.exe)
Expand All @@ -181,48 +191,112 @@ if(MINGW)
-o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj
-i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc)
set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj)
endif(MINGW)

add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)
set_target_properties(zlib PROPERTIES SOVERSION 1)
endif()

if(NOT CYGWIN)
# This property causes shared libraries on Linux to have the full version
# encoded into their final filename. We disable this on Cygwin because
# it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll
# seems to be the default.
#
# This has no effect with MSVC, on that platform the version info for
# the DLL comes from the resource file win32/zlib1.rc
set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION})
if(BUILD_STATIC_AND_SHARED)
# This preserves the original behavior of the zlib cmake build, however combining shared and static
# builds does not afford customization of compiler options typically used on some platforms for each type
add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
else()
# Use BUILD_SHARED_LIBS to control whether the result is shared or static like any other typical cmake build
add_library(zlib ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
endif()

if(UNIX)
# On unix-like platforms the library is almost always called libz
set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z)
if(NOT APPLE)
set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"")
endif()
elseif(BUILD_SHARED_LIBS AND WIN32)
# Creates zlib1.dll when building shared library version
set_target_properties(zlib PROPERTIES SUFFIX "1.dll")
set_target_properties(zlib PROPERTIES OUTPUT_NAME z)
if(BUILD_STATIC_AND_SHARED)
set_target_properties(zlibstatic PROPERTIES OUTPUT_NAME z)
endif()
endif()

if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL )
install(TARGETS zlib zlibstatic
if(BUILD_SHARED_LIBS)
message(STATUS "Building shared (dynamic) library")
set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)
set_target_properties(zlib PROPERTIES SOVERSION 1)

if(NOT CYGWIN)
# This property causes shared libraries on Linux to have the full version
# encoded into their final filename. We disable this on Cygwin because
# it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll
# seems to be the default.
#
# This has no effect with MSVC, on that platform the version info for
# the DLL comes from the resource file win32/zlib1.rc
set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION})
endif()

if (UNIX AND NOT APPLE)
set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"")
endif()

if(WIN32)
# Creates zlib1.dll when building shared library version
set_target_properties(zlib PROPERTIES SUFFIX "1.dll")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#273 recommends a different approach - tagging so we don't lose track of it.

endif()
endif()

if(BUILD_STATIC_AND_SHARED OR NOT BUILD_SHARED_LIBS)
message(STATUS "Building static library")
endif()

if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL)
install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION "${INSTALL_INC_DIR}")
endif()
if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL)
install(TARGETS zlib
RUNTIME DESTINATION "${INSTALL_BIN_DIR}"
ARCHIVE DESTINATION "${INSTALL_LIB_DIR}"
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" )
if(BUILD_STATIC_AND_SHARED)
install(TARGETS zlibstatic
RUNTIME DESTINATION "${INSTALL_BIN_DIR}"
ARCHIVE DESTINATION "${INSTALL_LIB_DIR}"
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" )
endif()

# This entire block is just for handling the PDB file properly!
# It is all OPTIONAL because there is no way to test and see if
# "/Zi" or "/ZI" was used in the build process.
if(MSVC)
if(BUILD_SHARED_LIBS)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Debug/zlibd.pdb"
CONFIGURATIONS Debug
DESTINATION "${INSTALL_BIN_DIR}"
RENAME "zlibd1.pdb"
OPTIONAL)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/zlib.pdb"
CONFIGURATIONS RelWithDebInfo
DESTINATION "${INSTALL_BIN_DIR}"
RENAME "zlib1.pdb"
OPTIONAL)
endif()
if(BUILD_STATIC_AND_SHARED)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zlibstatic.dir/Debug/zlibstatic.pdb"
CONFIGURATIONS Debug
DESTINATION "${INSTALL_LIB_DIR}"
RENAME "zlibstaticd.pdb"
OPTIONAL)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zlibstatic.dir/RelWithDebInfo/zlibstatic.pdb"
CONFIGURATIONS RelWithDebInfo
DESTINATION "${INSTALL_LIB_DIR}"
OPTIONAL)
elseif(NOT BUILD_SHARED_LIBS)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zlib.dir/Debug/zlib.pdb"
CONFIGURATIONS Debug
DESTINATION "${INSTALL_LIB_DIR}"
RENAME "zlibd.pdb"
OPTIONAL)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zlib.dir/RelWithDebInfo/zlib.pdb"
CONFIGURATIONS RelWithDebInfo
DESTINATION "${INSTALL_LIB_DIR}"
OPTIONAL)
endif()
endif()
endif()
if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL )
install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION "${INSTALL_INC_DIR}")
endif()
if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL )
if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL)
install(FILES zlib.3 DESTINATION "${INSTALL_MAN_DIR}/man3")
endif()
if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL )
install(FILES ${ZLIB_PC} DESTINATION "${INSTALL_PKGCONFIG_DIR}")
endif()

Expand Down
57 changes: 57 additions & 0 deletions win32/cmake-matrix.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
::
:: Builds many different combinations of zlib on windows.
:: Provided as an example on how to build exactly what you want.
::

@ECHO ON
SETLOCAL EnableDelayedExpansion

::
:: Set these to your BUILDDIR, INSTDIR, and SRCDIR if needed - the
:: defaults should work fine if you run the batch script from win32.
:: The LOGFILE will receive directory listings from each build combo.
::

SET ZLIB_BUILD=C:\temp\zlib\zlib-build
SET ZLIB_INSTALL=C:\temp\zlib\zlib-install
SET ZLIB_HOME=%CD%\..
SET LOGFILE=C:\temp\zlib\zlib-matrix.log
@ECHO/ > %LOGFILE%

:: These are the stock builds for zlib (builds static and shared together)
CALL :BUILDONE Debug stock-debug
CALL :BUILDONE RelWithDebInfo stock-relwithdebinfo
CALL :BUILDONE Release stock-release

:: These are new build configurations targeting a single type and mode
CALL :BUILDONE Debug shared-debug "-DBUILD_STATIC_AND_SHARED=OFF"
CALL :BUILDONE RelWithDebInfo shared-relwithdebinfo "-DBUILD_STATIC_AND_SHARED=OFF"
CALL :BUILDONE Release shared-release "-DBUILD_STATIC_AND_SHARED=OFF"

:: For static debug library builds put the debug information in the library with /Z7
CALL :BUILDONE Debug static-debug "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF" "-DCMAKE_C_FLAGS_DEBUG=/Z7"
CALL :BUILDONE RelWithDebInfo static-relwithdebinfo "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF" "-DCMAKE_C_FLAGS_RELWITHDEBINFO=/Z7"
CALL :BUILDONE Release static-release "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF"

:: Static library with a static MSVC runtime:
CALL :BUILDONE Debug static-debug-static-rtl "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF" "-DCMAKE_C_FLAGS_DEBUG=/MTd /Z7"
CALL :BUILDONE RelWithDebInfo static-relwithdebinfo-static-rtl "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF" "-DCMAKE_C_FLAGS_RELWITHDEBINFO=/MT /Z7"
CALL :BUILDONE Release static-release-static-rtl "-DBUILD_STATIC_AND_SHARED=OFF" "-DBUILD_SHARED_LIBS=OFF" "-DCMAKE_C_FLAGS_RELEASE=/MT"

EXIT /B

:: \param[in] %1 Build Config
:: \param[in] %2 Build Name
:: \param[in] %3.. Build Options
:BUILDONE
IF EXIST %ZLIB_BUILD%\%2 (RMDIR /S /Q %ZLIB_BUILD%\%2)
IF EXIST %ZLIB_INSTALL%\%2 (RMDIR /S /Q %ZLIB_INSTALL%\%2)
MKDIR %ZLIB_BUILD%\%2
CD %ZLIB_BUILD%\%2
cmake %ZLIB_HOME% -DCMAKE_INSTALL_PREFIX=%ZLIB_INSTALL%\%2 %3 %4 %5 %6 %7 || EXIT /B
cmake --build . --target install --config %1
@ECHO/ >> %LOGFILE%
@ECHO "-------------------------------------------------------------------------------" >> %LOGFILE%
@ECHO "-- %2" >> %LOGFILE%
@ECHO "-------------------------------------------------------------------------------" >> %LOGFILE%
DIR /S %ZLIB_INSTALL%\%2 >> %LOGFILE%