Skip to content
This repository has been archived by the owner on Oct 2, 2018. It is now read-only.

Added support for export_options and app thinning #194

Merged
merged 1 commit into from
Feb 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ output_directory "./build" # store the ipa in this folder
output_name "MyApp" # the name of the ipa file
```

## Export options

Since Xcode 7, `gym` is using new Xcode API which allows us to specify export options using `plist` file. By default `gym` creates this file for you and you are able to modify some parameters by using `export_method`, `export_team_id`, `include_symbols` or `include_bitcode`. If you want to have more options, like creating manifest file or app thinning, you can provide your own `plist` file:

```ruby
export_options "./ExportOptions.plist"
```

or you can provide hash of values directly in the `Gymfile`:

```ruby
export_options(
method: "ad-hoc",
manifest: {
appURL: "https://example.com/My App.ipa",
},
thinning: "<thin-for-all-variants>"
)
```

For the list of available options run `xcodebuild -help`.

# Automating the whole process

`gym` works great together with [fastlane](https://fastlane.tools), which connects all deployment tools into one streamlined workflow.
Expand Down
19 changes: 19 additions & 0 deletions examples/standard/ExampleExport.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>embedOnDemandResourcesAssetPacksInBundle</key>
<true/>
<key>manifest</key>
<dict>
<key>appURL</key>
<string>https://www.example.com/Example.ipa</string>
<key>displayImageURL</key>
<string>https://www.example.com/display.png</string>
<key>fullSizeImageURL</key>
<string>https://www.example.com/fullSize.png</string>
</dict>
<key>method</key>
<string>ad-hoc</string>
</dict>
</plist>
3 changes: 0 additions & 3 deletions lib/gym/detect_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ def self.set_additional_default_values

config[:output_name] ||= Gym.project.app_name

# we do it here, since the value is optional and should be pre-filled by fastlane if necessary
config[:export_method] ||= "app-store"

return config
end

Expand Down
16 changes: 16 additions & 0 deletions lib/gym/generators/package_command_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ def dsym_path
generator.dsym_path
end

def manifest_path
generator.manifest_path
end

def app_thinning_path
generator.app_thinning_path
end

def app_thinning_size_report_path
generator.app_thinning_size_report_path
end

def apps_path
generator.apps_path
end

# The generator we need to use for the currently used Xcode version
def generator
if Gym.config[:use_legacy_build_api]
Expand Down
16 changes: 16 additions & 0 deletions lib/gym/generators/package_command_generator_legacy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ def ipa_path
def dsym_path
Dir[BuildCommandGenerator.archive_path + "/**/*.app.dSYM"].last
end

def manifest_path
""
end

def app_thinning_path
""
end

def app_thinning_size_report_path
""
end

def apps_path
""
end
end
end
end
95 changes: 88 additions & 7 deletions lib/gym/generators/package_command_generator_xcode7.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,20 @@ def temporary_output_path
def ipa_path
unless Gym.cache[:ipa_path]
path = Dir[File.join(temporary_output_path, "*.ipa")].last
ErrorHandler.handle_empty_archive unless path

Gym.cache[:ipa_path] = File.join(temporary_output_path, "#{Gym.config[:output_name]}.ipa")
FileUtils.mv(path, Gym.cache[:ipa_path]) if File.expand_path(path).downcase != File.expand_path(Gym.cache[:ipa_path]).downcase
if path
# Try to find IPA file in the output directory, used when app thinning was not set
Gym.cache[:ipa_path] = File.join(temporary_output_path, "#{Gym.config[:output_name]}.ipa")
FileUtils.mv(path, Gym.cache[:ipa_path]) if File.expand_path(path).downcase != File.expand_path(Gym.cache[:ipa_path]).downcase
elsif Dir.exist?(apps_path)
# Try to find "generic" IPA file inside "Apps" folder, used when app thinning was set
files = Dir[File.join(apps_path, "*.ipa")]
# Generic IPA file doesn't have suffix so its name is the shortest
path = files.min_by(&:length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why and what for do we have all those line changes here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we export IPA files without app thinning, the Xcode generates one IPA file inside the output folder. But when the app thinning is used, the Xcode creates Apps folder inside the output folder. All the IPA files (thinned and one generic) are stored inside the Apps directory. So
If the IPA file exists in the output directory (line 46), the "old" logic is used.
If the IPA file doesn't exists in the output directory (line 49), gym tries to find all IPA files in the Apps folder. One of those files is always the "generic" IPA file, it doesn't contain any suffix, so its name is the shortest one (line 51).

I hope this will be enough. Or, maybe I should add some comments?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, adding this comment to the source code would be good 👍

Gym.cache[:ipa_path] = File.join(temporary_output_path, "#{Gym.config[:output_name]}.ipa")
FileUtils.cp(path, Gym.cache[:ipa_path]) if File.expand_path(path).downcase != File.expand_path(Gym.cache[:ipa_path]).downcase
else
ErrorHandler.handle_empty_archive unless path
end
end
Gym.cache[:ipa_path]
end
Expand All @@ -62,16 +72,87 @@ def config_path
return Gym.cache[:config_path]
end

# The path to the manifest plist file
def manifest_path
Gym.cache[:manifest_path] ||= File.join(temporary_output_path, "manifest.plist")
end

# The path to the app-thinning plist file
def app_thinning_path
Gym.cache[:app_thinning] ||= File.join(temporary_output_path, "app-thinning.plist")
end

# The path to the App Thinning Size Report file
def app_thinning_size_report_path
Gym.cache[:app_thinning_size_report] ||= File.join(temporary_output_path, "App Thinning Size Report.txt")
end

# The path to the Apps folder
def apps_path
Gym.cache[:apps_path] ||= File.join(temporary_output_path, "Apps")
end

private

def normalize_export_options(hash)
# Normalize some values
hash[:onDemandResourcesAssetPacksBaseURL] = URI.escape(hash[:onDemandResourcesAssetPacksBaseURL]) if hash[:onDemandResourcesAssetPacksBaseURL]
if hash[:manifest]
hash[:manifest][:appURL] = URI.escape(hash[:manifest][:appURL]) if hash[:manifest][:appURL]
hash[:manifest][:displayImageURL] = URI.escape(hash[:manifest][:displayImageURL]) if hash[:manifest][:displayImageURL]
hash[:manifest][:fullSizeImageURL] = URI.escape(hash[:manifest][:fullSizeImageURL]) if hash[:manifest][:fullSizeImageURL]
hash[:manifest][:assetPackManifestURL] = URI.escape(hash[:manifest][:assetPackManifestURL]) if hash[:manifest][:assetPackManifestURL]
end
hash
end

def keys_to_symbols(hash)
# Convert keys to symbols
hash = hash.each_with_object({}) do |(k, v), memo|
memo[k.to_sym] = v
memo
end
hash
end

def read_export_options
# Reads export options
if Gym.config[:export_options]
if Gym.config[:export_options].kind_of?(Hash)
# Reads options from hash
hash = normalize_export_options(Gym.config[:export_options])
else
# Reads optoins from file
hash = Plist.parse_xml(Gym.config[:export_options])
# Convert keys to symbols
hash = keys_to_symbols(hash)
end

# Saves configuration for later use
Gym.config[:export_method] ||= hash[:method]
Gym.config[:include_symbols] = hash[:uploadSymbols] if Gym.config[:include_symbols].nil?
Gym.config[:include_bitcode] = hash[:uploadBitcode] if Gym.config[:include_bitcode].nil?
Gym.config[:export_team_id] ||= hash[:teamID]
else
hash = {}
# Sets default values
Gym.config[:export_method] ||= "app-store"
Gym.config[:include_symbols] = true if Gym.config[:include_symbols].nil?
Gym.config[:include_bitcode] = false if Gym.config[:include_bitcode].nil?
end
hash
end

def config_content
require 'plist'

hash = { method: Gym.config[:export_method] }
hash = read_export_options

# Overrides export options if needed
hash[:method] = Gym.config[:export_method]
if Gym.config[:export_method] == 'app-store'
hash[:uploadSymbols] = (Gym.config[:include_symbols] ? true : false)
hash[:uploadBitcode] = (Gym.config[:include_bitcode] ? true : false)
hash[:uploadSymbols] = (Gym.config[:include_symbols] ? true : false) unless Gym.config[:include_symbols].nil?
hash[:uploadBitcode] = (Gym.config[:include_bitcode] ? true : false) unless Gym.config[:include_bitcode].nil?
end
hash[:teamID] = Gym.config[:export_team_id] if Gym.config[:export_team_id]

Expand Down
17 changes: 13 additions & 4 deletions lib/gym/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ def self.plain_options
short_option: "-m",
env_name: "GYM_INCLUDE_SYMBOLS",
description: "Should the ipa file include symbols?",
default_value: true,
is_string: false),
is_string: false,
optional: true),
FastlaneCore::ConfigItem.new(key: :include_bitcode,
short_option: "-z",
env_name: "GYM_INCLUDE_BITCODE",
description: "Should the ipa include bitcode?",
default_value: false,
is_string: false),
is_string: false,
optional: true),
FastlaneCore::ConfigItem.new(key: :use_legacy_build_api,
env_name: "GYM_USE_LEGACY_BUILD_API",
description: "Don't use the new API because of https://openradar.appspot.com/radar?id=4952000420642816",
Expand All @@ -113,6 +113,15 @@ def self.plain_options
av = %w(app-store ad-hoc package enterprise development developer-id)
UI.user_error!("Unsupported export_method, must be: #{av}") unless av.include?(value)
end),
FastlaneCore::ConfigItem.new(key: :export_options,
env_name: "GYM_EXPORT_OPTIONS",
description: "Specifies path to export options plist. User xcodebuild -help to print the full set of available options",
is_string: false,
optional: true,
conflicting_options: [:use_legacy_build_api],
conflict_block: proc do |value|
UI.user_error!("'#{value.key}' must be false to use 'export_options'")
end),

# Very optional
FastlaneCore::ConfigItem.new(key: :archive_path,
Expand Down
56 changes: 55 additions & 1 deletion lib/gym/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ def run
package_app
fix_package
compress_and_move_dsym
move_ipa
path = move_ipa
move_manifest
move_app_thinning
move_app_thinning_size_report
move_apps_folder

path
elsif Gym.project.mac?
compress_and_move_dsym
copy_mac_app
Expand Down Expand Up @@ -148,6 +154,54 @@ def copy_mac_app
app_path
end

# Move the manifest.plist if exists into the output directory
def move_manifest
if File.exist?(PackageCommandGenerator.manifest_path)
FileUtils.mv(PackageCommandGenerator.manifest_path, File.expand_path(Gym.config[:output_directory]), force: true)
manifest_path = File.join(File.expand_path(Gym.config[:output_directory]), File.basename(PackageCommandGenerator.manifest_path))

UI.success "Successfully exported the manifest.plist file:"
UI.message manifest_path
manifest_path
end
end

# Move the app-thinning.plist file into the output directory
def move_app_thinning
if File.exist?(PackageCommandGenerator.app_thinning_path)
FileUtils.mv(PackageCommandGenerator.app_thinning_path, File.expand_path(Gym.config[:output_directory]), force: true)
app_thinning_path = File.join(File.expand_path(Gym.config[:output_directory]), File.basename(PackageCommandGenerator.app_thinning_path))

UI.success "Successfully exported the app-thinning.plist file:"
UI.message app_thinning_path
app_thinning_path
end
end

# Move the App Thinning Size Report.txt file into the output directory
def move_app_thinning_size_report
if File.exist?(PackageCommandGenerator.app_thinning_size_report_path)
FileUtils.mv(PackageCommandGenerator.app_thinning_size_report_path, File.expand_path(Gym.config[:output_directory]), force: true)
app_thinning_size_report_path = File.join(File.expand_path(Gym.config[:output_directory]), File.basename(PackageCommandGenerator.app_thinning_size_report_path))

UI.success "Successfully exported the App Thinning Size Report.txt file:"
UI.message app_thinning_size_report_path
app_thinning_size_report_path
end
end

# Move the Apps folder to the output directory
def move_apps_folder
if Dir.exist?(PackageCommandGenerator.apps_path)
FileUtils.mv(PackageCommandGenerator.apps_path, File.expand_path(Gym.config[:output_directory]), force: true)
apps_path = File.join(File.expand_path(Gym.config[:output_directory]), File.basename(PackageCommandGenerator.apps_path))

UI.success "Successfully exported Apps folder:"
UI.message apps_path
apps_path
end
end

private

def find_archive_path
Expand Down
Loading