Module: CocoapodsJiffy

Defined in:
lib/cocoapods-jiffy.rb,
lib/cocoapods-jiffy.rb,
lib/cocoapods-jiffy.rb,
lib/cocoapods-jiffy/gem_version.rb,
lib/cocoapods-jiffy/post_install.rb

Constant Summary collapse

PLATFORMS =
{ 'iphonesimulator' => 'iOS',
'appletvsimulator' => 'tvOS',
'watchsimulator' => 'watchOS' }.freeze
PLATFORM_SYMBOL_HASH =
{
  ios: 'iphoneos',
  osx: 'osx',
  tvos: 'appletvos',
  watchos: 'watchos'
}.freeze
JIFFY_PODFILE_LOCK =
'Podfile.jiffy.lock'.freeze
PODFILE_LOCK =
'Podfile.lock'.freeze
XCODE_OUTPUT =
`xcodebuild -version`.lines
BUILD_TOOLS_VERSION =
XCODE_OUTPUT[0].strip + ' ' + XCODE_OUTPUT[1].strip.split(' ').last
CONFIGURATIONS =
%w(Debug Release).freeze
CONFIGURATION_MAPPINGS =
generate_configuration_mappings
VERSION =
'0.1.3'.freeze
@@build_jiffy =
ENV['BUILD_JIFFY'].to_i == 1
@@use_jiffy =
ENV['USE_JIFFY'].to_i == 1
@@dependencies_per_pod =
nil
@@podfile_path =
nil
@@cached_dependencies =
[]
@@normal_dependencies =
[]

Class Method Summary collapse

Class Method Details

.build_all_configurations_for_iosish_platform(sandbox, build_dir, target, device, simulator, destination, lockfile) ⇒ Object



12
13
14
15
16
# File 'lib/cocoapods-jiffy/post_install.rb', line 12

def self.build_all_configurations_for_iosish_platform(sandbox, build_dir, target, device, simulator, destination, lockfile)
  for configuration in CONFIGURATIONS
    build_for_iosish_platform(sandbox, build_dir, target, device, simulator, configuration, destination, lockfile)
  end
end

.build_all_configurations_for_osx(sandbox, target, _destination) ⇒ Object



18
19
20
21
22
# File 'lib/cocoapods-jiffy/post_install.rb', line 18

def self.build_all_configurations_for_osx(sandbox, target, _destination)
  for configuration in CONFIGURATIONS
    xcodebuild(sandbox, target, 'macosx', nil, configuration)
  end
end

.build_dependencies(installer_context, lockfile, sandbox, build_dir, destination) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/cocoapods-jiffy/post_install.rb', line 109

def self.build_dependencies(installer_context, lockfile, sandbox, build_dir, destination)
  targets = installer_context.umbrella_targets.select { |t| t.specs.any? }
  targets.each do |target|
    puts "Processing #{target.cocoapods_target_label.green}"

    case target.platform_name
    when :ios then build_all_configurations_for_iosish_platform(sandbox, build_dir, target, 'iphoneos', 'iphonesimulator', destination, lockfile)
    when :osx then build_all_configurations_for_osx(sandbox, target.cocoapods_target_label, destination, lockfile)
    when :tvos then build_all_configurations_for_iosish_platform(sandbox, build_dir, target, 'appletvos', 'appletvsimulator', destination, lockfile)
    when :watchos then build_all_configurations_for_iosish_platform(sandbox, build_dir, target, 'watchos', 'watchsimulator', destination, lockfile)
    else raise "Unknown platform '#{target.platform_name}'" end
  end
end

.build_for_iosish_platform(sandbox, build_dir, target, device, simulator, configuration, destination, lockfile) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/cocoapods-jiffy/post_install.rb', line 38

def self.build_for_iosish_platform(sandbox, build_dir, target, device, simulator, configuration, destination, lockfile)
  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

  spec_names.each do |root_name, module_name|
    commit = commit_for_pod(lockfile, root_name)

    cache_path = cache_dir(root_name, device, configuration, commit)
    destination_for_configuration = File.join(destination, configuration, device, root_name)
    if Dir.exist?(cache_path)
      puts "Skipping #{root_name} - #{device}"
    else
      begin
        puts "Building #{root_name.green} - #{device.green}"
        xcodebuild(sandbox, root_name, device, deployment_target, configuration, 'arm64 armv7 armv7s')
        xcodebuild(sandbox, root_name, simulator, deployment_target, configuration, 'i386 x86_64')
      rescue => exception
        puts "Error building #{root_name.red}"
        puts exception.to_s
        puts exception.backtrace
      end
      executable_path = "#{build_dir}/#{root_name}"
      device_lib = "#{build_dir}/#{configuration}-#{device}/#{root_name}/#{module_name}.framework/#{module_name}"
      device_framework_lib = File.dirname(device_lib)
      simulator_lib = "#{build_dir}/#{configuration}-#{simulator}/#{root_name}/#{module_name}.framework/#{module_name}"
      simulator_framework_lib = File.dirname(simulator_lib)
      license_path = File.join(device_framework_lib, 'LICENSE.md')
      podfile_path = File.join(cache_path, "#{root_name}.#{configuration}.podspec")

      next unless File.file?(device_lib) && File.file?(simulator_lib)

      lipo_log = `lipo -create -output #{executable_path} #{device_lib} #{simulator_lib}`
      puts lipo_log unless File.exist?(executable_path)

      FileUtils.cp_r File.join(simulator_framework_lib, '.'), device_framework_lib
      FileUtils.mv executable_path, device_lib
      begin
        FileUtils.mkdir_p cache_path
        puts "Writing dummy LICENSE.md -> #{license_path}"
        File.open(license_path, 'w') do |file|
          file.write('')
        end
        FileUtils.cp_r device_framework_lib, cache_path, remove_destination: true
        podspec_content = podspec_content(root_name, module_name, configuration)
        File.open(podfile_path, 'w') do |file|
          file.write(podspec_content)
        end
      rescue
        puts "Failed to copy #{device_framework_lib} #{cache_path}"
      end

      # FileUtils.rm simulator_lib if File.file?(simulator_lib)
      # FileUtils.rm device_lib if File.file?(device_lib)
    end

    puts "Copying #{cache_path} -> #{destination_for_configuration}"
    FileUtils.mkdir_p destination_for_configuration
    FileUtils.cp_r File.join(cache_path, '.'), destination_for_configuration, remove_destination: true
  end
end

.build_jiffyObject



40
41
42
# File 'lib/cocoapods-jiffy.rb', line 40

def self.build_jiffy
  @@build_jiffy
end

.build_jiffy=(v) ⇒ Object



44
45
46
# File 'lib/cocoapods-jiffy.rb', line 44

def self.build_jiffy=(v)
  @@build_jiffy = v
end

.cache_dir(podspec, device, configuration, commit) ⇒ Object



6
7
8
9
10
# File 'lib/cocoapods-jiffy/post_install.rb', line 6

def self.cache_dir(podspec, device, configuration, commit)
  home = ENV['HOME']
  raise "Home doesn't exist" unless Dir.exist?(home)
  File.join(home, '.cocoapods', 'jiffy-cache', BUILD_TOOLS_VERSION, podspec, commit, device, configuration)
end

.collect_dependencies(name) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/cocoapods-jiffy.rb', line 92

def self.collect_dependencies(name)
  if CocoapodsJiffy.dependencies_per_pod == nil
    return [name]
  end
  dependencies = CocoapodsJiffy.dependencies_per_pod[name]
  return [name] if dependencies.nil? || dependencies.length.zero?

  all_dependencies = [[name]] + dependencies.collect do |dep|
    collect_dependencies(dep)
  end

  # exclude subspecs, we know it's a subspec if it contains "/"
  calculated_dependencies = all_dependencies.flatten.uniq.select { |x| !x.include? '/' }
  calculated_dependencies
end

.commit_for_pod(lockfile, pod_name) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/cocoapods-jiffy/post_install.rb', line 24

def self.commit_for_pod(lockfile, pod_name)
  checkout_options = lockfile.checkout_options_for_pod_named(pod_name)
  checksum = lockfile.checksum(pod_name)
  commit = if !checkout_options.nil?
             checkout_options[:commit]
           elsif !checksum.nil?
             checksum
           else
             raise "Commit not found for #{pod_name}" if checkout_options.nil?
  end

  commit
end

.create_lockfile(installer_context, name = PODFILE_LOCK) ⇒ Object



22
23
24
# File 'lib/cocoapods-jiffy.rb', line 22

def self.create_lockfile(installer_context, name = PODFILE_LOCK)
  create_lockfile_for_path(installer_context.sandbox_root + "/../#{name}")
end

.create_lockfile_for_path(path) ⇒ Object



26
27
28
29
30
# File 'lib/cocoapods-jiffy.rb', line 26

def self.create_lockfile_for_path(path)
  lockfile_path = Pathname.new(path)
  lockfile = Pod::Lockfile.from_file(lockfile_path)
  lockfile
end

.dependencies_per_podObject



56
57
58
# File 'lib/cocoapods-jiffy.rb', line 56

def self.dependencies_per_pod
  @@dependencies_per_pod
end

.dependencies_per_pod=(v) ⇒ Object



60
61
62
# File 'lib/cocoapods-jiffy.rb', line 60

def self.dependencies_per_pod=(v)
  @@dependencies_per_pod = v
end

.ensure_dependencies_exclusiveObject



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/cocoapods-jiffy.rb', line 157

def self.ensure_dependencies_exclusive
  intersection = @@cached_dependencies.collect { |x| x[0] } & @@normal_dependencies.collect { |x| x[0] }

  unless intersection.empty?
    cached_dependencies_causing_issues = @@cached_dependencies.select { |dependency_name, _| intersection.include? dependency_name } .uniq
    normal_dependencies_causing_issues = @@normal_dependencies.select { |dependency_name, _| intersection.include? dependency_name } .uniq

    extract_description = lambda do |dependency_name, included_as_a_dependency_of|
      if dependency_name == included_as_a_dependency_of
        result = "        #{dependency_name}"
      else
        result = "        #{dependency_name} included as a dependency of #{included_as_a_dependency_of}"
      end

      result
    end

    cached_dependencies_causing_issues = cached_dependencies_causing_issues.collect(&extract_description)
    normal_dependencies_causing_issues = normal_dependencies_causing_issues.collect(&extract_description)

    cached_description = " >> dependencies causing issues that were included using `cachedpod`\n" + cached_dependencies_causing_issues.join("\n")
    normal_description = " >> dependencies causing issues that were included using `pod`\n" + normal_dependencies_causing_issues.join("\n")
    raise 'Found dependencies that were included as both `cachedpod` and `pod`.' + cached_description + "\n" + normal_description
  end
end

.ensure_podfile_jiffy_loaded(podfile_path) ⇒ Object



135
136
137
138
139
140
141
142
143
144
# File 'lib/cocoapods-jiffy.rb', line 135

def self.ensure_podfile_jiffy_loaded(podfile_path)
  unless dependencies_per_pod.nil?
    if podfile_path != podfile_path
      raise "Podfiles differ #{podfile_path} != @{podfile_path}"
    end
    return
  end

  parse_dependencies(File.join(podfile_path.parent.to_s, JIFFY_PODFILE_LOCK))
end

.generate_configuration_mappingsObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/cocoapods-jiffy.rb', line 72

def self.generate_configuration_mappings
  configuration_mappings = { 'Debug' => 'Debug', 'Release' => 'Release' }
  env_configurations = ''
  env_configurations = ENV['CONFIGURATIONS_JIFFY'] unless ENV['CONFIGURATIONS_JIFFY'].nil?
  environment_configuration_mappings = env_configurations.split(',').collect do |pair|
    parts = pair.split('=')
    result = [parts[0].strip, parts[1].strip]
    result
  end

  environment_configuration_mappings.each do |project_config, jiffy_prebuilt_config|
    configuration_mappings[project_config] = jiffy_prebuilt_config
  end
  configuration_mappings
end

.parse_dependencies(jiffy_lockfile_path) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/cocoapods-jiffy.rb', line 113

def self.parse_dependencies(jiffy_lockfile_path)
  raise "#{jiffy_lockfile_path} is missing, please run `BUILD_JIFFY pod install` first" unless File.exist?(jiffy_lockfile_path)
  lockfile = create_lockfile_for_path(jiffy_lockfile_path)

  dependencies_per_pod = {}

  lockfile.to_hash['PODS'].each do |pod|
    pod_name_version = pod.is_a?(String) ? pod : pod.keys.first
    dependencies = []
    unless pod.is_a?(String)
      dependencies = pod.values.first.collect do |dep|
        dependency_name = parse_name_from_name_version(dep)
        dependency_name
      end
    end
    pod_name = parse_name_from_name_version(pod_name_version)
    dependencies_per_pod[pod_name] = dependencies
  end

  @@dependencies_per_pod = dependencies_per_pod
end

.parse_name_from_name_version(name_version) ⇒ Object



108
109
110
111
# File 'lib/cocoapods-jiffy.rb', line 108

def self.parse_name_from_name_version(name_version)
  # Pod::Spec.name_and_version_from_string(
  name_version.split(' ').first
end

.podfile_pathObject



64
65
66
# File 'lib/cocoapods-jiffy.rb', line 64

def self.podfile_path
  @@podfile_path
end

.podfile_path=(v) ⇒ Object



68
69
70
# File 'lib/cocoapods-jiffy.rb', line 68

def self.podfile_path=(v)
  @@podfile_path = v
end

.podspec_content(pod_name, module_name, configuration) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cocoapods-jiffy/post_install.rb', line 123

def self.podspec_content(pod_name, module_name, configuration)
  <<-DESC
    Pod::Spec.new do |s|
    s.name             = "#{pod_name}.#{configuration}"
    s.version          = "1.0.0"
    s.summary          = "Caching podspec for #{pod_name}"
    s.description      = "Caching podspec for #{pod_name}"
    s.homepage         = "https://localhost.com/.cocoapods/cache/#{pod_name}"
    s.license          = 'MIT'
    s.author           = { "Krunoslav Zaher" => "[email protected]" }
    s.source           = { :path => "." }

    s.requires_arc          = true

    s.source_files          = '*.nothing'
    s.frameworks	         =  '#{module_name}'
    s.vendored_frameworks  = '#{module_name}.framework'
    end
  DESC
end

.post_install(installer_context) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/cocoapods-jiffy/post_install.rb', line 144

def self.post_install(installer_context)
  return unless CocoapodsJiffy.build_jiffy
  sandbox_root = Pathname(installer_context.sandbox_root)
  sandbox = Pod::Sandbox.new(sandbox_root)

  build_dir = sandbox_root.parent + 'build'
  destination = sandbox_root.parent + 'Pods'

  Pod::UI.puts 'Building frameworks'

  for configuration in CONFIGURATIONS
    destination_for_configuration = File.join(destination, configuration)
    Pod::UI.puts "Cleaning #{destination_for_configuration}"

    FileUtils.rm_r destination_for_configuration, force: true
    FileUtils.mkdir_p destination_for_configuration
  end

  puts 'SANDBOX parent: ' + sandbox_root.parent.to_s

  Dir.chdir(sandbox.project_path.dirname) do
    lockfile = create_lockfile(installer_context)
    build_dependencies(installer_context, lockfile, sandbox, build_dir, destination)
  end

  # to be able to figure out what were original dependencies
  puts 'Persisting lockfile Podfile.lock -> Podfile.jiffy.lock'
  FileUtils.cp PODFILE_LOCK, JIFFY_PODFILE_LOCK
end

.register_cached_dependency(dependency_name, included_as_a_dependency_of) ⇒ Object



149
150
151
# File 'lib/cocoapods-jiffy.rb', line 149

def self.register_cached_dependency(dependency_name, included_as_a_dependency_of)
  @@cached_dependencies << [dependency_name, included_as_a_dependency_of]
end

.register_normal_dependency(dependency_name, included_as_a_dependency_of) ⇒ Object



153
154
155
# File 'lib/cocoapods-jiffy.rb', line 153

def self.register_normal_dependency(dependency_name, included_as_a_dependency_of)
  @@normal_dependencies << [dependency_name, included_as_a_dependency_of]
end

.use_jiffyObject



48
49
50
# File 'lib/cocoapods-jiffy.rb', line 48

def self.use_jiffy
  @@use_jiffy
end

.use_jiffy=(v) ⇒ Object



52
53
54
# File 'lib/cocoapods-jiffy.rb', line 52

def self.use_jiffy=(v)
  @@use_jiffy = v
end

.xcodebuild(sandbox, target, sdk = 'macosx', deployment_target = nil, configuration, _arch) ⇒ Object



101
102
103
104
105
106
107
# File 'lib/cocoapods-jiffy/post_install.rb', line 101

def self.xcodebuild(sandbox, target, sdk = 'macosx', deployment_target = nil, configuration, _arch)
  puts sandbox.project_path
  args = %W(-project #{sandbox.project_path} -scheme #{target} -configuration #{configuration} -sdk #{sdk}) + ['ONLY_ACTIVE_ARCH=NO', 'BITCODE_GENERATION_MODE=bitcode', 'CODE_SIGNING_REQUIRED=NO', 'CODE_SIGN_IDENTITY=']
  platform = PLATFORMS[sdk]
  args += Fourflusher::SimControl.new.destination(:oldest, platform, deployment_target) unless platform.nil?
  Pod::Executable.execute_command 'xcodebuild', args, true
end