# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. include(CMakeParseArguments) include(FindASANDylib) # firebase_ios_add_executable( # target # sources... # ) # # Wraps a call to add_executable (including arguments like EXCLUDE_FROM_ALL) # that additionally sets common options that apply to all executables in this # repo. function(firebase_ios_add_executable target) firebase_ios_split_target_options(flag ${ARGN}) add_executable(${target} ${flag_REST}) firebase_ios_set_common_target_options(${target} ${flag_OPTIONS}) endfunction() # firebase_ios_add_library( # target # sources... # ) # # Wraps a call to add_library (including any arguments like EXCLUDE_FROM_ALL) # that additionally sets common options that apply to all libraries in this # repo. function(firebase_ios_add_library target) firebase_ios_split_target_options(flag ${ARGN}) add_library(${target} ${flag_REST}) firebase_ios_set_common_target_options(${target} ${flag_OPTIONS}) endfunction() # firebase_ios_add_framework( # target # sources... # ) # # Wraps a call to add_library (including arguments like EXCLUDE_FROM_ALL) that # additionally sets common options that apply to all frameworks in this repo. # # Note that this does not build an actual framework bundle--even recent versions # of CMake (3.15.5 as of this writing) produce problematic output when # frameworks are enabled. However `firebase_ios_add_framework` along with # `firebase_ios_framework_public_headers` produces a library target that # can be compiled against as if it *were* a framework. Notably, imports of the # form #import and public headers that refer to each # other unqualified will work. function(firebase_ios_add_framework target) firebase_ios_split_target_options(flag ${ARGN}) add_library(${target} ${flag_REST}) firebase_ios_set_common_target_options(${target} ${flag_OPTIONS}) target_link_libraries(${target} INTERFACE -ObjC) endfunction() # firebase_ios_framework_public_headers( # target # headers... # ) # # Prepares the given headers for use as public headers of the given target. # This emulates CocoaPods behavior of flattening public headers by creating # a separate directory tree in the build output, consisting of symbolic links to # the actual header files. This makes it possible to use imports of these forms: # # * #import # * #import "Header.h" function(firebase_ios_framework_public_headers target) target_sources(${target} PRIVATE ${ARGN}) podspec_prep_headers(${target} ${ARGN}) target_include_directories( ${target} # ${target} is a subdirectory, making work. PUBLIC ${PROJECT_BINARY_DIR}/Headers # Make unqualified imports work too, often needed for peer imports from # one public header to another. PUBLIC ${PROJECT_BINARY_DIR}/Headers/${target} ) endfunction() # firebase_ios_add_test( # target # sources... # ) # # Wraps a call to `add_executable` (including arguments like `EXCLUDE_FROM_ALL`) # and `add_test` that additionally sets common options that apply to all tests # in this repo. # # Also adds dependencies on GTest::GTest and GTest::Main. function(firebase_ios_add_test target) firebase_ios_split_target_options(flag ${ARGN}) add_executable(${target} ${flag_REST}) add_test(${target} ${target}) firebase_ios_set_common_target_options(${target} ${flag_OPTIONS}) target_link_libraries(${target} PRIVATE GTest::GTest GTest::Main) endfunction() # firebase_ios_add_objc_test( # target # host_app # sources... # ) # # Creates an XCTest bundle suitable for running tests written in Objective-C by # wrapping a call to `xctest_add_bundle` (including arguments like # `EXCLUDE_FROM_ALL`) and `xctest_add_test` that additionally sets common # options that apply to all tests in this repo. function(firebase_ios_add_objc_test target host) xctest_add_bundle(${target} ${host} ${ARGN}) firebase_ios_set_common_target_options(${target}) xctest_add_test(${target} ${target}) if(WITH_ASAN) set_property( TEST ${target} APPEND PROPERTY ENVIRONMENT DYLD_INSERT_LIBRARIES=${CLANG_ASAN_DYLIB} ) endif() if(WITH_TSAN) set_property( TEST ${target} APPEND PROPERTY ENVIRONMENT DYLD_INSERT_LIBRARIES=${CLANG_TSAN_DYLIB} ) endif() endfunction() # firebase_ios_cc_library( # target # SOURCES sources... # DEPENDS libraries... # [EXCLUDE_FROM_ALL] # ) # # Defines a new library target with the given target name, sources, and # dependencies. function(firebase_ios_cc_library name) set(flag DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL HEADER_ONLY) set(multi DEPENDS SOURCES) cmake_parse_arguments(ccl "${flag}" "" "${multi}" ${ARGN}) if(ccl_HEADER_ONLY) firebase_ios_generate_dummy_source(${name} ccl_SOURCES) endif() firebase_ios_maybe_remove_objc_sources(sources ${ccl_SOURCES}) add_library(${name} ${sources}) set(warnings_flag "") if(ccl_DISABLE_STRICT_WARNINGS) set(warnings_flag DISABLE_STRICT_WARNINGS) endif() firebase_ios_add_compile_options(${name} ${warnings_flag} ${sources}) target_compile_options(${name} PRIVATE ${FIREBASE_IOS_CXX_FLAGS}) target_link_libraries(${name} PUBLIC ${ccl_DEPENDS}) if(ccl_EXCLUDE_FROM_ALL) set_property( TARGET ${name} PROPERTY EXCLUDE_FROM_ALL ON ) endif() endfunction() # firebase_ios_cc_select( # interface_library # CONDITION1 implementation_library1 # [CONDITION2 implementation_library2 ...] # [DEFAULT implementation_library_default] # ) # # Creates an INTERFACE library named `interface_library`. # # For each pair of condition and implementation_library, evaluates the condition # and if true makes that library an INTERFACE link library of # `interface_library`. # # If supplied, uses the `DEFAULT` implementation if no other condition matches. # # If no condition matches, fails the configuration cycle with an error message # indicating that no suitable implementation was found. function(firebase_ios_cc_select library_name) add_library(${library_name} INTERFACE) list(LENGTH ARGN length) if(length GREATER 0) math(EXPR length "${length} - 1") foreach(key RANGE 0 ${length} 2) math(EXPR value "${key} + 1") list(GET ARGN ${key} condition) list(GET ARGN ${value} impl_library) if((${condition} STREQUAL "DEFAULT") OR (${${condition}})) message("Using ${library_name} = ${impl_library}") target_link_libraries( ${library_name} INTERFACE ${impl_library} ) return() endif() endforeach() endif() message(FATAL_ERROR "Could not find implementation for ${library_name}") endfunction() # firebase_ios_cc_binary( # target # SOURCES sources... # DEPENDS libraries... # [EXCLUDE_FROM_ALL] # ) # # Defines a new executable target with the given target name, sources, and # dependencies. function(firebase_ios_cc_binary name) set(flag DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL) set(multi DEPENDS SOURCES) cmake_parse_arguments(ccb "${flag}" "" "${multi}" ${ARGN}) firebase_ios_maybe_remove_objc_sources(sources ${ccb_SOURCES}) add_executable(${name} ${sources}) set(warnings_flag "") if(ccb_DISABLE_STRICT_WARNINGS) set(warnings_flag DISABLE_STRICT_WARNINGS) endif() firebase_ios_add_compile_options(${name} ${warnings_flag} ${sources}) target_compile_options(${name} PRIVATE ${FIREBASE_CXX_FLAGS}) target_include_directories(${name} PRIVATE ${FIREBASE_SOURCE_DIR}) target_link_libraries(${name} PRIVATE ${ccb_DEPENDS}) if(ccb_EXCLUDE_FROM_ALL) set_property( TARGET ${name} PROPERTY EXCLUDE_FROM_ALL ON ) endif() endfunction() # firebase_ios_cc_test( # target # SOURCES sources... # DEPENDS libraries... # ) # # Defines a new test executable target with the given target name, sources, and # dependencies. Implicitly adds DEPENDS on GTest::GTest and GTest::Main. function(firebase_ios_cc_test name) if(NOT FIREBASE_IOS_BUILD_TESTS) return() endif() set(flag DISABLE_STRICT_WARNINGS) set(multi DEPENDS SOURCES) cmake_parse_arguments(cct "${flag}" "" "${multi}" ${ARGN}) list(APPEND cct_DEPENDS GTest::GTest GTest::Main) firebase_ios_maybe_remove_objc_sources(sources ${cct_SOURCES}) add_executable(${name} ${sources}) set(warnings_flag "") if(cct_DISABLE_STRICT_WARNINGS) set(warnings_flag DISABLE_STRICT_WARNINGS) endif() firebase_ios_add_compile_options(${name} ${warnings_flag} ${sources}) add_test(${name} ${name}) target_compile_options(${name} PRIVATE ${FIREBASE_CXX_FLAGS}) target_include_directories(${name} PRIVATE ${FIREBASE_SOURCE_DIR}) target_link_libraries(${name} PRIVATE ${cct_DEPENDS}) endfunction() # firebase_ios_add_fuzz_test( # target # DICTIONARY dict_file # CORPUS corpus_dir # sources... # ) # # Defines a new executable fuzz testing target with the given target name, # (optional) dictionary file, (optional) corpus directory, along with the given # sources and arguments you might pass to add_executable. # # Implicitly adds a dependency on the `Fuzzer` library, which corresponds to # libFuzzer if fuzzing runs locally or a provided fuzzing library if fuzzing # runs on OSS Fuzz. # # If provided, copies the DICTIONARY file as '${target}.dict' and copies the # CORPUS directory as '${target}_seed_corpus' after building the target. This # naming convention is critical for OSS Fuzz build script to capture new fuzzing # targets. function(firebase_ios_add_fuzz_test target) firebase_ios_split_target_options(flag ${ARGN}) set(single DICTIONARY CORPUS) cmake_parse_arguments(args "" "${single}" "" ${flag_REST}) firebase_ios_add_executable(${target} ${args_UNPARSED_ARGUMENTS}) firebase_ios_set_common_target_options(${target} ${flag_OPTIONS}) target_link_libraries(${target} PRIVATE Fuzzer) # Copy the dictionary file and corpus directory, if they are defined. if(DEFINED ccf_DICTIONARY) add_custom_command( TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${ccf_DICTIONARY} ${target}.dict ) endif() if(DEFINED ccf_CORPUS) add_custom_command( TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${ccf_CORPUS} ${target}_seed_corpus ) endif() endfunction() # maybe_remove_objc_sources(output_var sources...) # # Removes Objective-C/C++ sources from the given sources if not on an Apple # platform. Stores the resulting list in the variable named by `output_var`. function(firebase_ios_maybe_remove_objc_sources output_var) unset(sources) foreach(source ${ARGN}) get_filename_component(ext ${source} EXT) if(NOT APPLE AND ((ext STREQUAL ".m") OR (ext STREQUAL ".mm"))) continue() endif() list(APPEND sources ${source}) endforeach() set(${output_var} ${sources} PARENT_SCOPE) endfunction() # firebase_ios_add_compile_options(target [DISABLE_STRICT_WARNINGS] sources...) # # Adds FIREBASE_IOS_CXX_FLAGS or FIREBASE_IOS_CXX_FLAGS_STRICT to the compile # options of the given target depending on whether or not # DISABLE_STRICT_WARNINGS was passed. # # If any of the sources have filenames that indicate they are Objective-C adds # Either FIREBASE_IOS_OBJC_FLAGS or FIREBASE_IOS_OBJC_FLAGS_STRICT depending on # whether or not DISABLE_STRICT_WARNINGS was passed. function(firebase_ios_add_compile_options target) set(flag DISABLE_STRICT_WARNINGS) cmake_parse_arguments(aco "${flag}" "" "" ${ARGN}) # Only set Objective-C flags if there's at least once source file to which # that applies. set(has_objc OFF) # Default to applying the strict warnings to all targets, but targets can # opt out. set(suffix _STRICT) if(aco_DISABLE_STRICT_WARNINGS) set(suffix "") endif() foreach(source ${ARGN}) get_filename_component(ext ${source} EXT) if((ext STREQUAL ".m") OR (ext STREQUAL ".mm")) set(has_objc ON) endif() endforeach() target_compile_options( ${target} PRIVATE ${FIREBASE_IOS_CXX_FLAGS${suffix}} ) if(has_objc) target_compile_options( ${target} PRIVATE ${FIREBASE_IOS_OBJC_FLAGS${suffix}} ) endif() target_include_directories( ${target} PRIVATE # Put the binary dir first so that the generated config.h trumps any one # generated statically by a Cocoapods-based build in the same source tree. ${FIREBASE_BINARY_DIR} ${FIREBASE_SOURCE_DIR} ) endfunction() # firebase_ios_add_alias(alias_target actual_target) # # Adds a library alias target `alias_target` if it does not already exist, # aliasing to the given `actual_target` target. This allows library dependencies # to be specified uniformly in terms of the targets found in various # find_package modules even if the library is being built internally. function(firebase_ios_add_alias ALIAS_TARGET ACTUAL_TARGET) if(NOT TARGET ${ALIAS_TARGET}) add_library(${ALIAS_TARGET} ALIAS ${ACTUAL_TARGET}) endif() endfunction() function(firebase_ios_objc_test target) if(NOT APPLE OR NOT FIREBASE_IOS_BUILD_TESTS) return() endif() set(flag DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL) set(single HOST VERSION WORKING_DIRECTORY) set(multi DEPENDS DEFINES HEADERS INCLUDES SOURCES) cmake_parse_arguments(ot "${flag}" "${single}" "${multi}" ${ARGN}) xctest_add_bundle( ${target} ${ot_HOST} ${ot_SOURCES} ) set(warnings_flag "") if(ot_DISABLE_STRICT_WARNINGS) set(warnings_flag DISABLE_STRICT_WARNINGS) endif() firebase_ios_add_compile_options(${target} ${warnings_flag} ${ot_SOURCES}) target_compile_options(${target} PRIVATE ${FIREBASE_CXX_FLAGS}) target_link_libraries(${target} PRIVATE ${ot_DEPENDS}) xctest_add_test( ${target} ${target} ) if(ot_WORKING_DIRECTORY) set_property( TEST ${target} PROPERTY WORKING_DIRECTORY ${ot_WORKING_DIRECTORY} ) endif() if(WITH_ASAN) set_property( TEST ${target} APPEND PROPERTY ENVIRONMENT DYLD_INSERT_LIBRARIES=${CLANG_ASAN_DYLIB} ) endif() if(WITH_TSAN) set_property( TEST ${target} APPEND PROPERTY ENVIRONMENT DYLD_INSERT_LIBRARIES=${CLANG_TSAN_DYLIB} ) endif() endfunction() # firebase_ios_generate_dummy_source(target, sources_list) # # Generates a dummy source file containing a single symbol, suitable for use as # a source file in when defining a header-only library. # # Appends the generated source file target to the list named by sources_list. macro(firebase_ios_generate_dummy_source target sources_list) set( __empty_header_only_file "${CMAKE_CURRENT_BINARY_DIR}/${target}_header_only_empty.cc" ) if(NOT EXISTS ${__empty_header_only_file}) file(WRITE ${__empty_header_only_file} "// Generated file that keeps header-only CMake libraries happy. // single meaningless symbol void ${target}_header_only_fakesym() {} " ) endif() list(APPEND ${sources_list} ${__empty_header_only_file}) endmacro() # Splits the given arguments into two lists: those suitable for passing to # `firebase_ios_set_common_target_options` and everything else, usually passed # to `add_library`, `add_executable`, or similar. # # The resulting lists are set in the parent scope as `${prefix}_OPTIONS` for use # when calling `firebase_ios_set_common_target_options`, and `${prefix}_REST` # that contains any arguments that were unrecognized. # # See `firebase_ios_set_common_target_options` for details on which arguments # are split out. function(firebase_ios_split_target_options prefix) set(option_names DISABLE_STRICT_WARNINGS) set(${prefix}_OPTIONS) set(${prefix}_REST) foreach(arg ${ARGN}) list(FIND option_names ${arg} option_index) if(NOT option_index EQUAL -1) list(APPEND ${prefix}_OPTIONS ${arg}) else() list(APPEND ${prefix}_REST ${arg}) endif() endforeach() set(${prefix}_OPTIONS ${${prefix}_OPTIONS} PARENT_SCOPE) set(${prefix}_REST ${${prefix}_REST} PARENT_SCOPE) endfunction() # Sets common options for a given target. This includes: # # * `DISABLE_STRICT_WARNINGS`: disables stricter warnings usually applied to # targets in this repo. Use this to compile code that's local but doesn't # hold itself to Firestore's warning strictness. # # All other arguments are ignored. function(firebase_ios_set_common_target_options target) set(options DISABLE_STRICT_WARNINGS) cmake_parse_arguments(flag "${options}" "" "" ${ARGN}) if(flag_DISABLE_STRICT_WARNINGS) set(cxx_flags ${FIREBASE_IOS_CXX_FLAGS}) set(objc_flags ${FIREBASE_IOS_OBJC_FLAGS}) else() set(cxx_flags ${FIREBASE_IOS_CXX_FLAGS_STRICT}) set(objc_flags ${FIREBASE_IOS_OBJC_FLAGS_STRICT}) endif() target_compile_options(${target} PRIVATE ${cxx_flags}) if(APPLE) target_compile_options(${target} PRIVATE ${objc_flags}) endif() target_include_directories( ${target} PRIVATE # Put the binary dir first so that the generated config.h trumps any one # generated statically by a Cocoapods-based build in the same source tree. ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ) endfunction() # firebase_ios_glob( # list_variable # [APPEND] # [globs...] # [EXCLUDE globs...] # [INCLUDE globs...] # ) # # Applies globbing rules, accumulating a list of matching files, and sets the # result to list_variable in the parent scope. # # If APPEND is specified, the result will start with any existing values in the # list_variable. Otherwise, the list_variable will be overwritten with only the # matches of this invocation. # # EXCLUDE and INCLUDE change the direction of the match. EXCLUDE will subtract # files matching the glob patterns from the result. INCLUDE will again add # files matching the glob patterns to the result. These can be used sequentially # to finely tune the match. # # As a special case, if invoked with EXCLUDE in the first argument position, # this is treated as if APPEND had been passed. This makes the common case of # subtracting something from the list less prone to error. That is, these two # invocations are equivalent: # # firebase_ios_glob(sources APPEND EXCLUDE *.h) # firebase_ios_glob(sources EXCLUDE *.h) # # Without this exception, the second form would always end up an empty list and # would never be useful. function(firebase_ios_glob list_variable) set(result) set(list_op APPEND) set(first_arg TRUE) foreach(arg IN LISTS ARGN) if(arg STREQUAL "APPEND") list(APPEND result ${${list_variable}}) elseif(arg STREQUAL "INCLUDE") set(list_op APPEND) elseif(arg STREQUAL "EXCLUDE") if(first_arg) list(APPEND result ${${list_variable}}) endif() set(list_op REMOVE_ITEM) else() # Anything else is a glob pattern. Match the pattern and perform the list # operation implied by the current INCLUDE or EXCLUDE mode. file(GLOB matched ${arg}) list(${list_op} result ${matched}) endif() set(first_arg FALSE) endforeach() set(${list_variable} ${result} PARENT_SCOPE) endfunction()