Class: PodBuilder::Install

Inherits:
Object
  • Object
show all
Defined in:
lib/pod_builder/install.rb

Class Method Summary collapse

Class Method Details

.activate_pod_schemeObject



491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/pod_builder/install.rb', line 491

def self.activate_pod_scheme
  if scheme_file = Dir.glob("#{Configuration.build_path}/Pods/**/xcschememanagement.plist").first
    plist = CFPropertyList::List.new(:file => scheme_file)
    data = CFPropertyList.native_types(plist.value)

    if !data.dig("SchemeUserState", "Pods-DummyTarget.xcscheme").nil?
      data["SchemeUserState"]["Pods-DummyTarget.xcscheme"]["isShown"] = true
    end

    plist.value = CFPropertyList.guess(data)
    plist.save(scheme_file, CFPropertyList::List::FORMAT_BINARY)  
  end
end

.build_folder_hash(podfile_item, exclude_files) ⇒ Object



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/pod_builder/install.rb', line 437

def self.build_folder_hash(podfile_item, exclude_files)
  if podfile_item.is_development_pod
    if Pathname.new(podfile_item.path).absolute?
      item_path = podfile_item.path
    else 
      item_path = PodBuilder::basepath(podfile_item.path)
    end
    
    rootpath = PodBuilder::git_rootpath
    file_hashes = []
    Dir.glob("#{item_path}/**/*", File::FNM_DOTMATCH) do |path|
      unless File.file?(path)
        next
      end
      
      path = File.expand_path(path)
      rel_path = path.gsub(rootpath, "")[1..]
      unless exclude_files.include?(rel_path)
        file_hashes.push(Digest::MD5.hexdigest(File.read(path)))
      end
    end
    
    return Digest::MD5.hexdigest(file_hashes.join)
  else
    # Pod folder might be under .gitignore
    item_path = "#{Configuration.build_path}/Pods/#{podfile_item.root_name}"
    if File.directory?(item_path)
      return `find '#{item_path}' -type f -print0 | sort -z | xargs -0 shasum | shasum | cut -d' ' -f1`.strip()
    else
      return nil
    end
  end
end

.build_folder_hash_in_prebuilt_info_file(podfile_item) ⇒ Object



426
427
428
429
430
431
432
433
434
435
# File 'lib/pod_builder/install.rb', line 426

def self.build_folder_hash_in_prebuilt_info_file(podfile_item)
  prebuilt_info_path = PodBuilder::prebuiltpath(File.join(podfile_item.root_name, Configuration.prebuilt_info_filename))
  
  if File.exist?(prebuilt_info_path)
    data = JSON.parse(File.read(prebuilt_info_path))
    return data['build_folder_hash']  
  else
    return nil
  end
end

.copy_development_pods_source_code(podfile_content, podfile_items) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/pod_builder/install.rb', line 259

def self.copy_development_pods_source_code(podfile_content, podfile_items)      
  # Development pods are normally built/integrated without moving files from their original paths.
  # It is important that CocoaPods compiles the files under Configuration.build_path in order that 
  # DWARF debug info reference to this constant path. Doing otherwise breaks the assumptions that 
  # makes  the `generate_lldbinit` command work.
  development_pods = podfile_items.select { |x| x.is_development_pod }      
  development_pods.each do |podfile_item|
    destination_path = "#{Configuration.build_path}/Pods/#{podfile_item.name}"
    FileUtils.mkdir_p(destination_path)
    
    if Pathname.new(podfile_item.path).absolute?
      FileUtils.cp_r("#{podfile_item.path}/.", destination_path)
    else 
      FileUtils.cp_r("#{PodBuilder::basepath(podfile_item.path)}/.", destination_path)
    end
    
    unless Configuration.build_using_repo_paths
      podfile_content.gsub!("'#{podfile_item.path}'", "'#{destination_path}'")
    end
  end
  
  return podfile_content
end

.copy_prebuilt_items(podfile_items) ⇒ Object



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/pod_builder/install.rb', line 367

def self.copy_prebuilt_items(podfile_items)
  FileUtils.mkdir_p(PodBuilder::prebuiltpath)

  non_prebuilt_items = podfile_items.reject(&:is_prebuilt)

  pod_names = non_prebuilt_items.map(&:root_name).uniq

  pod_names.reject! { |t| 
    folder_path = PodBuilder::buildpath_prebuiltpath(t)
    File.directory?(folder_path) && Dir.empty?(folder_path) # When using prebuilt items we end up with empty folders
  } 

  pod_names.each do |pod_name|   
    root_name = pod_name.split("/").first

    items_to_delete = Dir.glob("#{PodBuilder::prebuiltpath(root_name)}/**/*")
    items_to_delete.each { |t| PodBuilder::safe_rm_rf(t) }
  end

  # Now copy
  pod_names.each do |pod_name|        
    root_name = pod_name.split("/").first
    source_path = PodBuilder::buildpath_prebuiltpath(root_name)

    unless File.directory?(source_path)
      puts "Prebuilt items for #{pod_name} not found".blue
      next
    end

    unless Dir.glob("#{source_path}/**/*").select { |t| File.file?(t) }.empty?
      destination_folder = PodBuilder::prebuiltpath(root_name)
      FileUtils.mkdir_p(destination_folder)
      FileUtils.cp_r("#{source_path}/.", destination_folder)  
    end
  end
  
  # Folder won't exist if no dSYM were generated (all static libs)
  if File.directory?(PodBuilder::buildpath_dsympath)
    FileUtils.mkdir_p(PodBuilder::dsympath)
    FileUtils.cp_r(PodBuilder::buildpath_dsympath, PodBuilder::basepath)
  end
end

.downloadObject



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/pod_builder/install.rb', line 347

def self.download
  puts "Downloading Pods source code".yellow
  
  CLAide::Command::PluginManager.load_plugins("cocoapods")
  
  Dir.chdir(Configuration.build_path) do
    Pod::UserInterface::config.silent = true
    
    config = Pod::Config.new()
    installer = Pod::Installer.new(config.sandbox, config.podfile, config.lockfile)
    installer.repo_update = false
    installer.update = false
    installer.prepare
    installer.resolve_dependencies
    installer.download_dependencies
    
    Pod::UserInterface::config.silent = false
  end
end

.init_git(path) ⇒ Object



418
419
420
421
422
423
424
# File 'lib/pod_builder/install.rb', line 418

def self.init_git(path)
  current_dir = Dir.pwd
  
  Dir.chdir(path)
  system("git init")
  Dir.chdir(current_dir)
end

.installObject



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/pod_builder/install.rb', line 327

def self.install
  puts "Preparing build".yellow
  
  CLAide::Command::PluginManager.load_plugins("cocoapods")
  
  Dir.chdir(Configuration.build_path) do
    config = Pod::Config.new()
    installer = Pod::Installer.new(config.sandbox, config.podfile, config.lockfile)
    installer.repo_update = false
    installer.update = false
    
    install_start_time = Time.now

    installer.install! 
    install_time = Time.now - install_start_time
    
    puts "Build completed in #{install_time.to_i} seconds".blue
  end
end

.license_specifiersObject



247
248
249
250
251
252
253
254
255
256
257
# File 'lib/pod_builder/install.rb', line 247

def self.license_specifiers
  acknowledge_file = "#{Configuration.build_path}/Pods/Target Support Files/Pods-DummyTarget/Pods-DummyTarget-acknowledgements.plist"
  unless File.exist?(acknowledge_file)
    raise "\n\nLicense file not found".red
  end
  
  plist = CFPropertyList::List.new(:file => acknowledge_file)
  data = CFPropertyList.native_types(plist.value)
  
  return data["PreferenceSpecifiers"] || []
end

.podfile(podfile_content, podfile_items, build_configuration) ⇒ Object

This method will generate prebuilt data by building from “/tmp/pod_builder/Podfile”



142
143
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/pod_builder/install.rb', line 142

def self.podfile(podfile_content, podfile_items, build_configuration)
  puts "Preparing build Podfile".yellow
  
  PodBuilder::safe_rm_rf(Configuration.build_path)
  FileUtils.mkdir_p(Configuration.build_path)
  
  init_git(Configuration.build_path) # this is needed to be able to call safe_rm_rf
  
  podfile_content = copy_development_pods_source_code(podfile_content, podfile_items)
  
  podfile_content = Podfile.update_path_entries(podfile_content, Install.method(:podfile_path_transform))
  podfile_content = Podfile.update_project_entries(podfile_content, Install.method(:podfile_path_transform))
  podfile_content = Podfile.update_require_entries(podfile_content, Install.method(:podfile_path_transform))
  
  podfile_path = File.join(Configuration.build_path, "Podfile")
  File.write(podfile_path, podfile_content)
  
  begin  
    lock_file = "#{Configuration.build_path}/pod_builder.lock"
    FileUtils.touch(lock_file)
    
    prebuilt_entries = use_prebuilt_entries_for_unchanged_pods(podfile_path, podfile_items)
    
    install
    
    copy_prebuilt_items(podfile_items - prebuilt_entries)   

    prebuilt_info = prebuilt_info(podfile_items)
    licenses = license_specifiers()
    
    if !OPTIONS.has_key?(:debug)
      PodBuilder::safe_rm_rf(Configuration.build_path)
    end  
    
    return InstallResult.new(licenses, prebuilt_info)
  rescue Exception => e
    if File.directory?("#{Configuration.build_path}/Pods/Pods.xcodeproj")
      activate_pod_scheme()

      if ENV["DEBUGGING"]
        system("xed #{Configuration.build_path}/Pods")  
      elsif !OPTIONS.has_key?(:no_stdin_available)
        confirm = ask("\n\nOh no! Something went wrong during prebuild phase! Do you want to open the prebuild project to debug the error, you will need to add and run the Pods-Dummy scheme? [Y/N] ".red) { |yn| yn.limit = 1, yn.validate = /[yn]/i }

        if confirm.downcase == 'y'
          system("xed #{Configuration.build_path}/Pods")  
        end
      end
    end
    
    raise e
  ensure        
    FileUtils.rm(lock_file) if File.exist?(lock_file)
  end
end

.podfile_path_transform(path) ⇒ Object



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/pod_builder/install.rb', line 471

def self.podfile_path_transform(path)
  if Configuration.build_using_repo_paths
    return File.expand_path(PodBuilder::basepath(path))
  else
    use_absolute_paths = true
    podfile_path = File.join(Configuration.build_path, "Podfile")
    original_basepath = PodBuilder::basepath
    
    podfile_base_path = Pathname.new(File.dirname(podfile_path))
    
    original_path = Pathname.new(File.join(original_basepath, path))
    replace_path = original_path.relative_path_from(podfile_base_path)
    if use_absolute_paths
      replace_path = replace_path.expand_path(podfile_base_path)
    end
    
    return replace_path
  end
end

.prebuilt_info(podfile_items) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/pod_builder/install.rb', line 198

def self.prebuilt_info(podfile_items)
  gitignored_files = PodBuilder::gitignoredfiles
  
  swift_version = PodBuilder::system_swift_version

  write_prebuilt_info_filename_gitattributes
  
  ret = Hash.new
  root_names = podfile_items.reject(&:is_prebuilt).map(&:root_name).uniq
  root_names.each do |prebuilt_name| 
    path = PodBuilder::prebuiltpath(prebuilt_name)
    
    unless File.directory?(path)
      puts "Prebuilt items for #{prebuilt_name} not found".blue
      next
    end
    
    unless podfile_item = podfile_items.detect { |t| t.name == prebuilt_name } || podfile_items.detect { |t| t.root_name == prebuilt_name }
      puts "Prebuilt items for #{prebuilt_name} not found #2".blue
      next
    end
    
    podbuilder_file = File.join(path, Configuration.prebuilt_info_filename)
    entry = podfile_item.entry(true, false)
    
    data = {}
    data["entry"] = entry
    data["is_prebuilt"] = podfile_item.is_prebuilt  
    if Dir.glob(File.join(path, "#{podfile_item.prebuilt_rel_path}/Headers/*-Swift.h")).count > 0
      data["swift_version"] = swift_version
    end
    
    specs = podfile_items.select { |x| x.module_name == podfile_item.module_name }
    subspecs_deps = specs.map(&:dependency_names).flatten
    subspec_self_deps = subspecs_deps.select { |x| x.start_with?("#{prebuilt_name}/") }
    data["specs"] = (specs.map(&:name) + subspec_self_deps).uniq
    data["is_static"] = podfile_item.is_static
    data["original_compile_path"] = Pathname.new(Configuration.build_path).realpath.to_s
    if hash = build_folder_hash(podfile_item, gitignored_files)
      data["build_folder_hash"] = hash
    end
    
    ret.merge!({ podbuilder_file => data })
  end

  return ret
end

.use_prebuilt_entries_for_unchanged_pods(podfile_path, podfile_items) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/pod_builder/install.rb', line 283

def self.use_prebuilt_entries_for_unchanged_pods(podfile_path, podfile_items)
  podfile_content = File.read(podfile_path)
  
  replaced_items = []

  if OPTIONS.has_key?(:force_rebuild)
    podfile_content.gsub!("%%%prebuilt_root_paths%%%", "{}")
  else
    download # Copy files under #{Configuration.build_path}/Pods so that we can determine build folder hashes

    gitignored_files = PodBuilder::gitignoredfiles

    prebuilt_root_paths = Hash.new

    # Replace prebuilt entries in Podfile for Pods that have no changes in source code which will avoid rebuilding them
    items = podfile_items.group_by { |t| t.root_name }.map { |k, v| v.first } # Return one podfile_item per root_name
    items.each do |item|
      podspec_path = item.prebuilt_podspec_path
      if last_build_folder_hash = build_folder_hash_in_prebuilt_info_file(item)
        if last_build_folder_hash == build_folder_hash(item, gitignored_files)
          puts "No changes detected to '#{item.root_name}', will skip rebuild".blue

          replaced_items.push(item)

          podfile_items.select { |t| t.root_name == item.root_name }.each do |replace_item|
            replace_regex = "pod '#{Regexp.quote(replace_item.name)}', .*"
            replace_line_found = podfile_content =~ /#{replace_regex}/i
            raise "\n\nFailed finding pod entry for '#{replace_item.name}'".red unless replace_line_found
            podfile_content.gsub!(/#{replace_regex}/, replace_item.prebuilt_entry(true, true))

            prebuilt_root_paths[replace_item.root_name] = PodBuilder::prebuiltpath
          end
        end
      end
    end

    podfile_content.gsub!("%%%prebuilt_root_paths%%%", prebuilt_root_paths.to_s)
  end

  File.write(podfile_path, podfile_content)

  return replaced_items
end

.write_prebuilt_info_filename_gitattributesObject



410
411
412
413
414
415
416
# File 'lib/pod_builder/install.rb', line 410

def self.write_prebuilt_info_filename_gitattributes
  gitattributes_path = PodBuilder::basepath(".gitattributes")
  expected_attributes = ["#{Configuration.configuration_filename} binary"].join
  unless File.exists?(gitattributes_path) && File.read(gitattributes_path).include?(expected_attributes)
    File.write(gitattributes_path, expected_attributes, mode: 'a')
  end
end