class Fastlane::Actions::AliossAction
Public Class Methods
available_options()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 299 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)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 452 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)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 468 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)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 400 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)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 485 def self.delete_file(params) file = params[:file] # 上传完成后删除本地文件 if File.file?(File.expand_path(file)) File.delete(File.expand_path(file)) end end
description()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 272 def self.description "upload ipa/apk to aliyun oos server, and scan QRcode to install app on mobile phone." end
details()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 294 def self.details # Optional: "将App发布到公司的阿里云文件服务器中,方便内部员工扫码测试" end
example_code()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 383 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)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 375 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
output()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 280 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_value()
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 290 def self.return_value # If your method provides a return value, you can describe here what it does end
run(params)
click to toggle source
# File lib/fastlane/plugin/alioss/actions/alioss_action.rb, line 17 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