Class: Fastlane::Actions::AliossAction

Inherits:
Action
  • Object
show all
Defined in:
lib/fastlane/plugin/alioss/actions/alioss_action.rb

Class Method Summary collapse

Class Method Details

.authorsObject



277
278
279
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 277

def self.authors
  ["woodwu"]
end

.available_optionsObject



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 300

def self.available_options
  [
      FastlaneCore::ConfigItem.new(key: :endpoint,
                                   env_name: "ALIOSS_ENDPOINT",
                                   description: "请提供 endpoint,Endpoint 表示 OSS 对外服务的访问域名。OSS 以 HTTP RESTful API 的形式对外提供服务,当访问不同的 Region 的时候,需要不同的域名。",
                                   optional: false,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :access_key_id,
                                   env_name: "ALIOSS_ACCESS_KEY_ID",
                                   description: "请提供 AccessKeyId,OSS 通过使用 AccessKeyId 和 AccessKeySecret 对称加密的方法来验证某个请求的发送者身份。AccessKeyId 用于标识用户。",
                                   optional: false,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :access_key_secret,
                                   env_name: "ALIOSS_ACCESS_KEY_SECRET",
                                   description: "请提供 AccessKeySecret,OSS 通过使用 AccessKeyId 和 AccessKeySecret 对称加密的方法来验证某个请求的发送者身份。AccessKeySecret 是用户用于加密签名字符串和 OSS 用来验证签名字符串的密钥,必须保密。",
                                   optional: false,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :bucket_name,
                                   env_name: "ALIOSS_BUCKET_NAME",
                                   description: "请提供 bucket_name,存储空间(Bucket)是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。存储空间具有各种配置属性,包括地域、访问权限、存储类型等。您可以根据实际需求,创建不同类型的存储空间来存储不同的数据。",
                                   optional: false,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :apk,
                                   env_name: "ALIOSS_APK",
                                   description: "APK文件路径",
                                   default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH],
                                   optional: true,
                                   verify_block: proc do |value|
                                     UI.user_error!("请检查apk文件路径 '#{value}' )") unless File.exist?(value)
                                   end,
                                   conflicting_options: [:ipa],
                                   conflict_block: proc do |value|
                                     UI.user_error!("在运行的选项中不能使用 'apk' and '#{value.key}')")
                                   end),
      FastlaneCore::ConfigItem.new(key: :ipa,
                                   env_name: "ALIOSS_IPA",
                                   description: "IPA文件路径,可选的action有_gym_和_xcodebuild_,Mac是.app文件,安卓是.apk文件。",
                                   default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH],
                                   optional: true,
                                   verify_block: proc do |value|
                                     UI.user_error!("请检查apk文件路径 '#{value}' ") unless File.exist?(value)
                                   end,
                                   conflicting_options: [:apk],
                                   conflict_block: proc do |value|
                                     UI.user_error!("在运行的选项中不能使用 'ipa' 和 '#{value.key}'")
                                   end),
      FastlaneCore::ConfigItem.new(key: :app_name,
                                   env_name: "APP_NAME",
                                   description: "App的名称,你的服务器中可能有多个App,需要用App名称来区分,这个名称也是文件目录的名称,可以是App的路径。",
                                   default_value: "app",
                                   optional: true,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :download_domain,
                                   env_name: "ALIOSS_DOWNLOAD_DOMAIN",
                                   description: "下载域名,默认是https://{bucket_name}.{endpoint}/",
                                   optional: true,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :html_header_title,
                                   env_name: "ALIOSS_HTML_HEADER_TITLE",
                                   description: "html下载页面header title",
                                   default_value: "很高兴邀请您安装我们的App,测试并反馈问题,便于我们及时解决您遇到的问题,十分谢谢!Thanks♪(・ω・)ノ",
                                   optional: true,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :update_description,
                                   env_name: "ALIOSS_UPDATE_DESCRIPTION",
                                   description: "设置app更新日志,描述你修改了哪些内容。",
                                   optional: true,
                                   type: String),
      FastlaneCore::ConfigItem.new(key: :list_buckets,
                                   env_name: "ALIOSS_LIST_BUCKETS",
                                   description: "是否列出已经上传的所有的buckets",
                                   default_value: nil,
                                   optional: true)
  ]
end

.create_config_json_file(params) ⇒ Object



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 453

def self.create_config_json_file(params)
  # create config.json file
  config_json_file = File.new("config.json", "w")
  config_json_file.puts("{
    \"version\": \"0.1.0\",
    \"domain\": \"#{params[:download_domain]}\",
    \"desc\": \"每个版本的配置文件,记录着所有历史版本。\",
    \"title\": \"#{params[:html_header_title]}\",
    \"ipaList\": [],
    \"apkList\": [],
    \"appList\": []
}")
  config_json_file.close
  return config_json_file
end

.create_index_html_file(params) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 469

def self.create_index_html_file(params)
  # create index.html file
  filename = Helper::AliossHelper.index_html_template_path
  temp_filename = "temp_index.html"
  if File.readable?(filename)
    temp_file = File.new(temp_filename, "w")
    IO.foreach(filename) do |block|
      # temp_file.puts(block.gsub("your_config_json_url", "#{params[:download_domain]}#{params[:path_for_app_name]}/config.json"))
      temp_file.puts(block.gsub("__html_header_title__", params[:html_header_title]))
    end
    temp_file.close
    return temp_file
  else
    UI.user_error!("项目index.html不存在")
  end
end

.create_manifest_file(params) ⇒ Object



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 401

def self.create_manifest_file(params)
  # create manifest.plist file
  manifest_file = File.new("manifest.plist", "w")
  manifest_file.puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
  <dict>
    <key>items</key>
    <array>
<dict>
  <key>assets</key>
  <array>
    <dict>
      <key>kind</key>
      <string>software-package</string>
      <key>url</key>
      <string>#{params[:download_url]}</string>
    </dict>
    <dict>
      <key>kind</key>
      <string>display-image</string>
      <key>url</key>
      <string>#{params[:download_domain]}#{params[:path_for_app_name]}/iOS/icon/icon-57.png</string>
    </dict>
    <dict>
      <key>kind</key>
      <string>full-size-image</string>
      <key>url</key>
      <string>#{params[:download_domain]}#{params[:path_for_app_name]}/iOS/icon/icon-512.png</string>
    </dict>
  </array>
  <key>metadata</key>
  <dict>
    <key>bundle-identifier</key>
    <string>#{params[:app_identifier]}</string>
    <key>bundle-version</key>
    <string>#{params[:version_number]}</string>
    <key>kind</key>
    <string>software</string>
    <key>platform-identifier</key>
    <string>com.apple.platform.iphoneos</string>
    <key>title</key>
    <string>#{params[:app_name]}</string>
  </dict>
</dict>
    </array>
  </dict>
</plist>")
  manifest_file.close
  return manifest_file
end

.delete_file(params) ⇒ Object



486
487
488
489
490
491
492
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 486

def self.delete_file(params)
  file = params[:file]
  # 上传完成后删除本地文件
  if File.file?(File.expand_path(file))
    File.delete(File.expand_path(file))
  end
end

.descriptionObject



273
274
275
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 273

def self.description
  "upload ipa/apk to aliyun oos server, and scan QRcode to install app on mobile phone."
end

.detailsObject



295
296
297
298
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 295

def self.details
  # Optional:
  "将App发布到公司的阿里云文件服务器中,方便内部员工扫码测试"
end

.example_codeObject



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 384

def self.example_code
  [
      # 上传App到阿里云oss服务器
      'alioss(
          endpoint: "oss-cn-shenzhen.aliyuncs.com",
          access_key_id: "xxxxx",
          access_key_secret: "xxxxx",
          bucket_name: "cn-app-test",
          app_name: "app/appname",
          download_domain: "https://dl.yourdomain.com/",
          update_description: "update description",
          ipa: "valid ipa path", # iOS project required
          apk: "valid apk path" # Android project required
      )'
  ]
end

.is_supported?(platform) ⇒ Boolean

Returns:

  • (Boolean)


376
377
378
379
380
381
382
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 376

def self.is_supported?(platform)
  # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
  # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
  #
  # [:ios, :mac, :android].include?(platform)
  true
end

.outputObject



281
282
283
284
285
286
287
288
289
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 281

def self.output
  [
      ['ALIOSS_BUILD_NUMBER', 'Update the new build number(version code) of your iOS/Android project'],
      ['ALIOSS_VERSION_NUMBER', 'Update the new version number(version name) of your iOS/Android project'],
      ['ALIOSS_PUBLISH_TIMESTAMP', 'The timestamp of auto-build'],
      ['ALIOSS_FILE_SIZE', 'The size of your ipa/apk file'],
      ['ALIOSS_DOWNLOAD_URL', 'The website url that you can download it']
  ]
end

.return_valueObject



291
292
293
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 291

def self.return_value
  # If your method provides a return value, you can describe here what it does
end

.run(params) ⇒ Object



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
51
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
79
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
175
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/fastlane/plugin/alioss/actions/alioss_action.rb', line 18

def self.run(params)
  UI.message("The alioss plugin is working!")

  endpoint = params[:endpoint]
  bucket_name = params[:bucket_name]
  access_key_id = params[:access_key_id]
  access_key_secret = params[:access_key_secret]
  path_for_app_name = params[:app_name]
  html_header_title = params[:html_header_title]
  list_buckets = params[:list_buckets]

  build_file = [
      params[:ipa],
      params[:apk]
  ].detect { |e| !e.to_s.empty? }

  # build_file = "/Users/wood/Documents/SuperbuyApp/build/output/Beta_20191127202707/Superbuy.ipa"

  if build_file.nil?
    UI.user_error!("请提供构建文件")
  end

  UI.message "endpoint: #{endpoint}  bucket_name: #{bucket_name}"
  UI.message "构建文件: #{build_file}"

  download_domain = params[:download_domain]
  if download_domain.nil?
    download_domain = "https://#{bucket_name}.#{endpoint}/"
  end

  update_description = params[:update_description]
  if update_description.nil?
    update_description = ""
  end

  # create aliyun oss client
  client = Aliyun::OSS::Client.new(
      endpoint: endpoint,
      access_key_id: access_key_id,
      access_key_secret: access_key_secret
  )

  bucket = client.get_bucket(bucket_name)

  # list all buckets
  unless list_buckets.nil? || %w(NO no false FALSE).include?(list_buckets) || list_buckets == false
    UI.message "========== list all buckets =========="
    bucket_objects = []
    bucket.list_objects.each do |o|
      UI.message o.key
      bucket_objects.push(o.key)
    end
  end
  UI.message "======================================"

  # 配置logo
  unless bucket.object_exists?("#{path_for_app_name}/icon.png")
    input = File.expand_path(UI.input("icon.png不存在,请上传您App的logo(120*120 <= 尺寸 <= 480*480),请输入文件路径(例如:/Users/xxx/Desktop/icon.png):"))
    # 判断是一个普通文件 && 可读 && 扩展名是png
    if File.readable_real?(input) && File.extname(input) == ".png"
      bucket.put_object("#{path_for_app_name}/icon.png", :file => input)
      UI.message "#{path_for_app_name}/icon.png 配置完成"
    else
      UI.important "文件不存在或无权限读写,请确认icon.png的文件路径"
    end
  end

  # 必须包含的文件,没有就引导去下载
  # must_bucket_file = ["app/config.json", "app/index.html"]
  unless bucket.object_exists?("#{path_for_app_name}/config.json")
    UI.message "配置文件不存在,初始化#{path_for_app_name}/config.json"
    config_json_file = self.create_config_json_file(
        download_domain: download_domain,
        html_header_title: html_header_title
    )
    bucket.put_object("#{path_for_app_name}/config.json", :file => File.expand_path(config_json_file))
    # 上传完成后删除本地文件
    self.delete_file(file: config_json_file)
    UI.message "#{path_for_app_name}/config.json 配置完成"
  end

  index_html_template_path = Helper::AliossHelper.index_html_template_path
  UI.message "模板文件路径:#{index_html_template_path}"

  if !bucket.object_exists?("#{path_for_app_name}/index.html")
    UI.message "配置文件不存在,初始化#{path_for_app_name}/index.html"
    bucket.put_object("#{path_for_app_name}/index.html", :file => index_html_template_path)
    UI.message "#{path_for_app_name}/index.html 配置完成"
  else
    if Alioss::VERSION >= "0.1.6"
      temp_index_html_path = File.expand_path('temp_index.html')
      bucket.get_object("#{path_for_app_name}/index.html", :file => temp_index_html_path)
      temp_index_html = File.read(temp_index_html_path)
      last_match_obj = temp_index_html.to_s.scan(/>version:(.*)<\/div>/).last
      if last_match_obj.class == Array && !last_match_obj.empty?
        html_version = last_match_obj.first
        UI.message "index.html version: #{html_version}"
        if html_version < "0.1.8"
          # 这里最好是跟本地版本作比较,如果大于线上版本号就更新
          UI.message "发现index.html有新版本0.1.8,开始更新"
          bucket.put_object("#{path_for_app_name}/index.html", :file => index_html_template_path)
          UI.message "成功更新index.html"
        end
      else
        # 匹配不到版本号,是以前版本,上传新文件覆盖
        bucket.put_object("#{path_for_app_name}/index.html", :file => index_html_template_path)
        UI.message "成功更新index.html"
      end
      self.delete_file(file: temp_index_html_path)
    end
  end

  file_size = File.size(build_file)
  filename = File.basename(build_file)

  # 获取 build_number & version_number
  build_number = Actions.lane_context[SharedValues::BUILD_NUMBER]
  version_number = Actions.lane_context[SharedValues::VERSION_NUMBER]

  # 根据不同的文件类型区分平台,拼接bucket_path路径
  case File.extname(filename)
  when ".ipa"
    bucket_path = "#{path_for_app_name}/iOS"
    # 如果无法从lane_context中获取则从实际的ipa文件中读取
    if build_number.nil? || version_number.nil?
      build_number = GetIpaInfoPlistValueAction.run(ipa: build_file, key: 'CFBundleVersion')
      version_number = GetIpaInfoPlistValueAction.run(ipa: build_file, key: 'CFBundleShortVersionString')
    end
  when ".apk"
    bucket_path = "#{path_for_app_name}/Android"
    # versionName、versionCode先从output.json文件里取
    apk_output_json_path = File.join(File.dirname(build_file), "output.json")
    if File.readable?(apk_output_json_path)
      apk_output_content = File.read(apk_output_json_path)
      version_number_regex = /"versionName":"(\d+\.\d+\.\d+)"/.match(apk_output_content)
      build_number_regex = /"versionCode":(\d+)/.match(apk_output_content)
      if !version_number_regex.nil? && !build_number_regex.nil?
        version_number = version_number_regex.captures.first
        build_number = build_number_regex.captures.first
      end
    end
    # 如果output.json文件里取不到则从Actions.lane_context中取
    if build_number.nil? || version_number.nil?
      if Actions.lane_context[:ANDROID_VERSION_NAME].nil? || Actions.lane_context[:ANDROID_VERSION_CODE].nil?
        UI.important "Actions.lane_context 不包含[ANDROID_VERSION_NAME, ANDROID_VERSION_CODE],请配置fastlane env(推荐在Fastlane文件中使用fastlane-plugin-versioning_android获取versionCode/versionName)。"
      else
        build_number = Actions.lane_context[SharedValues::ANDROID_VERSION_CODE]
        version_number = Actions.lane_context[SharedValues::ANDROID_VERSION_NAME]
      end
    end
  when ".app"
    bucket_path = "#{path_for_app_name}/Mac"
  else
    bucket_path = "#{path_for_app_name}/unknown"
    UI.user_error!("不支持的APP类型")
  end
  timestamp = Time.now
  bucket_path = "#{bucket_path}/#{timestamp.strftime('%Y%m%d%H%M%S')}/"

  UI.message "正在上传文件,可能需要几分钟,请稍等..."

  bucket.put_object(bucket_path + filename, :file => build_file)
  download_url = "#{download_domain}#{bucket_path}#{filename}"
  UI.message "download_url: #{download_url}"

  UI.message "上传成功,正在更新配置文件..."

  config_json_file_path = File.expand_path('config.json')
  bucket.get_object("#{path_for_app_name}/config.json", :file => config_json_file_path)
  config_json = JSON.parse(File.read(config_json_file_path))

  UI.message "build_number: #{build_number}  version_number: #{version_number}"

  # 构建文件元信息
  item_hash = {
      "domain" => download_domain,
      "path" => bucket_path,
      "name" => filename,
      "size" => file_size,
      "desc" => update_description,
      "version" => version_number,
      "build" => build_number,
      "time" => timestamp.to_i
  }
  # Store the build_number, version_number, time, size
  Actions.lane_context[SharedValues::ALIOSS_BUILD_NUMBER] = build_number
  Actions.lane_context[SharedValues::ALIOSS_VERSION_NUMBER] = version_number
  Actions.lane_context[SharedValues::ALIOSS_PUBLISH_TIMESTAMP] = timestamp.to_i
  Actions.lane_context[SharedValues::ALIOSS_FILE_SIZE] = file_size
  Actions.lane_context[SharedValues::ALIOSS_DOWNLOAD_URL] = "#{download_domain}#{path_for_app_name}/index.html"

  # 根据不同的平台,将bucket_path记录到json中
  case File.extname(filename)
  when ".ipa"
    UI.message "配置 manifest.plist ..."
    app_name = GetIpaInfoPlistValueAction.run(ipa: build_file, key: 'CFBundleDisplayName')
    app_identifier = GetIpaInfoPlistValueAction.run(ipa: build_file, key: 'CFBundleIdentifier')
    UI.message "app_name: #{app_name}"
    UI.message "app_identifier: #{app_identifier}"
    UI.message "version_number: #{version_number}"
    manifest_file = self.create_manifest_file(
        app_name: app_name,
        path_for_app_name: path_for_app_name,
        app_identifier: app_identifier,
        version_number: version_number,
        download_domain: download_domain,
        download_url: download_url
    )
    bucket_plist_path = "#{bucket_path}#{File.basename(manifest_file)}"
    UI.message "bucket_plist_path: #{bucket_plist_path}"
    bucket.put_object(bucket_plist_path, :file => File.expand_path(manifest_file))
    # 上传完成后删除本地文件
    self.delete_file(file: manifest_file)

    ipaList = config_json["ipaList"]
    if ipaList.nil?
      ipaList = []
    end
    item_hash["version"] = version_number
    item_hash["identifier"] = app_identifier
    ipaList.push(item_hash)
    config_json["ipaList"] = ipaList
  when ".apk"
    apkList = config_json["apkList"]
    if apkList.nil?
      apkList = []
    end
    apkList.push(item_hash)
    config_json["apkList"] = apkList
  when ".app"
    appList = config_json["appList"]
    if appList.nil?
      appList = []
    end
    appList.push(item_hash)
    config_json["appList"] = appList
  else
    UI.message ""
  end
  config_json["title"] = html_header_title

  UI.message "配置 config_json ..."
  # 将新的json数据写入到config_json_file中
  config_json_file = File.new(config_json_file_path, "w")
  config_json_file.puts(JSON.generate(config_json))
  config_json_file.close
  # 上传新文件覆盖oss的config.json
  bucket.put_object("#{path_for_app_name}/config.json", :file => config_json_file_path)
  # 上传完成后删除本地文件
  self.delete_file(file: config_json_file)

  UI.success "Success!✌️,请访问: #{download_domain}#{path_for_app_name}/index.html"

end