module Pod class Builder def initialize(source_dir, static_sandbox_root, dynamic_sandbox_root, public_headers_root, spec, embedded, mangle, dynamic, config, bundle_identifier, exclude_deps) @source_dir = source_dir @static_sandbox_root = static_sandbox_root @dynamic_sandbox_root = dynamic_sandbox_root @public_headers_root = public_headers_root @spec = spec @embedded = embedded @mangle = mangle @dynamic = dynamic @config = config @bundle_identifier = bundle_identifier @exclude_deps = exclude_deps end def build(platform, library) UI.puts `xcrun xcodebuild -version` if library build_static_library(platform) else build_framework(platform) end end def build_static_library(platform) UI.puts("Building static library #{@spec} with configuration #{@config}") defines = compile(platform) build_sim_libraries(platform, defines) platform_path = Pathname.new(platform.name.to_s) platform_path.mkdir unless platform_path.exist? build_library(platform, defines, platform_path + Pathname.new("lib#{@spec.name}.a")) create_static_library(platform.name.to_s) copy_static_headers copy_static_resources end def build_framework(platform) UI.puts("Building framework #{@spec} with configuration #{@config}") defines = compile(platform) build_sim_libraries(platform, defines) if @dynamic build_dynamic_framework(platform, defines, "#{@dynamic_sandbox_root}/build/#{@spec.name}.framework/#{@spec.name}") else create_framework(platform.name.to_s) build_library(platform, defines, @fwk.versions_path + Pathname.new(@spec.name)) copy_headers copy_license end copy_resources(platform) end def link_embedded_resources target_path = @fwk.root_path + Pathname.new('Resources') target_path.mkdir unless target_path.exist? Dir.glob(@fwk.resources_path.to_s + '/*').each do |resource| resource = Pathname.new(resource).relative_path_from(target_path) `ln -sf #{resource} #{target_path}` end end private def build_dynamic_framework(platform, defines, output) UI.puts("Building dynamic Framework #{@spec} with configuration #{@config}") if @bundle_identifier defines = "#{defines} PRODUCT_BUNDLE_IDENTIFIER='#{@bundle_identifier}'" end clean_directory_for_dynamic_build if platform.name == :ios build_dynamic_framework_for_ios(platform, defines, output) elsif platform.name == :watchos raise 'not supported yet' else build_dynamic_framework_for_mac(platform, defines, output) end end def build_library(platform, defines, output) static_libs = static_libs_in_sandbox if platform.name == :ios build_static_lib_for_ios(static_libs, defines, output) elsif platform.name == :watchos build_static_lib_for_watchos(static_libs, defines, output) else build_static_lib_for_mac(static_libs, output) end end def build_dynamic_framework_for_ios(platform, defines, output) # Specify frameworks to link and search paths linker_flags = static_linker_flags_in_sandbox defines = "#{defines} OTHER_LDFLAGS='$(inherited) #{linker_flags.join(' ')}'" # Build Target Dynamic Framework for both device and Simulator device_defines = "#{defines} LIBRARY_SEARCH_PATHS=\"#{Dir.pwd}/#{@static_sandbox_root}/build\"" device_options = ios_build_options << ' -sdk iphoneos' xcodebuild(device_defines, device_options, 'build', @spec.name.to_s, @dynamic_sandbox_root.to_s) sim_defines = "#{defines} LIBRARY_SEARCH_PATHS=\"#{Dir.pwd}/#{@static_sandbox_root}/build-sim\" ONLY_ACTIVE_ARCH=NO" xcodebuild(sim_defines, '-sdk iphonesimulator', 'build-sim', @spec.name.to_s, @dynamic_sandbox_root.to_s) # Combine architectures `lipo #{@dynamic_sandbox_root}/build/#{@spec.name}.framework/#{@spec.name} #{@dynamic_sandbox_root}/build-sim/#{@spec.name}.framework/#{@spec.name} -create -output #{output}` FileUtils.mkdir(platform.name.to_s) `mv #{@dynamic_sandbox_root}/build/#{@spec.name}.framework #{platform.name}` `mv #{@dynamic_sandbox_root}/build/#{@spec.name}.framework.dSYM #{platform.name}` end def build_dynamic_framework_for_mac(platform, defines, _output) # Specify frameworks to link and search paths linker_flags = static_linker_flags_in_sandbox defines = "#{defines} OTHER_LDFLAGS=\"#{linker_flags.join(' ')}\"" # Build Target Dynamic Framework for osx defines = "#{defines} LIBRARY_SEARCH_PATHS=\"#{Dir.pwd}/#{@static_sandbox_root}/build\"" xcodebuild(defines, nil, 'build', @spec.name.to_s, @dynamic_sandbox_root.to_s) FileUtils.mkdir(platform.name.to_s) `mv #{@dynamic_sandbox_root}/build/#{@spec.name}.framework #{platform.name}` `mv #{@dynamic_sandbox_root}/build/#{@spec.name}.framework.dSYM #{platform.name}` end def build_sim_libraries(platform, defines) if platform.name == :ios xcodebuild(defines, '-sdk iphonesimulator', 'build-sim') elsif platform.name == :watchos xcodebuild(defines, '-sdk watchsimulator', 'build-sim') end end def build_static_lib_for_ios(static_libs, _defines, output) return if static_libs.count == 0 `libtool -static -o #{@static_sandbox_root}/build/package.a #{static_libs.join(' ')}` sim_libs = static_libs_in_sandbox('build-sim') `libtool -static -o #{@static_sandbox_root}/build-sim/package.a #{sim_libs.join(' ')}` `lipo #{@static_sandbox_root}/build/package.a #{@static_sandbox_root}/build-sim/package.a -create -output #{output}` end def build_static_lib_for_watchos(static_libs, _defines, output) build_static_lib_for_ios(static_libs, _defines, output) end def build_static_lib_for_mac(static_libs, output) return if static_libs.count == 0 `libtool -static -o #{output} #{static_libs.join(' ')}` end def build_with_mangling(platform, options) UI.puts 'Mangling symbols' defines = Symbols.mangle_for_pod_dependencies(@spec.name, @static_sandbox_root) defines << " OTHER_CFLAGS='" << @spec.consumer(platform).compiler_flags.join(' ') << "'" UI.puts 'Building mangled framework' xcodebuild(defines, options) defines end def clean_directory_for_dynamic_build # Remove static headers to avoid duplicate declaration conflicts FileUtils.rm_rf("#{@static_sandbox_root}/Headers/Public/#{@spec.name}") FileUtils.rm_rf("#{@static_sandbox_root}/Headers/Private/#{@spec.name}") # Equivalent to removing derrived data FileUtils.rm_rf('Pods/build') end def compile(platform) defines = "GCC_PREPROCESSOR_DEFINITIONS='$(inherited) PodsDummy_Pods_#{@spec.name}=PodsDummy_PodPackage_#{@spec.name}'" defines << " OTHER_CFLAGS='" << @spec.consumer(platform).compiler_flags.join(' ') << "'" if platform.name == :ios options = ios_build_options end if platform.name == :watchos options = watchos_build_options UI.puts "add watchos options: #{options}" end xcodebuild(defines, options) if @mangle return build_with_mangling(platform, options) end defines end def copy_static_headers headers_source_root = "#{@public_headers_root}/#{@spec.name}" UI.puts("copy static public headers from #{headers_source_root}") Dir.glob("#{headers_source_root}/**/*.h"). each { |h| `ditto #{h} #{@fwk.headers_path}/Public/#{h.sub(headers_source_root, '')}` } end def copy_static_resources assets_path = _create_assets_dir file_accessor = Sandbox::FileAccessor.new(Pathname.new("Pods/#{@spec.name}"), @spec.consumer(:ios)) spec_resource_bundles = file_accessor.resource_bundles spec_resources = file_accessor.resources spec_resource_bundles.map do |bundle, resources| UI.puts("copy resource bundle #{bundle}.bundle to Assets") bundle_path = "#{assets_path}/#{bundle}.bundle/" `mkdir #{bundle_path}` resources.map { |resource| `cp -a #{resource} #{bundle_path}` } end puts "copy #{spec_resources.count} resource(s) to Assets" if spec_resources.count != 0 spec_resources.map do |resource| `cp -a #{resource} #{assets_path}` end end def _create_assets_dir assets_path = 'Assets' return assets_path if File.exist?(assets_path) `mkdir #{assets_path}` assets_path end def copy_headers headers_source_root = "#{@public_headers_root}/#{@spec.name}" Dir.glob("#{headers_source_root}/**/*.h"). each { |h| `ditto #{h} #{@fwk.headers_path}/#{h.sub(headers_source_root, '')}` } # If custom 'module_map' is specified add it to the framework distribution # otherwise check if a header exists that is equal to 'spec.name', if so # create a default 'module_map' one using it. if !@spec.module_map.nil? module_map_file = "#{@static_sandbox_root}/#{@spec.name}/#{@spec.module_map}" module_map = File.read(module_map_file) if Pathname(module_map_file).exist? elsif File.exist?("#{@public_headers_root}/#{@spec.name}/#{@spec.name}.h") module_map = <&1` else `cp -rp #{@static_sandbox_root}/build/*.bundle #{@fwk.resources_path} 2>&1` resources = expand_paths(@spec.consumer(platform).resources) if resources.count == 0 && bundles.count == 0 @fwk.delete_resources return end if resources.count > 0 `cp -rp #{resources.join(' ')} #{@fwk.resources_path}` end end end def create_framework(platform) @fwk = Framework::Tree.new(@spec.name, platform, @embedded) @fwk.make end def create_static_library(platform) @fwk = Framework::Tree.new(@spec.name, platform, @embedded) @fwk.make_static end def dependency_count count = @spec.dependencies.count @spec.subspecs.each do |subspec| count += subspec.dependencies.count end count end def expand_paths(path_specs) path_specs.map do |path_spec| Dir.glob(File.join(@source_dir, path_spec)) end end def static_libs_in_sandbox(build_dir = 'build') if @exclude_deps UI.puts 'Excluding dependencies' Dir.glob("#{@static_sandbox_root}/#{build_dir}/lib#{@spec.name}.a") else Dir.glob("#{@static_sandbox_root}/#{build_dir}/lib*.a") end end def static_linker_flags_in_sandbox linker_flags = static_libs_in_sandbox.map do |lib| lib.slice!('lib') lib_flag = lib.chomp('.a').split('/').last "-l#{lib_flag}" end linker_flags.reject { |e| e == "-l#{@spec.name}" || e == '-lPods-packager' } end def ios_build_options "ARCHS=\'x86_64 i386 arm64 armv7 armv7s\' OTHER_CFLAGS=\'-fembed-bitcode -Qunused-arguments\'" end def watchos_build_options "ARCHS=\'i386 arm64_32 armv7k\' OTHER_CFLAGS=\'-fembed-bitcode -Qunused-arguments\'" end def xcodebuild(defines = '', args = '', build_dir = 'build', target = 'Pods-packager', project_root = @static_sandbox_root, config = @config) if defined?(Pod::DONT_CODESIGN) args = "#{args} CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO" end command = "xcrun xcodebuild #{defines} #{args} CONFIGURATION_BUILD_DIR=#{build_dir} clean build -configuration #{config} -target #{target} -project #{project_root}/Pods.xcodeproj 2>&1" output = `#{command}`.lines.to_a if $?.exitstatus != 0 puts UI::BuildFailedReport.report(command, output) # Note: We use `Process.exit` here because it fires a `SystemExit` # exception, which gives the caller a chance to clean up before the # process terminates. # # See http://ruby-doc.org/core-1.9.3/Process.html#method-c-exit Process.exit end end end end