gym 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16d37d8d48ab649fc02d99958515229f50444a33
4
- data.tar.gz: c7ac3402ef0e98ccbaf7e1656a97fc4b89340a0c
3
+ metadata.gz: 574af8b98bbf18a7cd50ea0df99abf0a56614635
4
+ data.tar.gz: d0479a56afa1949ef434976da023a69fb6a56e5b
5
5
  SHA512:
6
- metadata.gz: 2250554abb1d9f2ff99738c8a95a7ca8f7c79f0587116f8119e70ff96cdfb801ce62ed0c229e44aff446d6d07210d6c5ce1e3e36e1ad4b3c988cf3ed341eca13
7
- data.tar.gz: 1f60d9e7173d84c3eca50a3d66ee37c84e181cde2315c5136f2d90d822f8c6d60ddbabe29be122a2901d3f6a044c99e799a36d2ad893b76c44549841c30c5d7f
6
+ metadata.gz: 7b14141ac66187610e817a34f1940a68d4ecf531942b625d267b8d6618afd6ea43320c53bba7399919018a15f51f6301afd6e03ed51aa43284d16612ccedb489
7
+ data.tar.gz: 6b0046422e07785ae133f0b7ea9edeb43ce524d01f769c8aeb1629a258e48ff37321a29336905ce08e5f397a2cb9ad5b8590fe74aca730bc3596eeddd2fe47a7
data/README.md CHANGED
@@ -1 +1,198 @@
1
- #### Still super secret
1
+ <h3 align="center">
2
+ <a href="https://github.com/KrauseFx/fastlane">
3
+ <img src="assets/fastlane.png" width="150" />
4
+ <br />
5
+ fastlane
6
+ </a>
7
+ </h3>
8
+ <p align="center">
9
+ <a href="https://github.com/KrauseFx/deliver">deliver</a> &bull;
10
+ <a href="https://github.com/KrauseFx/snapshot">snapshot</a> &bull;
11
+ <a href="https://github.com/KrauseFx/frameit">frameit</a> &bull;
12
+ <a href="https://github.com/KrauseFx/PEM">PEM</a> &bull;
13
+ <a href="https://github.com/fastlane/sigh">sigh</a> &bull;
14
+ <a href="https://github.com/KrauseFx/produce">produce</a> &bull;
15
+ <a href="https://github.com/KrauseFx/cert">cert</a> &bull;
16
+ <a href="https://github.com/KrauseFx/codes">codes</a> &bull;
17
+ <a href="https://github.com/fastlane/spaceship">spaceship</a> &bull;
18
+ <a href="https://github.com/fastlane/pilot">pilot</a> &bull;
19
+ <a href="https://github.com/fastlane/boarding">boarding</a> &bull;
20
+ <b>gym</b>
21
+ </p>
22
+ -------
23
+
24
+ <p align="center">
25
+ <img src="assets/gym.png">
26
+ </p>
27
+
28
+ gym
29
+ ============
30
+
31
+ [![Twitter: @KauseFx](https://img.shields.io/badge/contact-@KrauseFx-blue.svg?style=flat)](https://twitter.com/KrauseFx)
32
+ [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/fastlane/gym/blob/master/LICENSE)
33
+ [![Gem](https://img.shields.io/gem/v/gym.svg?style=flat)](http://rubygems.org/gems/gym)
34
+ [![Build Status](https://img.shields.io/travis/fastlane/gym/master.svg?style=flat)](https://travis-ci.org/fastlane/gym)
35
+
36
+ ###### Building your app has never been easier
37
+
38
+ Get in contact with the developer on Twitter: [@KrauseFx](https://twitter.com/KrauseFx)
39
+
40
+ -------
41
+ <p align="center">
42
+ <a href="#whats-gym">Features</a> &bull;
43
+ <a href="#installation">Installation</a> &bull;
44
+ <a href="#usage">Usage</a> &bull;
45
+ <a href="#tips">Tips</a> &bull;
46
+ <a href="#need-help">Need help?</a>
47
+ </p>
48
+
49
+ -------
50
+
51
+ <h5 align="center"><code>gym</code> is part of <a href="https://fastlane.tools">fastlane</a>: connect all deployment tools into one streamlined workflow.</h5>
52
+
53
+ # What's gym?
54
+
55
+ `gym` builds and packages iOS apps for you. It takes care of all the heavy lifting and makes it super easy to generate a signed `ipa` file :muscle:
56
+
57
+ `gym` is a replacement for [shenzhen](https://github.com/nomad/shenzhen).
58
+
59
+ ### Before `gym`
60
+
61
+ ```
62
+ xcodebuild clean archive -archivePath build/MyApp \
63
+ -scheme MyApp
64
+ xcodebuild -exportArchive \
65
+ -exportFormat ipa \
66
+ -archivePath "build/MyApp.xcarchive" \
67
+ -exportPath "build/MyApp.ipa" \
68
+ -exportProvisioningProfile "ProvisioningProfileName"
69
+ ```
70
+
71
+ ### With `gym`
72
+
73
+ ```
74
+ gym
75
+ ```
76
+
77
+ ### Why `gym`?
78
+
79
+ `gym` uses the latest APIs to build and sign your application which results in much faster build times.
80
+
81
+ | Gym Features
82
+ --------------------------|------------------------------------------------------------
83
+ :rocket: | `gym` builds 30% faster than other build tools like [shenzhen](https://github.com/nomad/shenzhen)
84
+ :checkered_flag: | Beautiful inline build output
85
+ :book: | helps you resolve common build errors like code signing issues
86
+ :mountain_cableway: | Sensible defaults: Automatically detect the project, its schemes and more
87
+ :link: | Works perfectly with [fastlane](https://fastlane.tools) and other tools
88
+ :package: | Automatically generates an `ipa` and a compressed `dSYM` file
89
+ :bullettrain_side: | Don't remember any complicated build commands, just `gym`
90
+ :wrench: | Easy and dynamic configuration using parameters and environment variables
91
+ :floppy_disk: | Store common build settings in a `Gymfile`
92
+ :computer: | All archives are stored and accessible in the Xcode Organisier
93
+
94
+ ![/assets/gymScreenshot.png](/assets/gymScreenshot.png)
95
+
96
+ -----
97
+
98
+ ![/assets/gym.gif](/assets/gym.gif)
99
+
100
+ # Installation
101
+
102
+ This tool is still work in progress. You can already try it by cloning the repo and running
103
+
104
+ sudo gem install gym
105
+
106
+ Make sure, you have the latest version of the Xcode command line tools installed:
107
+
108
+ xcode-select --install
109
+
110
+ # Usage
111
+
112
+ gym
113
+
114
+ That's all you need to build your application. If you want more control, here are some available parameters:
115
+
116
+ gym --workspace "Example.xcworkspace" --scheme "AppName" --clean
117
+
118
+ For a list of all available parameters use
119
+
120
+ gym --help
121
+
122
+ # Gymfile
123
+
124
+ Since you might want to manually trigger a new build but don't want to specify all the parameters every time, you can store your defaults in a so called `Gymfile`.
125
+
126
+ Run `gym init` to create a new configuration file. Example:
127
+
128
+ ```ruby
129
+ scheme "Example"
130
+
131
+ sdk "9.0"
132
+
133
+ output_directory "./build" # store the ipa in this folder
134
+ output_name "MyApp" # the name of the ipa file
135
+ ```
136
+
137
+ # How does it work?
138
+
139
+ `gym` uses the latest APIs to build and sign your application. The 2 main components are
140
+
141
+ - `xcodebuild`
142
+ - [xcpretty](https://github.com/supermarin/xcpretty)
143
+
144
+ When you run `gym` without the `--silent` mode it will print out every command it executes.
145
+
146
+ To build the archive `gym` uses the following command:
147
+
148
+ ```
149
+ set -o pipefail && \
150
+ xcodebuild -scheme 'Example' \
151
+ -project './Example.xcodeproj' \
152
+ -configuration 'Release' \
153
+ -destination 'generic/platform=iOS' \
154
+ -archivePath '/Users/felixkrause/Library/Developer/Xcode/Archives/2015-08-11/ExampleProductName 2015-08-11 18.15.30.xcarchive' \
155
+ archive | xcpretty
156
+ ```
157
+
158
+
159
+ After building the archive it is being checked by `gym`. If it's valid, it gets packaged up and signed into an `ipa` file
160
+
161
+ ```
162
+ xcodebuild -exportArchive -archivePath \
163
+ '/Users/felixkrause/Library/Developer/Xcode/Archives/2015-08-11/ExampleProductName 2015-08-11 18.15.30.xcarchive' \
164
+ exportFormat ipa \
165
+ -exportPath '/Users/felixkrause/Library/Developer/Xcode/Archives/2015-08-11/ExampleProductName.ipa' \
166
+ -exportProvisioningProfile 'Profile Name'
167
+ ```
168
+
169
+ Afterwards the `ipa` file is moved to the output folder. The `dSYM` file is compressed and moved to the output folder as well.
170
+
171
+ # Tips
172
+ ## [`fastlane`](https://fastlane.tools) Toolchain
173
+
174
+ - [`fastlane`](https://fastlane.tools): Connect all deployment tools into one streamlined workflow
175
+ - [`deliver`](https://github.com/KrauseFx/deliver): Upload screenshots, metadata and your app to the App Store
176
+ - [`snapshot`](https://github.com/KrauseFx/snapshot): Automate taking localized screenshots of your iOS app on every device
177
+ - [`frameit`](https://github.com/KrauseFx/frameit): Quickly put your screenshots into the right device frames
178
+ - [`PEM`](https://github.com/KrauseFx/pem): Automatically generate and renew your push notification profiles
179
+ - [`produce`](https://github.com/KrauseFx/produce): Create new iOS apps on iTunes Connect and Dev Portal using the command line
180
+ - [`cert`](https://github.com/KrauseFx/cert): Automatically create and maintain iOS code signing certificates
181
+ - [`codes`](https://github.com/KrauseFx/codes): Create promo codes for iOS Apps using the command line
182
+ - [`spaceship`](https://github.com/fastlane/spaceship): Ruby library to access the Apple Dev Center and iTunes Connect
183
+ - [`pilot`](https://github.com/fastlane/pilot): The best way to manage your TestFlight testers and builds from your terminal
184
+ - [`boarding`](https://github.com/fastlane/boarding): The easiest way to invite your TestFlight beta testers
185
+
186
+ ##### [Like this tool? Be the first to know about updates and new fastlane tools](https://tinyletter.com/krausefx)
187
+
188
+ ## Use the 'Provisioning Quicklook plugin'
189
+ Download and install the [Provisioning Plugin](https://github.com/chockenberry/Provisioning).
190
+
191
+ # Need help?
192
+ - If there is a technical problem with `gym`, submit an issue.
193
+ - I'm available for contract work - drop me an email: gym@krausefx.com
194
+
195
+ # License
196
+ This project is licensed under the terms of the MIT license. See the LICENSE file.
197
+
198
+ > This project and all fastlane tools are in no way affiliated with Apple Inc. This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs. All fastlane tools run on your own computer or server, so your credentials or other sensitive information will never leave your own computer. You are responsible for how you use fastlane tools.
data/bin/gym ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.push File.expand_path("../../lib", __FILE__)
3
+
4
+ require "gym"
5
+ require "gym/commands_generator"
6
+ Gym::CommandsGenerator.start
@@ -0,0 +1,14 @@
1
+ # For more information about this configuation visit
2
+ # https://github.com/fastlane/gym#gymfile
3
+
4
+ # In general, you can use the options available
5
+ # gym --help
6
+
7
+ # Remove the # in front of the line to enable this
8
+ # particular option
9
+
10
+ # scheme "Example"
11
+
12
+ # sdk "9.0"
13
+
14
+ output_directory "./"
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+ require 'gym/version'
3
+ require 'gym/manager'
4
+ require 'gym/project'
5
+ require 'gym/build_command_generator'
6
+ require 'gym/package_command_generator'
7
+ require 'gym/runner'
8
+ require 'gym/error_handler'
9
+ require 'gym/options'
10
+ require 'gym/detect_values'
11
+
12
+ require 'fastlane_core'
13
+ require 'terminal-table'
14
+
15
+ module Gym
16
+ class << self
17
+ attr_accessor :config
18
+
19
+ attr_accessor :project
20
+
21
+ def config=(value)
22
+ @config = value
23
+ DetectValues.set_additional_default_values
24
+ end
25
+
26
+ def gymfile_name
27
+ "Gymfile"
28
+ end
29
+ end
30
+
31
+ Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
32
+ end
@@ -0,0 +1,86 @@
1
+ module Gym
2
+ # Responsible for building the fully working xcodebuild command
3
+ class BuildCommandGenerator
4
+ class << self
5
+ def generate
6
+ parts = prefix
7
+ parts << "xcodebuild"
8
+ parts += options
9
+ parts += actions
10
+ parts += suffix
11
+ parts += pipe
12
+
13
+ parts
14
+ end
15
+
16
+ def prefix
17
+ ["set -o pipefail &&"]
18
+ end
19
+
20
+ # Path to the project or workspace as parameter
21
+ # This will also include the scheme (if given)
22
+ # @return [Array] The array with all the components to join
23
+ def project_path_array
24
+ config = Gym.config
25
+ proj = []
26
+ proj << "-workspace '#{config[:workspace]}'" if config[:workspace]
27
+ proj << "-scheme '#{config[:scheme]}'" if config[:scheme]
28
+ proj << "-project '#{config[:project]}'" if config[:project]
29
+
30
+ return proj if proj.count > 0
31
+ raise "No project/workspace found"
32
+ end
33
+
34
+ def options
35
+ config = Gym.config
36
+
37
+ options = []
38
+ options += project_path_array
39
+ options << "-configuration '#{config[:configuration]}'" # We need `Release` to export the DSYM file as well
40
+ options << "-sdk '#{config[:sdk]}'" if config[:sdk]
41
+ options << "-destination '#{config[:destination]}'" if config[:destination]
42
+ options << "-archivePath '#{archive_path}'"
43
+ options << "-xcargs '#{config[:xcargs]}'" if config[:xcargs]
44
+
45
+ options
46
+ end
47
+
48
+ def actions
49
+ config = Gym.config
50
+
51
+ actions = []
52
+ actions << :clean if config[:clean]
53
+ actions << :archive
54
+
55
+ actions
56
+ end
57
+
58
+ def suffix
59
+ []
60
+ end
61
+
62
+ def pipe
63
+ ["| xcpretty"]
64
+ end
65
+
66
+ # The path to set the Derived Data to
67
+ def build_path
68
+ unless @build_path
69
+ day = Time.now.strftime("%F") # e.g. 2015-08-07
70
+
71
+ @build_path = File.expand_path("~/Library/Developer/Xcode/Archives/#{day}/")
72
+ FileUtils.mkdir_p @build_path
73
+ end
74
+ @build_path
75
+ end
76
+
77
+ def archive_path
78
+ unless @archive_path
79
+ file_name = [Gym.config[:output_name], Time.now.strftime("%F %H.%M.%S")] # e.g. 2015-08-07 14.49.12
80
+ @archive_path = File.join(build_path, file_name.join(" ") + ".xcarchive")
81
+ end
82
+ return @archive_path
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,63 @@
1
+ require "commander"
2
+ require "fastlane_core"
3
+
4
+ HighLine.track_eof = false
5
+
6
+ module Gym
7
+ class CommandsGenerator
8
+ include Commander::Methods
9
+
10
+ FastlaneCore::CommanderGenerator.new.generate(Gym::Options.available_options)
11
+
12
+ def self.start
13
+ FastlaneCore::UpdateChecker.start_looking_for_update("gym")
14
+ new.run
15
+ ensure
16
+ FastlaneCore::UpdateChecker.show_update_status("gym", Gym::VERSION)
17
+ end
18
+
19
+ def convert_options(options)
20
+ o = options.__hash__.dup
21
+ o.delete(:verbose)
22
+ o
23
+ end
24
+
25
+ def run
26
+ program :version, Gym::VERSION
27
+ program :description, Gym::DESCRIPTION
28
+ program :help, "Author", "Felix Krause <gym@krausefx.com>"
29
+ program :help, "Website", "https://fastlane.tools"
30
+ program :help, "GitHub", "https://github.com/fastlane/gym"
31
+ program :help_formatter, :compact
32
+
33
+ global_option("--verbose") { $verbose = true }
34
+
35
+ always_trace!
36
+
37
+ command :build do |c|
38
+ c.syntax = "gym"
39
+ c.description = "Just builds your app"
40
+ c.action do |_args, options|
41
+ config = FastlaneCore::Configuration.create(Gym::Options.available_options,
42
+ convert_options(options))
43
+ Gym::Manager.new.work(config)
44
+ end
45
+ end
46
+
47
+ command :init do |c|
48
+ c.syntax = "gym init"
49
+ c.description = "Creates a new Gymfile for you"
50
+ c.action do |_args, options|
51
+ raise "Gymfile already exists" if File.exist?(Gym.gymfile_name)
52
+ template = File.read("#{Helper.gem_path('gym')}/lib/assets/GymfileTemplate")
53
+ File.write(Gym.gymfile_name, template)
54
+ Helper.log.info "Successfully created '#{Gym.gymfile_name}'. Open the file using a code editor.".green
55
+ end
56
+ end
57
+
58
+ default_command :build
59
+
60
+ run!
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,132 @@
1
+ module Gym
2
+ # This class detects all kinds of default values
3
+ class DetectValues
4
+ # This is needed as these are more complex default values
5
+ # Returns the finished config object
6
+ def self.set_additional_default_values
7
+ config = Gym.config
8
+
9
+ detect_projects
10
+
11
+ if config[:workspace].to_s.length > 0 and config[:project].to_s.length > 0
12
+ raise "You can only pass either a workspace or a project path, not both".red
13
+ end
14
+
15
+ if config[:workspace].to_s.length == 0 and config[:project].to_s.length == 0
16
+ raise "No project/workspace found in the current directory.".red
17
+ end
18
+
19
+ Gym.project = Project.new(config)
20
+ detect_provisioning_profile
21
+
22
+ # Go into the project's folder
23
+ Dir.chdir(File.expand_path("..", Gym.project.path)) do
24
+ config.load_configuration_file(Gym.gymfile_name)
25
+ end
26
+
27
+ detect_scheme
28
+
29
+ config[:output_name] ||= Gym.project.app_name
30
+
31
+ return config
32
+ end
33
+
34
+ # Helper Methods
35
+
36
+ def self.detect_provisioning_profile
37
+ unless Gym.config[:provisioning_profile_path]
38
+ Dir.chdir(File.expand_path("..", Gym.project.path)) do
39
+ profiles = Dir["*.mobileprovision"]
40
+ if profiles.count == 1
41
+ profile = File.expand_path(profiles.last)
42
+ elsif profiles.count > 1
43
+ puts "Found more than one provisioning profile in the project directory:"
44
+ profile = choose(*(profiles))
45
+ end
46
+
47
+ Gym.config[:provisioning_profile_path] = profile
48
+ end
49
+ end
50
+
51
+ if Gym.config[:provisioning_profile_path]
52
+ FastlaneCore::ProvisioningProfile.install(Gym.config[:provisioning_profile_path])
53
+ data = FastlaneCore::ProvisioningProfile.parse(Gym.config[:provisioning_profile_path])
54
+
55
+ if data['Name']
56
+ Helper.log.info "Using provisioning profile with name '#{data['Name']}'...".green if $verbose
57
+ Gym.config[:provisioning_profile_name] = data['Name']
58
+ end
59
+ end
60
+ end
61
+
62
+ def self.detect_projects
63
+ if Gym.config[:workspace].to_s.length == 0
64
+ workspace = Dir["./*.xcworkspace"]
65
+ if workspace.count > 1
66
+ puts "Select Workspace: "
67
+ Gym.config[:workspace] = choose(*(workspace))
68
+ else
69
+ Gym.config[:workspace] = workspace.first # this will result in nil if no files were found
70
+ end
71
+ end
72
+
73
+ if Gym.config[:workspace].to_s.length == 0 and Gym.config[:project].to_s.length == 0
74
+ project = Dir["./*.xcodeproj"]
75
+ if project.count > 1
76
+ puts "Select Project: "
77
+ Gym.config[:project] = choose(*(project))
78
+ else
79
+ Gym.config[:project] = project.first # this will result in nil if no files were found
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.choose_project
85
+ loop do
86
+ path = ask("Couldn't automatically detect the project file, please provide a path: ".yellow).strip
87
+ if File.directory? path
88
+ if path.end_with? ".xcworkspace"
89
+ config[:workspace] = path
90
+ break
91
+ elsif path.end_with? ".xcodeproj"
92
+ config[:project] = path
93
+ break
94
+ else
95
+ Helper.log.error "Path must end with either .xcworkspace or .xcodeproj"
96
+ end
97
+ else
98
+ Helper.log.error "Couldn't find project at path '#{File.expand_path(path)}'".red
99
+ end
100
+ end
101
+ end
102
+
103
+ def self.detect_scheme
104
+ config = Gym.config
105
+ if config[:scheme].to_s.length > 0
106
+ # Verify the scheme is available
107
+ unless Gym.project.schemes.include?(config[:scheme].to_s)
108
+ Helper.log.error "Couldn't find specified scheme '#{config[:scheme]}'.".red
109
+ config[:scheme] = nil
110
+ end
111
+ end
112
+
113
+ if config[:scheme].to_s.length == 0
114
+ proj_schemes = Gym.project.schemes
115
+ if proj_schemes.count == 1
116
+ config[:scheme] = proj_schemes.last
117
+ elsif proj_schemes.count > 1
118
+ if Helper.is_ci?
119
+ Helper.log.error "Multiple schemes found but you haven't specified one.".red
120
+ Helper.log.error "Since this is a CI, please pass one using the `scheme` option".red
121
+ raise "Multiple schemes found".red
122
+ else
123
+ puts "Select Scheme: "
124
+ config[:scheme] = choose(*(proj_schemes))
125
+ end
126
+ else
127
+ raise "Couldn't find any schemes in this project".red
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,95 @@
1
+ module Gym
2
+ # This classes methods are called when something goes wrong in the building process
3
+ class ErrorHandler
4
+ class << self
5
+ # @param [Array] The output of the errored build (line by line)
6
+ # This method should raise an exception in any case, as the return code indicated a failed build
7
+ def handle_build_error(output)
8
+ # The order of the handling below is import
9
+ case output
10
+ when /Your build settings specify a provisioning profile with the UUID/
11
+ print "Invalid code signing settings"
12
+ print "Your project defines a provisioning profile which doesn't exist on your local machine"
13
+ print "You can use sigh (https://github.com/KrauseFx/sigh) to download and install the provisioning profile"
14
+ print "Follow this guide: https://github.com/KrauseFx/fastlane/blob/master/docs/CodeSigning.md"
15
+ when /Provisioning profile does not match bundle identifier/
16
+ print "Invalid code signing settings"
17
+ print "Your project defines a provisioning profile that doesn't match the bundle identifier of your app"
18
+ print "Make sure you use the correct provisioning profile for this app"
19
+ print "Take a look at the ouptput above for more information"
20
+ print "You can follow this guide: https://github.com/KrauseFx/fastlane/blob/master/docs/CodeSigning.md"
21
+ when /provisioning profiles matching the bundle identifier “(.*)”/
22
+ print "You don't have the provisioning profile for '#{$1}' installed on the local machine"
23
+ print "Make sure you have the profile on this computer and it's properly installed"
24
+ print "You can use sigh (https://github.com/KrauseFx/sigh) to download and install the provisioning profile"
25
+ print "Follow this guide: https://github.com/KrauseFx/fastlane/blob/master/docs/CodeSigning.md"
26
+ when /code signing is required/
27
+ print "Your project settings define invalid code signing settings"
28
+ print "To generate an ipa file you need to enable code signing for your project"
29
+ print "Additionally make sure you have a code signing identity set"
30
+ print "Follow this guide: https://github.com/KrauseFx/fastlane/blob/master/docs/CodeSigning.md"
31
+ when /US\-ASCII/
32
+ print "Your shell environment is not correctly configured"
33
+ print "Instead of UTF-8 your shell uses US-ASCII"
34
+ print "Please add the following to your '~/.bashrc':"
35
+ print ""
36
+ print " export LANG=en_US.UTF-8"
37
+ print " export LANGUAGE=en_US.UTF-8"
38
+ print " export LC_ALL=en_US.UTF-8"
39
+ print ""
40
+ print "You'll have to restart your shell session after updating the file."
41
+ print "If you are using zshell or another shell, make sure to edit the correct bash file."
42
+ print "For more information visit this stackoverflow answer:"
43
+ print "http://stackoverflow.com/a/17031697/445598"
44
+ end
45
+ raise "Error building the application - see the log above".red
46
+ end
47
+
48
+ # @param [Array] The output of the errored build (line by line)
49
+ # This method should raise an exception in any case, as the return code indicated a failed build
50
+ def handle_package_error(output)
51
+ case output
52
+ when /single\-bundle/
53
+ print "Your project does not contain a single–bundle application or contains multiple products"
54
+ print "Please read the documentation provided by Apple: https://developer.apple.com/library/ios/technotes/tn2215/_index.html"
55
+ when /no signing identity matches '(.*)'/
56
+ print "Could not find code signing identity '#{$1}'"
57
+ print "Make sure the name of the code signing identity is correct"
58
+ print "and it matches a locally installed code signing identity"
59
+ print "You can pass the name of the code signing identity using the"
60
+ print "`codesigning_identity` option"
61
+ when /no provisioning profile matches '(.*)'/
62
+ print "Could not find provisioning profile with the name '#{$1}'"
63
+ print "Make sure the name of the provisioning profile is correct"
64
+ print "and it matches a locally installed profile"
65
+ print "You can pass the name of the provisioning profile using the"
66
+ print "`--provisioning_profile_name` option"
67
+ when /mismatch between specified provisioning profile and signing identity/
68
+ print "Mismatch between provisioning profile and code signing identity"
69
+ print "This means, the specified provisioning profile was not created using"
70
+ print "the specified certificate."
71
+ print "Run cert and sigh before gym to make sure to have all signing resources ready"
72
+ # insert more specific code signing errors here
73
+ when /Codesign check fails/
74
+ print "A general code signing error occurred. Make sure you passed a valid"
75
+ print "provisioning profile and code signing identity."
76
+ end
77
+ raise "Error packaging up the application".red
78
+ end
79
+
80
+ def handle_empty_archive
81
+ print "The generated archive is invalid, this can have various reasons:"
82
+ print "Usually it's caused by the `Skip Install` option in Xcode, set it to `NO`"
83
+ print "For more information visit https://developer.apple.com/library/ios/technotes/tn2215/_index.html"
84
+ raise "Archive invalid"
85
+ end
86
+
87
+ private
88
+
89
+ # Just to make things easier
90
+ def print(text)
91
+ Helper.log.error text.red
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,28 @@
1
+ module Gym
2
+ class Manager
3
+ def work(options)
4
+ Gym.config = options
5
+
6
+ print_summary
7
+
8
+ return Runner.new.run
9
+ end
10
+
11
+ private
12
+
13
+ def print_summary
14
+ config = Gym.config
15
+ rows = []
16
+ rows << ["Project", config[:project]] if config[:project]
17
+ rows << ["Workspace", config[:workspace]] if config[:workspace]
18
+ rows << ["Scheme", config[:scheme]] if config[:scheme]
19
+
20
+ puts ""
21
+ puts Terminal::Table.new(
22
+ title: "Building Application".green,
23
+ rows: rows
24
+ )
25
+ puts ""
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,111 @@
1
+ require "fastlane_core"
2
+ require "credentials_manager"
3
+
4
+ module Gym
5
+ class Options
6
+ def self.available_options
7
+ return @options if @options
8
+
9
+ @options = plain_options
10
+ end
11
+
12
+ # rubocop:disable Metrics/MethodLength
13
+ def self.plain_options
14
+ [
15
+ FastlaneCore::ConfigItem.new(key: :workspace,
16
+ short_option: "-w",
17
+ env_name: "GYM_WORKSPACE",
18
+ optional: true,
19
+ description: "Path the workspace file",
20
+ verify_block: proc do |value|
21
+ raise "Workspace file not found at path '#{File.expand_path(value)}'".red unless File.exist?(value.to_s)
22
+ raise "Workspace file invalid".red unless File.directory?(value.to_s)
23
+ raise "Workspace file is not a workspace, must end with .xcworkspace".red unless value.include?(".xcworkspace")
24
+ end),
25
+ FastlaneCore::ConfigItem.new(key: :project,
26
+ short_option: "-p",
27
+ optional: true,
28
+ env_name: "GYM_PROJECT",
29
+ description: "Path the project file",
30
+ verify_block: proc do |value|
31
+ raise "Project file not found at path '#{File.expand_path(value)}'".red unless File.exist?(value.to_s)
32
+ raise "Project file invalid".red unless File.directory?(value.to_s)
33
+ raise "Project file is not a project file, must end with .xcodeproj".red unless value.include?(".xcodeproj")
34
+ end),
35
+ FastlaneCore::ConfigItem.new(key: :provisioning_profile_path,
36
+ short_option: "-e",
37
+ env_name: "GYM_PROVISIONING_PROFILE_PATH",
38
+ description: "The path to the provisioning profile (found automatically when located in current folder)",
39
+ optional: true,
40
+ verify_block: proc do |value|
41
+ raise "Provisioning profile not found at path '#{File.expand_path(value)}'".red unless File.exist?(value)
42
+ end),
43
+ FastlaneCore::ConfigItem.new(key: :scheme,
44
+ short_option: "-s",
45
+ optional: true,
46
+ env_name: "GYM_SCHEME",
47
+ description: "The project's scheme. Make sure it's marked as `Shared`"),
48
+ FastlaneCore::ConfigItem.new(key: :clean,
49
+ short_option: "-c",
50
+ env_name: "GYM_CLEAN",
51
+ description: "Should the project be cleaned before building it?",
52
+ is_string: false,
53
+ default_value: false),
54
+ FastlaneCore::ConfigItem.new(key: :output_directory,
55
+ short_option: "-o",
56
+ env_name: "GYM_OUTPUT_DIRECTORY",
57
+ description: "The directory in which the ipa file should be stored in",
58
+ default_value: ".",
59
+ verify_block: proc do |value|
60
+ raise "Directory not found at path '#{File.expand_path(value)}'".red unless File.directory?(value)
61
+ end),
62
+ FastlaneCore::ConfigItem.new(key: :output_name,
63
+ short_option: "-n",
64
+ env_name: "GYM_OUTPUT_NAME",
65
+ description: "The name of the resulting ipa file",
66
+ optional: true,
67
+ verify_block: proc do |value|
68
+ value.gsub!(".ipa", "")
69
+ end),
70
+ FastlaneCore::ConfigItem.new(key: :sdk,
71
+ short_option: "-k",
72
+ env_name: "GYM_SDK",
73
+ description: "The SDK that should be used for building the application",
74
+ optional: true),
75
+ FastlaneCore::ConfigItem.new(key: :configuration,
76
+ short_option: "-q",
77
+ env_name: "GYM_CONFIGURATION",
78
+ description: "The configuration to use when building the app. Defaults to 'Release'",
79
+ default_value: "Release"),
80
+ FastlaneCore::ConfigItem.new(key: :silent,
81
+ short_option: "-t",
82
+ env_name: "GYM_SILENT",
83
+ description: "Hide all information that's not necessary while building",
84
+ default_value: false,
85
+ is_string: false),
86
+ FastlaneCore::ConfigItem.new(key: :provisioning_profile_name,
87
+ short_option: "-l",
88
+ env_name: "GYM_PROVISIONING_PROFILE_NAME",
89
+ description: "The name of the provisioning profile to use. It has to match the name exactly",
90
+ optional: true),
91
+ FastlaneCore::ConfigItem.new(key: :codesigning_identity,
92
+ short_option: "-i",
93
+ env_name: "GYM_CODE_SIGNING_IDENTITY",
94
+ description: "The name of the code signing identity to use. It has to match the name exactly. You usually don't need this! e.g. 'iPhone Distribution: SunApps GmbH'",
95
+ optional: true),
96
+ FastlaneCore::ConfigItem.new(key: :destination,
97
+ short_option: "-d",
98
+ env_name: "GYM_DESTINATION",
99
+ description: "Use a custom destination for building the app",
100
+ default_value: "generic/platform=iOS"),
101
+ FastlaneCore::ConfigItem.new(key: :xcargs,
102
+ short_option: "-x",
103
+ env_name: "GYM_XCARGS",
104
+ description: "Pass additional arguments to xcodebuild when building the app. Be sure to quote multiple args",
105
+ optional: true)
106
+
107
+ ]
108
+ end
109
+ # rubocop:enable Metrics/MethodLength
110
+ end
111
+ end
@@ -0,0 +1,50 @@
1
+ module Gym
2
+ # Responsible for building the fully working xcodebuild command
3
+ class PackageCommandGenerator
4
+ class << self
5
+ def generate
6
+ parts = ["/usr/bin/xcrun -sdk iphoneos PackageApplication -v"]
7
+ parts += options
8
+ parts += pipe
9
+
10
+ parts
11
+ end
12
+
13
+ def options
14
+ options = []
15
+
16
+ options << "'#{appfile_path}'"
17
+ options << "-o '#{ipa_path}'"
18
+ options << "exportFormat ipa"
19
+
20
+ if Gym.config[:provisioning_profile_name]
21
+ options << "--embed '#{Gym.config[:provisioning_profile_name]}'"
22
+ end
23
+
24
+ if Gym.config[:codesigning_identity]
25
+ options << "--sign '#{Gym.config[:codesigning_identity]}'"
26
+ end
27
+
28
+ options
29
+ end
30
+
31
+ def pipe
32
+ [""]
33
+ end
34
+
35
+ def appfile_path
36
+ Dir.glob("#{BuildCommandGenerator.archive_path}/Products/Applications/*.app").first
37
+ end
38
+
39
+ # We export it to the temporary folder and move it over to the actual output once it's finished and valid
40
+ def ipa_path
41
+ File.join(BuildCommandGenerator.build_path, "#{Gym.config[:output_name]}.ipa")
42
+ end
43
+
44
+ # The path the the dsym file for this app. Might be nil
45
+ def dsym_path
46
+ Dir[BuildCommandGenerator.archive_path + "/**/*.dsym"].last
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,93 @@
1
+ module Gym
2
+ # Represents the Xcode project/workspace
3
+ class Project
4
+ # Path to the project/workspace
5
+ attr_accessor :path
6
+
7
+ attr_accessor :is_workspace
8
+
9
+ def initialize(options)
10
+ self.path = File.expand_path(options[:workspace] || options[:project])
11
+ self.is_workspace = (options[:workspace].to_s.length > 0)
12
+
13
+ if !path or !File.directory? path
14
+ raise "Could not find project at path '#{path}'".red
15
+ end
16
+ end
17
+
18
+ # Get all available schemes in an array
19
+ def schemes
20
+ results = []
21
+ output = raw_info.split("Schemes:").last.split(":").first
22
+ output.split("\n").each do |current|
23
+ current = current.strip
24
+
25
+ next if current.length == 0
26
+ results << current
27
+ end
28
+
29
+ results
30
+ end
31
+
32
+ def app_name
33
+ # WRAPPER_NAME: Example.app
34
+ # WRAPPER_SUFFIX: .app
35
+ build_settings("WRAPPER_NAME").gsub(build_settings("WRAPPER_SUFFIX"), "")
36
+ end
37
+
38
+ #####################################################
39
+ # @!group Raw Access
40
+ #####################################################
41
+
42
+ # Get the build settings for our project
43
+ # this is used to properly get the DerivedData folder
44
+ # @param [String] The key of which we want the value for (e.g. "PRODUCT_NAME")
45
+ def build_settings(key)
46
+ unless @build_settings
47
+ # We also need to pass the workspace and scheme to this command
48
+ command = "xcrun xcodebuild -showBuildSettings #{BuildCommandGenerator.project_path_array.join(' ')}"
49
+ Helper.log.info command.yellow unless Gym.config[:silent]
50
+ @build_settings = `#{command}`
51
+ end
52
+
53
+ begin
54
+ result = @build_settings.split("\n").find { |c| c.include? key }
55
+ result.split(" = ").last
56
+ rescue => ex
57
+ Helper.log.error caller.join("\n\t")
58
+ Helper.log.error "Could not fetch #{key} from project file: #{ex}"
59
+ end
60
+ end
61
+
62
+ def raw_info
63
+ # e.g.
64
+ # Information about project "Example":
65
+ # Targets:
66
+ # Example
67
+ # ExampleUITests
68
+ #
69
+ # Build Configurations:
70
+ # Debug
71
+ # Release
72
+ #
73
+ # If no build configuration is specified and -scheme is not passed then "Release" is used.
74
+ #
75
+ # Schemes:
76
+ # Example
77
+ # ExampleUITests
78
+
79
+ return @raw if @raw
80
+
81
+ # We DO NOT want to pass the workspace, as this would also show
82
+ # all the CocoaPods schemes which we don't want here
83
+ containing_path = File.expand_path("..", path)
84
+ command = "xcrun xcodebuild -list"
85
+ Helper.log.info command.yellow unless Gym.config[:silent]
86
+
87
+ Dir.chdir(containing_path) do
88
+ @raw = `#{command}`
89
+ end
90
+ @raw
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,166 @@
1
+ require 'pty'
2
+ require 'open3'
3
+
4
+ module Gym
5
+ class Runner
6
+ # @return (String) The path to the resulting ipa
7
+ def run
8
+ clear_old_files
9
+ build_app
10
+ verify_archive
11
+ package_app
12
+ move_results
13
+ end
14
+
15
+ private
16
+
17
+ #####################################################
18
+ # @!group Printing out things
19
+ #####################################################
20
+
21
+ # @param [Array] An array containing all the parts of the command
22
+ def print_command(command, title)
23
+ rows = command.map do |c|
24
+ current = c.to_s.dup
25
+ next unless current.length > 0
26
+
27
+ if current.include? "-" and current.to_s.split(" '").count == 2
28
+ # That's a default parameter, like `-project 'Name'`
29
+ # We use " '" to not split by spaces within the value (e.g. path)
30
+ current.split(" '")
31
+ else
32
+ current.gsub!("| ", "") # as the | will somehow break the terminal table
33
+ [current, ""]
34
+ end
35
+ end
36
+
37
+ puts Terminal::Table.new(
38
+ title: title.green,
39
+ headings: ["Option", "Value"],
40
+ rows: rows.delete_if { |c| c.to_s.empty? }
41
+ )
42
+ end
43
+
44
+ #####################################################
45
+ # @!group The individual steps
46
+ #####################################################
47
+
48
+ def clear_old_files
49
+ if File.exist? PackageCommandGenerator.ipa_path
50
+ File.delete(PackageCommandGenerator.ipa_path)
51
+ end
52
+ end
53
+
54
+ # Builds the app and prepares the archive
55
+ def build_app
56
+ command = BuildCommandGenerator.generate
57
+ print_command(command, "Generated Build Command") if $verbose
58
+ execute_command(command: command, print_all: true, error: proc do |output|
59
+ ErrorHandler.handle_build_error(output)
60
+ end)
61
+
62
+ Helper.log.info("Successfully stored the archive. You can find it in the Xcode Organizer.".green)
63
+ Helper.log.info("Stored the archive in: ".green + BuildCommandGenerator.archive_path) if $verbose
64
+ end
65
+
66
+ # Makes sure the archive is there and valid
67
+ def verify_archive
68
+ if Dir[BuildCommandGenerator.archive_path + "/*"].count == 0
69
+ ErrorHandler.handle_empty_archive
70
+ end
71
+ end
72
+
73
+ def package_app
74
+ command = PackageCommandGenerator.generate
75
+ print_command(command, "Generated Package Command") if $verbose
76
+
77
+ execute_command(command: command, print_all: false, error: proc do |output|
78
+ ErrorHandler.handle_package_error(output)
79
+ end)
80
+ end
81
+
82
+ # Moves over the binary and dsym file to the output directory
83
+ # @return (String) The path to the resulting ipa file
84
+ def move_results
85
+ require 'fileutils'
86
+
87
+ FileUtils.mv(PackageCommandGenerator.ipa_path, Gym.config[:output_directory], force: true)
88
+
89
+ if PackageCommandGenerator.dsym_path
90
+ # Compress and move the dsym file
91
+ containing_directory = File.expand_path("..", PackageCommandGenerator.dsym_path)
92
+ file_name = File.basename(PackageCommandGenerator.dsym_path)
93
+
94
+ output_path = File.expand_path(File.join(Gym.config[:output_directory], Gym.config[:output_name] + ".app.dSYM.zip"))
95
+ command = "cd '#{containing_directory}' && zip -r '#{output_path}' '#{file_name}'"
96
+ Helper.log.info command.yellow unless Gym.config[:silent]
97
+ command_result = `#{command}`
98
+ Helper.log.info command_result if $verbose
99
+
100
+ puts "" # new line
101
+
102
+ Helper.log.info "Successfully exported and compressed dSYM file: ".green
103
+ Helper.log.info File.join(Gym.config[:output_directory], File.basename(PackageCommandGenerator.dsym_path))
104
+ end
105
+
106
+ ipa_path = File.join(Gym.config[:output_directory], File.basename(PackageCommandGenerator.ipa_path))
107
+
108
+ Helper.log.info "Successfully exported and signed ipa file:".green
109
+ Helper.log.info ipa_path
110
+ ipa_path
111
+ end
112
+
113
+ #####################################################
114
+ # @!group Actually executing the commands
115
+ #####################################################
116
+
117
+ # @param command [String] The command to be executed
118
+ # @param print_all [Boolean] Do we want to print out the command output while building?
119
+ # If set to false, nothing will be printed
120
+ # @param error [Block] A block that's called if an error occurs
121
+ # @return [String] All the output as string
122
+ def execute_command(command: nil, print_all: false, error: nil)
123
+ print_all = true if $verbose
124
+
125
+ output = []
126
+ command = command.join(" ")
127
+ Helper.log.info command.yellow.strip unless Gym.config[:silent]
128
+
129
+ puts "\n-----".cyan if print_all
130
+
131
+ last_length = 0
132
+ begin
133
+ PTY.spawn(command) do |stdin, stdout, pid|
134
+ stdin.each do |l|
135
+ line = l.strip # strip so that \n gets removed
136
+ output << line
137
+
138
+ next unless print_all
139
+
140
+ current_length = line.length
141
+ spaces = [last_length - current_length, 0].max
142
+ print((line + " " * spaces + "\r").cyan)
143
+ last_length = current_length
144
+ end
145
+ Process.wait(pid)
146
+ puts "-----\n".cyan if print_all
147
+ end
148
+ rescue => ex
149
+ # This could happen when the environment is wrong:
150
+ # > invalid byte sequence in US-ASCII (ArgumentError)
151
+ output << ex.to_s
152
+ o = output.join("\n")
153
+ puts o
154
+ error.call(o)
155
+ end
156
+
157
+ # Exit status for build command, should be 0 if build succeeded
158
+ # Disabled Rubocop, since $CHILD_STATUS just is not the same
159
+ if $?.exitstatus != 0 # rubocop:disable Style/SpecialGlobalVars
160
+ o = output.join("\n")
161
+ puts o # the user has the right to see the raw output
162
+ error.call(o)
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,4 @@
1
+ module Gym
2
+ VERSION = "0.1.0"
3
+ DESCRIPTION = "Building your iOS apps has never been easier"
4
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gym
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix Krause
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-01 00:00:00.000000000 Z
11
+ date: 2015-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fastlane_core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.2
19
+ version: 0.13.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.2
26
+ version: 0.13.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: xcpretty
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fastlane
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.15.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.15.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,15 +178,29 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
167
- description: Building your iOS app, made easy
181
+ description: Building your iOS apps has never been easier
168
182
  email:
169
183
  - gym@krausefx.com
170
- executables: []
184
+ executables:
185
+ - gym
171
186
  extensions: []
172
187
  extra_rdoc_files: []
173
188
  files:
174
189
  - LICENSE
175
190
  - README.md
191
+ - bin/gym
192
+ - lib/assets/GymfileTemplate
193
+ - lib/gym.rb
194
+ - lib/gym/build_command_generator.rb
195
+ - lib/gym/commands_generator.rb
196
+ - lib/gym/detect_values.rb
197
+ - lib/gym/error_handler.rb
198
+ - lib/gym/manager.rb
199
+ - lib/gym/options.rb
200
+ - lib/gym/package_command_generator.rb
201
+ - lib/gym/project.rb
202
+ - lib/gym/runner.rb
203
+ - lib/gym/version.rb
176
204
  homepage: https://fastlane.tools
177
205
  licenses:
178
206
  - MIT
@@ -196,6 +224,6 @@ rubyforge_project:
196
224
  rubygems_version: 2.4.8
197
225
  signing_key:
198
226
  specification_version: 4
199
- summary: Building your iOS app, made easy
227
+ summary: Building your iOS apps has never been easier
200
228
  test_files: []
201
229
  has_rdoc: