Module: AnnFlavorCore::Resolver

Defined in:
lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb

Constant Summary collapse

STANDARD_BUILD_TYPES =
%w[debug release profile].freeze

Class Method Summary collapse

Class Method Details

.effective_bt_config(flavor_bts, default_bts, build_type) ⇒ Object



209
210
211
212
213
214
215
216
217
218
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 209

def self.effective_bt_config(flavor_bts, default_bts, build_type)
  f = (flavor_bts  || {})[build_type]
  d = (default_bts || {})[build_type]
  BuildTypeConfig.new(
    id_suffix:   effective_build_type_suffix(f&.id_suffix,   d&.id_suffix),
    name_suffix: effective_build_type_suffix(f&.name_suffix, d&.name_suffix),
    firebase:    merge_firebase_config(f&.firebase, d&.firebase),
    auth:        merge_auth_config(f&.auth, d&.auth),
  )
end

.effective_build_type_suffix(flavor_suffix, default_suffix) ⇒ Object

── Low-level cascade helpers (used by all plugins) ───────────────────────



9
10
11
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 9

def self.effective_build_type_suffix(flavor_suffix, default_suffix)
  flavor_suffix.to_s.empty? ? default_suffix.to_s : flavor_suffix.to_s
end

.merge_auth_config(flavor, default) ⇒ Object



264
265
266
267
268
269
270
271
272
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 264

def self.merge_auth_config(flavor, default)
  return default if flavor.nil?
  return flavor  if default.nil?
  AuthConfig.new(
    client_id:          flavor.client_id          || default.client_id,
    reversed_client_id: flavor.reversed_client_id || default.reversed_client_id,
    others:             (default.others || {}).merge(flavor.others || {}),
  )
end

.merge_defines_map(default_map, default_bt_map, flavor_map, flavor_bt_map) ⇒ Object

Shallow key-union across all four cascade levels — most specific key wins.



187
188
189
190
191
192
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 187

def self.merge_defines_map(default_map, default_bt_map, flavor_map, flavor_bt_map)
  (default_map || {})
    .merge(default_bt_map || {})
    .merge(flavor_map || {})
    .merge(flavor_bt_map || {})
end

.merge_firebase_config(flavor, default) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 251

def self.merge_firebase_config(flavor, default)
  return default if flavor.nil?
  return flavor  if default.nil?
  # service_account cascades independently — a flavor can override project_id without having to
  # repeat the service_account key, and vice versa.
  FirebaseConfig.new(
    project_id:      flavor.project_id      || default.project_id,
    config_file:     flavor.config_file     || default.config_file,
    service_account: flavor. || default.,
    target:          flavor.target          || default.target,
  )
end

.resolve_all(spec, build_types: STANDARD_BUILD_TYPES) ⇒ Object

── High-level resolver ───────────────────────────────────────────────────

Usage patterns:

# From YAML (batch — all platforms, all build types)
spec = AnnFlavorCore::Parser.parse_file('annspec.yaml')
resolved = AnnFlavorCore::Resolver.resolve_all(spec)

# From in-memory model (e.g. live UI data before saving to YAML)
output = AnnFlavorCore::Resolver.resolve_android_flavor(flavor, defaults, 'debug')


32
33
34
35
36
37
38
39
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 32

def self.resolve_all(spec, build_types: STANDARD_BUILD_TYPES)
  ResolvedSpec.new(
    android: spec.app&.android ? resolve_android(spec.app.android, build_types: build_types) : {},
    ios:     spec.app&.ios     ? resolve_ios(spec.app.ios,         build_types: build_types) : {},
    web:     spec.app&.web     ? resolve_web(spec.app.web,         build_types: build_types) : {},
    windows: spec.app&.windows ? resolve_windows(spec.app.windows, build_types: build_types) : {},
  )
end

.resolve_android(platform, build_types: STANDARD_BUILD_TYPES) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 41

def self.resolve_android(platform, build_types: STANDARD_BUILD_TYPES)
  platform.flavors.each_with_object({}) do |(name, flavor), result|
    result[name] = ResolvedFlavor.new(
      flavor_name:   name,
      by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_android_flavor(flavor, platform.default, bt) },
    )
  end
end

.resolve_android_flavor(flavor, defaults, build_type) ⇒ Object

── Per-flavor resolvers (callable directly from UI / in-memory data) ─────



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 79

def self.resolve_android_flavor(flavor, defaults, build_type)
  bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
  ResolvedBuildOutput.new(
    flavor_name:            nil,
    build_type:             build_type,
    effective_id:           resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
    effective_name:         resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
    effective_version_name: (flavor.version_name || defaults.version_name).to_s,
    effective_version_code: flavor.version_code || defaults.version_code,
    effective_main_file:    flavor.main_file || defaults.main_file,
    effective_firebase:     resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
    effective_auth:         bt.auth,
    effective_gms_ads_id:   flavor.admob&.gms_ads_id || defaults.admob&.gms_ads_id,
    effective_icon:         flavor.icon || defaults.icon,
    effective_custom:       resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
    effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
  )
end

.resolve_cloudflare(flavor_cf, default_cf) ⇒ Object



145
146
147
148
149
150
151
152
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 145

def self.resolve_cloudflare(flavor_cf, default_cf)
  CloudflareConfig.new(
    project_name: flavor_cf&.project_name,
    account_id:   flavor_cf&. || default_cf&.,
    branch:       flavor_cf&.branch     || default_cf&.branch || 'main',
    mode:         flavor_cf&.mode       || default_cf&.mode   || 'pages',
  )
end

.resolve_custom(default_custom, default_bt_custom, flavor_custom, flavor_bt_custom) ⇒ Object

Deep-merges custom config — lowest priority first, key-by-key within each group.



176
177
178
179
180
181
182
183
184
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 176

def self.resolve_custom(default_custom, default_bt_custom, flavor_custom, flavor_bt_custom)
  merge = lambda do |base, over|
    next base if over.nil? || over.empty?
    result = base.dup
    over.each { |group, group_map| result[group] = (result[group] || {}).merge(group_map) }
    result
  end
  merge.call(merge.call(merge.call(default_custom || {}, default_bt_custom), flavor_custom), flavor_bt_custom)
end

.resolve_dart_defines(default_defines, default_bt_defines, flavor_defines, flavor_bt_defines) ⇒ Object

Resolves all four dart_defines sub-maps (common + three action scopes) independently.



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 196

def self.resolve_dart_defines(default_defines, default_bt_defines, flavor_defines, flavor_bt_defines)
  d  = default_defines || DartDefines.defaults
  db = default_bt_defines || DartDefines.defaults
  f  = flavor_defines || DartDefines.defaults
  fb = flavor_bt_defines || DartDefines.defaults
  DartDefines.new(
    common:  merge_defines_map(d.common,  db.common,  f.common,  fb.common),
    compile: merge_defines_map(d.compile, db.compile, f.compile, fb.compile),
    run:     merge_defines_map(d.run,     db.run,     f.run,     fb.run),
    deploy:  merge_defines_map(d.deploy,  db.deploy,  f.deploy,  fb.deploy),
  )
end

.resolve_firebase(flavor_bt, flavor_top, default_bt, default_top) ⇒ Object

Full 4-level cascade for every firebase field (most specific wins):

L1: flavor_bt   — flavor.build_types[bt].firebase
L2: flavor_top  — flavor.firebase
L3: default_bt  — default.build_types[bt].firebase
L4: default_top — defaults.firebase


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
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 225

def self.resolve_firebase(flavor_bt, flavor_top, default_bt, default_top)
  project_id = flavor_bt&.project_id ||
               flavor_top&.project_id ||
               default_bt&.project_id ||
               default_top&.project_id
  config_file = flavor_bt&.config_file ||
                flavor_top&.config_file ||
                default_bt&.config_file ||
                default_top&.config_file
  sa = flavor_bt&. ||
       flavor_top&. ||
       default_bt&. ||
       default_top&.
  target = flavor_bt&.target ||
           flavor_top&.target ||
           default_bt&.target ||
           default_top&.target
  return nil if project_id.nil? && config_file.nil? && sa.nil? && target.nil?
  FirebaseConfig.new(
    project_id:      project_id,
    config_file:     config_file,
    service_account: sa,
    target:          target,
  )
end

.resolve_id(base_id, id_suffix, build_type_id_suffix = '') ⇒ Object



13
14
15
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 13

def self.resolve_id(base_id, id_suffix, build_type_id_suffix = '')
  base_id.to_s + id_suffix.to_s + build_type_id_suffix.to_s
end

.resolve_ios(platform, build_types: STANDARD_BUILD_TYPES) ⇒ Object



50
51
52
53
54
55
56
57
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 50

def self.resolve_ios(platform, build_types: STANDARD_BUILD_TYPES)
  platform.flavors.each_with_object({}) do |(name, flavor), result|
    result[name] = ResolvedFlavor.new(
      flavor_name:   name,
      by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_ios_flavor(flavor, platform.default, bt) },
    )
  end
end

.resolve_ios_flavor(flavor, defaults, build_type) ⇒ Object



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
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 98

def self.resolve_ios_flavor(flavor, defaults, build_type)
  bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
  ResolvedBuildOutput.new(
    flavor_name:            nil,
    build_type:             build_type,
    effective_id:           resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
    effective_name:         resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
    effective_version_name: (flavor.version_name || defaults.version_name).to_s,
    effective_version_code: flavor.version_code || defaults.version_code,
    effective_main_file:    flavor.main_file || defaults.main_file,
    effective_firebase:     resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
    effective_auth:         bt.auth,
    effective_gms_ads_id:   flavor.admob&.gms_ads_id || defaults.admob&.gms_ads_id,
    effective_icon:         flavor.icon || defaults.icon,
    effective_custom:       resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
    effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
    # Whole-value most-specific-wins — unlike firebase's per-field merge,
    # info_plist's value (an arbitrary Hash, or a file-path String) has no
    # fixed sub-fields to merge independently, so a more specific level's
    # value replaces a less specific one entirely rather than being merged
    # key-by-key here (per-key content merging across levels happens later,
    # in the consuming plugin, not in the core).
    effective_info_plist:   flavor.build_types[build_type]&.info_plist || flavor.info_plist ||
                             defaults.build_types[build_type]&.info_plist || defaults.info_plist,
  )
end

.resolve_name(base_name, name_suffix, build_type_name_suffix = '') ⇒ Object



17
18
19
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 17

def self.resolve_name(base_name, name_suffix, build_type_name_suffix = '')
  base_name.to_s + name_suffix.to_s + build_type_name_suffix.to_s
end

.resolve_web(platform, build_types: STANDARD_BUILD_TYPES) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 59

def self.resolve_web(platform, build_types: STANDARD_BUILD_TYPES)
  platform.flavors.each_with_object({}) do |(name, flavor), result|
    result[name] = ResolvedFlavor.new(
      flavor_name:   name,
      by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_web_flavor(flavor, platform.default, bt) },
    )
  end
end

.resolve_web_flavor(flavor, defaults, build_type) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 125

def self.resolve_web_flavor(flavor, defaults, build_type)
  bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
  ResolvedBuildOutput.new(
    flavor_name:            nil,
    build_type:             build_type,
    effective_id:           resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
    effective_name:         resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
    effective_version_name: (flavor.version_name || defaults.version_name).to_s,
    effective_version_code: flavor.version_code || defaults.version_code,
    effective_main_file:    flavor.main_file || defaults.main_file,
    effective_firebase:     resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
    effective_auth:         bt.auth,
    effective_gms_ads_id:   nil,
    effective_icon:         flavor.icon || defaults.icon,
    effective_cloudflare:   resolve_cloudflare(flavor.cloudflare, defaults.cloudflare),
    effective_custom:       resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
    effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
  )
end

.resolve_windows(platform, build_types: STANDARD_BUILD_TYPES) ⇒ Object



68
69
70
71
72
73
74
75
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 68

def self.resolve_windows(platform, build_types: STANDARD_BUILD_TYPES)
  platform.flavors.each_with_object({}) do |(name, flavor), result|
    result[name] = ResolvedFlavor.new(
      flavor_name:   name,
      by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_windows_flavor(flavor, platform.default, bt) },
    )
  end
end

.resolve_windows_flavor(flavor, defaults, build_type) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb', line 154

def self.resolve_windows_flavor(flavor, defaults, build_type)
  bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
  ResolvedBuildOutput.new(
    flavor_name:            nil,
    build_type:             build_type,
    effective_id:           resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
    effective_name:         resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
    effective_version_name: (flavor.version_name || defaults.version_name).to_s,
    effective_version_code: flavor.version_code || defaults.version_code,
    effective_main_file:    flavor.main_file || defaults.main_file,
    effective_firebase:     resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
    effective_auth:         bt.auth,
    effective_gms_ads_id:   nil,
    effective_icon:         nil,
    effective_custom:       resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
    effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
  )
end