Class: Supply::Uploader

Inherits:
Object
  • Object
show all
Defined in:
supply/lib/supply/uploader.rb

Overview

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Classes: UploadJob

Instance Method Summary collapse

Instance Method Details

#fetch_track_and_release!(track, version_code, status = nil) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'supply/lib/supply/uploader.rb', line 109

def fetch_track_and_release!(track, version_code, status = nil)
  tracks = client.tracks(track)
  return nil, nil if tracks.empty?

  track = tracks.first
  releases = track.releases

  releases = releases.select { |r| r.status == status } if status
  releases = releases.select { |r| (r.version_codes || []).map(&:to_s).include?(version_code.to_s) } if version_code

  if releases.size > 1
    UI.user_error!("More than one release found in this track. Please specify with the :version_code option to select a release.")
  end

  return track, releases.first
end

#perform_uploadObject



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'supply/lib/supply/uploader.rb', line 8

def perform_upload
  FastlaneCore::PrintTable.print_values(config: Supply.config, hide_keys: [:issuer], mask_keys: [:json_key_data], title: "Summary for supply #{Fastlane::VERSION}")

  client.begin_edit(package_name: Supply.config[:package_name])

  verify_config!

  apk_version_codes = []
  apk_version_codes.concat(upload_apks) unless Supply.config[:skip_upload_apk]
  apk_version_codes.concat(upload_bundles) unless Supply.config[:skip_upload_aab]
  upload_mapping(apk_version_codes)

  track_to_update = Supply.config[:track]

  apk_version_codes.concat(Supply.config[:version_codes_to_retain]) if Supply.config[:version_codes_to_retain]

  if !apk_version_codes.empty?
    # Only update tracks if we have version codes
    # update_track handle setting rollout if needed
    # Updating a track with empty version codes can completely clear out a track
    update_track(apk_version_codes)
  else
    # Only promote or rollout if we don't have version codes
    if Supply.config[:track_promote_to]
      track_to_update = Supply.config[:track_promote_to]
      promote_track
    elsif !Supply.config[:rollout].nil? && Supply.config[:track].to_s != ""
      update_rollout
    end
  end

  perform_upload_meta(apk_version_codes, track_to_update)

  if Supply.config[:validate_only]
    UI.message("Validating all changes with Google Play...")
    client.validate_current_edit!
    UI.success("Successfully validated the upload to Google Play")
  else
    UI.message("Uploading all changes to Google Play...")
    client.commit_current_edit!
    UI.success("Successfully finished the upload to Google Play")
  end
end

#perform_upload_meta(version_codes, track_name) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'supply/lib/supply/uploader.rb', line 80

def perform_upload_meta(version_codes, track_name)
  if (!Supply.config[:skip_upload_metadata] || !Supply.config[:skip_upload_images] || !Supply.config[:skip_upload_changelogs] || !Supply.config[:skip_upload_screenshots]) && 
    # Use version code from config if version codes is empty and no nil or empty string
    version_codes = [Supply.config[:version_code]] if version_codes.empty?
    version_codes = version_codes.reject do |version_code|
      version_codes.to_s == ""
    end

    version_codes.each do |version_code|
      UI.user_error!("Could not find folder #{}") unless File.directory?()

      track, release = fetch_track_and_release!(track_name, version_code)
      UI.user_error!("Unable to find the requested track - '#{Supply.config[:track]}'") unless track
      UI.user_error!("Could not find release for version code '#{version_code}' to update changelog") unless release

      release_notes_queue = Queue.new
      upload_worker = create_meta_upload_worker
      upload_worker.batch_enqueue(
        # skip . or .. or hidden folders
        all_languages.reject { |lang| lang.start_with?('.') }.map { |lang| UploadJob.new(lang, version_code, release_notes_queue) }
      )
      upload_worker.start

      release_notes = Array.new(release_notes_queue.size) { release_notes_queue.pop } # Queue to Array
      upload_changelogs(release_notes, release, track, track_name) unless release_notes.empty?
    end
  end
end

#perform_upload_to_internal_app_sharingObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'supply/lib/supply/uploader.rb', line 52

def perform_upload_to_internal_app_sharing
  download_urls = []

  package_name = Supply.config[:package_name]

  apk_paths = [Supply.config[:apk]] unless (apk_paths = Supply.config[:apk_paths])
  apk_paths.compact!
  apk_paths.each do |apk_path|
    download_url = client.upload_apk_to_internal_app_sharing(package_name, apk_path)
    download_urls << download_url
    UI.success("Successfully uploaded APK to Internal App Sharing URL: #{download_url}")
  end

  aab_paths = [Supply.config[:aab]] unless (aab_paths = Supply.config[:aab_paths])
  aab_paths.compact!
  aab_paths.each do |aab_path|
    download_url = client.upload_bundle_to_internal_app_sharing(package_name, aab_path)
    download_urls << download_url
    UI.success("Successfully uploaded AAB to Internal App Sharing URL: #{download_url}")
  end

  if download_urls.count == 1
    return download_urls.first
  else
    return download_urls
  end
end

#promote_trackObject



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'supply/lib/supply/uploader.rb', line 176

def promote_track
  track_from = client.tracks(Supply.config[:track]).first
  unless track_from
    UI.user_error!("Cannot promote from track '#{Supply.config[:track]}' - track doesn't exist")
  end

  releases = track_from.releases
  if Supply.config[:version_code].to_s != ""
    releases = releases.select do |release|
      release.version_codes.include?(Supply.config[:version_code].to_s)
    end
  else
    releases = releases.select do |release|
      release.status == Supply.config[:release_status]
    end
  end

  if releases.size == 0
    UI.user_error!("Track '#{Supply.config[:track]}' doesn't have any releases")
  elsif releases.size > 1
    UI.user_error!("Track '#{Supply.config[:track]}' has more than one release - use :version_code to filter the release to promote")
  end

  release = releases.first
  track_to = client.tracks(Supply.config[:track_promote_to]).first

  rollout = (Supply.config[:rollout] || 0).to_f
  if rollout > 0 && rollout < 1
    release.status = Supply::ReleaseStatus::IN_PROGRESS
    release.user_fraction = rollout
  else
    release.status = Supply.config[:track_promote_release_status]
    release.user_fraction = nil
  end

  if track_to
    # Its okay to set releases to an array containing the newest release
    # Google Play will keep previous releases there this release is a partial rollout
    track_to.releases = [release]
  else
    track_to = AndroidPublisher::Track.new(
      track: Supply.config[:track_promote_to],
      releases: [release]
    )
  end

  client.update_track(Supply.config[:track_promote_to], track_to)
end

#update_rolloutObject



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'supply/lib/supply/uploader.rb', line 126

def update_rollout
  track, release = fetch_track_and_release!(Supply.config[:track], Supply.config[:version_code], Supply::ReleaseStatus::IN_PROGRESS)
  UI.user_error!("Unable to find the requested track - '#{Supply.config[:track]}'") unless track
  UI.user_error!("Unable to find the requested release on track - '#{Supply.config[:track]}'") unless release

  version_code = release.version_codes.max

  UI.message("Updating #{version_code}'s rollout to '#{Supply.config[:rollout]}' on track '#{Supply.config[:track]}'...")

  if track && release
    completed = Supply.config[:rollout].to_f == 1
    release.user_fraction = completed ? nil : Supply.config[:rollout]
    release.status = Supply::ReleaseStatus::COMPLETED if completed

    # Deleted other version codes if completed because only allowed on completed version in a release
    track.releases.delete_if { |r| !(r.version_codes || []).map(&:to_s).include?(version_code) } if completed
  else
    UI.user_error!("Unable to find version to rollout in track '#{Supply.config[:track]}'")
  end

  client.update_track(Supply.config[:track], track)
end

#upload_apksObject



324
325
326
327
328
329
330
331
332
333
334
335
# File 'supply/lib/supply/uploader.rb', line 324

def upload_apks
  apk_paths = [Supply.config[:apk]] unless (apk_paths = Supply.config[:apk_paths])
  apk_paths.compact!

  apk_version_codes = []

  apk_paths.each do |apk_path|
    apk_version_codes.push(upload_binary_data(apk_path))
  end

  return apk_version_codes
end

#upload_bundlesObject



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'supply/lib/supply/uploader.rb', line 347

def upload_bundles
  aab_paths = [Supply.config[:aab]] unless (aab_paths = Supply.config[:aab_paths])
  return [] unless aab_paths
  aab_paths.compact!

  aab_version_codes = []

  aab_paths.each do |aab_path|
    UI.message("Preparing aab at path '#{aab_path}' for upload...")
    bundle_version_code = client.upload_bundle(aab_path)
    aab_version_codes.push(bundle_version_code)
  end

  return aab_version_codes
end

#upload_changelog(language, version_code) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'supply/lib/supply/uploader.rb', line 225

def upload_changelog(language, version_code)
  UI.user_error!("Cannot find changelog because no version code given - please specify :version_code") unless version_code

  path = File.join(Supply.config[:metadata_path], language, Supply::CHANGELOGS_FOLDER_NAME, "#{version_code}.txt")
  changelog_text = ''
  if File.exist?(path)
    UI.message("Updating changelog for '#{version_code}' and language '#{language}'...")
    changelog_text = File.read(path, encoding: 'UTF-8')
  else
    default_changelog_path = File.join(Supply.config[:metadata_path], language, Supply::CHANGELOGS_FOLDER_NAME, "default.txt")
    if File.exist?(default_changelog_path)
      UI.message("Updating changelog for '#{version_code}' and language '#{language}' to default changelog...")
      changelog_text = File.read(default_changelog_path, encoding: 'UTF-8')
    else
      UI.message("Could not find changelog for '#{version_code}' and language '#{language}' at path #{path}...")
    end
  end

  AndroidPublisher::LocalizedText.new(
    language: language,
    text: changelog_text
  )
end

#upload_changelogs(release_notes, release, track, track_name) ⇒ Object



249
250
251
252
# File 'supply/lib/supply/uploader.rb', line 249

def upload_changelogs(release_notes, release, track, track_name)
  release.release_notes = release_notes
  client.upload_changelogs(track, track_name)
end

#upload_images(language) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'supply/lib/supply/uploader.rb', line 267

def upload_images(language)
  Supply::IMAGES_TYPES.each do |image_type|
    search = File.join(, language, Supply::IMAGES_FOLDER_NAME, image_type) + ".#{IMAGE_FILE_EXTENSIONS}"
    path = Dir.glob(search, File::FNM_CASEFOLD).last
    next unless path

    if Supply.config[:sync_image_upload]
      UI.message("🔍 Checking #{image_type} checksum...")
      existing_images = client.fetch_images(image_type: image_type, language: language)
      sha256 = Digest::SHA256.file(path).hexdigest
      if existing_images.map(&:sha256).include?(sha256)
        UI.message("🟰 Skipping upload of screenshot #{path} as remote sha256 matches.")
        next
      end
    end

    UI.message("⬆️ Uploading image file #{path}...")
    client.upload_image(image_path: File.expand_path(path),
                        image_type: image_type,
                          language: language)
  end
end

#upload_mapping(apk_version_codes) ⇒ Object



337
338
339
340
341
342
343
344
345
# File 'supply/lib/supply/uploader.rb', line 337

def upload_mapping(apk_version_codes)
  mapping_paths = [Supply.config[:mapping]] unless (mapping_paths = Supply.config[:mapping_paths])
  mapping_paths.product(apk_version_codes).each do |mapping_path, version_code|
    if mapping_path
      UI.message("Preparing mapping at path '#{mapping_path}', version code #{version_code} for upload...")
      client.upload_mapping(mapping_path, version_code)
    end
  end
end

#upload_metadata(language, listing) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
# File 'supply/lib/supply/uploader.rb', line 254

def (language, listing)
  Supply::AVAILABLE_METADATA_FIELDS.each do |key|
    path = File.join(, language, "#{key}.txt")
    listing.send("#{key}=".to_sym, File.read(path, encoding: 'UTF-8')) if File.exist?(path)
  end
  begin
    listing.save
  rescue Encoding::InvalidByteSequenceError => ex
    message = (ex.message || '').capitalize
    UI.user_error!("Metadata must be UTF-8 encoded. #{message}")
  end
end

#upload_screenshots(language) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'supply/lib/supply/uploader.rb', line 290

def upload_screenshots(language)
  Supply::SCREENSHOT_TYPES.each do |screenshot_type|
    search = File.join(, language, Supply::IMAGES_FOLDER_NAME, screenshot_type, "*.#{IMAGE_FILE_EXTENSIONS}")
    paths = Dir.glob(search, File::FNM_CASEFOLD).sort
    next unless paths.count > 0

    if Supply.config[:sync_image_upload]
      UI.message("🔍 Checking #{screenshot_type} checksums...")
      existing_images = client.fetch_images(image_type: screenshot_type, language: language)
      # Don't keep images that either don't exist locally, or that are out of order compared to the `paths` to upload
      first_path_checksum = Digest::SHA256.file(paths.first).hexdigest
      existing_images.each do |image|
        if image.sha256 == first_path_checksum
          UI.message("🟰 Skipping upload of screenshot #{paths.first} as remote sha256 matches.")
          paths.shift # Remove first path from the list of paths to be uploaded
          first_path_checksum = paths.empty? ? nil : Digest::SHA256.file(paths.first).hexdigest
        else
          UI.message("🚮 Deleting #{language} screenshot id ##{image.id} as it does not exist locally or is out of order...")
          client.clear_screenshot(image_type: screenshot_type, language: language, image_id: image.id)
        end
      end
    else
      client.clear_screenshots(image_type: screenshot_type, language: language)
    end

    paths.each do |path|
      UI.message("⬆️  Uploading screenshot #{path}...")
      client.upload_image(image_path: File.expand_path(path),
                          image_type: screenshot_type,
                            language: language)
    end
  end
end

#verify_config!Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'supply/lib/supply/uploader.rb', line 149

def verify_config!
  unless  || Supply.config[:apk] || Supply.config[:apk_paths] || Supply.config[:aab] || Supply.config[:aab_paths] || (Supply.config[:track] && Supply.config[:track_promote_to]) || (Supply.config[:track] && Supply.config[:rollout])
    UI.user_error!("No local metadata, apks, aab, or track to promote were found, make sure to run `fastlane supply init` to setup supply")
  end

  # Can't upload both at apk and aab at same time
  # Need to error out users when there both apks and aabs are detected
  apk_paths = [Supply.config[:apk], Supply.config[:apk_paths]].flatten.compact
  could_upload_apk = !apk_paths.empty? && !Supply.config[:skip_upload_apk]
  could_upload_aab = Supply.config[:aab] && !Supply.config[:skip_upload_aab]
  if could_upload_apk && could_upload_aab
    UI.user_error!("Cannot provide both apk(s) and aab - use `skip_upload_apk`, `skip_upload_aab`, or  make sure to remove any existing .apk or .aab files that are no longer needed")
  end

  if Supply.config[:release_status] == Supply::ReleaseStatus::DRAFT && Supply.config[:rollout]
    UI.user_error!(%(Cannot specify rollout percentage when the release status is set to 'draft'))
  end

  if Supply.config[:track_promote_release_status] == Supply::ReleaseStatus::DRAFT && Supply.config[:rollout]
    UI.user_error!(%(Cannot specify rollout percentage when the track promote release status is set to 'draft'))
  end

  unless Supply.config[:version_codes_to_retain].nil?
    Supply.config[:version_codes_to_retain] = Supply.config[:version_codes_to_retain].map(&:to_i)
  end
end