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 +4 -4
- data/README.md +198 -1
- data/bin/gym +6 -0
- data/lib/assets/GymfileTemplate +14 -0
- data/lib/gym.rb +32 -0
- data/lib/gym/build_command_generator.rb +86 -0
- data/lib/gym/commands_generator.rb +63 -0
- data/lib/gym/detect_values.rb +132 -0
- data/lib/gym/error_handler.rb +95 -0
- data/lib/gym/manager.rb +28 -0
- data/lib/gym/options.rb +111 -0
- data/lib/gym/package_command_generator.rb +50 -0
- data/lib/gym/project.rb +93 -0
- data/lib/gym/runner.rb +166 -0
- data/lib/gym/version.rb +4 -0
- metadata +35 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 574af8b98bbf18a7cd50ea0df99abf0a56614635
|
4
|
+
data.tar.gz: d0479a56afa1949ef434976da023a69fb6a56e5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b14141ac66187610e817a34f1940a68d4ecf531942b625d267b8d6618afd6ea43320c53bba7399919018a15f51f6301afd6e03ed51aa43284d16612ccedb489
|
7
|
+
data.tar.gz: 6b0046422e07785ae133f0b7ea9edeb43ce524d01f769c8aeb1629a258e48ff37321a29336905ce08e5f397a2cb9ad5b8590fe74aca730bc3596eeddd2fe47a7
|
data/README.md
CHANGED
@@ -1 +1,198 @@
|
|
1
|
-
|
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> •
|
10
|
+
<a href="https://github.com/KrauseFx/snapshot">snapshot</a> •
|
11
|
+
<a href="https://github.com/KrauseFx/frameit">frameit</a> •
|
12
|
+
<a href="https://github.com/KrauseFx/PEM">PEM</a> •
|
13
|
+
<a href="https://github.com/fastlane/sigh">sigh</a> •
|
14
|
+
<a href="https://github.com/KrauseFx/produce">produce</a> •
|
15
|
+
<a href="https://github.com/KrauseFx/cert">cert</a> •
|
16
|
+
<a href="https://github.com/KrauseFx/codes">codes</a> •
|
17
|
+
<a href="https://github.com/fastlane/spaceship">spaceship</a> •
|
18
|
+
<a href="https://github.com/fastlane/pilot">pilot</a> •
|
19
|
+
<a href="https://github.com/fastlane/boarding">boarding</a> •
|
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> •
|
43
|
+
<a href="#installation">Installation</a> •
|
44
|
+
<a href="#usage">Usage</a> •
|
45
|
+
<a href="#tips">Tips</a> •
|
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,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 "./"
|
data/lib/gym.rb
ADDED
@@ -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
|
data/lib/gym/manager.rb
ADDED
@@ -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
|
data/lib/gym/options.rb
ADDED
@@ -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
|
data/lib/gym/project.rb
ADDED
@@ -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
|
data/lib/gym/runner.rb
ADDED
@@ -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
|
data/lib/gym/version.rb
ADDED
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
|
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-
|
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.
|
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.
|
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
|
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
|
227
|
+
summary: Building your iOS apps has never been easier
|
200
228
|
test_files: []
|
201
229
|
has_rdoc:
|