1cmake_minimum_required(VERSION 3.15)
2
3include(CheckSymbolExists)
4include(CheckIPOSupported)
5
6option(NINJA_BUILD_BINARY "Build ninja binary" ON)
7option(NINJA_FORCE_PSELECT "Use pselect() even on platforms that provide ppoll()" OFF)
8
9project(ninja CXX)
10
11# --- optional link-time optimization
12check_ipo_supported(RESULT lto_supported OUTPUT error)
13
14if(lto_supported)
15	message(STATUS "IPO / LTO enabled")
16	set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
17else()
18	message(STATUS "IPO / LTO not supported: <${error}>")
19endif()
20
21# --- compiler flags
22if(MSVC)
23	set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
24	string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
25	# Note that these settings are separately specified in configure.py, and
26	# these lists should be kept in sync.
27	add_compile_options(/W4 /wd4100 /wd4267 /wd4706 /wd4702 /wd4244 /GR- /Zc:__cplusplus)
28	add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
29else()
30	include(CheckCXXCompilerFlag)
31	check_cxx_compiler_flag(-Wno-deprecated flag_no_deprecated)
32	if(flag_no_deprecated)
33		add_compile_options(-Wno-deprecated)
34	endif()
35	check_cxx_compiler_flag(-fdiagnostics-color flag_color_diag)
36	if(flag_color_diag)
37		add_compile_options(-fdiagnostics-color)
38	endif()
39
40	if(NOT NINJA_FORCE_PSELECT)
41		# Check whether ppoll() is usable on the target platform.
42		# Set -DUSE_PPOLL=1 if this is the case.
43		#
44		# NOTE: Use check_cxx_symbol_exists() instead of check_symbol_exists()
45		# because on Linux, <poll.h> only exposes the symbol when _GNU_SOURCE
46		# is defined.
47		#
48		# Both g++ and clang++ define the symbol by default, because the C++
49		# standard library headers require it, but *not* gcc and clang, which
50		# are used by check_symbol_exists().
51		include(CheckSymbolExists)
52		check_symbol_exists(ppoll poll.h HAVE_PPOLL)
53		if(HAVE_PPOLL)
54			add_compile_definitions(USE_PPOLL=1)
55		endif()
56	endif()
57endif()
58
59# --- optional re2c
60set(RE2C_MAJOR_VERSION 0)
61find_program(RE2C re2c)
62if(RE2C)
63	execute_process(COMMAND "${RE2C}" --vernum OUTPUT_VARIABLE RE2C_RAW_VERSION)
64	math(EXPR RE2C_MAJOR_VERSION "${RE2C_RAW_VERSION} / 10000")
65endif()
66if(${RE2C_MAJOR_VERSION} GREATER 1)
67	# the depfile parser and ninja lexers are generated using re2c.
68	function(re2c IN OUT)
69		add_custom_command(DEPENDS ${IN} OUTPUT ${OUT}
70			COMMAND ${RE2C} -b -i --no-generation-date --no-version -o ${OUT} ${IN}
71		)
72	endfunction()
73	re2c(${PROJECT_SOURCE_DIR}/src/depfile_parser.in.cc ${PROJECT_BINARY_DIR}/depfile_parser.cc)
74	re2c(${PROJECT_SOURCE_DIR}/src/lexer.in.cc ${PROJECT_BINARY_DIR}/lexer.cc)
75	add_library(libninja-re2c OBJECT ${PROJECT_BINARY_DIR}/depfile_parser.cc ${PROJECT_BINARY_DIR}/lexer.cc)
76else()
77	message(WARNING "re2c 2 or later was not found; changes to src/*.in.cc will not affect your build.")
78	add_library(libninja-re2c OBJECT src/depfile_parser.cc src/lexer.cc)
79endif()
80target_include_directories(libninja-re2c PRIVATE src)
81
82# --- Check for 'browse' mode support
83function(check_platform_supports_browse_mode RESULT)
84	# Make sure the inline.sh script works on this platform.
85	# It uses the shell commands such as 'od', which may not be available.
86
87	execute_process(
88		COMMAND sh -c "echo 'TEST' | src/inline.sh var"
89		WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
90		RESULT_VARIABLE inline_result
91		OUTPUT_QUIET
92		ERROR_QUIET
93	)
94	if(NOT inline_result EQUAL "0")
95		# The inline script failed, so browse mode is not supported.
96		set(${RESULT} "0" PARENT_SCOPE)
97		if(NOT WIN32)
98			message(WARNING "browse feature omitted due to inline script failure")
99		endif()
100		return()
101	endif()
102
103	# Now check availability of the unistd header
104	check_symbol_exists(fork "unistd.h" HAVE_FORK)
105	check_symbol_exists(pipe "unistd.h" HAVE_PIPE)
106	set(browse_supported 0)
107	if (HAVE_FORK AND HAVE_PIPE)
108		set(browse_supported 1)
109	endif ()
110	set(${RESULT} "${browse_supported}" PARENT_SCOPE)
111	if(NOT browse_supported)
112		message(WARNING "browse feature omitted due to missing `fork` and `pipe` functions")
113	endif()
114
115endfunction()
116
117set(NINJA_PYTHON "python" CACHE STRING "Python interpreter to use for the browse tool")
118
119check_platform_supports_browse_mode(platform_supports_ninja_browse)
120
121# Core source files all build into ninja library.
122add_library(libninja OBJECT
123	src/build_log.cc
124	src/build.cc
125	src/clean.cc
126	src/clparser.cc
127	src/dyndep.cc
128	src/dyndep_parser.cc
129	src/debug_flags.cc
130	src/deps_log.cc
131	src/disk_interface.cc
132	src/edit_distance.cc
133	src/eval_env.cc
134	src/graph.cc
135	src/graphviz.cc
136	src/json.cc
137	src/line_printer.cc
138	src/manifest_parser.cc
139	src/metrics.cc
140	src/missing_deps.cc
141	src/parser.cc
142	src/state.cc
143	src/status.cc
144	src/string_piece_util.cc
145	src/util.cc
146	src/version.cc
147)
148if(WIN32)
149	target_sources(libninja PRIVATE
150		src/subprocess-win32.cc
151		src/includes_normalize-win32.cc
152		src/msvc_helper-win32.cc
153		src/msvc_helper_main-win32.cc
154		src/getopt.c
155		src/minidump-win32.cc
156	)
157	# Build getopt.c, which can be compiled as either C or C++, as C++
158	# so that build environments which lack a C compiler, but have a C++
159	# compiler may build ninja.
160	set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX)
161else()
162	target_sources(libninja PRIVATE src/subprocess-posix.cc)
163	if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
164		target_sources(libninja PRIVATE src/getopt.c)
165		# Build getopt.c, which can be compiled as either C or C++, as C++
166		# so that build environments which lack a C compiler, but have a C++
167		# compiler may build ninja.
168		set_source_files_properties(src/getopt.c PROPERTIES LANGUAGE CXX)
169	endif()
170
171	# Needed for perfstat_cpu_total
172	if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
173		target_link_libraries(libninja PUBLIC "-lperfstat")
174	endif()
175endif()
176
177target_compile_features(libninja PUBLIC cxx_std_11)
178
179#Fixes GetActiveProcessorCount on MinGW
180if(MINGW)
181target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601 __USE_MINGW_ANSI_STDIO=1)
182endif()
183
184# On IBM i (identified as "OS400" for compatibility reasons) and AIX, this fixes missing
185# PRId64 (and others) at compile time in C++ sources
186if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
187	add_compile_definitions(__STDC_FORMAT_MACROS)
188endif()
189
190# Main executable is library plus main() function.
191if(NINJA_BUILD_BINARY)
192	add_executable(ninja src/ninja.cc)
193	target_link_libraries(ninja PRIVATE libninja libninja-re2c)
194
195	if(WIN32)
196		target_sources(ninja PRIVATE windows/ninja.manifest)
197	endif()
198endif()
199
200# Adds browse mode into the ninja binary if it's supported by the host platform.
201if(platform_supports_ninja_browse)
202	# Inlines src/browse.py into the browse_py.h header, so that it can be included
203	# by src/browse.cc
204	add_custom_command(
205		OUTPUT build/browse_py.h
206		MAIN_DEPENDENCY src/browse.py
207		DEPENDS src/inline.sh
208		COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/build
209		COMMAND src/inline.sh kBrowsePy
210						< src/browse.py
211						> ${PROJECT_BINARY_DIR}/build/browse_py.h
212		WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
213		VERBATIM
214	)
215
216	if(NINJA_BUILD_BINARY)
217		target_compile_definitions(ninja PRIVATE NINJA_HAVE_BROWSE)
218		target_sources(ninja PRIVATE src/browse.cc)
219	endif()
220	set_source_files_properties(src/browse.cc
221		PROPERTIES
222			OBJECT_DEPENDS "${PROJECT_BINARY_DIR}/build/browse_py.h"
223			INCLUDE_DIRECTORIES "${PROJECT_BINARY_DIR}"
224			COMPILE_DEFINITIONS NINJA_PYTHON="${NINJA_PYTHON}"
225	)
226endif()
227
228include(CTest)
229if(BUILD_TESTING)
230  find_package(GTest)
231  if(NOT GTest_FOUND)
232    include(FetchContent)
233    FetchContent_Declare(
234      googletest
235      URL https://github.com/google/googletest/archive/release-1.10.0.tar.gz
236      URL_HASH SHA1=9c89be7df9c5e8cb0bc20b3c4b39bf7e82686770
237    )
238    FetchContent_MakeAvailable(googletest)
239
240    # Before googletest-1.11.0, the CMake files provided by the source archive
241    # did not define the GTest::gtest target, only the gtest one, so define
242    # an alias when needed to ensure the rest of this file works with all
243    # GoogleTest releases.
244    #
245    # Note that surprisingly, this is not needed when using GTEST_ROOT to
246    # point to a local installation, because this one contains CMake-generated
247    # files that contain the right target definition, and which will be
248    # picked up by the find_package(GTest) file above.
249    #
250    # This comment and the four lines below can be removed once Ninja only
251    # depends on release-1.11.0 or above.
252    if (NOT TARGET GTest::gtest)
253      message(STATUS "Defining GTest::gtest alias to work-around bug in older release.")
254      add_library(GTest::gtest ALIAS gtest)
255
256      # NOTE: gtest uninit some variables, gcc >= 1.11.3 may cause error on compile.
257      # Remove this comment and six lines below, once ninja deps gtest-1.11.0 or above.
258      if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "1.11.3")
259        check_cxx_compiler_flag(-Wmaybe-uninitialized flag_maybe_uninit)
260        if (flag_maybe_uninit)
261          target_compile_options(gtest PRIVATE -Wno-maybe-uninitialized)
262        endif()
263      endif()
264
265    endif()
266  endif()
267
268  # Tests all build into ninja_test executable.
269  add_executable(ninja_test
270    src/build_log_test.cc
271    src/build_test.cc
272    src/clean_test.cc
273    src/clparser_test.cc
274    src/depfile_parser_test.cc
275    src/deps_log_test.cc
276    src/disk_interface_test.cc
277    src/dyndep_parser_test.cc
278    src/edit_distance_test.cc
279    src/graph_test.cc
280    src/json_test.cc
281    src/lexer_test.cc
282    src/manifest_parser_test.cc
283    src/missing_deps_test.cc
284    src/ninja_test.cc
285    src/state_test.cc
286    src/string_piece_util_test.cc
287    src/subprocess_test.cc
288    src/test.cc
289    src/util_test.cc
290  )
291  if(WIN32)
292    target_sources(ninja_test PRIVATE src/includes_normalize_test.cc src/msvc_helper_test.cc
293      windows/ninja.manifest)
294  endif()
295  find_package(Threads REQUIRED)
296  target_link_libraries(ninja_test PRIVATE libninja libninja-re2c GTest::gtest Threads::Threads)
297
298  foreach(perftest
299    build_log_perftest
300    canon_perftest
301    clparser_perftest
302    depfile_parser_perftest
303    hash_collision_bench
304    manifest_parser_perftest
305  )
306    add_executable(${perftest} src/${perftest}.cc)
307    target_link_libraries(${perftest} PRIVATE libninja libninja-re2c)
308  endforeach()
309
310  if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
311    # These tests require more memory than will fit in the standard AIX shared stack/heap (256M)
312    target_link_options(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000")
313    target_link_options(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000")
314  endif()
315
316  add_test(NAME NinjaTest COMMAND ninja_test)
317endif()
318
319if(NINJA_BUILD_BINARY)
320	install(TARGETS ninja)
321endif()
322