ann-flutter-flavor 0.1.2
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 +7 -0
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +271 -0
- data/Rakefile +6 -0
- data/ann-flutter-flavor.gemspec +25 -0
- data/lib/ann-flutter-flavor.rb +10 -0
- data/lib/ann_flutter_flavor/version.rb +3 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_compile_build_action.rb +219 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_app_store_action.rb +183 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_play_store_action.rb +168 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_emulators_action.rb +180 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_run_tests_action.rb +128 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_setup_action.rb +137 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_app_store_action.rb +280 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_firebase_action.rb +199 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_play_store_action.rb +248 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_android.rb +120 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_annai.rb +502 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_ios.rb +157 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_setup.rb +161 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/podspec_bridge.rb +153 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/test_integration.rb +346 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_project_config.rb +96 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb +363 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_status.rb +115 -0
- data/lib/fastlane/plugin/ann_flutter_flavor.rb +23 -0
- data/plugin_manager.sh +140 -0
- metadata +112 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'fastlane/action'
|
|
3
|
+
|
|
4
|
+
module FastlaneFlutterFlavor
|
|
5
|
+
# Helper class containing maintenance and environment setup logic
|
|
6
|
+
class SetupLanes
|
|
7
|
+
# (upgrade_setup and cleanup) are inherently cross-platform.
|
|
8
|
+
def initialize(root_folder:, status_manager:)
|
|
9
|
+
@root_folder = root_folder
|
|
10
|
+
@status_manager = status_manager
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# ------------------------------------
|
|
14
|
+
# Core Maintenance and Setup
|
|
15
|
+
# ------------------------------------
|
|
16
|
+
|
|
17
|
+
def upgrade_setup(force: false)
|
|
18
|
+
begin
|
|
19
|
+
Fastlane::UI.message("🚀 Starting maintenance and environment checks...")
|
|
20
|
+
|
|
21
|
+
# 1. Update Flutter SDK
|
|
22
|
+
Fastlane::UI.message("-> Step 1: Running 'flutter upgrade' to update SDK.")
|
|
23
|
+
Fastlane::Actions::sh("flutter", "upgrade")
|
|
24
|
+
|
|
25
|
+
# 2. Aggressive Cleanup: Delete lock files and call cleanup lane to remove build artifacts
|
|
26
|
+
if force
|
|
27
|
+
Fastlane::UI.message("-> Step 2: Running aggressive pre-resolution cleanup (deleting lock files and build artifacts)...")
|
|
28
|
+
Dir.chdir @root_folder do
|
|
29
|
+
pubspec_lock_path = File.join(@root_folder, "pubspec.lock")
|
|
30
|
+
gemfile_lock_path = File.join(@root_folder, "Gemfile.lock")
|
|
31
|
+
|
|
32
|
+
# Delete Dart lock file
|
|
33
|
+
if File.exist?(pubspec_lock_path)
|
|
34
|
+
Fastlane::UI.message(" -> Deleting pubspec.lock to force full Flutter dependency re-resolution.")
|
|
35
|
+
FileUtils.rm_f(pubspec_lock_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Delete Ruby lock file
|
|
39
|
+
if File.exist?(gemfile_lock_path)
|
|
40
|
+
Fastlane::UI.message(" -> Deleting Gemfile.lock to force full Ruby dependency re-resolution.")
|
|
41
|
+
FileUtils.rm_f(gemfile_lock_path)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Call cleanup which, with force: true, deletes ios/Podfile.lock, ios/Pods, and all build folders
|
|
46
|
+
cleanup(force: force)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 3. Resolve Dependencies: Ruby first, then Flutter (Root task)
|
|
50
|
+
Dir.chdir @root_folder do
|
|
51
|
+
Fastlane::UI.message("-> Step 3: Resolving Ruby and Dart dependencies...")
|
|
52
|
+
|
|
53
|
+
# Resolve Ruby dependencies (Gemfile.lock is regenerated here)
|
|
54
|
+
bundle_install_command = ["bundle", "install"]
|
|
55
|
+
bundle_update_command = ["bundle", "update"]
|
|
56
|
+
bundle_install_command << "--force" if force
|
|
57
|
+
bundle_update_command << "--force" if force
|
|
58
|
+
|
|
59
|
+
Fastlane::UI.message(" -> Running 'bundle install' and 'bundle update'#{force ? ' (with --force)' : ''}...")
|
|
60
|
+
Fastlane::Actions::sh(*bundle_install_command)
|
|
61
|
+
Fastlane::Actions::sh(*bundle_update_command)
|
|
62
|
+
|
|
63
|
+
# Resolve Dart dependencies (pubspec.lock is regenerated here)
|
|
64
|
+
Fastlane::UI.message(" -> Running 'flutter pub get' to re-resolve Dart dependencies.")
|
|
65
|
+
Fastlane::Actions::sh("flutter", "pub", "get")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 4. Resolve Native iOS Dependencies
|
|
69
|
+
ios_dir = File.join(@root_folder, "ios")
|
|
70
|
+
if File.directory?(ios_dir)
|
|
71
|
+
Fastlane::UI.message("-> Step 4: Running 'pod install' in the 'ios' directory to ensure native dependencies are linked.")
|
|
72
|
+
|
|
73
|
+
# Use --repo-update when force is true to fix "CocoaPods could not find compatible versions" errors.
|
|
74
|
+
pod_command = ["bundle", "exec", "pod", "install"]
|
|
75
|
+
if force
|
|
76
|
+
pod_command << "--repo-update"
|
|
77
|
+
Fastlane::UI.message(" -> Adding '--repo-update' flag to pod install for aggressive source update.")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Dir.chdir ios_dir do
|
|
81
|
+
Fastlane::Actions::sh(*pod_command)
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
Fastlane::UI.important(" -> Skipped 'pod install': 'ios' directory not found (likely not an iOS-enabled project).")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 5. Doctor check (Root task)
|
|
88
|
+
Fastlane::UI.message("-> Step 5: Running 'flutter doctor' for final verification.")
|
|
89
|
+
Fastlane::Actions::sh("flutter", "doctor")
|
|
90
|
+
|
|
91
|
+
# Pass nil for platform since this is a cross-platform action
|
|
92
|
+
@status_manager.logSuccess nil, "upgrade_setup", nil
|
|
93
|
+
return true
|
|
94
|
+
rescue => e
|
|
95
|
+
Fastlane::UI.error "❌ Error during environment setup or upgrade: #{e}"
|
|
96
|
+
Fastlane::UI.error e
|
|
97
|
+
# Pass nil for platform since this is a cross-platform action
|
|
98
|
+
@status_manager.logError nil, "upgrade_setup", nil, e
|
|
99
|
+
return false
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ------------------------------------
|
|
104
|
+
# Core Cleanup
|
|
105
|
+
# ------------------------------------
|
|
106
|
+
|
|
107
|
+
def cleanup(force: false)
|
|
108
|
+
begin
|
|
109
|
+
Fastlane::UI.message("🧹 Cleaning build artifacts...")
|
|
110
|
+
|
|
111
|
+
# 1. Standard Flutter clean (Root task)
|
|
112
|
+
Fastlane::UI.message("-> Running 'flutter clean'...")
|
|
113
|
+
Dir.chdir @root_folder do
|
|
114
|
+
Fastlane::Actions::sh("flutter", "clean")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if force
|
|
118
|
+
Fastlane::UI.message("-> Performing aggressive cleanup (deleting platform-specific build folders)...")
|
|
119
|
+
# 2. Aggressive cleanup of general Flutter build folder and platform-specific folders
|
|
120
|
+
dirs_to_delete = [
|
|
121
|
+
"build", # General Flutter build
|
|
122
|
+
"android/.gradle",
|
|
123
|
+
"android/build",
|
|
124
|
+
"ios/Pods",
|
|
125
|
+
"ios/Podfile.lock", # Crucial for forcing a fresh dependency graph on next install
|
|
126
|
+
"ios/build",
|
|
127
|
+
"web/build"
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
# Change directory temporarily to simplify paths for deletion
|
|
131
|
+
Dir.chdir @root_folder do
|
|
132
|
+
dirs_to_delete.each do |dir|
|
|
133
|
+
full_path = File.join(dir)
|
|
134
|
+
|
|
135
|
+
# Only delete if the file/directory exists
|
|
136
|
+
if File.exist?(full_path)
|
|
137
|
+
Fastlane::UI.message("Deleting artifact: #{dir}")
|
|
138
|
+
FileUtils.rm_rf(full_path)
|
|
139
|
+
Fastlane::UI.success("Successfully deleted: #{dir}")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
Fastlane::UI.success("Successfully performed aggressive cleanup.")
|
|
144
|
+
else
|
|
145
|
+
Fastlane::UI.important("Skipped aggressive deletion of build directories and native lock files. Pass 'force: true' to perform full cleanup.")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Pass nil for platform since this is a cross-platform action
|
|
149
|
+
@status_manager.logSuccess nil, "cleanup", nil
|
|
150
|
+
return true
|
|
151
|
+
rescue => e
|
|
152
|
+
Fastlane::UI.error "❌ Error during cleanup of build artifacts: #{e}"
|
|
153
|
+
Fastlane::UI.error e
|
|
154
|
+
# Pass nil for platform since this is a cross-platform action
|
|
155
|
+
@status_manager.logError nil, "cleanup", nil, e
|
|
156
|
+
return false
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module FastlaneFlutterFlavor
|
|
2
|
+
class PodspecBridge
|
|
3
|
+
|
|
4
|
+
# Static entry point to initialize the bridge.
|
|
5
|
+
# @param config_name [String] optional specific Xcode configuration name (e.g., "Debug-ledger_in")
|
|
6
|
+
def self.current_config(config_name = nil)
|
|
7
|
+
# 1. FLAVOR DETECTION: Prioritize Flutter CLI, fallback to config name parsing
|
|
8
|
+
flavor = ENV['FLUTTER_APP_FLAVOR']
|
|
9
|
+
if flavor.nil? || flavor.empty?
|
|
10
|
+
active_config = config_name || ENV['CONFIGURATION'] || ""
|
|
11
|
+
flavor = active_config.split('-').last
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# 2. SMART ROOT DETECTION
|
|
15
|
+
# Start from the current directory and walk up until we find annspec.yaml
|
|
16
|
+
search_dir = Dir.pwd
|
|
17
|
+
spec_file = 'annspec.yaml'
|
|
18
|
+
root = nil
|
|
19
|
+
|
|
20
|
+
# Walk up to 10 levels to find the project root
|
|
21
|
+
10.times do
|
|
22
|
+
if File.exist?(File.join(search_dir, spec_file))
|
|
23
|
+
root = search_dir
|
|
24
|
+
break
|
|
25
|
+
end
|
|
26
|
+
search_dir = File.expand_path('..', search_dir)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if root.nil?
|
|
30
|
+
raise "Could not find #{spec_file} in #{Dir.pwd} or any parent directories. " +
|
|
31
|
+
"Ensure you are running pod install from your Flutter project's /ios folder."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
loader = YamlSpecLoader.new(root, spec_file)
|
|
35
|
+
self.new(loader, flavor, config_name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Injects YAML values into the Xcode Project Build Settings
|
|
39
|
+
def self.apply_build_settings(installer)
|
|
40
|
+
project = nil
|
|
41
|
+
|
|
42
|
+
installer.aggregate_targets.each do |target|
|
|
43
|
+
target.user_project.targets.each do |user_target|
|
|
44
|
+
# Only apply to the main app target (Runner)
|
|
45
|
+
next unless user_target.name == 'Runner'
|
|
46
|
+
project = target.user_project
|
|
47
|
+
|
|
48
|
+
user_target.build_configurations.each do |config|
|
|
49
|
+
# Initialize bridge per configuration iteration
|
|
50
|
+
bridge = self.current_config(config.name)
|
|
51
|
+
s = config.build_settings
|
|
52
|
+
|
|
53
|
+
# Injection Mapping: YAML -> Xcode Build Settings (User-Defined)
|
|
54
|
+
s['ASSET_PREFIX'] = bridge.asset_prefix
|
|
55
|
+
s['APP_DISPLAY_NAME'] = bridge.display_name
|
|
56
|
+
s['PRODUCT_BUNDLE_IDENTIFIER'] = bridge.bundle_id
|
|
57
|
+
s['MARKETING_VERSION'] = bridge.version_name
|
|
58
|
+
s['CURRENT_PROJECT_VERSION'] = bridge.version_code
|
|
59
|
+
s['GMS_ADS_ID'] = bridge.gms_ads_id
|
|
60
|
+
s['REVERSED_CLIENT_ID'] = bridge.auth_reversed_client_id
|
|
61
|
+
s['GOOGLE_APP_ID'] = bridge.google_app_id
|
|
62
|
+
|
|
63
|
+
# Optional: Auto-set development team from YAML if present
|
|
64
|
+
s['DEVELOPMENT_TEAM'] = bridge.team_id if bridge.team_id
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
installer.pods_project.targets.each do |pod_target|
|
|
70
|
+
pod_target.build_configurations.each do |config|
|
|
71
|
+
bridge = self.current_config(config.name)
|
|
72
|
+
s = config.build_settings
|
|
73
|
+
|
|
74
|
+
# We push the specific keys needed by the podscripts
|
|
75
|
+
s['GMS_ADS_ID'] = bridge.gms_ads_id
|
|
76
|
+
s['APP_DISPLAY_NAME'] = bridge.display_name
|
|
77
|
+
s['GOOGLE_APP_ID'] = bridge.google_app_id
|
|
78
|
+
s['REVERSED_CLIENT_ID'] = bridge.auth_reversed_client_id
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Save the project once after all configurations are updated
|
|
84
|
+
project.save if project
|
|
85
|
+
|
|
86
|
+
log_flavor = ENV['FLUTTER_APP_FLAVOR'] || "detected"
|
|
87
|
+
puts "[Annai] Successfully injected flavor '#{log_flavor}' settings into Xcode configurations."
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def initialize(loader, flavor, config_name = nil)
|
|
91
|
+
@loader = loader
|
|
92
|
+
@flavor = flavor
|
|
93
|
+
@platform = :ios
|
|
94
|
+
|
|
95
|
+
# BUILD TYPE DETECTION: Prioritize Flutter CLI mode, fallback to config name regex
|
|
96
|
+
flutter_mode = ENV['FLUTTER_BUILD_MODE']&.downcase
|
|
97
|
+
active_config = config_name || ENV['CONFIGURATION'] || ""
|
|
98
|
+
|
|
99
|
+
if flutter_mode && !flutter_mode.empty?
|
|
100
|
+
# Profile mode maps to release for production-like behavior
|
|
101
|
+
@build_type = (flutter_mode == 'profile') ? 'release' : flutter_mode
|
|
102
|
+
elsif active_config.match?(/debug/i)
|
|
103
|
+
@build_type = 'debug'
|
|
104
|
+
else
|
|
105
|
+
@build_type = 'release'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# --- Interface Methods (Proxy to YamlSpecLoader) ---
|
|
110
|
+
|
|
111
|
+
def asset_prefix
|
|
112
|
+
@flavor
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def display_name
|
|
116
|
+
@loader.get_display_name(@platform, @flavor)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def bundle_id
|
|
120
|
+
@loader.get_package_id(@platform, @flavor)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def version_name
|
|
124
|
+
@loader.get_version_name(@platform, @flavor)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def version_code
|
|
128
|
+
@loader.get_version_code(@platform, @flavor)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def gms_ads_id
|
|
132
|
+
@loader.get_gms_ads_id(@platform, @flavor)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def team_id
|
|
136
|
+
@loader.get_team_id(@platform, @flavor)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def auth_client_id
|
|
140
|
+
@loader.get_auth_client_id(@platform, @flavor, @build_type)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def auth_reversed_client_id
|
|
144
|
+
@loader.get_auth_reversed_client_id(@platform, @flavor, @build_type)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def google_app_id
|
|
148
|
+
# Manual lookup for nested Firebase IDs (using build_type for debug/release)
|
|
149
|
+
config = @loader.get_merged_flavor_config(@platform, @flavor)
|
|
150
|
+
config.dig('firebase', @build_type, 'firebase_app_id') || ''
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'fastlane/action'
|
|
4
|
+
|
|
5
|
+
module FastlaneFlutterFlavor
|
|
6
|
+
|
|
7
|
+
# ------------------------------------
|
|
8
|
+
# IntegrationTest (Helper Class)
|
|
9
|
+
# ------------------------------------
|
|
10
|
+
class IntegrationTest
|
|
11
|
+
|
|
12
|
+
def initialize(lane: , isAndroid: , isIos: , test_spec_file: , statusManager:, root_folder:) # Added root_folder
|
|
13
|
+
@lane = lane
|
|
14
|
+
@isAndroid = isAndroid
|
|
15
|
+
@isIos = isIos
|
|
16
|
+
@test_spec_file = test_spec_file
|
|
17
|
+
@loaded = false
|
|
18
|
+
@statusManager = statusManager
|
|
19
|
+
@root_folder = root_folder # Store root folder
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def cleanup
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def openEmulators(options: )
|
|
26
|
+
|
|
27
|
+
load_file()
|
|
28
|
+
|
|
29
|
+
@emulators.each_pair { |emulator,port|
|
|
30
|
+
if emulator == options.fetch(:emulator, emulator)
|
|
31
|
+
wipe_data = options.fetch(:wipe_data, true)
|
|
32
|
+
if @isAndroid
|
|
33
|
+
openAndroidEmulator(emulatorName: emulator, port: port, wipe_data: wipe_data, dns_server: options.fetch(:dns_server, ""))
|
|
34
|
+
elsif @isIos
|
|
35
|
+
openIosEmulator(emulatorName: emulator, wipe_data: wipe_data)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def closeEmulators(options: )
|
|
42
|
+
|
|
43
|
+
load_file()
|
|
44
|
+
|
|
45
|
+
@emulators.each_pair { |emulator,port|
|
|
46
|
+
if emulator == options.fetch(:emulator, emulator)
|
|
47
|
+
if @isAndroid
|
|
48
|
+
closeAndroidEmulator(emulatorName: emulator, port: port)
|
|
49
|
+
elsif @isIos
|
|
50
|
+
closeIosEmulator(emulatorName: emulator)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def runTests(flavor: nil, emulator: nil, open_emulator: true, close_emulator: true, wipe_data: true, skip_screenshots: nil, skip_sound_null_safety: false)
|
|
57
|
+
|
|
58
|
+
load_file()
|
|
59
|
+
|
|
60
|
+
openedEmulator = {}
|
|
61
|
+
|
|
62
|
+
# Determine target emulators: nil => all; string (comma-separated) or array => only those entries
|
|
63
|
+
target_emulators = if emulator.nil?
|
|
64
|
+
@emulators
|
|
65
|
+
else
|
|
66
|
+
emulators_list = emulator.is_a?(String) ? emulator.split(",").map(&:strip) : Array(emulator)
|
|
67
|
+
found = emulators_list.select { |e| @emulators.key?(e) }
|
|
68
|
+
missing = emulators_list - found
|
|
69
|
+
|
|
70
|
+
if missing.any?
|
|
71
|
+
Fastlane::UI.error("Emulator(s) not found in the test spec: #{missing.join(', ')}. Available: #{@emulators.keys.join(', ')}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if found.empty?
|
|
75
|
+
Fastlane::UI.error("None of the specified emulators were found. Aborting runTests.")
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@emulators.select { |k,_| found.include?(k) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
target_emulators.each_pair { |emulator_name, port|
|
|
83
|
+
# already limited to chosen emulators; treat as valid
|
|
84
|
+
isValidEmulator = true
|
|
85
|
+
isEmulatorAlreadyOpen = openedEmulator.fetch(emulator_name, false)
|
|
86
|
+
|
|
87
|
+
if open_emulator && isValidEmulator && !isEmulatorAlreadyOpen
|
|
88
|
+
if @isAndroid
|
|
89
|
+
openAndroidEmulator(emulatorName: emulator_name, port: port, wipe_data: wipe_data, dns_server: "")
|
|
90
|
+
elsif @isIos
|
|
91
|
+
openIosEmulator(emulatorName: emulator_name, wipe_data: wipe_data)
|
|
92
|
+
end
|
|
93
|
+
openedEmulator[emulator_name] = true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@flavorDevices.each_pair { |key,value|
|
|
97
|
+
device = key[0]
|
|
98
|
+
flavor_name = key[1]
|
|
99
|
+
next unless emulator_name == device
|
|
100
|
+
|
|
101
|
+
# Determine which flavors to run: if a filter was provided use it, otherwise default to the flavor from the spec
|
|
102
|
+
flavors = if flavor.nil?
|
|
103
|
+
[flavor_name]
|
|
104
|
+
else
|
|
105
|
+
flavor.split(",")
|
|
106
|
+
end
|
|
107
|
+
isValidFlavor = flavors.include?(flavor_name)
|
|
108
|
+
next unless isValidFlavor
|
|
109
|
+
|
|
110
|
+
deviceName = device
|
|
111
|
+
if @isAndroid
|
|
112
|
+
deviceName = "emulator-#{port}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
test_cases = value['tests']
|
|
116
|
+
test_cases.each_pair { |testcaseName,parameters|
|
|
117
|
+
begin
|
|
118
|
+
resolved_skip_screenshots = skip_screenshots.nil? ? !@enable_screenshots : skip_screenshots
|
|
119
|
+
|
|
120
|
+
runTest(
|
|
121
|
+
driver_file: value['driver_file'],
|
|
122
|
+
test_file: value['test_file'],
|
|
123
|
+
device_id: deviceName,
|
|
124
|
+
flavor: flavor_name,
|
|
125
|
+
testcase_name: testcaseName,
|
|
126
|
+
parameters: parameters,
|
|
127
|
+
skip_screenshots: resolved_skip_screenshots,
|
|
128
|
+
skip_sound_null_safety: skip_sound_null_safety
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@statusManager.logSuccess flavor_name, "runTests", emulator_name
|
|
132
|
+
rescue => e
|
|
133
|
+
Fastlane::UI.error "Error while running tests for flavor #{flavor_name}"
|
|
134
|
+
|
|
135
|
+
@statusManager.logError flavor_name, "runTests", emulator_name, e
|
|
136
|
+
end
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if close_emulator && isValidEmulator
|
|
141
|
+
if @isAndroid
|
|
142
|
+
closeAndroidEmulator(emulatorName: emulator_name, port: port)
|
|
143
|
+
elsif @isIos
|
|
144
|
+
closeIosEmulator(emulatorName: emulator_name)
|
|
145
|
+
end
|
|
146
|
+
openedEmulator[emulator_name] = false
|
|
147
|
+
end
|
|
148
|
+
}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def load_file()
|
|
154
|
+
@emulators = {}
|
|
155
|
+
@flavors = []
|
|
156
|
+
@flavorDevices = {}
|
|
157
|
+
@enable_screenshots = false
|
|
158
|
+
|
|
159
|
+
if File.exist?(@test_spec_file) && !@loaded
|
|
160
|
+
@annaiTestSpecFile = YAML.load_file(@test_spec_file)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if !@loaded && @annaiTestSpecFile['annai_app_tests']['enable_screenshots'] != nil
|
|
164
|
+
@enable_screenshots = @annaiTestSpecFile['annai_app_tests']['enable_screenshots']
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
if !@loaded && @isAndroid
|
|
168
|
+
@androidTestSpec = @annaiTestSpecFile['annai_app_tests']['android']
|
|
169
|
+
port = 5554
|
|
170
|
+
|
|
171
|
+
@androidTestSpec['flavor'].each_pair { |flavor,devices|
|
|
172
|
+
@flavors.push(flavor)
|
|
173
|
+
devices['devices'].each_pair { |device,value|
|
|
174
|
+
driver_file = value.fetch('driver_file', @androidTestSpec['default']['driver_file'])
|
|
175
|
+
test_file = value.fetch('test_file', @androidTestSpec['default']['test_file'])
|
|
176
|
+
port_number = value['port_number']
|
|
177
|
+
|
|
178
|
+
@flavorDevices[[device, flavor]] = {
|
|
179
|
+
'test_file' => test_file,
|
|
180
|
+
'driver_file' => driver_file,
|
|
181
|
+
'tests' => value['tests']
|
|
182
|
+
}
|
|
183
|
+
unless @emulators.has_key?(device)
|
|
184
|
+
if(port_number != nil)
|
|
185
|
+
@emulators[device] = port_number
|
|
186
|
+
else
|
|
187
|
+
@emulators[device] = port
|
|
188
|
+
port += 2
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
elsif !@loaded && @isIos
|
|
194
|
+
@iosTestSpec = @annaiTestSpecFile['annai_app_tests']['ios']
|
|
195
|
+
|
|
196
|
+
@iosTestSpec['flavor'].each_pair { |flavor,devices|
|
|
197
|
+
@flavors.push(flavor)
|
|
198
|
+
devices['devices'].each_pair { |device,value|
|
|
199
|
+
driver_file = value.fetch('driver_file', @iosTestSpec['default']['driver_file'])
|
|
200
|
+
test_file = value.fetch('test_file', @iosTestSpec['default']['test_file'])
|
|
201
|
+
|
|
202
|
+
@flavorDevices[[device, flavor]] = {
|
|
203
|
+
'test_file' => test_file,
|
|
204
|
+
'driver_file' => driver_file,
|
|
205
|
+
'tests' => value['tests']
|
|
206
|
+
}
|
|
207
|
+
unless @emulators.has_key?(device)
|
|
208
|
+
@emulators[device] = 0
|
|
209
|
+
end
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
end
|
|
213
|
+
@loaded = true
|
|
214
|
+
#puts @emulators
|
|
215
|
+
#puts @flavors
|
|
216
|
+
#puts @flavorDevices
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def openAndroidEmulator(emulatorName: , port: ,wipe_data: true, dns_server: "")
|
|
220
|
+
|
|
221
|
+
if wipe_data
|
|
222
|
+
if dns_server != ""
|
|
223
|
+
Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-wipe-data", "-dns-server", dns_server)
|
|
224
|
+
else
|
|
225
|
+
Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-wipe-data")
|
|
226
|
+
end
|
|
227
|
+
else
|
|
228
|
+
if dns_server != ""
|
|
229
|
+
Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName, "-dns-server", dns_server)
|
|
230
|
+
else
|
|
231
|
+
Fastlane::Actions::sh("screen", "-dmS", emulatorName, "emulator", "-port", "#{port}", "-avd", emulatorName)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
sleep(40)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def closeAndroidEmulator(emulatorName: , port:)
|
|
239
|
+
Fastlane::Actions::sh("screen", "-dmS", emulatorName, "adb", "-s", "emulator-#{port}", "emu", "kill")
|
|
240
|
+
sleep(5)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def openIosEmulator(emulatorName: ,wipe_data: true)
|
|
244
|
+
res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
|
|
245
|
+
udid = res.match(/"udid"\s+:\s+"(.*)"/)[1]
|
|
246
|
+
if wipe_data
|
|
247
|
+
Fastlane::Actions::sh("xcrun", "simctl", "erase", udid, log: false)
|
|
248
|
+
sleep(10)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
Fastlane::Actions::sh("open", "-a", "Simulator", "--args", "-CurrentDeviceUDID", udid)
|
|
252
|
+
sleep(10)
|
|
253
|
+
res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
|
|
254
|
+
state = res.match(/"state"\s+:\s+"(.*)"/)[1]
|
|
255
|
+
if state == "Shutdown"
|
|
256
|
+
Fastlane::Actions::sh("xcrun", "simctl", "boot", udid)
|
|
257
|
+
sleep(10)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def closeIosEmulator(emulatorName:)
|
|
262
|
+
res = Fastlane::Actions::sh("xcrun", "simctl", "list", "--json", "devices", "#{emulatorName}", "available", log: false)
|
|
263
|
+
udid = res.match(/"udid"\s+:\s+"(.*)"/)[1]
|
|
264
|
+
state = res.match(/"state"\s+:\s+"(.*)"/)[1]
|
|
265
|
+
unless state == "Shutdown"
|
|
266
|
+
Fastlane::Actions::sh("xcrun", "simctl", "shutdown", udid)
|
|
267
|
+
sleep(5)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def runTest(driver_file:, test_file:, device_id:, flavor: "", testcase_name: "", parameters:, skip_sound_null_safety: false, skip_screenshots: true)
|
|
272
|
+
|
|
273
|
+
dart_define_option = Array.new(10) {|i| '--dart-define=' }
|
|
274
|
+
index = 0
|
|
275
|
+
if testcase_name != ""
|
|
276
|
+
dart_define_option[index] = "--dart-define=testcase_name=#{testcase_name}"
|
|
277
|
+
index += 1
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
flavorOption = ""
|
|
281
|
+
if flavor != ""
|
|
282
|
+
flavorOption = "--flavor"
|
|
283
|
+
dart_define_option[index] = "--dart-define=flavor=#{flavor}"
|
|
284
|
+
index += 1
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
parameters.each_pair { |key,value|
|
|
288
|
+
dart_define_option[index] = "--dart-define=#{key}=#{value}"
|
|
289
|
+
index += 1
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
nullSafety = ""
|
|
293
|
+
if skip_sound_null_safety
|
|
294
|
+
nullSafety = "--no-sound-null-safety"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
device_option = ""
|
|
298
|
+
if device_id != ""
|
|
299
|
+
device_option = "-d"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Changed Dir.chdir ".." to use the dynamically found @root_folder
|
|
303
|
+
if skip_screenshots
|
|
304
|
+
Dir.chdir @root_folder do
|
|
305
|
+
Fastlane::Actions::sh(
|
|
306
|
+
"flutter", "test",
|
|
307
|
+
test_file,
|
|
308
|
+
flavorOption, flavor,
|
|
309
|
+
dart_define_option[0],
|
|
310
|
+
dart_define_option[1],
|
|
311
|
+
dart_define_option[2],
|
|
312
|
+
dart_define_option[3],
|
|
313
|
+
dart_define_option[4],
|
|
314
|
+
dart_define_option[5],
|
|
315
|
+
dart_define_option[6],
|
|
316
|
+
dart_define_option[7],
|
|
317
|
+
dart_define_option[8],
|
|
318
|
+
dart_define_option[9],
|
|
319
|
+
device_option, device_id
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
Dir.chdir @root_folder do
|
|
324
|
+
Fastlane::Actions::sh(
|
|
325
|
+
"flutter", "drive",
|
|
326
|
+
"--driver=#{driver_file}",
|
|
327
|
+
"--target=#{test_file}",
|
|
328
|
+
flavorOption, flavor,
|
|
329
|
+
nullSafety,
|
|
330
|
+
dart_define_option[0],
|
|
331
|
+
dart_define_option[1],
|
|
332
|
+
dart_define_option[2],
|
|
333
|
+
dart_define_option[3],
|
|
334
|
+
dart_define_option[4],
|
|
335
|
+
dart_define_option[5],
|
|
336
|
+
dart_define_option[6],
|
|
337
|
+
dart_define_option[7],
|
|
338
|
+
dart_define_option[8],
|
|
339
|
+
dart_define_option[9],
|
|
340
|
+
device_option, device_id
|
|
341
|
+
)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|