module PodBuilder

This class is the model that PodBuilder uses for every pod spec. The model is instantiated from Pod::Specification

This file contains the logic that generates the .podspec files that are placed along to the prebuild frameworks under PodBuilder/Prebuilt

Constants

VERSION

Public Class Methods

add_lockfile() click to toggle source
# File lib/pod_builder/core.rb, line 186
def self.add_lockfile
  lockfile_path = Configuration.lockfile_path

  if File.exist?(lockfile_path)
    if pid = File.read(lockfile_path)
      begin
        if Process.getpgid(pid)
          if Configuration.deterministic_build    
            raise "\n\nAnother PodBuilder pending task is running\n".red    
          else
            raise "\n\nAnother PodBuilder pending task is running on this project\n".red    
          end
        end
      rescue
      end
    end  
  end

  File.write(lockfile_path, Process.pid, mode: "w")
end
add_simulator_conditional(path) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 199
def self.add_simulator_conditional(path)
  file_content = File.read(path)
  content = %{
    #if TARGET_OS_SIMULATOR
    #{file_content}
    #endif
  }        
  File.write(path, content)
end
basepath(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 59
def self.basepath(child = "")
  if child.nil?
    return nil
  end

  return "#{Configuration.base_path}/#{child}".gsub("//", "/").gsub(/\/$/, '')
end
build_for_iosish_platform_framework(sandbox, build_dir, target, device, simulator, configuration, deterministic_build) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 26
def self.build_for_iosish_platform_framework(sandbox, build_dir, target, device, simulator, configuration, deterministic_build)    
  dsym_device_folder = File.join(build_dir, "dSYM", device)
  dsym_simulator_folder = File.join(build_dir, "dSYM", simulator)
  FileUtils.mkdir_p(dsym_device_folder)
  FileUtils.mkdir_p(dsym_simulator_folder)
  
  deployment_target = target.platform_deployment_target
  target_label = target.cocoapods_target_label
  
  xcodebuild(sandbox, target_label, device, deployment_target, configuration, deterministic_build, [], {})
  excluded_archs = ["i386"] # Fixes https://github.com/Subito-it/PodBuilder/issues/17
  excluded_archs += ["arm64"] # Exclude apple silicon slice
  xcodebuild(sandbox, target_label, simulator, deployment_target, configuration, deterministic_build, excluded_archs, {})
  
  spec_names = target.specs.map { |spec| [spec.root.name, spec.root.module_name] }.uniq
  spec_names.each do |root_name, module_name|
    device_base = "#{build_dir}/#{configuration}-#{device}/#{root_name}" 
    device_lib = "#{device_base}/#{module_name}.framework/#{module_name}"
    device_dsym = "#{device_base}/#{module_name}.framework.dSYM"
    device_framework_lib = File.dirname(device_lib)
    device_swift_header_path = "#{device_framework_lib}/Headers/#{module_name}-Swift.h"
    
    simulator_base = "#{build_dir}/#{configuration}-#{simulator}/#{root_name}"
    simulator_lib = "#{simulator_base}/#{module_name}.framework/#{module_name}"
    simulator_dsym = "#{simulator_base}/#{module_name}.framework.dSYM"
    simulator_framework_lib = File.dirname(simulator_lib)
    simulator_swift_header_path = "#{simulator_framework_lib}/Headers/#{module_name}-Swift.h"
    
    next unless File.file?(device_lib) && File.file?(simulator_lib)
    
    # Starting with Xcode 12b3 the simulator binary contains an arm64 slice as well which conflict with the one in the device_lib
    # when creating the fat library. A naive workaround is to remove the arm64 from the simulator_lib however this is wrong because
    # we might actually need to have 2 separated arm64 slices, one for simulator and one for device each built with different
    # compile time directives (e.g #if targetEnvironment(simulator))
    #
    # For the time being we remove the arm64 slice bacause otherwise the `xcrun lipo -create -output ...` would fail.
    if `xcrun lipo -info #{simulator_lib}`.include?("arm64")
      `xcrun lipo -remove arm64 #{simulator_lib} -o #{simulator_lib}`
    end
    
    raise "Lipo failed on #{device_lib}" unless system("xcrun lipo -create -output #{device_lib} #{device_lib} #{simulator_lib}")
    
    merge_header_into(device_swift_header_path, simulator_swift_header_path)
    
    # Merge device framework into simulator framework (so that e.g swift Module folder is merged)
    # letting device framework files overwrite simulator ones
    FileUtils.cp_r(File.join(device_framework_lib, "."), simulator_framework_lib) 
    source_lib = File.dirname(simulator_framework_lib)
    
    FileUtils.mv(device_dsym, dsym_device_folder) if File.exist?(device_dsym)
    FileUtils.mv(simulator_dsym, dsym_simulator_folder) if File.exist?(simulator_dsym)
    
    FileUtils.mv(source_lib, build_dir)
    
    # Remove frameworks leaving dSYMs
    FileUtils.rm_rf(device_framework_lib) 
    FileUtils.rm_rf(simulator_framework_lib)
  end
end
build_for_iosish_platform_lib(sandbox, build_dir, target, device, simulator, configuration, deterministic_build, prebuilt_root_paths) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 86
def self.build_for_iosish_platform_lib(sandbox, build_dir, target, device, simulator, configuration, deterministic_build, prebuilt_root_paths)    
  deployment_target = target.platform_deployment_target
  target_label = target.cocoapods_target_label
  
  spec_names = target.specs.map { |spec| [spec.root.name, spec.root.module_name] }.uniq
  
  xcodebuild(sandbox, target_label, device, deployment_target, configuration, deterministic_build, [], prebuilt_root_paths)
  excluded_archs = ["arm64"] # Exclude Apple silicon slice
  xcodebuild(sandbox, target_label, simulator, deployment_target, configuration, deterministic_build, excluded_archs, prebuilt_root_paths)
  
  spec_names.each do |root_name, module_name|
    simulator_base = "#{build_dir}/#{configuration}-#{simulator}/#{root_name}"
    simulator_lib = "#{simulator_base}/lib#{root_name}.a"
    
    device_base = "#{build_dir}/#{configuration}-#{device}/#{root_name}" 
    device_lib = "#{device_base}/lib#{root_name}.a"
    
    unless File.file?(device_lib) && File.file?(simulator_lib)
      next
    end
    
    # Starting with Xcode 12b3 the simulator binary contains an arm64 slice as well which conflict with the one in the device_lib
    # when creating the fat library. A naive workaround is to remove the arm64 from the simulator_lib however this is wrong because
    # we might actually need to have 2 separated arm64 slices, one for simulator and one for device each built with different
    # compile time directives (e.g #if targetEnvironment(simulator))
    #
    # For the time being we remove the arm64 slice bacause otherwise the `xcrun lipo -create -output ...` would fail.
    if `xcrun lipo -info #{simulator_lib}`.include?("arm64")
      `xcrun lipo -remove arm64 #{simulator_lib} -o #{simulator_lib}`
    end
    
    raise "Lipo failed on #{device_lib}" unless system("xcrun lipo -create -output #{device_lib} #{device_lib} #{simulator_lib}")
    
    device_headers = Dir.glob("#{device_base}/**/*.h")
    simulator_headers = Dir.glob("#{simulator_base}/**/*.h")
    device_headers.each do |device_path|
      simulator_path = device_path.gsub(device_base, simulator_base)
      
      merge_header_into(device_path, simulator_path)
    end 
    simulator_only_headers = simulator_headers - device_headers.map { |t| t.gsub(device_base, simulator_base) }
    simulator_only_headers.each do |path|
      add_simulator_conditional(path)
      dir_name = File.dirname(path)
      destination_folder = dir_name.gsub(simulator_base, device_base)
      FileUtils.mkdir_p(destination_folder)
      FileUtils.cp(path, destination_folder)
    end
    
    swiftmodule_path = "#{simulator_base}/#{root_name}.swiftmodule"
    if File.directory?(swiftmodule_path)
      FileUtils.cp_r("#{swiftmodule_path}/.", "#{device_base}/#{root_name}.swiftmodule")
    end
    
    if File.exist?("#{device_base}/#{root_name}.swiftmodule")
      # This is a swift pod with a swiftmodule in the root of the prebuilt folder
    else
      # Objective-C pods have the swiftmodule generated under Pods/Headers/Public
      public_headers_path = "#{Configuration.build_path}/Pods/Headers/Public/#{root_name}"
      module_public_headers_path = "#{Configuration.build_path}/Pods/Headers/Public/#{module_name}"  
      if public_headers_path.downcase != module_public_headers_path.downcase && File.directory?(public_headers_path) && File.directory?(module_public_headers_path)
        # For pods with module_name != name we have to move the modulemap files to the root_name one
        module_public_headers_path = "#{Configuration.build_path}/Pods/Headers/Public/#{module_name}"  
        FileUtils.cp_r("#{module_public_headers_path}/.", public_headers_path, :remove_destination => true)
      end
      Dir.glob("#{public_headers_path}/**/*.*").each do |path|
        destination_folder = "#{device_base}/Headers" + path.gsub(public_headers_path, "")
        destination_folder = File.dirname(destination_folder)
        FileUtils.mkdir_p(destination_folder)
        FileUtils.cp(path, destination_folder)
      end          
    end
    
    destination_path = "#{build_dir}/#{root_name}"
    if Dir.glob("#{device_base}/**/*.{a,framework,h}").count > 0
      FileUtils.mv(device_base, destination_path)
      
      module_maps = Dir.glob("#{destination_path}/**/*.modulemap")
      module_map_device_base = device_base.gsub(/^\/private/, "") + "/"
      module_maps.each do |module_map|
        content = File.read(module_map)
        content.gsub!(module_map_device_base, "")
        File.write(module_map, content)
      end      
    end
  end
end
buildpath_dsympath(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 93
def self.buildpath_dsympath(child = "")
  if child.nil?
    return nil
  end

  path = "#{Configuration.build_path}/dSYM"
  if child.length > 0
    path += "/#{child}"
  end

  return path
end
buildpath_prebuiltpath(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 80
def self.buildpath_prebuiltpath(child = "")
  if child.nil?
    return nil
  end

  path = "#{Configuration.build_path}/Prebuilt"
  if child.length > 0
    path += "/#{child}"
  end

  return path
end
clean_basepath() click to toggle source
# File lib/pod_builder/core.rb, line 169
def self.clean_basepath
  if path = PodBuilder::find_xcodeproj
    PodBuilder::safe_rm_rf(basepath(File.basename(path)))      
  end
  if path = PodBuilder::find_xcodeworkspace
    PodBuilder::safe_rm_rf(basepath(File.basename(path)))      
  end

  PodBuilder::safe_rm_rf(basepath("Pods"))
end
dsympath(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 106
def self.dsympath(child = "")
  if child.nil?
    return nil
  end

  path = basepath("dSYM")
  if child.length > 0
    path += "/#{child}"
  end

  return path
end
enable_debug_information(project_path, configuration) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 274
def self.enable_debug_information(project_path, configuration)
  project = Xcodeproj::Project.open(project_path)
  project.targets.each do |target|
    config = target.build_configurations.find { |config| config.name.eql? configuration }
    config.build_settings["DEBUG_INFORMATION_FORMAT"] = "dwarf-with-dsym"
    config.build_settings["ONLY_ACTIVE_ARCH"] = "NO"
  end
  project.save
end
execute_command(executable, command, raise_on_failure = true, environmental_variables = {}) click to toggle source

Copy paste implementation from CocoaPods internals to be able to call poopen3 passing environmental variables

# File lib/pod_builder/rome/post_install.rb, line 233
def self.execute_command(executable, command, raise_on_failure = true, environmental_variables = {})
  bin = Pod::Executable.which!(executable)
  
  command = command.map(&:to_s)
  full_command = "#{bin} #{command.join(' ')}"
  
  stdout = Pod::Executable::Indenter.new
  stderr = Pod::Executable::Indenter.new
  
  status = popen3(bin, command, stdout, stderr, environmental_variables)
  stdout = stdout.join
  stderr = stderr.join
  output = stdout + stderr
  unless status.success?
    if raise_on_failure
      raise "#{full_command}\n\n#{output}"
    else
      UI.message("[!] Failed: #{full_command}".red)
    end
  end
  
  output
end
find_xcodeproj() click to toggle source
# File lib/pod_builder/core.rb, line 125
def self.find_xcodeproj
  unless @@xcodeproj_path.nil?
    return @@xcodeproj_path
  end
  project_name = File.basename(find_xcodeworkspace, ".*")

  xcodeprojects = Dir.glob("#{home}/**/#{project_name}.xcodeproj").select { |x| 
    folder_in_home = x.gsub(home, "")
    !folder_in_home.include?("/Pods/") && !x.include?(PodBuilder::basepath("Sources")) && !x.include?(PodBuilder::basepath + "/") 
  }
  raise "\n\nxcodeproj not found!".red if xcodeprojects.count == 0
  raise "\n\nFound multiple xcodeproj:\n#{xcodeprojects.join("\n")}".red if xcodeprojects.count > 1

  @@xcodeproj_path = xcodeprojects.first
  return @@xcodeproj_path
end
find_xcodeworkspace() click to toggle source
# File lib/pod_builder/core.rb, line 142
def self.find_xcodeworkspace
  unless @@xcodeworkspace_path.nil?
    return @@xcodeworkspace_path
  end

  xcworkspaces = Dir.glob("#{home}/**/#{Configuration.project_name}*.xcworkspace").select { |x| 
    folder_in_home = x.gsub(home, "")
    !folder_in_home.include?("/Pods/") && !x.include?(PodBuilder::basepath("Sources")) && !x.include?(PodBuilder::basepath + "/") && !x.include?(".xcodeproj/")
  }
  raise "\n\nxcworkspace not found!".red if xcworkspaces.count == 0
  raise "\n\nFound multiple xcworkspaces:\n#{xcworkspaces.join("\n")}".red if xcworkspaces.count > 1

  @@xcodeworkspace_path = xcworkspaces.first
  return @@xcodeworkspace_path
end
git_rootpath() click to toggle source
# File lib/pod_builder/core.rb, line 22
def self.git_rootpath
  return `git rev-parse --show-toplevel`.strip()
end
gitignoredfiles() click to toggle source
# File lib/pod_builder/core.rb, line 53
def self.gitignoredfiles
  Dir.chdir(git_rootpath) do
    return `git status --ignored -s | grep "^\!\!" | cut -c4-`.strip().split("\n")
  end
end
merge_header_into(device_file, simulator_file) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 174
def self.merge_header_into(device_file, simulator_file)
  unless File.exist?(device_file) || File.exist?(simulator_file)
    return
  end
  
  device_content = File.file?(device_file) ? File.read(device_file) : ""
  simulator_content = File.file?(simulator_file) ? File.read(simulator_file) : ""
  merged_content = %{
    #if TARGET_OS_SIMULATOR
    // ->
    
    #{simulator_content}
    
    // ->
    #else
    // ->
    
    #{device_content}
    
    // ->
    #endif
  }        
  File.write(device_file, merged_content)
end
popen3(bin, command, stdout, stderr, environmental_variables) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 257
def self.popen3(bin, command, stdout, stderr, environmental_variables)
  require 'open3'
  Open3.popen3(environmental_variables, bin, *command) do |i, o, e, t|
    Pod::Executable::reader(o, stdout)
    Pod::Executable::reader(e, stderr)
    i.close
    
    status = t.value
    
    o.flush
    e.flush
    sleep(0.01)
    
    status
  end
end
prebuiltpath(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 67
def self.prebuiltpath(child = "")
  if child.nil?
    return nil
  end

  path = basepath("Prebuilt")
  if child.length > 0
    path += "/#{child}"
  end

  return path
end
prepare_basepath() click to toggle source
# File lib/pod_builder/core.rb, line 158
def self.prepare_basepath
  workspace_path = PodBuilder::find_xcodeworkspace
  project_path = PodBuilder::find_xcodeproj
  if workspace_path && project_path
    FileUtils.mkdir_p(basepath("Pods/Target Support Files"))
    FileUtils.cp_r(workspace_path, basepath)   
    FileUtils.cp_r(project_path, basepath)   
    FileUtils.rm_f(basepath("Podfile.lock"))
  end
end
project_path(child = "") click to toggle source
# File lib/pod_builder/core.rb, line 119
def self.project_path(child = "")
  project = PodBuilder::find_xcodeworkspace
  
  return project ? "#{File.dirname(project)}/#{child}".gsub("//", "/").gsub(/\/$/, '') : nil
end
remove_lockfile() click to toggle source
# File lib/pod_builder/core.rb, line 207
def self.remove_lockfile
  lockfile_path = Configuration.lockfile_path

  if File.exist?(lockfile_path)
    FileUtils.rm(lockfile_path)
  end
end
safe_rm_rf(path) click to toggle source
# File lib/pod_builder/core.rb, line 26
def self.safe_rm_rf(path)
  unless File.exist?(path)
    return
  end

  unless File.directory?(path)
    FileUtils.rm(path)

    return 
  end

  current_dir = Dir.pwd

  Dir.chdir(path)

  rootpath = git_rootpath()
  raise "\n\nNo git repository found in '#{path}', can't delete files!\n".red if rootpath.empty? && !path.start_with?(Configuration.build_base_path)

  FileUtils.rm_rf(path)

  if File.exist?(current_dir)
    Dir.chdir(current_dir)
  else
    Dir.chdir(basepath)
  end
end
system_swift_version() click to toggle source
# File lib/pod_builder/core.rb, line 180
def self.system_swift_version
  swift_version = `swiftc --version | grep -o 'swiftlang-.*\s'`.strip()
  raise "\n\nUnsupported swift compiler version, expecting `swiftlang` keyword in `swiftc --version`".red if swift_version.length == 0
  return swift_version
end
xcodebuild(sandbox, target, sdk='macosx', deployment_target=nil, configuration, deterministic_build, exclude_archs, prebuilt_root_paths) click to toggle source
# File lib/pod_builder/rome/post_install.rb, line 209
def self.xcodebuild(sandbox, target, sdk='macosx', deployment_target=nil, configuration, deterministic_build, exclude_archs, prebuilt_root_paths)
  args = %W(-project #{sandbox.project_path.realdirpath} -scheme #{target} -configuration #{configuration} -sdk #{sdk})
  supported_platforms = { 'iphonesimulator' => 'iOS', 'appletvsimulator' => 'tvOS', 'watchsimulator' => 'watchOS' }
  if platform = supported_platforms[sdk]
    args += Fourflusher::SimControl.new.destination(:oldest, platform, deployment_target) unless platform.nil?
  end
  
  xcodebuild_version = `xcodebuild -version | head -n1 | awk '{print $2}'`.strip().to_f
  if exclude_archs.count > 0 && xcodebuild_version >= 12.0
    args += ["EXCLUDED_ARCHS=#{exclude_archs.join(" ")}"]
  end
  prebuilt_root_paths.each do |k, v|
    args += ["#{k.upcase.gsub("-", "_")}_PREBUILT_ROOT=#{v.gsub(/ /, '\ ')}"]
  end
  
  environmental_variables = {}
  if deterministic_build
    environmental_variables["ZERO_AR_DATE"] = "1"
  end
  
  execute_command 'xcodebuild', args, true, environmental_variables
end

Private Class Methods

home() click to toggle source
# File lib/pod_builder/core.rb, line 217
def self.home
  rootpath = git_rootpath
  raise "\n\nNo git repository found in current folder `#{Dir.pwd}`!\n".red if rootpath.empty?
  return rootpath
end