CMakeLists.txt 12.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
# manual/installation.rst mentions the minimum cmake version.
cmake_minimum_required(VERSION 3.16)

# make_dist expects the version line to be on a line by itself after
# the project line. When updating the version, check make_dist for all
# the places it has to be updated. The doc configuration and CI build
# also find the version number here. generate_auto_job also reads the
# version from here.
project(qpdf
  VERSION 12.3.1
  LANGUAGES C CXX)

# Honor CMAKE_REQUIRED_LIBRARIES when checking for include files. This
# can be removed in cmake >= 3.17.
cmake_policy(SET CMP0075 NEW)

# *** OPTIONS ***

# Keep all options here. It's easier to see the interdependencies this
# way than spreading them throughout the files.
#
# ***** Keep manual/installation.rst (_build-options) up to date. *****

include(CMakeDependentOption)

# CMAKE_DEPENDENT_OPTION(
#   OPTION "Description" default-value-if-visible
#   "when-visible" value-if-not-visible)

# Don't write tests based on MAINTAINER_MODE or CI_MODE. Instead, use
# them as the basis for dependent options.

option(MAINTAINER_MODE "Set options for developing qpdf" OFF)
CMAKE_DEPENDENT_OPTION(
  CI_MODE "Set options for running in CI" OFF
  "NOT MAINTAINER_MODE" OFF)
CMAKE_DEPENDENT_OPTION(
  WERROR "Treat compiler warnings as errors" OFF
  "NOT MAINTAINER_MODE; NOT CI_MODE" ON)
CMAKE_DEPENDENT_OPTION(
  GENERATE_AUTO_JOB "Automatically regenerate job files" OFF
  "NOT MAINTAINER_MODE" ON)
CMAKE_DEPENDENT_OPTION(
  ENABLE_QTC "Enable QTC test coverage" OFF
  "NOT MAINTAINER_MODE" ON)
CMAKE_DEPENDENT_OPTION(
  SHOW_FAILED_TEST_OUTPUT "Show qtest output on failure" OFF
  "NOT CI_MODE" ON)

# To allow building doc to be disabled in maintainer mode, handle the
# condition manually rather than using a dependent option.
if(MAINTAINER_MODE)
  set(default_BUILD_DOC ON)
else()
  set(default_BUILD_DOC OFF)
endif()
option(BUILD_DOC "Build documentation" ${default_BUILD_DOC})
# The values of BUILD_DOC_HTML and BUILD_DOC_PDF are ignored without
# BUILD_DOC, so setting them to ON when not visible forces them to be
# on in MAINTAINER_MODE and is harmless if BUILD_DOC is off.
CMAKE_DEPENDENT_OPTION(
  BUILD_DOC_HTML "Build HTML documentation"
  ON "BUILD_DOC;NOT MAINTAINER_MODE" ON)
CMAKE_DEPENDENT_OPTION(
  BUILD_DOC_PDF "Build PDF documentation"
  ON "BUILD_DOC;NOT MAINTAINER_MODE" ON)
CMAKE_DEPENDENT_OPTION(
  BUILD_DOC_DIST "Create distribution of manual" ON
  "BUILD_DOC_PDF;BUILD_DOC_HTML" OFF)

option(ENABLE_COVERAGE "Enable coverage reporting" OFF)
option(BUILD_SHARED_LIBS "Build qpdf shared libraries" ON)
option(BUILD_STATIC_LIBS "Build qpdf static libraries" ON)
option(QTEST_COLOR "Whether qtest's output should be in color" ON)
option(USE_INSECURE_RANDOM "Use insecure random numbers" OFF)
option(SKIP_OS_SECURE_RANDOM
  "Suppress use of OS-provided secure random numbers" OFF)
CMAKE_DEPENDENT_OPTION(
  AVOID_WINDOWS_HANDLE "Avoid use of HANDLE in Windows" OFF
  "WIN32" OFF)

option(OSS_FUZZ "Specific build configuration for the oss-fuzz project" OFF)

option(USE_IMPLICIT_CRYPTO "Enable any available external crypto provider" ON)
CMAKE_DEPENDENT_OPTION(
  ALLOW_CRYPTO_NATIVE "Allow native crypto as as fallback" ON
  "USE_IMPLICIT_CRYPTO" OFF)
CMAKE_DEPENDENT_OPTION(
  REQUIRE_CRYPTO_NATIVE "Require native crypto provider" OFF
  "NOT MAINTAINER_MODE; NOT CI_MODE" ON)
option(REQUIRE_CRYPTO_OPENSSL "Require openssl crypto" OFF)
option(REQUIRE_CRYPTO_GNUTLS "Require gnutls crypto" OFF)
set(DEFAULT_CRYPTO CACHE STRING "")
option(DEFAULT_CRYPTO
  "Specify default crypto; otherwise chosen automatically" "")

option(ZOPFLI, "Use zopfli for zlib-compatible compression")

# INSTALL_MANUAL is not dependent on building docs. When creating some
# distributions, we build the doc in one run, copy doc-dist in, and
# install it elsewhere.
option(INSTALL_MANUAL "Install documentation" OFF)

option(INSTALL_PKGCONFIG "Install pkgconfig file" ON)
option(INSTALL_CMAKE_PACKAGE "Install cmake package files" ON)
option(INSTALL_EXAMPLES "Install example files" ON)

option(FUTURE "Include ABI-breaking changes CONSIDERED for the next major release" OFF)
option(CXX_NEXT "Build with next C++ standard version" OFF)

# *** END OPTIONS ***

if(NOT (BUILD_STATIC_LIBS OR BUILD_SHARED_LIBS))
  message(
    FATAL_ERROR "At least one of static or shared libraries must be built")
endif()

set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON)

if(ENABLE_QTC)
  set(ENABLE_QTC_ARG)
else()
  add_compile_definitions(QPDF_DISABLE_QTC=1)
  set(ENABLE_QTC_ARG --disable-tc)
endif()

if(FUTURE)
  add_compile_definitions(QPDF_FUTURE=1)
endif()

enable_testing()
set(RUN_QTEST perl ${qpdf_SOURCE_DIR}/run-qtest ${ENABLE_QTC_ARG})

if(WIN32)
  find_program(COPY_COMMAND NAMES cp copy)
  if(COPY_COMMAND STREQUAL "COPY_COMMAND-NOTFOUND")
    set(COPY_COMMAND "copy")
  endif()
else()
  set(COPY_COMMAND "cp")
endif()

# For a long time, qpdf used libtool's version system. We are no
# longer doing that, but to avoid potential conflict with older
# versions, continue to have the shared library symlink point to a
# file whose version shares minor and patch with the project version
# and major with the SOVERSION. Starting with the transition to cmake,
# increment SOVERSION every time we increment the project major
# version. This works because qpdf uses semantic versioning. qpdf 10.x
# was libqpdf28, so start from there.

if(FUTURE)
  math(EXPR qpdf_SOVERSION 0)
  set(qpdf_LIBVERSION 0)
else()
  math(EXPR qpdf_SOVERSION "${PROJECT_VERSION_MAJOR} + 18")
  set(qpdf_LIBVERSION ${qpdf_SOVERSION}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
endif()

if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
  message(FATAL_ERROR "
Please build with cmake in a subdirectory, e.g.
 mkdir build
 cmake ..
 cmake --build .
Please remove CMakeCache.txt and the CMakeFiles directories.")
endif()

if(CXX_NEXT)
  set(CMAKE_CXX_STANDARD 23)
else()
  # See also build-scripts/check-headers
  set(CMAKE_CXX_STANDARD 20)
endif()
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

if(WIN32 AND NOT SKIP_OS_SECURE_RANDOM)
  list(APPEND CMAKE_REQUIRED_LIBRARIES Advapi32)
endif()

include(CheckCXXSourceCompiles)
set(ATOMIC_LIBRARY)
function(check_atomic)
  foreach(I 0 1)
    if(I)
      set(CMAKE_REQUIRED_LIBRARIES atomic)
    endif()
    check_cxx_source_compiles(
      "#include <atomic>
int main() {
    static std::atomic<unsigned long long> a{0};
    a = a.fetch_add(1LL);
    return 0;
}"
      ATOMIC_WORKED${I})
    if(ATOMIC_WORKED0)
      return()
    endif()
  endforeach()
  if(ATOMIC_WORKED1)
    set(ATOMIC_LIBRARY atomic PARENT_SCOPE)
  endif()
endfunction()
check_atomic()

set(WINDOWS_WMAIN_COMPILE "")
set(WINDOWS_WMAIN_LINK "")
if(WIN32)
  function(check_wmain)
    foreach(I 0 1)
      if(NOT WINDOWS_WMAIN_COMPILE)
        if(I)
          set(CMAKE_REQUIRED_LINK_OPTIONS -municode)
        endif()
        check_cxx_source_compiles(
          "#include <windows.h>
#include <string.h>
#include <stdio.h>
extern \"C\"
int wmain(int argc, wchar_t* argv[])
{
    size_t x = wcslen(argv[0]);
    return 0;
}
"
          WMAIN_WORKED${I})
      endif()
    endforeach()
    if(WMAIN_WORKED1 OR WMAIN_WORKED1)
      set(WINDOWS_WMAIN_COMPILE -DWINDOWS_WMAIN PARENT_SCOPE)
      if(WMAIN_WORKED1 AND NOT WMAIN_WORKED0)
        set(WINDOWS_WMAIN_LINK -municode PARENT_SCOPE)
      endif()
    endif()
  endfunction()
  check_wmain()
endif()

if(MSVC)
  list(APPEND CMAKE_REQUIRED_LINK_OPTIONS -link setargv.obj)
  list(APPEND WINDOWS_WMAIN_LINK -link wsetargv.obj)
endif()
if(MINGW)
  get_filename_component(MINGW_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY)
  execute_process(
    COMMAND gcc --print-file-name=CRT_glob.o
    OUTPUT_VARIABLE CRT_GLOB_O
    OUTPUT_STRIP_TRAILING_WHITESPACE
  )
  list(APPEND WINDOWS_WMAIN_LINK ${CRT_GLOB_O})
endif()

include(GNUInstallDirs)

# Compiler flags
#
# **NOTE** -- each flag must have its own cache variable.

include(qpdfCheckFlag)
if(WERROR)
  if(MSVC)
    add_compile_options(/WX)
  else()
    qpdf_maybe_add_flag(C -Werror flag_werror)
  endif()
endif()

if(MSVC)
  # /Gy combines identical functions -- useful with C++ templates
  add_compile_options(/Gy)
  add_compile_options(/W3)    # warning level 3
else()
  qpdf_maybe_add_flag(C -Wall flag_wall)
  qpdf_maybe_add_flag(C -Wconversion flag_conversion)
  qpdf_maybe_add_flag(C -Wsign-conversion flag_sign-conversion)
  qpdf_maybe_add_flag(C -Wshadow=local flag_shadow_local)
  qpdf_maybe_add_flag(CXX -Wold-style-cast flag_old-style-cast)
endif()

# We don't include the jpeg library's include path in the PUBLIC
# interface for libqpdf since only Pl_DCT.hh requires it. This is
# documented. Some examples and tests use it though so we have to
# define it. CMakeLists.txt for libqpdf sets the value for
# JPEG_INCLUDE which can be selectively added to include paths other
# tools.
set(JPEG_INCLUDE)

if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
  set(WORDSIZE 64)
else()
  set(WORDSIZE 32)
endif()
if(MSVC)
  set(CPACK_SYSTEM_NAME "msvc${WORDSIZE}")
elseif(MINGW)
  set(CPACK_SYSTEM_NAME "mingw${WORDSIZE}")
endif()
set(CPACK_RESOURCE_FILE_LICENSE "${qpdf_SOURCE_DIR}/LICENSE.txt")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://qpdf.sourceforge.io/")
set(CPACK_NSIS_MUI_ICON "${qpdf_SOURCE_DIR}/logo/qpdf.ico")

if(ENABLE_COVERAGE)
  add_compile_options(--coverage -O0)
  add_link_options(--coverage)
endif()

include(CPack)

# Install components -- documented in _installation in
# manual/installation.rst.
set(COMPONENT_DEV "dev")
set(COMPONENT_LIB "lib") # runtime library
set(COMPONENT_CLI "cli")
set(COMPONENT_DOC "doc")
set(COMPONENT_EXAMPLES "examples") # example sources

if(WIN32)
  include(InstallRequiredSystemLibraries)
endif()

set(auto_job_inputs
  # Keep in sync with SOURCES in generate_auto_job
  generate_auto_job
  CMakeLists.txt
  manual/_ext/qpdf.py
  job.yml
  manual/cli.rst
  manual/qpdf.1.in
)

set(auto_job_outputs
  # Keep in sync with DESTS in generate_auto_job
  libqpdf/qpdf/auto_job_decl.hh
  libqpdf/qpdf/auto_job_init.hh
  libqpdf/qpdf/auto_job_help.hh
  libqpdf/qpdf/auto_job_schema.hh
  libqpdf/qpdf/auto_job_json_decl.hh
  libqpdf/qpdf/auto_job_json_init.hh
  manual/qpdf.1
)

if(GENERATE_AUTO_JOB)
  add_custom_command(
    OUTPUT ${auto_job_outputs}
    COMMAND ${qpdf_SOURCE_DIR}/generate_auto_job --generate
    WORKING_DIRECTORY ${qpdf_SOURCE_DIR}
    DEPENDS ${auto_job_inputs})
  add_custom_target(auto_job_files ALL DEPENDS ${auto_job_outputs})
endif()

add_test(
  NAME check-assert
  COMMAND perl ${qpdf_SOURCE_DIR}/check_assert)

# add_subdirectory order affects test order
add_subdirectory(include)
add_subdirectory(libqpdf)
add_subdirectory(compare-for-test)
add_subdirectory(qpdf)
add_subdirectory(libtests)
add_subdirectory(examples)
add_subdirectory(zlib-flate)
add_subdirectory(manual)
add_subdirectory(fuzz)

# We don't need to show everything -- just the things that we really
# need to be sure are right or that are turned on or off with complex
# logic.
get_property(MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
message(STATUS "")
message(STATUS "*** Summary ***")
message(STATUS "  qpdf version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
if(MULTI_CONFIG)
  message(STATUS "  build type: specify --config at build time")
elseif(CMAKE_BUILD_TYPE)
  message(STATUS "  build type: ${CMAKE_BUILD_TYPE}")
endif()
message(STATUS "  build shared libraries: ${BUILD_SHARED_LIBS}")
message(STATUS "  build static libraries: ${BUILD_STATIC_LIBS}")
message(STATUS "  build manual: ${BUILD_DOC}")
message(STATUS "  compiler warnings are errors: ${WERROR}")
message(STATUS "  QTC test coverage: ${ENABLE_QTC}")
message(STATUS "  include future changes: ${FUTURE}")
message(STATUS "  system: ${CPACK_SYSTEM_NAME}")
message(STATUS "")
if(MINGW)
    message(STATUS "MinGW compiler: ${CMAKE_CXX_COMPILER}")
    message(STATUS "MinGW bin directory: ${MINGW_BIN_DIR}")
    message(STATUS "")
endif()
message(STATUS "*** Options Summary ***")
foreach(PROP
    COMPILE_OPTIONS INTERFACE_COMPILE_OPTIONS
    COMPILE_DEFINITIONS INTERFACE_COMPILE_DEFINITIONS
    INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES
    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
    LINK_OPTIONS INTERFACE_LINK_OPTIONS
    LINK_LIBRARIES INTERFACE_LINK_LIBRARIES
    LINK_DIRECTORIES INTERFACE_LINK_DIRECTORIES)
  get_target_property(VAL libqpdf ${PROP})
  if(NOT (VAL STREQUAL "VAL-NOTFOUND"))
    message(STATUS "  ${PROP}: ${VAL}")
  endif()
endforeach()
if(APPLE)
  message(STATUS "  CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT}")
endif()
message(STATUS "")
message(STATUS "See above for crypto summary.")
message(STATUS "")
if(NOT (MULTI_CONFIG OR CMAKE_BUILD_TYPE))
  message(WARNING "  CMAKE_BUILD_TYPE is not set; using default settings")
endif()