CMakeLists.txt 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
  2. project(jpegoptim C)
  3. # LIBJPEG_INCLUDE_DIR and LIBJPEG_LIBRARY must both be specified if a custom libjpeg implementation is desired.
  4. option(WITH_ARITH "Enable arithmetic coding (if supported by the libjpeg implementation)" 1)
  5. option(USE_MOZJPEG "Download, build, and link with MozJPEG rather than the system libjpeg. Build with NASM installed for SIMD support." 1)
  6. set(LIBJPEG_INCLUDE_DIR "" CACHE PATH "Custom libjpeg header directory")
  7. set(LIBJPEG_LIBRARY "" CACHE FILEPATH "Custom libjpeg library binary")
  8. if(MSVC)
  9. option(BUILD_NO_SUBFOLDERS "Flatten the compiled program's output path")
  10. endif()
  11. # If LIBJPEG_INCLUDE_DIR and LIBJPEG_LIBRARY are set, USE_MOZJPEG is disabled.
  12. if(LIBJPEG_INCLUDE_DIR AND LIBJPEG_LIBRARY)
  13. set(USE_MOZJPEG 0)
  14. endif()
  15. # Set target architecture if empty. CMake's Visual Studio generator provides it, but others may not.
  16. if(MSVC)
  17. if(NOT CMAKE_VS_PLATFORM_NAME)
  18. set(CMAKE_VS_PLATFORM_NAME "x64")
  19. endif()
  20. message("${CMAKE_VS_PLATFORM_NAME} architecture in use")
  21. else()
  22. add_compile_definitions(HOST_TYPE="${CMAKE_HOST_SYSTEM_NAME}")
  23. endif()
  24. # Global configuration types
  25. set(CMAKE_CONFIGURATION_TYPES
  26. "Debug"
  27. "Release"
  28. CACHE STRING "" FORCE
  29. )
  30. # Global compiler options
  31. if(MSVC)
  32. # remove default compiler flags provided with CMake for MSVC
  33. set(CMAKE_C_FLAGS "")
  34. set(CMAKE_C_FLAGS_DEBUG "")
  35. set(CMAKE_C_FLAGS_RELEASE "")
  36. endif()
  37. # Global linker options
  38. if(MSVC)
  39. # remove default linker flags provided with CMake for MSVC
  40. set(CMAKE_EXE_LINKER_FLAGS "")
  41. set(CMAKE_MODULE_LINKER_FLAGS "")
  42. set(CMAKE_SHARED_LINKER_FLAGS "")
  43. set(CMAKE_STATIC_LINKER_FLAGS "")
  44. set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS}")
  45. set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "${CMAKE_MODULE_LINKER_FLAGS}")
  46. set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS}")
  47. set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${CMAKE_STATIC_LINKER_FLAGS}")
  48. set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS}")
  49. set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS}")
  50. set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS}")
  51. set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS}")
  52. endif()
  53. # Common utils
  54. include(CMake/Utils.cmake)
  55. # Additional Global Settings (add specific info there)
  56. include(CMake/GlobalSettingsInclude.cmake OPTIONAL)
  57. # Use solution folders feature
  58. set_property(GLOBAL PROPERTY USE_FOLDERS ON)
  59. # Source groups
  60. set(SOURCE_FILES
  61. jpegoptim.c
  62. jpegsrc.c
  63. jpegdest.c
  64. jpegmarker.c
  65. misc.c
  66. )
  67. source_group("Source Files" FILES ${SOURCE_FILES})
  68. # Target
  69. add_executable(${PROJECT_NAME} ${SOURCE_FILES})
  70. if(MSVC)
  71. use_props(${PROJECT_NAME} "${CMAKE_CONFIGURATION_TYPES}" "${DEFAULT_CXX_PROPS}")
  72. set_target_properties(${PROJECT_NAME} PROPERTIES
  73. VS_GLOBAL_KEYWORD "Win32Proj"
  74. )
  75. endif()
  76. # Output directory
  77. if(MSVC)
  78. if(BUILD_NO_SUBFOLDERS)
  79. set(BINARY_OUTPUT_PATH ".")
  80. else()
  81. set(BINARY_OUTPUT_PATH "$<CONFIG>/${CMAKE_VS_PLATFORM_NAME}")
  82. endif()
  83. set_target_properties(${PROJECT_NAME} PROPERTIES
  84. RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BINARY_OUTPUT_PATH}
  85. RUNTIME_OUTPUT_DIRECTORY_RELEASE ${BINARY_OUTPUT_PATH}
  86. )
  87. endif()
  88. # Interprocedural optimization (LTCG)
  89. set_target_properties(${PROJECT_NAME} PROPERTIES
  90. INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE"
  91. )
  92. if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  93. target_compile_options(${PROJECT_NAME} PUBLIC
  94. -fuse-linker-plugin
  95. )
  96. endif()
  97. # MSVC runtime library
  98. if(MSVC)
  99. get_property(MSVC_RUNTIME_LIBRARY_DEFAULT TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY)
  100. string(CONCAT "MSVC_RUNTIME_LIBRARY_STR"
  101. $<$<CONFIG:Debug>:
  102. MultiThreadedDebug
  103. >
  104. $<$<CONFIG:Release>:
  105. MultiThreaded
  106. >
  107. $<$<NOT:$<OR:$<CONFIG:Debug>,$<CONFIG:Release>>>:${MSVC_RUNTIME_LIBRARY_DEFAULT}>
  108. )
  109. set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY ${MSVC_RUNTIME_LIBRARY_STR})
  110. endif()
  111. # Include directories
  112. #target_include_directories(${PROJECT_NAME} PUBLIC
  113. # "${CMAKE_CURRENT_SOURCE_DIR}"
  114. #)
  115. # Compile definitions
  116. target_compile_definitions(${PROJECT_NAME} PRIVATE
  117. "$<$<CONFIG:Debug>:"
  118. "_DEBUG;"
  119. "DEBUG"
  120. ">"
  121. "$<$<CONFIG:Release>:"
  122. "NDEBUG"
  123. ">"
  124. )
  125. if(MSVC)
  126. target_compile_definitions(${PROJECT_NAME} PRIVATE
  127. "WIN32;"
  128. "_WIN64;"
  129. "WIN64;"
  130. "_WINDOWS;"
  131. "UNICODE;"
  132. "_UNICODE"
  133. )
  134. endif()
  135. # Compile and link options
  136. if(MSVC)
  137. target_compile_options(${PROJECT_NAME} PRIVATE
  138. $<$<CONFIG:Debug>:
  139. /Od; # Disable optimization
  140. /RTC1 # Enable stack frame run-time error checking and reporting when a variable is used without having been initialized.
  141. >
  142. $<$<CONFIG:Release>:
  143. /MP; # Build with multiple processes
  144. /O2; # Optimize for speed
  145. /GF # Enable string pooling
  146. >
  147. /Gy; # Link per-function
  148. /W3; # Warning level
  149. /Zi; # Emit debug info in a separate PDB
  150. /TC; # Compile all source files as C source code regardless of extension
  151. /wd4996; # Suppress deprecation warnings
  152. ${DEFAULT_CXX_EXCEPTION_HANDLING};
  153. /GS; # Enable security checks against buffer overruns
  154. /Y- # Disable precompiled headers
  155. )
  156. target_link_options(${PROJECT_NAME} PRIVATE
  157. $<$<CONFIG:Debug>:
  158. /INCREMENTAL # Enable incremental linking (faster builds, larger filesize)
  159. >
  160. $<$<CONFIG:Release>:
  161. /OPT:REF; # Don't link unused functions
  162. /OPT:ICF; # Remove duplicate function definitions
  163. /INCREMENTAL:NO # Disable incremental linking
  164. >
  165. /MANIFEST; # Generate a manifest file
  166. /DEBUG:FULL; # Generate debugging symbols (in a separate PDB file)
  167. /MACHINE:${CMAKE_VS_PLATFORM_NAME};
  168. /SUBSYSTEM:CONSOLE; # Not a driver or GUI program
  169. /NXCOMPAT; # Support Windows Data Execution Prevention
  170. /DYNAMICBASE # Use address space layout randomization
  171. )
  172. # Link with setargv for command line wildcard support
  173. # See https://learn.microsoft.com/en-us/cpp/c-language/expanding-wildcard-arguments
  174. target_link_options(${PROJECT_NAME} PRIVATE
  175. setargv.obj
  176. )
  177. endif()
  178. # Header and function checks
  179. include(CheckIncludeFile)
  180. check_include_file(config.h HAVE_CONFIG_H)
  181. check_include_file(unistd.h HAVE_UNISTD_H)
  182. check_include_file(getopt.h HAVE_GETOPT_H)
  183. check_include_file(string.h HAVE_STRING_H)
  184. check_include_file(libgen.h HAVE_LIBGEN_H)
  185. check_include_file(math.h HAVE_MATH_H)
  186. check_include_file(fcntl.h HAVE_FCNTL_H)
  187. check_include_file(dirent.h HAVE_DIRENT_H)
  188. check_include_file(sys/stat.h HAVE_SYS_STAT_H)
  189. check_include_file(sys/types.h HAVE_SYS_TYPES_H)
  190. check_include_file(sys/wait.h HAVE_SYS_WAIT_H)
  191. include(CheckSymbolExists)
  192. check_symbol_exists(mkstemps "stdlib.h" HAVE_MKSTEMPS)
  193. check_symbol_exists(labs "stdlib.h" HAVE_LABS)
  194. check_symbol_exists(fileno "stdio.h" HAVE_FILENO)
  195. check_symbol_exists(utimensat "sys/stat.h" HAVE_UTIMENSAT)
  196. check_symbol_exists(fork "unistd.h" HAVE_FORK)
  197. check_symbol_exists(wait "sys/wait.h" HAVE_WAIT)
  198. check_symbol_exists(getopt "unistd.h" HAVE_GETOPT)
  199. check_symbol_exists(getopt_long "getopt.h" HAVE_GETOPT_LONG)
  200. include(CheckStructHasMember)
  201. if(HAVE_SYS_STAT_H)
  202. check_struct_has_member(
  203. "struct stat" st_mtim "sys/stat.h" HAVE_STRUCT_STAT_ST_MTIM LANGUAGE C
  204. )
  205. endif()
  206. target_compile_definitions(${PROJECT_NAME} PRIVATE
  207. $<$<BOOL:${HAVE_CONFIG_H}>:HAVE_CONFIG_H>
  208. $<$<BOOL:${HAVE_UNISTD_H}>:HAVE_UNISTD_H>
  209. $<$<BOOL:${HAVE_GETOPT_H}>:HAVE_GETOPT_H>
  210. $<$<BOOL:${HAVE_STRING_H}>:HAVE_STRING_H>
  211. $<$<BOOL:${HAVE_LIBGEN_H}>:HAVE_LIBGEN_H>
  212. $<$<BOOL:${HAVE_MATH_H}>:HAVE_MATH_H>
  213. $<$<BOOL:${HAVE_FCNTL_H}>:HAVE_FCNTL_H>
  214. $<$<BOOL:${HAVE_DIRENT_H}>:HAVE_DIRENT_H>
  215. $<$<BOOL:${HAVE_SYS_STAT_H}>:HAVE_SYS_STAT_H>
  216. $<$<BOOL:${HAVE_SYS_TYPES_H}>:HAVE_SYS_TYPES_H>
  217. $<$<BOOL:${HAVE_SYS_WAIT_H}>:HAVE_SYS_WAIT_H>
  218. $<$<BOOL:${HAVE_MKSTEMPS}>:HAVE_MKSTEMPS>
  219. $<$<BOOL:${HAVE_LABS}>:HAVE_LABS>
  220. $<$<BOOL:${HAVE_FILENO}>:HAVE_FILENO>
  221. $<$<BOOL:${HAVE_UTIMENSAT}>:HAVE_UTIMENSAT>
  222. $<$<BOOL:${HAVE_FORK}>:HAVE_FORK>
  223. $<$<BOOL:${HAVE_WAIT}>:HAVE_WAIT>
  224. $<$<BOOL:${HAVE_GETOPT}>:HAVE_GETOPT>
  225. $<$<BOOL:${HAVE_GETOPT_LONG}>:HAVE_GETOPT_LONG>
  226. $<$<BOOL:${HAVE_STRUCT_STAT_ST_MTIM}>:HAVE_STRUCT_STAT_ST_MTIM>
  227. )
  228. # Include getopt only if no native implementation found.
  229. if(NOT HAVE_GETOPT_LONG)
  230. target_sources(${PROJECT_NAME} PRIVATE getopt.c getopt1.c)
  231. endif()
  232. # Attach a manifest file to support UTF-8 on compatible Windows systems (see https://learn.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
  233. if(MSVC)
  234. add_custom_command(
  235. TARGET ${PROJECT_NAME}
  236. POST_BUILD
  237. COMMAND "mt.exe" -nologo -manifest \"${CMAKE_CURRENT_SOURCE_DIR}/jpegoptim.manifest\" -outputresource:"${CMAKE_CURRENT_BINARY_DIR}/${BINARY_OUTPUT_PATH}/jpegoptim.exe"\;\#1
  238. COMMENT "Adding manifest..."
  239. )
  240. endif()
  241. # Dependencies
  242. if(USE_MOZJPEG)
  243. # Link with mozjpeg.
  244. # Version tree: https://github.com/mozilla/mozjpeg/tree/fd569212597dcc249752bd38ea58a4e2072da24f
  245. include(ExternalProject)
  246. if(WITH_ARITH)
  247. set(ARITH_FLAGS -DWITH_ARITH_DEC=1 -DWITH_ARITH_ENC=1)
  248. set(JPEGLIB_SUPPORTS_ARITH_CODE 1)
  249. endif()
  250. ExternalProject_Add(mozjpeg_lib
  251. GIT_REPOSITORY https://github.com/mozilla/mozjpeg.git
  252. GIT_TAG fd569212597dcc249752bd38ea58a4e2072da24f
  253. PREFIX ${CMAKE_CURRENT_BINARY_DIR}/mozjpeg
  254. CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/mozjpeg -DPNG_SUPPORTED=0 -DWITH_TURBOJPEG=0 -DENABLE_SHARED=0 ${ARITH_FLAGS}
  255. )
  256. # Building and linking mozjpeg as a library, as explained here https://mirkokiefer.com/cmake-by-example-f95eb47d45b1
  257. ExternalProject_Get_Property(mozjpeg_lib install_dir)
  258. add_library(mozjpeg STATIC IMPORTED)
  259. if(MSVC)
  260. set_property(TARGET mozjpeg PROPERTY IMPORTED_LOCATION ${install_dir}/lib/jpeg-static.lib)
  261. else()
  262. target_link_libraries(mozjpeg INTERFACE m)
  263. set_property(TARGET mozjpeg PROPERTY IMPORTED_LOCATION ${install_dir}/lib/libjpeg.a)
  264. endif()
  265. add_dependencies(mozjpeg mozjpeg_lib)
  266. target_include_directories(${PROJECT_NAME} BEFORE PRIVATE ${install_dir}/include)
  267. target_link_libraries(${PROJECT_NAME} mozjpeg)
  268. add_dependencies(${PROJECT_NAME} mozjpeg)
  269. # Note: check_include_file, check_symbol_exists, check_struct_has_member, and check_c_source_compiles
  270. # cannot be used on ExternalProject dependencies because they are not compiled until build time, while check_*
  271. # functions run during the initial CMake configuration step.
  272. # Since the version is hardcoded above, feature checks may be set as constants as a workaround.
  273. set(HAVE_JINT_DC_SCAN_OPT_MODE 1)
  274. else()
  275. if(LIBJPEG_INCLUDE_DIR AND LIBJPEG_LIBRARY)
  276. # Link with custom libjpeg
  277. add_library(libjpeg STATIC IMPORTED)
  278. if(NOT MSVC)
  279. target_link_libraries(libjpeg INTERFACE m)
  280. endif()
  281. set_target_properties(libjpeg PROPERTIES IMPORTED_LOCATION ${LIBJPEG_LIBRARY})
  282. target_include_directories(${PROJECT_NAME} BEFORE PRIVATE ${LIBJPEG_INCLUDE_DIR})
  283. target_link_libraries(${PROJECT_NAME} libjpeg)
  284. add_dependencies(${PROJECT_NAME} libjpeg)
  285. else()
  286. # Link with system libjpeg
  287. include(FindJPEG)
  288. if(NOT JPEG_FOUND)
  289. message(FATAL_ERROR "Could not automatically locate libjpeg. Either specify -DUSE_MOZJPEG=1 to download and build with MozJPEG, or -DLIBJPEG_INCLUDE_DIR=... and -DLIBJPEG_LIBRARY=... to the appropriate paths to build with a custom libjpeg implementation.")
  290. endif()
  291. message(STATUS "Include dirs: ${JPEG_INCLUDE_DIRS}")
  292. target_include_directories(${PROJECT_NAME} PRIVATE ${JPEG_INCLUDE_DIRS})
  293. target_link_libraries(${PROJECT_NAME} JPEG::JPEG)
  294. endif()
  295. # Use all include directories and linked libraries as the main project for feature tests
  296. get_target_property(CMAKE_REQUIRED_LIBRARIES ${PROJECT_NAME} LINK_LIBRARIES)
  297. get_target_property(CMAKE_REQUIRED_INCLUDES ${PROJECT_NAME} INCLUDE_DIRECTORIES)
  298. # check_include_file, check_symbol_exists, and check_struct_has_member cannot be used with libjpeg.h
  299. # because libjpeg.h requires stdio.h to be included before it to not throw an unrelated compilation error.
  300. include(CheckCSourceCompiles)
  301. check_c_source_compiles(
  302. "
  303. #include <stdio.h>
  304. #include <jpeglib.h>
  305. int main(void)
  306. {
  307. return sizeof (&jpeg_read_header);
  308. }
  309. "
  310. HAVE_APPROPRIATE_LIBJPEG_VERSION
  311. )
  312. if(NOT HAVE_APPROPRIATE_LIBJPEG_VERSION)
  313. message(FATAL_ERROR "Invalid version: libjpeg version 6 or later is required.")
  314. endif()
  315. check_c_source_compiles(
  316. "
  317. #include <stdio.h>
  318. #include <jpeglib.h>
  319. METHODDEF(void) foo(void) {};
  320. int main(void)
  321. {
  322. return 0;
  323. }
  324. "
  325. WORKING_METHODDEF
  326. )
  327. if(NOT WORKING_METHODDEF)
  328. target_compile_definitions(${PROJECT_NAME} PRIVATE -DBROKEN_METHODDEF)
  329. endif()
  330. if(WITH_ARITH)
  331. # Check for arithmetic coding support
  332. check_c_source_compiles(
  333. "
  334. #include <stdio.h>
  335. #include <jpeglib.h>
  336. int main(void)
  337. {
  338. return sizeof (((struct jpeg_compress_struct *)0)->arith_code);
  339. }
  340. "
  341. JPEGLIB_SUPPORTS_ARITH_CODE
  342. )
  343. endif()
  344. # Check for MozJPEG's JINT_DC_SCAN_OPT_MODE extension
  345. check_c_source_compiles(
  346. "
  347. #include <stdio.h>
  348. #include <jpeglib.h>
  349. int main(void)
  350. {
  351. struct jpeg_compress_struct cinfo;
  352. if (jpeg_c_int_param_supported(&cinfo, JINT_DC_SCAN_OPT_MODE))
  353. jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1);
  354. return 0;
  355. }
  356. "
  357. HAVE_JINT_DC_SCAN_OPT_MODE
  358. )
  359. endif()
  360. target_compile_definitions(${PROJECT_NAME} PRIVATE
  361. $<$<BOOL:${HAVE_JINT_DC_SCAN_OPT_MODE}>:HAVE_JINT_DC_SCAN_OPT_MODE>
  362. )
  363. if(WITH_ARITH AND JPEGLIB_SUPPORTS_ARITH_CODE)
  364. set(ARITH_ENABLED 1)
  365. target_compile_definitions(${PROJECT_NAME} PRIVATE -DHAVE_ARITH_CODE)
  366. endif()
  367. if(ARITH_ENABLED)
  368. message(STATUS "Arithmetic Coding: Enabled")
  369. else()
  370. message(STATUS "Arithmetic Coding: Disabled")
  371. endif()
  372. find_package(Python3 COMPONENTS Interpreter Development)
  373. if (Python3_FOUND)
  374. enable_testing()
  375. add_test(NAME unittests
  376. COMMAND ${Python3_EXECUTABLE} test.py
  377. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test
  378. )
  379. set_property(TEST unittests PROPERTY ENVIRONMENT "JPEGOPTIM=$<TARGET_FILE:jpegoptim>")
  380. endif()