Class: Gym::XcodebuildFixes

Inherits:
Object
  • Object
show all
Defined in:
lib/gym/xcodebuild_fixes/swift_fix.rb,
lib/gym/xcodebuild_fixes/watchkit_fix.rb,
lib/gym/xcodebuild_fixes/watchkit2_fix.rb,
lib/gym/xcodebuild_fixes/generic_archive_fix.rb,
lib/gym/xcodebuild_fixes/package_application_fix.rb

Class Method Summary collapse

Class Method Details

.check_for_swift(pcg) ⇒ Object

Returns true if swift.

Parameters:

  • the

    PackageCommandGenerator

Returns:

  • true if swift



79
80
81
82
83
# File 'lib/gym/xcodebuild_fixes/swift_fix.rb', line 79

def check_for_swift(pcg)
  UI.verbose "Checking for Swift framework"
  default_swift_libs = "#{pcg.appfile_path}/Frameworks/libswift.*" # note the extra ., this is a string representation of a regexp
  zip_entries_matching(pcg.ipa_path, /#{default_swift_libs}/).count > 0
end

.generic_archive_fixObject

Determine IPAs for the Watch App which aren’t inside of a containing iOS App and removes them.

In the future it may be nice to modify the plist file for the archive itself so that it points to the correct IPA as well.

This is a workaround for this bug github.com/CocoaPods/CocoaPods/issues/4178



14
15
16
17
18
19
20
21
22
23
# File 'lib/gym/xcodebuild_fixes/generic_archive_fix.rb', line 14

def generic_archive_fix
  UI.verbose "Looking For Orphaned WatchKit2 Applications"

  Dir.glob("#{BuildCommandGenerator.archive_path}/Products/Applications/*.app").each do |app_path|
    if is_watchkit_app?(app_path)
      UI.verbose "Removing Orphaned WatchKit2 Application #{app_path}"
      FileUtils.rm_rf(app_path)
    end
  end
end

.is_watchkit_app?(app_path) ⇒ Boolean

Does this application have a WatchKit target

Returns:

  • (Boolean)


26
27
28
29
# File 'lib/gym/xcodebuild_fixes/generic_archive_fix.rb', line 26

def is_watchkit_app?(app_path)
  plist_path = "#{app_path}/Info.plist"
  `/usr/libexec/PlistBuddy -c 'Print :DTSDKName' #{plist_path.shellescape} 2>&1`.match(/^\s*watchos2\.\d+\s*$/) != nil
end

.patch_package_applicationObject

Fix PackageApplication Perl script by Xcode to create the IPA from the archive



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/gym/xcodebuild_fixes/package_application_fix.rb', line 5

def patch_package_application
  require 'fileutils'

  # Initialization
  @patched_package_application_path = File.join("/tmp", "PackageApplication4Gym")

  return @patched_package_application_path if File.exist?(@patched_package_application_path)

  Dir.mktmpdir do |tmpdir|
    # Check current set of PackageApplication MD5 hashes
    require 'digest'

    path = File.join(Gym::ROOT, "lib/assets/package_application_patches/PackageApplication_MD5")
    expected_md5_hashes = File.read(path).split("\n")

    # If that location changes, search it using xcrun --sdk iphoneos -f PackageApplication
    package_application_path = "#{Xcode.xcode_path}/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication"

    UI.crash!("Unable to patch the `PackageApplication` script bundled in Xcode. This is not supported.") unless expected_md5_hashes.include?(Digest::MD5.file(package_application_path).hexdigest)

    # Duplicate PackageApplication script to PackageApplication4Gym
    FileUtils.copy_file(package_application_path, @patched_package_application_path)

    # Apply patches to PackageApplication4Gym from patches folder
    Dir[File.join(Gym::ROOT, "lib/assets/package_application_patches/*.diff")].each do |patch|
      UI.verbose "Applying Package Application patch: #{File.basename(patch)}"
      command = ["patch '#{@patched_package_application_path}' < '#{patch}'"]
      Runner.new.print_command(command, "Applying Package Application patch: #{File.basename(patch)}") if $verbose

      FastlaneCore::CommandExecutor.execute(command: command,
                                          print_all: false,
                                      print_command: $verbose,
                                              error: proc do |output|
                                                ErrorHandler.handle_package_error(output)
                                              end)
    end
  end

  return @patched_package_application_path # Return path to the patched PackageApplication
end

.should_apply_watchkit1_fix?Boolean

Should only be applied if watchkit app is not a watchkit2 app

Returns:

  • (Boolean)


35
36
37
# File 'lib/gym/xcodebuild_fixes/watchkit_fix.rb', line 35

def should_apply_watchkit1_fix?
  watchkit? && !Gym::XcodebuildFixes.watchkit2?
end

.swift_library_fixObject

Determine whether it is a Swift project and, eventually, include all required libraries to copy from Xcode’s toolchain directory. Since there’s no “xcodebuild” target to do just that, it is done post-build when exporting an archived build.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
# File 'lib/gym/xcodebuild_fixes/swift_fix.rb', line 12

def swift_library_fix
  require 'fileutils'

  return if check_for_swift(PackageCommandGenerator)

  UI.verbose "Packaging up the Swift Framework as the current app is a Swift app"
  ipa_swift_frameworks = Dir["#{PackageCommandGenerator.appfile_path}/Frameworks/libswift*"]

  Dir.mktmpdir do |tmpdir|
    # Copy all necessary Swift libraries to a temporary "SwiftSupport" directory so that we can
    # easily add it to the .ipa later.
    swift_support = File.join(tmpdir, "SwiftSupport")

    Dir.mkdir(swift_support)

    ipa_swift_frameworks.each do |path|
      framework = File.basename(path)

      begin
        toolchain_dir = toolchain_dir_name_for_toolchain(Gym.config[:toolchain])

        from = File.join(Xcode.xcode_path, "Toolchains/#{toolchain_dir}/usr/lib/swift/iphoneos/#{framework}")
        to = File.join(swift_support, framework)

        UI.verbose("Copying Swift framework from '#{from}'")
        FileUtils.copy_file(from, to)
      rescue => ex
        UI.error("Error copying over framework file. Please try running gym without the legacy build API enabled")
        UI.error("For more information visit https://github.com/fastlane/fastlane/issues/5863")
        UI.error("Missing file #{path} inside #{Xcode.xcode_path}")

        if Gym.config[:toolchain].nil?
          UI.important("If you're using Swift 2.3, but already updated to Xcode 8")
          UI.important("try adding the following parameter to your gym call:")
          UI.success("gym(use_legacy_build_api: true, toolchain: :swift_2_3)")
          UI.message("or")
          UI.success("gym --use_legacy_build_api --toolchain swift_2_3")
        end

        UI.user_error!(ex)
      end
    end

    # Add "SwiftSupport" to the .ipa archive
    Dir.chdir(tmpdir) do
      command_parts = ["zip --recurse-paths '#{PackageCommandGenerator.ipa_path}' SwiftSupport"]
      command_parts << "> /dev/null" unless $verbose

      FastlaneCore::CommandExecutor.execute(command: command_parts,
                                          print_all: false,
                                      print_command: !Gym.config[:silent],
                                              error: proc do |output|
                                                ErrorHandler.handle_package_error(output)
                                              end)
    end
  end
end

.toolchain_dir_name_for_toolchain(toolchain) ⇒ Object



70
71
72
73
74
75
# File 'lib/gym/xcodebuild_fixes/swift_fix.rb', line 70

def toolchain_dir_name_for_toolchain(toolchain)
  return "Swift_2.3.xctoolchain" if toolchain == "com.apple.dt.toolchain.Swift_2_3"

  UI.error("No specific folder was found for toolchain #{toolchain}. Using the default toolchain location.") if toolchain
  return "XcodeDefault.xctoolchain"
end

.watchkit2?Boolean

Does this application have a WatchKit target

Returns:

  • (Boolean)


28
29
30
31
32
# File 'lib/gym/xcodebuild_fixes/watchkit2_fix.rb', line 28

def watchkit2?
  Dir["#{PackageCommandGenerator.appfile_path}/**/*.plist"].any? do |plist_path|
    `/usr/libexec/PlistBuddy -c 'Print DTSDKName' '#{plist_path}' 2>&1`.match(/^\s*watchos2\.\d+\s*$/)
  end
end

.watchkit2_fixObject

Determine whether this app has WatchKit2 support and manually package up the WatchKit2 framework



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/gym/xcodebuild_fixes/watchkit2_fix.rb', line 5

def watchkit2_fix
  return unless watchkit2?

  UI.verbose "Adding WatchKit2 support"

  Dir.mktmpdir do |tmpdir|
    # Make watchkit support directory
    watchkit_support = File.join(tmpdir, "WatchKitSupport2")
    Dir.mkdir(watchkit_support)

    # Copy WK from Xcode into WatchKitSupport2
    FileUtils.copy_file("#{Xcode.xcode_path}/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/Library/Application Support/WatchKit/WK", File.join(watchkit_support, "WK"))

    # Add "WatchKitSupport2" to the .ipa archive
    Dir.chdir(tmpdir) do
      abort unless system %(zip --recurse-paths "#{PackageCommandGenerator.ipa_path}" "WatchKitSupport2" > /dev/null)
    end

    UI.verbose "Successfully added WatchKit2 support"
  end
end

.watchkit?Boolean

Does this application have a WatchKit target

Returns:

  • (Boolean)


28
29
30
31
32
# File 'lib/gym/xcodebuild_fixes/watchkit_fix.rb', line 28

def watchkit?
  Dir["#{PackageCommandGenerator.appfile_path}/**/*.plist"].any? do |plist_path|
    `/usr/libexec/PlistBuddy -c 'Print WKWatchKitApp' '#{plist_path}' 2>&1`.strip == 'true'
  end
end

.watchkit_fixObject

Determine whether this app has WatchKit support and manually package up the WatchKit framework



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/gym/xcodebuild_fixes/watchkit_fix.rb', line 5

def watchkit_fix
  return unless should_apply_watchkit1_fix?

  UI.verbose "Adding WatchKit support"

  Dir.mktmpdir do |tmpdir|
    # Make watchkit support directory
    watchkit_support = File.join(tmpdir, "WatchKitSupport")
    Dir.mkdir(watchkit_support)

    # Copy WK from Xcode into WatchKitSupport
    FileUtils.copy_file("#{Xcode.xcode_path}/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/Library/Application Support/WatchKit/WK", File.join(watchkit_support, "WK"))

    # Add "WatchKitSupport" to the .ipa archive
    Dir.chdir(tmpdir) do
      abort unless system %(zip --recurse-paths "#{PackageCommandGenerator.ipa_path}" "WatchKitSupport" > /dev/null)
    end

    UI.verbose "Successfully added WatchKit support"
  end
end

.wrap_xcodebuildObject

Wrap xcodebuild to work-around ipatool dependecy to system ruby



47
48
49
50
# File 'lib/gym/xcodebuild_fixes/package_application_fix.rb', line 47

def wrap_xcodebuild
  require 'fileutils'
  @wrapped_xcodebuild_path ||= File.join(Gym::ROOT, "lib/assets/wrap_xcodebuild/xcbuild-safe.sh")
end

.zip_entries_matching(zipfile, file_pattern) ⇒ Object

return the entries (files or directories) in the zip matching the pattern

Parameters:

  • zipfile

    a zipfile

Returns:

  • the files or directories matching the pattern



88
89
90
91
92
93
94
95
96
# File 'lib/gym/xcodebuild_fixes/swift_fix.rb', line 88

def zip_entries_matching(zipfile, file_pattern)
  files = []
  Zip::File.open(zipfile) do |zip_file|
    zip_file.each do |entry|
      files << entry.name if entry.name.force_encoding("utf-8").match file_pattern
    end
  end
  files
end