From c7fb0a487523b3686a03e3b7f9b459fcb9323a36 Mon Sep 17 00:00:00 2001 From: Gil Date: Mon, 16 Jul 2018 11:46:55 -0700 Subject: Generate CMake frameworks from podspecs (#1531) * Rename utils.cmake to cc_rules.cmake This makes it less of a dumping ground * Fix sign mismatch in FIRApp * Implement a podspec_framework CMake function ... that generates a CMake framework library target from a podspec. * Remove manual CMake scripts for xcodebuild --- Firebase/Core/FIRApp.m | 2 +- Firestore/CMakeLists.txt | 17 +- .../Example/Firestore.xcodeproj/project.pbxproj | 2 +- cmake/FindFirebaseCore.cmake | 56 --- cmake/FindGoogleUtilities.cmake | 56 --- cmake/cc_rules.cmake | 103 +++++ cmake/external/FirebaseCore.cmake | 27 -- cmake/external/GoogleUtilities.cmake | 27 -- cmake/external/firestore.cmake | 4 - cmake/podspec_cmake.rb | 495 +++++++++++++++++++++ cmake/podspec_rules.cmake | 51 +++ cmake/utils.cmake | 103 ----- cmake/xcodebuild.cmake | 89 ---- 13 files changed, 663 insertions(+), 369 deletions(-) delete mode 100644 cmake/FindFirebaseCore.cmake delete mode 100644 cmake/FindGoogleUtilities.cmake create mode 100644 cmake/cc_rules.cmake delete mode 100644 cmake/external/FirebaseCore.cmake delete mode 100644 cmake/external/GoogleUtilities.cmake create mode 100755 cmake/podspec_cmake.rb create mode 100644 cmake/podspec_rules.cmake delete mode 100644 cmake/utils.cmake delete mode 100644 cmake/xcodebuild.cmake diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m index 5fca127..f06185a 100644 --- a/Firebase/Core/FIRApp.m +++ b/Firebase/Core/FIRApp.m @@ -166,7 +166,7 @@ static NSMutableDictionary *sLibraryVersions; if ([name isEqualToString:kFIRDefaultAppName]) { [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be __FIRAPP_DEFAULT."]; } - for (NSInteger charIndex = 0; charIndex < name.length; charIndex++) { + for (NSUInteger charIndex = 0; charIndex < name.length; charIndex++) { char character = [name characterAtIndex:charIndex]; if (!((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_' || character == '-')) { diff --git a/Firestore/CMakeLists.txt b/Firestore/CMakeLists.txt index 039cb06..831d8ab 100644 --- a/Firestore/CMakeLists.txt +++ b/Firestore/CMakeLists.txt @@ -34,14 +34,11 @@ set(FIREBASE_BINARY_DIR ${PROJECT_BINARY_DIR}/..) list(INSERT CMAKE_MODULE_PATH 0 ${FIREBASE_SOURCE_DIR}/cmake) include(SanitizerOptions) -include(utils) +include(cc_rules) +include(podspec_rules) # External packages -if(APPLE) - find_package(FirebaseCore REQUIRED) - find_package(GoogleUtilities REQUIRED) -endif() find_package(LevelDB REQUIRED) find_package(ZLIB) @@ -128,6 +125,16 @@ target_include_directories( enable_testing() include(CompilerSetup) +# Firebase packages +podspec_framework( + ${FIREBASE_SOURCE_DIR}/GoogleUtilities.podspec + SPECS Logger +) + +podspec_framework( + ${FIREBASE_SOURCE_DIR}/FirebaseCore.podspec +) + # Superbuild installed results include_directories(${FIREBASE_INSTALL_DIR}/include) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index ddd9e83..3a5d30a 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -903,9 +903,9 @@ 6EDD3B5D20BF24A700C33877 /* FuzzTests */ = { isa = PBXGroup; children = ( - 6ED6DEA120F5502700FC6076 /* FuzzingResources */, 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */, 6EDD3B5C20BF247500C33877 /* Firestore_FuzzTests_iOS-Info.plist */, + 6ED6DEA120F5502700FC6076 /* FuzzingResources */, ); path = FuzzTests; sourceTree = ""; diff --git a/cmake/FindFirebaseCore.cmake b/cmake/FindFirebaseCore.cmake deleted file mode 100644 index eec29dd..0000000 --- a/cmake/FindFirebaseCore.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2017 Google -# -# 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. - -find_library( - FIREBASECORE_LIBRARY - FirebaseCore - PATHS ${FIREBASE_INSTALL_DIR}/Frameworks -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - FirebaseCore - DEFAULT_MSG - FIREBASECORE_LIBRARY -) - -if(FIREBASECORE_FOUND) - # Emulate CocoaPods behavior which makes all headers available unqualified. - set( - FIREBASECORE_INCLUDE_DIRS - ${FIREBASECORE_LIBRARY}/Headers - ${FIREBASECORE_LIBRARY}/PrivateHeaders - ) - - set( - FIREBASECORE_LIBRARIES - ${FIREBASECORE_LIBRARY} - "-framework Foundation" - ) - - if(NOT TARGET FirebaseCore) - # Add frameworks as INTERFACE libraries rather than IMPORTED so that - # framework behavior is preserved. - add_library(FirebaseCore INTERFACE) - - set_property( - TARGET FirebaseCore APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${FIREBASECORE_INCLUDE_DIRS} - ) - set_property( - TARGET FirebaseCore APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ${FIREBASECORE_LIBRARIES} - ) - endif() -endif(FIREBASECORE_FOUND) diff --git a/cmake/FindGoogleUtilities.cmake b/cmake/FindGoogleUtilities.cmake deleted file mode 100644 index 37f7361..0000000 --- a/cmake/FindGoogleUtilities.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2018 Google -# -# 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. - -find_library( - GoogleUtilities_LIBRARY - GoogleUtilities - PATHS ${FIREBASE_INSTALL_DIR}/Frameworks -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - GoogleUtilities - DEFAULT_MSG - GoogleUtilities_LIBRARY -) - -if(GoogleUtilities_FOUND) - # Emulate CocoaPods behavior which makes all headers available unqualified. - set( - GoogleUtilities_INCLUDE_DIRS - ${GoogleUtilities_LIBRARY}/Headers - ${GoogleUtilities_LIBRARY}/PrivateHeaders - ) - - set( - GoogleUtilities_LIBRARIES - ${GoogleUtilities_LIBRARY} - "-framework Foundation" - ) - - if(NOT TARGET GoogleUtilities) - # Add frameworks as INTERFACE libraries rather than IMPORTED so that - # framework behavior is preserved. - add_library(GoogleUtilities INTERFACE) - - set_property( - TARGET GoogleUtilities APPEND PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${GoogleUtilities_INCLUDE_DIRS} - ) - set_property( - TARGET GoogleUtilities APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ${GoogleUtilities_LIBRARIES} - ) - endif() -endif(GoogleUtilities_FOUND) diff --git a/cmake/cc_rules.cmake b/cmake/cc_rules.cmake new file mode 100644 index 0000000..7d32624 --- /dev/null +++ b/cmake/cc_rules.cmake @@ -0,0 +1,103 @@ +# Copyright 2017 Google +# +# 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) + +# cc_library( +# target +# SOURCES sources... +# DEPENDS libraries... +# ) +# +# Defines a new library target with the given target name, sources, and dependencies. +function(cc_library name) + set(flag EXCLUDE_FROM_ALL) + set(multi DEPENDS SOURCES) + cmake_parse_arguments(ccl "${flag}" "" "${multi}" ${ARGN}) + + add_library( + ${name} + ${ccl_SOURCES} + ) + add_objc_flags(${name} ccl) + target_link_libraries( + ${name} + PUBLIC + ${ccl_DEPENDS} + ) + + if(ccl_EXCLUDE_FROM_ALL) + set_property( + TARGET ${name} + PROPERTY EXCLUDE_FROM_ALL ON + ) + endif() + +endfunction() + +# 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(cc_test name) + set(multi DEPENDS SOURCES) + cmake_parse_arguments(cct "" "" "${multi}" ${ARGN}) + + list(APPEND cct_DEPENDS GTest::GTest GTest::Main) + + add_executable(${name} ${cct_SOURCES}) + add_objc_flags(${name} cct) + add_test(${name} ${name}) + + target_link_libraries(${name} ${cct_DEPENDS}) +endfunction() + +# add_objc_flags(target sources...) +# +# Adds OBJC_FLAGS to the compile options of the given target if any of the +# sources have filenames that indicate they are are Objective-C. +function(add_objc_flags target) + set(_has_objc OFF) + + foreach(source ${ARGN}) + get_filename_component(ext ${source} EXT) + if((ext STREQUAL ".m") OR (ext STREQUAL ".mm")) + set(_has_objc ON) + endif() + endforeach() + + if(_has_objc) + target_compile_options( + ${target} + PRIVATE + ${OBJC_FLAGS} + ) + endif() +endfunction() + +# 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(add_alias ALIAS_TARGET ACTUAL_TARGET) + if(NOT TARGET ${ALIAS_TARGET}) + add_library(${ALIAS_TARGET} ALIAS ${ACTUAL_TARGET}) + endif() +endfunction() diff --git a/cmake/external/FirebaseCore.cmake b/cmake/external/FirebaseCore.cmake deleted file mode 100644 index a14397e..0000000 --- a/cmake/external/FirebaseCore.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2017 Google -# -# 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(xcodebuild) - -if(TARGET FirebaseCore) - return() -endif() - -if(APPLE) - # FirebaseCore is only available as a CocoaPod build. - xcodebuild(FirebaseCore) -else() - # On non-Apple platforms, there's no way to build FirebaseCore. - add_custom_target(FirebaseCore) -endif() diff --git a/cmake/external/GoogleUtilities.cmake b/cmake/external/GoogleUtilities.cmake deleted file mode 100644 index a7a9ce8..0000000 --- a/cmake/external/GoogleUtilities.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2018 Google -# -# 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(xcodebuild) - -if(TARGET GoogleUtilities) - return() -endif() - -if(APPLE) - # GoogleUtilities is only available as a CocoaPod build. - xcodebuild(GoogleUtilities) -else() - # On non-Apple platforms, there's no way to build GoogleUtilities. - add_custom_target(GoogleUtilities) -endif() diff --git a/cmake/external/firestore.cmake b/cmake/external/firestore.cmake index d91a543..2409c50 100644 --- a/cmake/external/firestore.cmake +++ b/cmake/external/firestore.cmake @@ -13,8 +13,6 @@ # limitations under the License. include(ExternalProject) -include(external/FirebaseCore) -include(external/GoogleUtilities) include(external/googletest) include(external/grpc) include(external/leveldb) @@ -28,8 +26,6 @@ endif() ExternalProject_Add( Firestore DEPENDS - FirebaseCore - GoogleUtilities googletest grpc leveldb diff --git a/cmake/podspec_cmake.rb b/cmake/podspec_cmake.rb new file mode 100755 index 0000000..4063f35 --- /dev/null +++ b/cmake/podspec_cmake.rb @@ -0,0 +1,495 @@ +#!/usr/bin/env ruby + +# Copyright 2018 Google +# +# 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. + +require 'cocoapods' +require 'fileutils' +require 'pathname' +require 'set' + +PLATFORM = :osx + +def usage() + script = File.basename($0) + STDERR.puts <<~EOF + USAGE: #{script} podspec cmake-file [subspecs...] + EOF +end + +def main(args) + if args.size < 2 then + usage() + exit(1) + end + + process(*args) +end + +# A CMake command, like add_library. The command name is stored in the first +# argument. +class CMakeCommand + # Create the command with its initial identifying arguments. + def initialize(*args) + @args = args + @checked_count = 0 + end + + def name() + return @args[0] + end + + def rest() + return @args[1..-1] + end + + def skip?() + return @checked_count == 0 + end + + def allow_missing_args() + @checked_count = nil + end + + # Adds the given arguments to the end of the command + def add_args(*args) + args = args.flatten + + unless @checked_count.nil? + @checked_count += args.size + end + + args.each do |arg| + unless @args.include?(arg) + @args.push(arg) + end + end + end +end + +# A model of a macOS or iOS Framework and the CMake commands required to build +# it. +class Framework + def initialize(name) + @name = name + @add_library = CMakeCommand.new('add_library', @name, 'STATIC') + @add_library.allow_missing_args() + + @public_headers = CMakeCommand.new( + 'set_property', 'TARGET', @name, 'PROPERTY', 'PUBLIC_HEADER') + + @properties = [] + + @extras = {} + end + + # Returns all the CMake commands required to build the framework. + def commands() + result = [@add_library] + result.push(@public_headers, *@properties) + @extras.keys.sort.each do |key| + result.push(@extras[key]) + end + return result + end + + # Adds library sources to the CMake add_library command that declares the + # library. + def add_sources(*sources) + @add_library.add_args(sources) + end + + # Adds public headers to the Framework + def add_public_headers(*headers) + @public_headers.add_args(headers) + end + + # Sets a target-level CMake property on the library target that declares the + # framework. + def set_property(property, *values) + command = CMakeCommand.new('set_property', 'TARGET', @name, 'PROPERTY', property) + command.add_args(values) + @properties.push(command) + end + + # Adds target-level preprocessor definitions. + # + # Args: + # - type: PUBLIC, PRIVATE, or INTERFACE + # - values: C preprocessor defintion arguments starting with -D + def compile_definitions(type, *values) + extra_command('target_compile_definitions', @name, type) + .add_args(values) + end + + # Adds target-level compile-time include path for the preprocessor. + # + # Args: + # - type: PUBLIC, PRIVATE, or INTERFACE + # - values: directory names, not including a leading -I flag + def include_directories(type, *dirs) + extra_command('target_include_directories', @name, type) + .add_args(dirs) + end + + # Adds target-level compile-time compiler options that aren't macro + # definitions or include directories. Link-time options should be added via + # lib_libraries. + # + # Args: + # - type: PUBLIC, PRIVATE, or INTERFACE + # - values: compiler flags, e.g. -fno-autolink + def compile_options(type, *values) + extra_command('target_compile_options', @name, type) + .add_args(values) + end + + # Adds target-level dependencies or link-time compiler options. CMake + # interprets any quoted string that starts with "-" as an option and anything + # else as a library target to depend upon. + # + # Args: + # - type: PUBLIC, PRIVATE, or INTERFACE + # - values: compiler flags, e.g. -fno-autolink + def link_libraries(type, *dirs) + extra_command('target_link_libraries', @name, type) + .add_args(dirs) + end + + private + def extra_command(*key_args) + key = key_args.join('|') + command = @extras[key] + if command.nil? + command = CMakeCommand.new(*key_args) + @extras[key] = command + end + return command + end +end + +# Generates a framework target based on podspec contents. Models the translation +# of a single podspec (and possible subspecs) to a single CMake framework +# target. +class CMakeGenerator + + # Initializes the generator with the given root Pod::Spec and the binary + # directory for the current CMake configuration. + # + # Args: + # - spec: A root specification, the name of which becomes the name of the + # Framework. + # - path_list: A Pod::Sandbox::PathList used to cache file operations. + # - cmake_binary_dir: A directory in which additional files may be written. + def initialize(spec, path_list, cmake_binary_dir) + @target = Framework.new(spec.name) + + headers_root = File.join(cmake_binary_dir, 'Headers') + @headers_dir = File.join(headers_root, spec.name) + + @root = spec + + @target.set_property('FRAMEWORK', 'ON') + @target.set_property('VERSION', spec.version) + + @target.include_directories('PRIVATE', headers_root, @headers_dir) + @target.link_libraries('PUBLIC', "\"-framework Foundation\"") + + root_dir = Pathname.new(__FILE__).expand_path().dirname().dirname() + @path_list = Pod::Sandbox::PathList.new(root_dir) + end + + attr_reader :target + + # Adds information from the given Pod::Spec to the definition of the CMake + # framework target. Subspecs are not automatically handled. + # + # Cocoapods subspecs are not independent libraries--they contribute sources + # and dependencies to a final single Framework. + # + # Args: + # - spec: A root or subspec that contributes to the final state of the of the + # Framework. + def add_framework(spec) + spec = spec.consumer(PLATFORM) + files = Pod::Sandbox::FileAccessor.new(@path_list, spec) + sources = [ + files.source_files, + files.public_headers, + files.private_headers, + ].flatten + @target.add_sources(sources) + + add_headers(files, sources) + + add_dependencies(spec) + add_framework_dependencies(spec) + + @target.compile_options('INTERFACE', '-F${CMAKE_CURRENT_BINARY_DIR}') + @target.compile_options('PRIVATE', '${OBJC_FLAGS}') + + add_xcconfig('PRIVATE', spec.pod_target_xcconfig) + add_xcconfig('PUBLIC', spec.user_target_xcconfig) + end + + private + # Sets up the framework headers so that compilation can succeed. + # Xcode/CocoaPods allow for several different include mechanisms to work: + # + # * Unqualified headers, e.g. +#import "FIRLoggerLevel.h"+, typically + # resolved via the header map. + # * Qualified relative to some source root, e.g. + # +#import "Public/FIRLoggerLevel.h"+, typically resolved by an include + # path + # * Framework imports, e.g. +#import +, + # resolved by a build process that copies headers into the framework + # structure. + # * Umbrella imports e.g. +#import + (which + # happens to import all the public headers). + # + # CMake's framework support is incomplete. It has no support at all for + # generating umbrella headers. + # + # CMake also does not completely support framework imports. It does work for + # sources outside the framework that want to build against it, but until the + # framework has been completely built the headers aren't available in this + # form. This prevents frameworks from referring to their own code via + # framework imports. + # + # This method cheats by creating a subdirectory in the build results that has + # symbolic links of all the public headers accessible with the right path. + # This makes it possible to use framework imports within the framework itself. + # The parent of this path is then added as a PRIVATE include directory of the + # target, making it possible for the framework to see itself this way. + def add_headers(files, sources) + # CMake-built frameworks don't have a notion of private headers, but they + # also don't have a notion of umbrella headers, so all framework headers + # need to be accessed by name. This means that just dumping all the private + # and public headers into what CMake considers the public headers makes + # everything work as we expect. + headers = [ + files.public_headers, + files.private_headers + ].flatten + + @target.add_public_headers(headers) + + # Also, link the headers into a directory that looks like a framework layout + # so that self-references via framework imports work. These *must* be + # symbolic links, otherwise our usual sloppiness causes file contents to be + # included multiple times, usually resulting in ambiguity errors. + FileUtils.mkdir_p(@headers_dir) + headers.each do |header| + FileUtils.ln_sf(header, File.join(@headers_dir, File.basename(header))) + end + + # Simulate header maps by adding include paths for all the directories + # containing non-public headers. + hmap_dirs = Set.new() + sources.each do |source| + next if File.extname(source) != '.h' + next if headers.include?(source) + + hmap_dirs.add(File.dirname(source)) + end + @target.include_directories('PRIVATE', hmap_dirs.to_a.sort) + end + + # Adds Pod::Spec +dependencies+ as target_link_libraries. Only root-specs are + # added as dependencies because in the CMake build there can be only one + # target for the framework. + def add_dependencies(spec) + prefix = "#{@root.name}/" + spec.dependencies.each do |dep| + # Dependencies on subspecs of this same spec are handled elsewhere. + next if dep.name.start_with?(prefix) + + name = dep.name.sub(/\/.*/, '') + @target.link_libraries('PUBLIC', name) + end + end + + # Adds target_link_libraries entries for all the items in the Pod::Spec + # +frameworks+ attribute. + def add_framework_dependencies(spec) + spec.frameworks.each do |framework| + @target.link_libraries('PUBLIC', "\"-framework #{framework}\"") + end + end + + # Mirrors known entries from the xcconfig entries into their equivalents in + # CMake. This translates OTHER_CFLAGS, GCC_PREPROCESSOR_DEFINITIONS, and + # HEADER_SEARCH_PATHS. + # + # Args: + # - type: PUBLIC for +pod_user_xcconfig+ or PRIVATE for + # +pod_target_xcconfig+. + # - xcconfig: the hash of xcconfig values. + def add_xcconfig(type, xcconfig) + if xcconfig.empty? + return + end + + @target.compile_options(type, split(xcconfig['OTHER_CFLAGS'])) + + defs = split(xcconfig['GCC_PREPROCESSOR_DEFINITIONS']) + defs = defs.map { |x| '-D' + x } + @target.compile_definitions(type, defs) + + @target.include_directories(type, split(xcconfig['HEADER_SEARCH_PATHS'])) + end + + # Splits a textual value in xcconfig. Always returns an array, but that array + # may be empty if the value didn't exist in the podspec. + def split(value) + if value.nil? + return [] + elsif value.kind_of?(String) + return value.split + else + return [value] + end + end +end + +# Processes a podspec file, translating all the specs within it into cmake file +# describing how to build it. +# +# Args: +# - podspec_file: The filename of the podspec to use as a source. +# - cmake_file: The filename of the cmake script to produce. +# - req_subspecs: Which subspecs to include. If empty, all subspecs are +# included (which corresponds to CocoaPods behavior. The default_subspec +# property is not handled. +def process(podspec_file, cmake_file, *req_subspecs) + root_dir = Pathname.new(__FILE__).expand_path().dirname().dirname() + path_list = Pod::Sandbox::PathList.new(root_dir) + + spec = Pod::Spec.from_file(podspec_file) + + writer = Writer.new() + writer.append <<~EOF + # This file was generated by #{File.basename(__FILE__)} + # from #{File.basename(podspec_file)}. + # Do not edit! + EOF + + cmake_binary_dir = File.expand_path(File.dirname(cmake_file)) + + gen = CMakeGenerator.new(spec, path_list, cmake_binary_dir) + gen.add_framework(spec) + + req_subspecs = normalize_requested_subspecs(spec, req_subspecs) + req_subspecs = resolve_subspec_deps(spec, req_subspecs) + + spec.subspecs.each do |subspec| + if req_subspecs.include?(subspec.name) + gen.add_framework(subspec) + end + end + + gen.target.commands.each do |command| + writer.write(command) + end + + File.open(cmake_file, 'w') do |fd| + fd.write(writer.result) + end +end + +# Translates the (possibly empty) list of requested subspecs into the list of +# subspecs to actually include. If +req_subspecs+ is empty, returns all +# subspecs. If non-empty, all subspecs are returned as qualified names, e.g. +# "Logger" may become "GoogleUtilities/Logger". +def normalize_requested_subspecs(spec, req_subspecs) + subspecs = spec.subspecs + if req_subspecs.empty? + return subspecs.map { |s| s.name } + else + return req_subspecs.map do |name| + if name.include?(?/) + name + else + "#{spec.name}/#{name}" + end + end + end +end + +# Expands the list of requested subspecs to include any dependencies within the +# same root subspec. For example, if +req_subspecs+ where +# +# +["GoogleUtilties/Logger"]+, +# +# the result would be +# +# +["GoogleUtilties/Logger", "GoogleUtilities/Environment"]+ +# +# because Logger depends upon Environment within the same root spec. +def resolve_subspec_deps(spec, req_subspecs) + prefix = spec.name + '/' + + result = Set.new() + while !req_subspecs.empty? + req = req_subspecs.pop + result.add(req) + + subspec = spec.subspec_by_name(req) + subspec.dependencies(PLATFORM).each do |dep| + if dep.name.start_with?(prefix) && !result.include?(dep.name) + req_subspecs.push(dep.name) + end + end + end + + return result.to_a.sort +end + +# Writes CMake commands out to textual form, taking care of line wrapping. +class Writer + def initialize() + @last_command = nil + @result = "" + end + + attr_reader :result + + def write(command) + if command.skip? + return + end + + if command.name != @last_command + @result << "\n" + end + @last_command = command.name + + single = "#{command.name}(#{command.rest.join(' ')})\n" + if single.size < 80 + @result << single + else + @result << "#{command.name}(\n" + command.rest.each do |arg| + @result << " #{arg}\n" + end + @result << ")\n" + end + end + + def append(text) + @result << text + end +end + +main(ARGV) diff --git a/cmake/podspec_rules.cmake b/cmake/podspec_rules.cmake new file mode 100644 index 0000000..043c1a2 --- /dev/null +++ b/cmake/podspec_rules.cmake @@ -0,0 +1,51 @@ +# Copyright 2018 Google +# +# 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) + +# Reads properties from the given podspec and generates a cmake file that +# defines the equivalent framework. +# +# Only does anything useful on Apple platforms. On non-Apple platforms, this +# function has no effect--no target is created. +macro(podspec_framework PODSPEC_FILE) + if(APPLE) + set(multi SPECS) + cmake_parse_arguments(psf "" "" "${multi}" ${ARGN}) + + get_filename_component(_properties_file ${PODSPEC_FILE} NAME_WE) + set(_properties_file ${_properties_file}.cmake) + + execute_process( + COMMAND + ${FIREBASE_SOURCE_DIR}/cmake/podspec_cmake.rb + ${PODSPEC_FILE} + ${CMAKE_CURRENT_BINARY_DIR}/${_properties_file} + ${psf_SPECS} + ) + + # Get CMake to automatically re-run if the generation script or the podspec + # source changes. + set_property( + DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS ${FIREBASE_SOURCE_DIR}/cmake/podspec_cmake.rb + ) + set_property( + DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS ${PODSPEC_FILE} + ) + + include(${CMAKE_CURRENT_BINARY_DIR}/${_properties_file}) + endif() +endmacro() diff --git a/cmake/utils.cmake b/cmake/utils.cmake deleted file mode 100644 index 7d32624..0000000 --- a/cmake/utils.cmake +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2017 Google -# -# 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) - -# cc_library( -# target -# SOURCES sources... -# DEPENDS libraries... -# ) -# -# Defines a new library target with the given target name, sources, and dependencies. -function(cc_library name) - set(flag EXCLUDE_FROM_ALL) - set(multi DEPENDS SOURCES) - cmake_parse_arguments(ccl "${flag}" "" "${multi}" ${ARGN}) - - add_library( - ${name} - ${ccl_SOURCES} - ) - add_objc_flags(${name} ccl) - target_link_libraries( - ${name} - PUBLIC - ${ccl_DEPENDS} - ) - - if(ccl_EXCLUDE_FROM_ALL) - set_property( - TARGET ${name} - PROPERTY EXCLUDE_FROM_ALL ON - ) - endif() - -endfunction() - -# 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(cc_test name) - set(multi DEPENDS SOURCES) - cmake_parse_arguments(cct "" "" "${multi}" ${ARGN}) - - list(APPEND cct_DEPENDS GTest::GTest GTest::Main) - - add_executable(${name} ${cct_SOURCES}) - add_objc_flags(${name} cct) - add_test(${name} ${name}) - - target_link_libraries(${name} ${cct_DEPENDS}) -endfunction() - -# add_objc_flags(target sources...) -# -# Adds OBJC_FLAGS to the compile options of the given target if any of the -# sources have filenames that indicate they are are Objective-C. -function(add_objc_flags target) - set(_has_objc OFF) - - foreach(source ${ARGN}) - get_filename_component(ext ${source} EXT) - if((ext STREQUAL ".m") OR (ext STREQUAL ".mm")) - set(_has_objc ON) - endif() - endforeach() - - if(_has_objc) - target_compile_options( - ${target} - PRIVATE - ${OBJC_FLAGS} - ) - endif() -endfunction() - -# 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(add_alias ALIAS_TARGET ACTUAL_TARGET) - if(NOT TARGET ${ALIAS_TARGET}) - add_library(${ALIAS_TARGET} ALIAS ${ACTUAL_TARGET}) - endif() -endfunction() diff --git a/cmake/xcodebuild.cmake b/cmake/xcodebuild.cmake deleted file mode 100644 index cc72c60..0000000 --- a/cmake/xcodebuild.cmake +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2017 Google -# -# 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(ExternalProject) - -# Builds an existing Xcode project or workspace as an external project in CMake. -# -# xcodebuild( [