-
Notifications
You must be signed in to change notification settings - Fork 9
Add android_generate_apk_from_aab
action
#467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
d5cd729
f9b2fc0
d27847d
96df190
3dbfd45
dec39d8
16e5050
f99c55e
1f43c4c
0e801e1
b28c1f0
1d566d1
2d499f9
658e8f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,132 @@ | ||||||||
module Fastlane | ||||||||
module Actions | ||||||||
class AndroidGenerateApkFromAabAction < Action | ||||||||
def self.generate_command(aab_file_path, apk_output_file_path, keystore_path, keystore_password, keystore_key_alias, signing_key_password) | ||||||||
command = "bundletool build-apks --mode universal --bundle #{aab_file_path} --output-format DIRECTORY --output #{apk_output_file_path} " | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
code_sign_arguments = "--ks #{keystore_path} --ks-pass #{keystore_password} --ks-key-alias #{keystore_key_alias} --key-pass #{signing_key_password} " | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
move_and_cleanup_command = "&& mv #{apk_output_file_path}/universal.apk #{apk_output_file_path}_tmp && rm -rf #{apk_output_file_path} && mv #{apk_output_file_path}_tmp #{apk_output_file_path}" | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
# Attempt to code sign the APK if a keystore_path parameter is specified | ||||||||
command += code_sign_arguments unless keystore_path.nil? | ||||||||
|
||||||||
# Move and rename the universal.apk file to the specified output path and cleanup the directory created by bundletool | ||||||||
command += move_and_cleanup_command | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
return command | ||||||||
end | ||||||||
|
||||||||
def self.run(params) | ||||||||
begin | ||||||||
sh('command -v bundletool > /dev/null') | ||||||||
rescue StandardError | ||||||||
UI.user_error!('bundletool is not installed. Please install it using the instructions at https://developer.android.com/studio/command-line/bundletool.') | ||||||||
raise | ||||||||
end | ||||||||
|
||||||||
# If no AAB param was provided, attempt to get it from the lane context | ||||||||
# First GRADLE_ALL_AAB_OUTPUT_PATHS if only one | ||||||||
# Second GRADLE_AAB_OUTPUT_PATH if it is set | ||||||||
# Else use the specified parameter value | ||||||||
if params[:aab_file_path].nil? | ||||||||
all_aab_paths = Actions.lane_context[SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS] || [] | ||||||||
aab_file_path = if all_aab_paths.count == 1 | ||||||||
all_aab_paths.first | ||||||||
else | ||||||||
Actions.lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH] | ||||||||
end | ||||||||
else | ||||||||
aab_file_path = params[:aab_file_path] | ||||||||
end | ||||||||
|
||||||||
# If no AAB file path was found, raise an error | ||||||||
if aab_file_path.nil? | ||||||||
UI.user_error!('No AAB file path was specified and none was found in the lane context. Please specify the `aab_file_path` parameter or ensure that the relevant build action has been run prior to this action.') | ||||||||
raise | ||||||||
end | ||||||||
|
||||||||
apk_output_file_path = params[:apk_output_file_path] | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
keystore_path = params[:keystore_path] | ||||||||
keystore_password = params[:keystore_password] | ||||||||
keystore_key_alias = params[:keystore_key_alias] | ||||||||
signing_key_password = params[:signing_key_password] | ||||||||
|
||||||||
sh(generate_command(aab_file_path, apk_output_file_path, keystore_path, keystore_password, keystore_key_alias, signing_key_password)) | ||||||||
end | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
##################################################### | ||||||||
# @!group Documentation | ||||||||
##################################################### | ||||||||
|
||||||||
def self.description | ||||||||
'Generates an APK from the specified AAB' | ||||||||
end | ||||||||
|
||||||||
def self.details | ||||||||
'Generates an APK from the specified AAB' | ||||||||
end | ||||||||
|
||||||||
def self.available_options | ||||||||
[ | ||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :aab_file_path, | ||||||||
env_name: 'ANDROID_AAB_FILE_PATH', | ||||||||
description: 'The path to the AAB file. If not speicified, the action will attempt to read from the lane context using the `SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS` and `SharedValues::GRADLE_AAB_OUTPUT_PATH` keys', | ||||||||
type: String, | ||||||||
optional: true, | ||||||||
default_value: nil, | ||||||||
verify_block: proc { |p| UI.user_error!("AAB path `#{p}` is not a valid file path.") unless File.file?(p) } | ||||||||
), | ||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :apk_output_file_path, | ||||||||
env_name: 'ANDROID_APK_OUTPUT_PATH', | ||||||||
description: 'The output path where the APK file will be generated. The directory will be created if it does not yet exist', | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
type: String, | ||||||||
optional: false, | ||||||||
default_value: nil | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
), | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we decided to not create intermediate folders if they don't exist, we should add a check to ensure the parent dir exists. Otherwise we'd still have the action run
Suggested change
|
||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :keystore_path, | ||||||||
env_name: 'ANDROID_KEYSTORE_PATH', | ||||||||
description: 'The path to the keystore file', | ||||||||
type: String, | ||||||||
optional: true, | ||||||||
default_value: nil, | ||||||||
verify_block: proc { |p| UI.user_error!("Keystore file path `#{p}` is not a valid file path.") unless File.file?(p) || p.nil } | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
), | ||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :keystore_password, | ||||||||
env_name: 'ANDROID_KEYSTORE_PASSWORD', | ||||||||
description: 'The password for the keystore', | ||||||||
type: String, | ||||||||
optional: true, | ||||||||
default_value: nil | ||||||||
), | ||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :keystore_key_alias, | ||||||||
env_name: 'ANDROID_KEYSTORE_KEY_ALIAS', | ||||||||
description: 'The alias of the key in the keystore', | ||||||||
type: String, | ||||||||
optional: true, | ||||||||
default_value: nil | ||||||||
), | ||||||||
FastlaneCore::ConfigItem.new( | ||||||||
key: :signing_key_password, | ||||||||
env_name: 'ANDROID_SIGNING_KEY_PASSWORD', | ||||||||
description: 'The password for the signing key', | ||||||||
type: String, | ||||||||
optional: true, | ||||||||
default_value: nil | ||||||||
), | ||||||||
] | ||||||||
end | ||||||||
|
||||||||
def self.authors | ||||||||
['Automattic'] | ||||||||
end | ||||||||
|
||||||||
def self.is_supported?(platform) | ||||||||
true | ||||||||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
end | ||||||||
end | ||||||||
end | ||||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
require 'spec_helper' | ||
|
||
describe Fastlane::Actions::AndroidGenerateApkFromAabAction do | ||
before do | ||
allow(File).to receive(:file?).with(aab_file_path).and_return('mocked file data') | ||
allow(File).to receive(:file?).with(apk_output_file_path).and_return('mocked file data') | ||
allow(File).to receive(:file?).with(keystore_path).and_return('mocked file data') | ||
end | ||
|
||
let(:aab_file_path) { 'path/to/app.aab' } | ||
let(:apk_output_file_path) { 'path/to/app.apk' } | ||
let(:keystore_path) { 'path/to/keystore' } | ||
let(:keystore_password) { 'keystore_password' } | ||
let(:keystore_key_alias) { 'keystore_key_alias' } | ||
let(:signing_key_password) { 'signing_key_password' } | ||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def generate_command(apk_output_file_path:, aab_file_path: nil, keystore_path: nil, keystore_password: nil, keystore_key_alias: nil, signing_key_password: nil) | ||
command = "bundletool build-apks --mode universal --bundle #{aab_file_path} --output-format DIRECTORY --output #{apk_output_file_path} " | ||
code_sign_arguments = "--ks #{keystore_path} --ks-pass #{keystore_password} --ks-key-alias #{keystore_key_alias} --key-pass #{signing_key_password} " | ||
move_and_cleanup_command = "&& mv #{apk_output_file_path}/universal.apk #{apk_output_file_path}_tmp && rm -rf #{apk_output_file_path} && mv #{apk_output_file_path}_tmp #{apk_output_file_path}" | ||
|
||
# Append the code signing arguments | ||
command += code_sign_arguments unless keystore_path.nil? | ||
|
||
# Append the move and cleanup command | ||
command += move_and_cleanup_command | ||
return command | ||
end | ||
AliSoftware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
describe 'android_generate_apk_from_aab' do | ||
it 'calls the `bundletool` command with the correct arguments when generating a signed APK' do | ||
cmd = run_described_fastlane_action( | ||
aab_file_path: aab_file_path, | ||
apk_output_file_path: apk_output_file_path, | ||
keystore_path: keystore_path, | ||
keystore_password: keystore_password, | ||
keystore_key_alias: keystore_key_alias, | ||
signing_key_password: signing_key_password | ||
) | ||
expected_command = generate_command(aab_file_path: aab_file_path, | ||
apk_output_file_path: apk_output_file_path, | ||
keystore_path: keystore_path, | ||
keystore_password: keystore_password, | ||
keystore_key_alias: keystore_key_alias, | ||
signing_key_password: signing_key_password) | ||
expect(cmd).to eq(expected_command) | ||
end | ||
|
||
it 'calls the `bundletool` command with the correct arguments when generating an unsigned APK' do | ||
cmd = run_described_fastlane_action( | ||
aab_file_path: aab_file_path, | ||
apk_output_file_path: apk_output_file_path | ||
) | ||
expected_command = generate_command(aab_file_path: aab_file_path, | ||
apk_output_file_path: apk_output_file_path) | ||
expect(cmd).to eq(expected_command) | ||
end | ||
|
||
it 'calls the `bundletool` command with the correct arguments and use the path to the AAB from the lane context if the SharedValues::GRADLE_AAB_OUTPUT_PATH key is set' do | ||
aab_path_from_context = 'path/from/context/app.aab' | ||
|
||
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_AAB_OUTPUT_PATH] = aab_path_from_context | ||
|
||
cmd = run_described_fastlane_action( | ||
apk_output_file_path: apk_output_file_path | ||
) | ||
|
||
expected_command = generate_command(aab_file_path: aab_path_from_context, | ||
apk_output_file_path: apk_output_file_path) | ||
expect(cmd).to eq(expected_command) | ||
end | ||
|
||
it 'calls the `bundletool` command with the correct arguments and use the path to the AAB from the lane context if the SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS key is set' do | ||
all_aab_paths_from_context = ['first/path/from/context/app.aab'] | ||
|
||
Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_ALL_AAB_OUTPUT_PATHS] = all_aab_paths_from_context | ||
|
||
cmd = run_described_fastlane_action( | ||
apk_output_file_path: apk_output_file_path | ||
) | ||
|
||
expected_command = generate_command(aab_file_path: all_aab_paths_from_context.first, | ||
apk_output_file_path: apk_output_file_path) | ||
expect(cmd).to eq(expected_command) | ||
end | ||
end | ||
end |
Uh oh!
There was an error while loading. Please reload this page.