fastlane 2.154.0 → 2.156.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +74 -74
- data/deliver/lib/deliver/app_screenshot_iterator.rb +98 -0
- data/deliver/lib/deliver/html_generator.rb +8 -1
- data/deliver/lib/deliver/queue_worker.rb +64 -0
- data/deliver/lib/deliver/upload_screenshots.rb +122 -125
- data/fastlane/lib/fastlane/actions/create_keychain.rb +5 -1
- data/fastlane/lib/fastlane/actions/sync_code_signing.rb +5 -0
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +1 -1
- data/fastlane/swift/Fastlane.swift +375 -182
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +6 -2
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +5 -1
- data/fastlane_core/lib/fastlane_core/project.rb +1 -0
- data/gym/lib/gym/detect_values.rb +6 -3
- data/gym/lib/gym/generators/build_command_generator.rb +0 -1
- data/gym/lib/gym/module.rb +22 -0
- data/gym/lib/gym/runner.rb +8 -10
- data/match/lib/match/generator.rb +6 -0
- data/match/lib/match/options.rb +7 -2
- data/match/lib/match/runner.rb +12 -5
- data/match/lib/match/spaceship_ensure.rb +7 -9
- data/match/lib/match/storage/google_cloud_storage.rb +1 -1
- data/scan/lib/scan/test_command_generator.rb +3 -1
- data/screengrab/lib/screengrab/runner.rb +7 -7
- data/sigh/lib/sigh/download_all.rb +42 -27
- data/sigh/lib/sigh/module.rb +26 -0
- data/sigh/lib/sigh/options.rb +2 -2
- data/sigh/lib/sigh/runner.rb +100 -35
- data/snapshot/lib/snapshot/options.rb +5 -0
- data/snapshot/lib/snapshot/test_command_generator.rb +3 -2
- data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -1
- data/snapshot/lib/snapshot/test_command_generator_xcode_8.rb +4 -1
- data/spaceship/lib/spaceship/.client.rb.swp +0 -0
- data/spaceship/lib/spaceship/.spaceauth_runner.rb.swp +0 -0
- data/spaceship/lib/spaceship/.two_step_or_factor_client.rb.swp +0 -0
- data/spaceship/lib/spaceship/connect_api/client.rb +2 -0
- data/spaceship/lib/spaceship/connect_api/model.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/models/app.rb +3 -1
- data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +17 -5
- data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +41 -7
- data/spaceship/lib/spaceship/connect_api/models/profile.rb +32 -1
- data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +46 -4
- data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +41 -0
- data/supply/lib/supply/client.rb +2 -1
- data/supply/lib/supply/options.rb +8 -1
- metadata +24 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70c9faed2541a1cead4b030cf41328487f06a8b17e129a6d4ad8428de8fcaf25
|
|
4
|
+
data.tar.gz: 262293381bd7435f0b9dcac69ed77870c31eb59cfb023a97b09e747e89f7015a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c27c2225cc7459a8d10e3c9b91dff5fa878d2b5c07f4cb11991132422f1de975a4549b5d0b460c9e213c7cb3c255e4c7caa655ab9b3369a78cdd7e6eb7258ff6
|
|
7
|
+
data.tar.gz: 37b00b6000617888e7940b7a55c2b57ad8cc8575d119c3583bfab9d10244831dc6007093d9e4d3933513abccd889f8eb6a6fcc14bcf6692e35a693ade259a769
|
data/README.md
CHANGED
|
@@ -34,43 +34,55 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
|
34
34
|
<!-- This table is regenerated and resorted on each release -->
|
|
35
35
|
<table id='team'>
|
|
36
36
|
<tr>
|
|
37
|
+
<td id='fumiya-nakamura'>
|
|
38
|
+
<a href='https://github.com/nafu'>
|
|
39
|
+
<img src='https://github.com/nafu.png?size=140'>
|
|
40
|
+
</a>
|
|
41
|
+
<h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
|
|
42
|
+
</td>
|
|
43
|
+
<td id='danielle-tomlinson'>
|
|
44
|
+
<a href='https://github.com/endocrimes'>
|
|
45
|
+
<img src='https://github.com/endocrimes.png?size=140'>
|
|
46
|
+
</a>
|
|
47
|
+
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
|
48
|
+
</td>
|
|
37
49
|
<td id='jérôme-lacoste'>
|
|
38
50
|
<a href='https://github.com/lacostej'>
|
|
39
51
|
<img src='https://github.com/lacostej.png?size=140'>
|
|
40
52
|
</a>
|
|
41
53
|
<h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
|
|
42
54
|
</td>
|
|
55
|
+
<td id='olivier-halligon'>
|
|
56
|
+
<a href='https://github.com/AliSoftware'>
|
|
57
|
+
<img src='https://github.com/AliSoftware.png?size=140'>
|
|
58
|
+
</a>
|
|
59
|
+
<h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
|
|
60
|
+
</td>
|
|
43
61
|
<td id='kohki-miki'>
|
|
44
62
|
<a href='https://github.com/giginet'>
|
|
45
63
|
<img src='https://github.com/giginet.png?size=140'>
|
|
46
64
|
</a>
|
|
47
65
|
<h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
|
|
48
66
|
</td>
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
</td>
|
|
55
|
-
<td id='daniel-jankowski'>
|
|
56
|
-
<a href='https://github.com/mollyIV'>
|
|
57
|
-
<img src='https://github.com/mollyIV.png?size=140'>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<td id='jorge-revuelta-h'>
|
|
70
|
+
<a href='https://github.com/minuscorp'>
|
|
71
|
+
<img src='https://github.com/minuscorp.png?size=140'>
|
|
58
72
|
</a>
|
|
59
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
73
|
+
<h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
|
|
60
74
|
</td>
|
|
61
|
-
<td id='
|
|
62
|
-
<a href='https://github.com/
|
|
63
|
-
<img src='https://github.com/
|
|
75
|
+
<td id='luka-mirosevic'>
|
|
76
|
+
<a href='https://github.com/lmirosevic'>
|
|
77
|
+
<img src='https://github.com/lmirosevic.png?size=140'>
|
|
64
78
|
</a>
|
|
65
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
79
|
+
<h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
|
|
66
80
|
</td>
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
<a href='https://github.com/snatchev'>
|
|
71
|
-
<img src='https://github.com/snatchev.png?size=140'>
|
|
81
|
+
<td id='manu-wallner'>
|
|
82
|
+
<a href='https://github.com/milch'>
|
|
83
|
+
<img src='https://github.com/milch.png?size=140'>
|
|
72
84
|
</a>
|
|
73
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
85
|
+
<h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
|
|
74
86
|
</td>
|
|
75
87
|
<td id='jan-piotrowski'>
|
|
76
88
|
<a href='https://github.com/janpio'>
|
|
@@ -84,63 +96,51 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
|
84
96
|
</a>
|
|
85
97
|
<h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
|
|
86
98
|
</td>
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
<img src='https://github.com/nafu.png?size=140'>
|
|
90
|
-
</a>
|
|
91
|
-
<h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
|
|
92
|
-
</td>
|
|
99
|
+
</tr>
|
|
100
|
+
<tr>
|
|
93
101
|
<td id='iulian-onofrei'>
|
|
94
102
|
<a href='https://github.com/revolter'>
|
|
95
103
|
<img src='https://github.com/revolter.png?size=140'>
|
|
96
104
|
</a>
|
|
97
105
|
<h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
|
|
98
106
|
</td>
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<a href='https://github.com/endocrimes'>
|
|
103
|
-
<img src='https://github.com/endocrimes.png?size=140'>
|
|
104
|
-
</a>
|
|
105
|
-
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
|
106
|
-
</td>
|
|
107
|
-
<td id='josh-holtz'>
|
|
108
|
-
<a href='https://github.com/joshdholtz'>
|
|
109
|
-
<img src='https://github.com/joshdholtz.png?size=140'>
|
|
107
|
+
<td id='matthew-ellis'>
|
|
108
|
+
<a href='https://github.com/matthewellis'>
|
|
109
|
+
<img src='https://github.com/matthewellis.png?size=140'>
|
|
110
110
|
</a>
|
|
111
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
111
|
+
<h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
|
|
112
112
|
</td>
|
|
113
|
-
<td id='
|
|
114
|
-
<a href='https://github.com/
|
|
115
|
-
<img src='https://github.com/
|
|
113
|
+
<td id='max-ott'>
|
|
114
|
+
<a href='https://github.com/max-ott'>
|
|
115
|
+
<img src='https://github.com/max-ott.png?size=140'>
|
|
116
116
|
</a>
|
|
117
|
-
<h4 align='center'>
|
|
117
|
+
<h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
|
|
118
118
|
</td>
|
|
119
|
-
<td id='
|
|
120
|
-
<a href='https://github.com/
|
|
121
|
-
<img src='https://github.com/
|
|
119
|
+
<td id='daniel-jankowski'>
|
|
120
|
+
<a href='https://github.com/mollyIV'>
|
|
121
|
+
<img src='https://github.com/mollyIV.png?size=140'>
|
|
122
122
|
</a>
|
|
123
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
123
|
+
<h4 align='center'><a href='https://twitter.com/mollyIV'>Daniel Jankowski</a></h4>
|
|
124
124
|
</td>
|
|
125
|
-
<td id='
|
|
126
|
-
<a href='https://github.com/
|
|
127
|
-
<img src='https://github.com/
|
|
125
|
+
<td id='aaron-brager'>
|
|
126
|
+
<a href='https://github.com/getaaron'>
|
|
127
|
+
<img src='https://github.com/getaaron.png?size=140'>
|
|
128
128
|
</a>
|
|
129
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
129
|
+
<h4 align='center'><a href='https://twitter.com/getaaron'>Aaron Brager</a></h4>
|
|
130
130
|
</td>
|
|
131
131
|
</tr>
|
|
132
132
|
<tr>
|
|
133
|
-
<td id='
|
|
134
|
-
<a href='https://github.com/
|
|
135
|
-
<img src='https://github.com/
|
|
133
|
+
<td id='jimmy-dee'>
|
|
134
|
+
<a href='https://github.com/jdee'>
|
|
135
|
+
<img src='https://github.com/jdee.png?size=140'>
|
|
136
136
|
</a>
|
|
137
|
-
<h4 align='center'
|
|
137
|
+
<h4 align='center'>Jimmy Dee</h4>
|
|
138
138
|
</td>
|
|
139
|
-
<td id='
|
|
140
|
-
<a href='https://github.com/
|
|
141
|
-
<img src='https://github.com/
|
|
139
|
+
<td id='josh-holtz'>
|
|
140
|
+
<a href='https://github.com/joshdholtz'>
|
|
141
|
+
<img src='https://github.com/joshdholtz.png?size=140'>
|
|
142
142
|
</a>
|
|
143
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
143
|
+
<h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
|
|
144
144
|
</td>
|
|
145
145
|
<td id='felix-krause'>
|
|
146
146
|
<a href='https://github.com/KrauseFx'>
|
|
@@ -148,31 +148,31 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
|
148
148
|
</a>
|
|
149
149
|
<h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
|
|
150
150
|
</td>
|
|
151
|
-
<td id='
|
|
152
|
-
<a href='https://github.com/
|
|
153
|
-
<img src='https://github.com/
|
|
151
|
+
<td id='joshua-liebowitz'>
|
|
152
|
+
<a href='https://github.com/taquitos'>
|
|
153
|
+
<img src='https://github.com/taquitos.png?size=140'>
|
|
154
154
|
</a>
|
|
155
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
155
|
+
<h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
|
|
156
156
|
</td>
|
|
157
|
-
<td id='
|
|
158
|
-
<a href='https://github.com/
|
|
159
|
-
<img src='https://github.com/
|
|
157
|
+
<td id='maksym-grebenets'>
|
|
158
|
+
<a href='https://github.com/mgrebenets'>
|
|
159
|
+
<img src='https://github.com/mgrebenets.png?size=140'>
|
|
160
160
|
</a>
|
|
161
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
161
|
+
<h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
|
|
162
162
|
</td>
|
|
163
163
|
</tr>
|
|
164
164
|
<tr>
|
|
165
|
-
<td id='
|
|
166
|
-
<a href='https://github.com/
|
|
167
|
-
<img src='https://github.com/
|
|
165
|
+
<td id='stefan-natchev'>
|
|
166
|
+
<a href='https://github.com/snatchev'>
|
|
167
|
+
<img src='https://github.com/snatchev.png?size=140'>
|
|
168
168
|
</a>
|
|
169
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
169
|
+
<h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
|
|
170
170
|
</td>
|
|
171
|
-
<td id='
|
|
172
|
-
<a href='https://github.com/
|
|
173
|
-
<img src='https://github.com/
|
|
171
|
+
<td id='andrew-mcburney'>
|
|
172
|
+
<a href='https://github.com/armcburney'>
|
|
173
|
+
<img src='https://github.com/armcburney.png?size=140'>
|
|
174
174
|
</a>
|
|
175
|
-
<h4 align='center'><a href='https://twitter.com/
|
|
175
|
+
<h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
|
|
176
176
|
</td>
|
|
177
177
|
</table>
|
|
178
178
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Deliver
|
|
2
|
+
# This is a convinient class that enumerates app store connect's screenshots in various degrees.
|
|
3
|
+
class AppScreenshotIterator
|
|
4
|
+
NUMBER_OF_THREADS = Helper.test? ? 1 : [ENV.fetch("DELIVER_NUMBER_OF_THREADS", 10).to_i, 10].min
|
|
5
|
+
|
|
6
|
+
# @param localizations [Array<Spaceship::ConnectAPI::AppStoreVersionLocalization>]
|
|
7
|
+
def initialize(localizations)
|
|
8
|
+
@localizations = localizations
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Iterate app_screenshot_set over localizations
|
|
12
|
+
#
|
|
13
|
+
# @yield [localization, app_screenshot_set]
|
|
14
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreVersionLocalization] localization
|
|
15
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreScreenshotSet] app_screenshot_set
|
|
16
|
+
def each_app_screenshot_set(&block)
|
|
17
|
+
return enum_for(__method__) unless block_given?
|
|
18
|
+
|
|
19
|
+
# Collect app_screenshot_sets from localizations in parallel but
|
|
20
|
+
# limit the number of threads working at a time with using `lazy` and `force` controls
|
|
21
|
+
# to not attack App Store Connect
|
|
22
|
+
results = @localizations.each_slice(NUMBER_OF_THREADS).lazy.map do |localizations|
|
|
23
|
+
localizations.map do |localization|
|
|
24
|
+
Thread.new do
|
|
25
|
+
[localization, localization.get_app_screenshot_sets]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end.flat_map do |threads|
|
|
29
|
+
threads.map { |t| t.join.value }
|
|
30
|
+
end.force
|
|
31
|
+
|
|
32
|
+
results.each do |localization, app_screenshot_sets|
|
|
33
|
+
app_screenshot_sets.each do |app_screenshot_set|
|
|
34
|
+
yield(localization, app_screenshot_set)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Iterate app_screenshot over localizations and app_screenshot_sets
|
|
40
|
+
#
|
|
41
|
+
# @yield [localization, app_screenshot_set, app_screenshot]
|
|
42
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreVersionLocalization] localization
|
|
43
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreScreenshotSet] app_screenshot_set
|
|
44
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreScreenshot] app_screenshot
|
|
45
|
+
def each_app_screenshot(&block)
|
|
46
|
+
return enum_for(__method__) unless block_given?
|
|
47
|
+
|
|
48
|
+
each_app_screenshot_set do |localization, app_screenshot_set|
|
|
49
|
+
app_screenshot_set.app_screenshots.each do |app_screenshot|
|
|
50
|
+
yield(localization, app_screenshot_set, app_screenshot)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Iterate given local app_screenshot over localizations and app_screenshot_sets with index within each app_screenshot_set
|
|
56
|
+
#
|
|
57
|
+
# @param screenshots_per_language [Hash<String, Array<Deliver::AppScreenshot>]
|
|
58
|
+
# @yield [localization, app_screenshot_set, app_screenshot, index]
|
|
59
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreVersionLocalization] localization
|
|
60
|
+
# @yieldparam [optional, Spaceship::ConnectAPI::AppStoreScreenshotSet] app_screenshot_set
|
|
61
|
+
# @yieldparam [optional, Deliver::AppScreenshot] screenshot
|
|
62
|
+
# @yieldparam [optional, Integer] index a number reperesents which position the screenshot will be
|
|
63
|
+
def each_local_screenshot(screenshots_per_language, &block)
|
|
64
|
+
return enum_for(__method__, screenshots_per_language) unless block_given?
|
|
65
|
+
|
|
66
|
+
# Iterate over all the screenshots per language and display_type
|
|
67
|
+
# and then enqueue them to worker one by one if it's not duplciated on App Store Connect
|
|
68
|
+
screenshots_per_language.map do |language, screenshots_for_language|
|
|
69
|
+
localization = @localizations.find { |l| l.locale == language }
|
|
70
|
+
[localization, screenshots_for_language]
|
|
71
|
+
end.reject do |localization, _|
|
|
72
|
+
localization.nil?
|
|
73
|
+
end.each do |localization, screenshots_for_language|
|
|
74
|
+
iterate_over_screenshots_per_language(localization, screenshots_for_language, &block)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def iterate_over_screenshots_per_language(localization, screenshots_for_language, &block)
|
|
81
|
+
app_screenshot_sets_per_display_type = localization.get_app_screenshot_sets.map { |set| [set.screenshot_display_type, set] }.to_h
|
|
82
|
+
screenshots_per_display_type = screenshots_for_language.reject { |screenshot| screenshot.device_type.nil? }.group_by(&:device_type)
|
|
83
|
+
|
|
84
|
+
screenshots_per_display_type.each do |display_type, screenshots|
|
|
85
|
+
# Create AppScreenshotSet for given display_type if it doesn't exsit
|
|
86
|
+
app_screenshot_set = app_screenshot_sets_per_display_type[display_type]
|
|
87
|
+
app_screenshot_set ||= localization.create_app_screenshot_set(attributes: { screenshotDisplayType: display_type })
|
|
88
|
+
iterate_over_screenshots_per_display_type(localization, app_screenshot_set, screenshots, &block)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def iterate_over_screenshots_per_display_type(localization, app_screenshot_set, screenshots, &block)
|
|
93
|
+
screenshots.each.with_index do |screenshot, index|
|
|
94
|
+
yield(localization, app_screenshot_set, screenshot, index)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'spaceship'
|
|
2
|
+
|
|
1
3
|
require_relative 'module'
|
|
2
4
|
|
|
3
5
|
module Deliver
|
|
@@ -51,7 +53,12 @@ module Deliver
|
|
|
51
53
|
@app_name ||= options[:app].name
|
|
52
54
|
|
|
53
55
|
@languages = options[:description].keys if options[:description]
|
|
54
|
-
@languages ||=
|
|
56
|
+
@languages ||= begin
|
|
57
|
+
platform = Spaceship::ConnectAPI::Platform.map(options[:platform])
|
|
58
|
+
version = options[:app].get_edit_app_store_version(platform: platform)
|
|
59
|
+
|
|
60
|
+
version.get_app_store_version_localizations.collect(&:locale)
|
|
61
|
+
end
|
|
55
62
|
|
|
56
63
|
html_path = File.join(Deliver::ROOT, "lib/assets/summary.html.erb")
|
|
57
64
|
html = ERB.new(File.read(html_path)).result(binding) # https://web.archive.org/web/20160430190141/www.rrn.dk/rubys-erb-templating-system
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module Deliver
|
|
4
|
+
# This dispatches jobs to worker threads and make it work in parallel.
|
|
5
|
+
# It's suitable for I/O bounds works and not for CPU bounds works.
|
|
6
|
+
# Use this when you have all the items that you'll process in advance.
|
|
7
|
+
# Simply enqueue them to this and call `QueueWorker#start`.
|
|
8
|
+
class QueueWorker
|
|
9
|
+
# @param concurrency (Numeric) - A number of threads to be created
|
|
10
|
+
# @param block (Proc) - A task you want to execute with enqueued items
|
|
11
|
+
def initialize(concurrency, &block)
|
|
12
|
+
@concurrency = concurrency
|
|
13
|
+
@block = block
|
|
14
|
+
@queue = Queue.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param job (Object) - An arbitary object that keeps parameters
|
|
18
|
+
def enqueue(job)
|
|
19
|
+
@queue.push(job)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Call this after you enqueuned all the jobs you want to process
|
|
23
|
+
# This method blocks current thread until all the enqueued jobs are processed
|
|
24
|
+
def start
|
|
25
|
+
threads = []
|
|
26
|
+
@concurrency.times do
|
|
27
|
+
threads << Thread.new do
|
|
28
|
+
while running? && !empty?
|
|
29
|
+
job = @queue.pop
|
|
30
|
+
@block.call(job) if job
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
wait_for_complete
|
|
36
|
+
threads.each(&:join)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def running?
|
|
42
|
+
!@queue.closed?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def empty?
|
|
46
|
+
@queue.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def wait_for_complete
|
|
50
|
+
wait_thread = Thread.new do
|
|
51
|
+
loop do
|
|
52
|
+
if @queue.empty?
|
|
53
|
+
@queue.close
|
|
54
|
+
break
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sleep(1)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
wait_thread.join
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -4,10 +4,17 @@ require 'digest/md5'
|
|
|
4
4
|
require_relative 'app_screenshot'
|
|
5
5
|
require_relative 'module'
|
|
6
6
|
require_relative 'loader'
|
|
7
|
+
require_relative 'queue_worker'
|
|
8
|
+
require_relative 'app_screenshot_iterator'
|
|
7
9
|
|
|
8
10
|
module Deliver
|
|
9
11
|
# upload screenshots to App Store Connect
|
|
10
12
|
class UploadScreenshots
|
|
13
|
+
DeleteScreenshotJob = Struct.new(:app_screenshot, :localization, :app_screenshot_set)
|
|
14
|
+
UploadScreenshotJob = Struct.new(:app_screenshot_set, :path)
|
|
15
|
+
|
|
16
|
+
NUMBER_OF_THREADS = Helper.test? ? 1 : [ENV.fetch("DELIVER_NUMBER_OF_THREADS", 10).to_i, 10].min
|
|
17
|
+
|
|
11
18
|
def upload(options, screenshots)
|
|
12
19
|
return if options[:skip_screenshots]
|
|
13
20
|
return if options[:edit_live]
|
|
@@ -50,57 +57,47 @@ module Deliver
|
|
|
50
57
|
localizations = version.get_app_store_version_localizations
|
|
51
58
|
end
|
|
52
59
|
|
|
53
|
-
upload_screenshots(
|
|
60
|
+
upload_screenshots(localizations, screenshots_per_language)
|
|
61
|
+
|
|
62
|
+
Helper.show_loading_indicator("Sorting screenshots uploaded...")
|
|
63
|
+
sort_screenshots(localizations)
|
|
64
|
+
Helper.hide_loading_indicator
|
|
65
|
+
|
|
66
|
+
UI.success("Successfully uploaded screenshots to App Store Connect")
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
def delete_screenshots(localizations, screenshots_per_language, tries: 5)
|
|
57
70
|
tries -= 1
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
errors = []
|
|
70
|
-
|
|
71
|
-
screenshot_sets.each do |screenshot_set|
|
|
72
|
-
UI.message("Removing all previously uploaded screenshots for '#{localization.locale}' '#{screenshot_set.screenshot_display_type}'...")
|
|
73
|
-
screenshot_set.app_screenshots.each do |screenshot|
|
|
74
|
-
UI.verbose("Deleting screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
|
|
75
|
-
threads << Thread.new do
|
|
76
|
-
begin
|
|
77
|
-
screenshot.delete!
|
|
78
|
-
UI.verbose("Deleted screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
|
|
79
|
-
rescue => error
|
|
80
|
-
UI.verbose("Failed to delete screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
|
|
81
|
-
errors << error
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
72
|
+
worker = QueueWorker.new(NUMBER_OF_THREADS) do |job|
|
|
73
|
+
start_time = Time.now
|
|
74
|
+
target = "#{job.localization.locale} #{job.app_screenshot_set.screenshot_display_type} #{job.app_screenshot.id}"
|
|
75
|
+
begin
|
|
76
|
+
UI.verbose("Deleting '#{target}'")
|
|
77
|
+
job.app_screenshot.delete!
|
|
78
|
+
UI.message("Deleted '#{target}' - (#{Time.now - start_time} secs)")
|
|
79
|
+
rescue => error
|
|
80
|
+
UI.error("Failed to delete screenshot #{target} - (#{Time.now - start_time} secs)")
|
|
81
|
+
UI.error(error.message)
|
|
85
82
|
end
|
|
83
|
+
end
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
threads.each(&:join)
|
|
92
|
-
Helper.hide_loading_indicator unless FastlaneCore::Globals.verbose?
|
|
93
|
-
end
|
|
85
|
+
iterator = AppScreenshotIterator.new(localizations)
|
|
86
|
+
iterator.each_app_screenshot do |localization, app_screenshot_set, app_screenshot|
|
|
87
|
+
# Only delete screenshots if trying to upload
|
|
88
|
+
next unless screenshots_per_language.keys.include?(localization.locale)
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
UI.error(error.message)
|
|
98
|
-
end
|
|
90
|
+
UI.verbose("Queued delete sceeenshot job for #{localization.locale} #{app_screenshot_set.screenshot_display_type} #{app_screenshot.id}")
|
|
91
|
+
worker.enqueue(DeleteScreenshotJob.new(app_screenshot, localization, app_screenshot_set))
|
|
99
92
|
end
|
|
100
93
|
|
|
94
|
+
worker.start
|
|
95
|
+
|
|
101
96
|
# Verify all screenshots have been deleted
|
|
102
97
|
# Sometimes API requests will fail but screenshots will still be deleted
|
|
103
|
-
count =
|
|
98
|
+
count = iterator.each_app_screenshot_set.map { |_, app_screenshot_set| app_screenshot_set }
|
|
99
|
+
.reduce(0) { |sum, app_screenshot_set| sum + app_screenshot_set.app_screenshots.size }
|
|
100
|
+
|
|
104
101
|
UI.important("Number of screenshots not deleted: #{count}")
|
|
105
102
|
if count > 0
|
|
106
103
|
if tries.zero?
|
|
@@ -114,113 +111,107 @@ module Deliver
|
|
|
114
111
|
end
|
|
115
112
|
end
|
|
116
113
|
|
|
117
|
-
def
|
|
118
|
-
|
|
119
|
-
localizations.each do |localization|
|
|
120
|
-
screenshot_sets = localization.get_app_screenshot_sets
|
|
121
|
-
screenshot_sets.each do |screenshot_set|
|
|
122
|
-
count += screenshot_set.app_screenshots.size
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
return count
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def upload_screenshots(screenshots_per_language, localizations, options)
|
|
130
|
-
# Check if should wait for processing
|
|
131
|
-
# Default to waiting if submitting for review (since needed for submission)
|
|
132
|
-
# Otherwise use enviroment variable
|
|
133
|
-
if ENV["DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING"].nil?
|
|
134
|
-
wait_for_processing = options[:submit_for_review]
|
|
135
|
-
UI.verbose("Setting wait_for_processing from ':submit_for_review' option")
|
|
136
|
-
else
|
|
137
|
-
UI.verbose("Setting wait_for_processing from 'DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING' environment variable")
|
|
138
|
-
wait_for_processing = !FastlaneCore::Env.truthy?("DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING")
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
if wait_for_processing
|
|
142
|
-
UI.important("Will wait for screenshot image processing")
|
|
143
|
-
UI.important("Set env DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING=true to skip waiting for screenshots to process")
|
|
144
|
-
else
|
|
145
|
-
UI.important("Skipping the wait for screenshot image processing (which may affect submission)")
|
|
146
|
-
UI.important("Set env DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING=false to wait for screenshots to process")
|
|
147
|
-
end
|
|
114
|
+
def upload_screenshots(localizations, screenshots_per_language, tries: 5)
|
|
115
|
+
tries -= 1
|
|
148
116
|
|
|
149
117
|
# Upload screenshots
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
118
|
+
worker = QueueWorker.new(NUMBER_OF_THREADS) do |job|
|
|
119
|
+
begin
|
|
120
|
+
UI.verbose("Uploading '#{job.path}'...")
|
|
121
|
+
start_time = Time.now
|
|
122
|
+
job.app_screenshot_set.upload_screenshot(path: job.path, wait_for_processing: false)
|
|
123
|
+
UI.message("Uploaded '#{job.path}'... (#{Time.now - start_time} secs)")
|
|
124
|
+
rescue => error
|
|
125
|
+
UI.error(error)
|
|
156
126
|
end
|
|
127
|
+
end
|
|
157
128
|
|
|
158
|
-
|
|
159
|
-
|
|
129
|
+
number_of_screenshots = 0
|
|
130
|
+
iterator = AppScreenshotIterator.new(localizations)
|
|
131
|
+
iterator.each_local_screenshot(screenshots_per_language) do |localization, app_screenshot_set, screenshot, index|
|
|
132
|
+
if index >= 10
|
|
133
|
+
UI.error("Too many screenshots found for device '#{screenshot.device_type}' in '#{screenshot.language}', skipping this one (#{screenshot.path})")
|
|
160
134
|
next
|
|
161
135
|
end
|
|
162
136
|
|
|
163
|
-
|
|
137
|
+
checksum = UploadScreenshots.calculate_checksum(screenshot.path)
|
|
138
|
+
duplicate = app_screenshot_set.app_screenshots.any? { |s| s.source_file_checksum == checksum }
|
|
164
139
|
|
|
165
|
-
#
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
140
|
+
# Enqueue uploading job if it's not duplicated otherwise screenshot will be skipped
|
|
141
|
+
if duplicate
|
|
142
|
+
UI.message("Previous uploaded. Skipping '#{screenshot.path}'...")
|
|
143
|
+
else
|
|
144
|
+
worker.enqueue(UploadScreenshotJob.new(app_screenshot_set, screenshot.path))
|
|
145
|
+
end
|
|
170
146
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
count: app_screenshot_set.app_screenshots.size,
|
|
174
|
-
checksums: []
|
|
175
|
-
}
|
|
147
|
+
number_of_screenshots += 1
|
|
148
|
+
end
|
|
176
149
|
|
|
177
|
-
|
|
178
|
-
indized[localization.locale][app_screenshot_set.screenshot_display_type][:checksums] = checksums
|
|
179
|
-
end
|
|
150
|
+
worker.start
|
|
180
151
|
|
|
181
|
-
|
|
182
|
-
screenshots_for_language.each do |screenshot|
|
|
183
|
-
display_type = screenshot.device_type
|
|
184
|
-
set = app_screenshot_sets_map[display_type]
|
|
152
|
+
UI.verbose('Uploading jobs are completed')
|
|
185
153
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
154
|
+
Helper.show_loading_indicator("Waiting for all the screenshots processed...")
|
|
155
|
+
states = wait_for_complete(iterator)
|
|
156
|
+
Helper.hide_loading_indicator
|
|
157
|
+
retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language)
|
|
190
158
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
screenshotDisplayType: display_type
|
|
194
|
-
})
|
|
195
|
-
app_screenshot_sets_map[display_type] = set
|
|
159
|
+
UI.message("Successfully uploaded all screenshots")
|
|
160
|
+
end
|
|
196
161
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
162
|
+
# Verify all screenshots have been processed
|
|
163
|
+
def wait_for_complete(iterator)
|
|
164
|
+
loop do
|
|
165
|
+
states = iterator.each_app_screenshot.map { |_, _, app_screenshot| app_screenshot }.each_with_object({}) do |app_screenshot, hash|
|
|
166
|
+
state = app_screenshot.asset_delivery_state['state']
|
|
167
|
+
hash[state] ||= 0
|
|
168
|
+
hash[state] += 1
|
|
169
|
+
end
|
|
202
170
|
|
|
203
|
-
|
|
171
|
+
is_processing = states.fetch('UPLOAD_COMPLETE', 0) > 0
|
|
172
|
+
return states unless is_processing
|
|
204
173
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
174
|
+
UI.verbose("There are still incomplete screenshots - #{states}")
|
|
175
|
+
sleep(5)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Verify all screenshots states on App Store Connect are okay
|
|
180
|
+
def retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language)
|
|
181
|
+
is_failure = states.fetch("FAILED", 0) > 0
|
|
182
|
+
is_missing_screenshot = states.reduce(0) { |sum, (k, v)| sum + v } != number_of_screenshots
|
|
183
|
+
if is_failure || is_missing_screenshot
|
|
184
|
+
if tries.zero?
|
|
185
|
+
incomplete_screenshot_count = states.reject { |k, v| k == 'COMPLETE' }.reduce(0) { |sum, (k, v)| sum + v }
|
|
186
|
+
UI.user_error!("Failed verification of all screenshots uploaded... #{incomplete_screenshot_count} incomplete screenshot(s) still exist")
|
|
187
|
+
else
|
|
188
|
+
UI.error("Failed to upload all screenshots... Tries remaining: #{tries}")
|
|
189
|
+
# Delete bad entries before retry
|
|
190
|
+
iterator.each_app_screenshot do |_, _, app_screenshot|
|
|
191
|
+
app_screenshot.delete! unless app_screenshot.complete?
|
|
208
192
|
end
|
|
193
|
+
upload_screenshots(localizations, screenshots_per_language, tries: tries)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
209
197
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
duplicate = indized[localization.locale][set.screenshot_display_type][:checksums].include?(checksum)
|
|
198
|
+
def sort_screenshots(localizations)
|
|
199
|
+
iterator = AppScreenshotIterator.new(localizations)
|
|
213
200
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
end
|
|
201
|
+
# Re-order screenshots within app_screenshot_set
|
|
202
|
+
worker = QueueWorker.new(NUMBER_OF_THREADS) do |app_screenshot_set|
|
|
203
|
+
original_ids = app_screenshot_set.app_screenshots.map(&:id)
|
|
204
|
+
sorted_ids = app_screenshot_set.app_screenshots.sort_by(&:file_name).map(&:id)
|
|
205
|
+
if original_ids != sorted_ids
|
|
206
|
+
app_screenshot_set.reorder_screenshots(app_screenshot_ids: sorted_ids)
|
|
221
207
|
end
|
|
222
208
|
end
|
|
223
|
-
|
|
209
|
+
|
|
210
|
+
iterator.each_app_screenshot_set do |_, app_screenshot_set|
|
|
211
|
+
worker.enqueue(app_screenshot_set)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
worker.start
|
|
224
215
|
end
|
|
225
216
|
|
|
226
217
|
def collect_screenshots(options)
|
|
@@ -299,5 +290,11 @@ module Deliver
|
|
|
299
290
|
Spaceship::Tunes.client.available_languages
|
|
300
291
|
end
|
|
301
292
|
end
|
|
293
|
+
|
|
294
|
+
# helper method to mock this step in tests
|
|
295
|
+
def self.calculate_checksum(path)
|
|
296
|
+
bytes = File.binread(path)
|
|
297
|
+
Digest::MD5.hexdigest(bytes)
|
|
298
|
+
end
|
|
302
299
|
end
|
|
303
300
|
end
|