ann-flavor-cocoapods 0.1.39 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae52557478525628b402c7b13b8adb941cc23ea40f8b3b35b92b463124d65604
4
- data.tar.gz: f2dc91944fce473c165b379b71c789eba4e8f7d606de693a9137ac23387b2107
3
+ metadata.gz: 203077b214e8b6dcbf87c8792ea6df6055d34b4b6ea36af00c1c28a94207aab9
4
+ data.tar.gz: fcdbfba3ba5e54184fb3d05ace52259c10476947259d550cc4b134009d83e560
5
5
  SHA512:
6
- metadata.gz: a5cb40ac4c29185fd367bc703fb3e5e165dcc19e28592b9303637648e6f3b28a5dfed1846317a7811ac2952eb2269deb7f3c0af4a4da4d84ea15a0aeeda71702
7
- data.tar.gz: 6c32a064349c7eb9f49f984adb396ef96a7caa0e7832065c27811fba44d1b750f4737ab5674107a138194e0b7211dd667f1723358076d6097856e77cd30ca5fa
6
+ metadata.gz: 1968396623737c7d68439d68cb2fb4d8b8ac6d1da7bba9c32722bc9a0408e3d73fc2b6a4087fbff63231d0d8bd1403949d62e46bf2382a7d9361bd33a7d9fbd1
7
+ data.tar.gz: 5f6f3d65b87f5f8c41d52f6201c622fb379da14d81cf7d6338edf5a76bde7168213c2722f067495d0e8571f0ce35415f67e37538f47f4b5d266e297887672cee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Added
6
+ - **New `[ann-flavor] Merge Info.plist` build phase composes the final
7
+ Info.plist Xcode builds against at build time**, not sync time: copies
8
+ `ios/Runner/Info.plist` (the generic/base layer, developer-owned, always
9
+ present) to `$(DERIVED_FILE_DIR)/Info-merged.plist`, then merges in every
10
+ key from the current flavor's `ios/ann/Info/Info-<flavor>.plist` delta (see
11
+ `ann_flutter_flavor` 1.2.0 — that file now holds only the flavor's
12
+ `info_plist:` cascade, not a full copy of `Runner/Info.plist`), with the
13
+ flavor's value winning on any key both define. Uses `PlistBuddy`
14
+ (`/usr/libexec/PlistBuddy`), not `python3`/`ruby` — ships as part of Xcode
15
+ itself, so this phase adds no dependency on any scripting runtime beyond
16
+ what Xcode already requires. Every plist value type (string, bool, integer,
17
+ real, array, nested dict — e.g. `SKAdNetworkItems`) round-trips correctly
18
+ through the merge, not just strings. Prints a one-line notice to the Xcode
19
+ build log for any key the flavor delta actually overrides, to make an
20
+ unintentional override easy to spot. Found-and-updated in place on a
21
+ re-run, same UUID-stability guarantee as the Firebase-copy and Print Build
22
+ Info phases.
23
+
24
+ ### Changed
25
+ - **`INFOPLIST_FILE` now points at `$(DERIVED_FILE_DIR)/Info-merged.plist`**
26
+ (the Merge Info.plist phase's output above) instead of directly at
27
+ `ios/ann/Info/Info-<flavor>.plist`. Enables `Runner/Info.plist` to remain a
28
+ live, always-consulted base layer instead of a one-time sync-time template
29
+ — the change this whole release exists to support. See
30
+ [plan 038](../../docs/03-planning/features/038-info-plist-build-time-merge.md)
31
+ and the [`Info.plist`
32
+ keys](../../docs/04-userguide/ios/overview.md#infoplist-keys) section of the
33
+ iOS userguide for the full design and migration notes.
34
+
35
+ ## 0.2.0
36
+
37
+ ### Added
38
+ - **Wires the new `entitlements:` schema field (see `ann_flutter_flavor`
39
+ 1.1.0) into the Xcode build.** When `ios/ann/Entitlements/<flavor>.entitlements`
40
+ exists on disk for a flavor, `configure_build_configuration` sets
41
+ `CODE_SIGN_ENTITLEMENTS` to it — the same find-or-create-by-name pattern
42
+ already used for `INFOPLIST_FILE`. `entitlements:` is opt-in (unlike
43
+ `info_plist`/the icon catalog, which every flavor always has): a flavor with
44
+ no generated entitlements file gets no `CODE_SIGN_ENTITLEMENTS` set at all,
45
+ and a stale value from a flavor's `entitlements:` being removed from
46
+ `annspec.yaml` is explicitly cleared on the next `pod install` rather than
47
+ left as a dangling, build-breaking reference to a file that no longer
48
+ exists. `CODE_SIGN_ENTITLEMENTS` is also added to `PROJECT_LEVEL_EXCLUDED_KEYS`
49
+ (same shadowing risk as `INFOPLIST_FILE`/`PRODUCT_BUNDLE_IDENTIFIER`).
50
+
3
51
  ## 0.1.39
4
52
 
5
53
  ### Fixed
@@ -174,14 +174,24 @@ module AnnFlavorCocoapods
174
174
  # until checked against what the xcconfig #include actually
175
175
  # applied).
176
176
  xcconfig_path = "ann/xcconfig/#{flavor_key}#{bt.capitalize}.xcconfig"
177
- info_plist_path = "ann/Info/Info-#{flavor_key}.plist"
178
177
  icon_name = "#{flavor_key.capitalize}AppIcon"
179
178
 
180
179
  assert_icon_generated!(user_project, flavor_key, icon_name)
181
180
 
181
+ # Unlike the icon catalog and Info.plist, entitlements: is
182
+ # opt-in -- ios_generator.dart's _generateEntitlements skips
183
+ # creating ios/ann/Entitlements/ entirely when no annspec.yaml
184
+ # entitlements: block and no stock Runner.entitlements exist
185
+ # anywhere. Only set CODE_SIGN_ENTITLEMENTS when the file
186
+ # actually exists on disk; leaving it unset is correct (no
187
+ # entitlements to sign with), not an error condition.
188
+ entitlements_path = "ann/Entitlements/#{flavor_key}.entitlements"
189
+ entitlements_file = File.join(user_project.project_dir.to_s, 'ann', 'Entitlements', "#{flavor_key}.entitlements")
190
+ entitlements_path = nil unless File.exist?(entitlements_file)
191
+
182
192
  configure_build_configuration(
183
- target, user_project, xcode_config, xcconfig_path, info_plist_path,
184
- icon_name
193
+ target, user_project, xcode_config, xcconfig_path,
194
+ icon_name, entitlements_path
185
195
  )
186
196
  end
187
197
 
@@ -191,6 +201,8 @@ module AnnFlavorCocoapods
191
201
  if print_build_info
192
202
  inject_build_info_phase(target)
193
203
  end
204
+
205
+ inject_merge_info_plist_phase(target)
194
206
  end
195
207
 
196
208
  # Fixes a real pod-install failure: PBXNativeTarget#resolved_build_setting
@@ -584,9 +596,10 @@ module AnnFlavorCocoapods
584
596
  # `xcodebuild` time — excluding it here is what fixes that.
585
597
  PROJECT_LEVEL_EXCLUDED_KEYS = %w[
586
598
  PRODUCT_BUNDLE_IDENTIFIER INFOPLIST_FILE ASSETCATALOG_COMPILER_APPICON_NAME
599
+ CODE_SIGN_ENTITLEMENTS
587
600
  ].freeze
588
601
 
589
- def self.configure_build_configuration(target, user_project, xcode_config, xcconfig_path, info_plist_path, icon_name)
602
+ def self.configure_build_configuration(target, user_project, xcode_config, xcconfig_path, icon_name, entitlements_path = nil)
590
603
  config_list = target.build_configuration_list
591
604
  config = config_list[xcode_config]
592
605
 
@@ -612,8 +625,26 @@ module AnnFlavorCocoapods
612
625
  user_project.main_group.new_reference(xcconfig_path)
613
626
  config.base_configuration_reference = xcconfig_ref
614
627
 
615
- config.build_settings['INFOPLIST_FILE'] = info_plist_path
628
+ # Points at the [ann-flavor] Merge Info.plist run-script phase's output
629
+ # (plan 038), not directly at ann/Info/Info-<flavor>.plist -- that file
630
+ # now holds only the flavor's info_plist: cascade delta, not a complete
631
+ # plist; the merge phase composes it with Runner/Info.plist (base) at
632
+ # build time and writes the result here.
633
+ config.build_settings['INFOPLIST_FILE'] = '$(DERIVED_FILE_DIR)/Info-merged.plist'
616
634
  config.build_settings['ASSETCATALOG_COMPILER_APPICON_NAME'] = icon_name
635
+ # entitlements: is opt-in (unlike INFOPLIST_FILE/the icon catalog, which
636
+ # every flavor always has) -- only set CODE_SIGN_ENTITLEMENTS when the
637
+ # caller found a generated file on disk. Also explicitly clear a
638
+ # find-or-create'd EXISTING config's stale value when a flavor's
639
+ # entitlements: block was removed from annspec.yaml, rather than
640
+ # silently leaving Xcode signing with an entitlements file that no
641
+ # longer exists on disk (a build-breaking dangling reference, not
642
+ # merely a stale value like most other settings here).
643
+ if entitlements_path
644
+ config.build_settings['CODE_SIGN_ENTITLEMENTS'] = entitlements_path
645
+ else
646
+ config.build_settings.delete('CODE_SIGN_ENTITLEMENTS')
647
+ end
617
648
  # flutter create's own stock Debug/Release configs never set
618
649
  # MARKETING_VERSION at all (confirmed against a real project) --
619
650
  # CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)" is present on the
@@ -744,5 +775,124 @@ module AnnFlavorCocoapods
744
775
  target.build_phases.unshift(phase)
745
776
  end
746
777
  end
778
+
779
+ # Composes the final Info.plist Xcode actually builds against, from two
780
+ # layers -- Runner/Info.plist (generic/base, developer-owned) and the
781
+ # flavor-specific ann/Info/Info-<flavor>.plist (annspec.yaml's info_plist:
782
+ # cascade, opt-in -- may not exist for a flavor with no cascade) -- with
783
+ # the flavor layer winning on any key collision. Plan 038: since plan 035,
784
+ # ios_generator.dart's _generateInfoPlists used to bake a full copy of
785
+ # Runner/Info.plist into every flavor's generated plist at sync time; that
786
+ # made the generated file impossible to read as "what's actually
787
+ # flavor-specific" and gave nothing else (e.g. a CocoaPods plugin like
788
+ # ann_ads) any way to contribute Info.plist keys, since INFOPLIST_FILE
789
+ # pointed at the fully-resolved flavor file and Runner/Info.plist was
790
+ # never read again. This phase moves that composition to build time
791
+ # instead, so Runner/Info.plist stays a live, always-consulted base layer.
792
+ #
793
+ # Uses PlistBuddy (/usr/libexec/PlistBuddy), not python3/ruby -- PlistBuddy
794
+ # ships as part of Xcode itself, so this phase has no dependency on any
795
+ # scripting runtime beyond what Xcode already requires (OQ-1,
796
+ # docs/03-planning/features/038-info-plist-build-time-merge.md).
797
+ #
798
+ # Runs before_compile, ahead of Xcode's own Info.plist processing --
799
+ # INFOPLIST_FILE (set in configure_build_configuration) points at this
800
+ # phase's output, not at either source file directly.
801
+ def self.inject_merge_info_plist_phase(target)
802
+ script = <<~SHELL
803
+ # Injected by ann-flavor-cocoapods: compose the final Info.plist from
804
+ # two layers -- Runner/Info.plist (generic/base) and the flavor-
805
+ # specific ann/Info/Info-<flavor>.plist (annspec.yaml's info_plist:
806
+ # cascade) -- with the flavor layer winning on any key collision.
807
+ # See docs/03-planning/features/038-info-plist-build-time-merge.md.
808
+ PLISTBUDDY=/usr/libexec/PlistBuddy
809
+ BASE="${SRCROOT}/Runner/Info.plist"
810
+ FLAVOR_NAME="${CONFIGURATION#*-}"
811
+ FLAVOR_PLIST="${SRCROOT}/ann/Info/Info-${FLAVOR_NAME}.plist"
812
+ OUT="${DERIVED_FILE_DIR}/Info-merged.plist"
813
+
814
+ if [ ! -f "$BASE" ]; then
815
+ echo "error: [ann-flavor] Runner/Info.plist not found at $BASE"
816
+ exit 1
817
+ fi
818
+
819
+ # Start from a full copy of the base layer -- every key
820
+ # Runner/Info.plist already has is correct by default; only the
821
+ # flavor delta's keys need explicit merging in below.
822
+ cp "$BASE" "$OUT"
823
+
824
+ if [ ! -f "$FLAVOR_PLIST" ]; then
825
+ echo "warning: [ann-flavor] no flavor Info.plist found for '$FLAVOR_NAME' at $FLAVOR_PLIST -- using Runner/Info.plist unmerged."
826
+ exit 0
827
+ fi
828
+
829
+ # Enumerate the flavor delta's top-level keys. PlistBuddy "Print" with
830
+ # no key path dumps the whole dict; grep the top-level "Key = " lines
831
+ # (four leading spaces = depth 1 in PlistBuddy's indented dump format).
832
+ FLAVOR_KEYS=$("$PLISTBUDDY" -c "Print" "$FLAVOR_PLIST" | grep -E '^ [A-Za-z]' | sed 's/ =.*//' | sed 's/^ *//')
833
+
834
+ for KEY in $FLAVOR_KEYS; do
835
+ # OQ-2: warn (non-fatal) only on a genuine collision -- Runner/Info.plist
836
+ # already had this key, with a different value, before the flavor delta
837
+ # is applied. A net-new key (not previously in Runner/Info.plist) is not
838
+ # a collision and stays silent -- that's the expected, common case.
839
+ # Compared via PlistBuddy's human-readable Print output, which is
840
+ # good enough for equality-detection purposes even though it can't
841
+ # reliably tell types apart (see the VALUE_XML extraction below,
842
+ # which is what actually determines the merged value/type).
843
+ BASE_VALUE=$("$PLISTBUDDY" -c "Print :$KEY" "$OUT" 2>/dev/null)
844
+ BASE_HAD_KEY=$?
845
+ FLAVOR_VALUE=$("$PLISTBUDDY" -c "Print :$KEY" "$FLAVOR_PLIST")
846
+
847
+ if [ "$BASE_HAD_KEY" -eq 0 ] && [ "$BASE_VALUE" != "$FLAVOR_VALUE" ]; then
848
+ echo "[ann-flavor] Info.plist: '$KEY' overridden by flavor value ($FLAVOR_NAME) -- Runner/Info.plist's value ignored for this build."
849
+ fi
850
+
851
+ # Extract the flavor value's own XML fragment (e.g. <string>x</string>,
852
+ # <true/>, <integer>42</integer>, <array>...</array>) via PlistBuddy's
853
+ # -x (XML) output, scoped to just this key, stripping the <plist
854
+ # version="1.0">...</plist> wrapper that -x always adds. This
855
+ # preserves the flavor delta's real plist type -- Print's
856
+ # human-readable output alone can't distinguish an integer 42 from
857
+ # the string "42", or a bool true from the string "true", so a
858
+ # scalar-vs-composite TYPE guess from Print was unreliable and has
859
+ # been replaced with this approach (verified against a real
860
+ # PlistBuddy: bool/int/real/string/array/dict all round-trip
861
+ # correctly this way, plan 038 STEP-02).
862
+ VALUE_XML=$("$PLISTBUDDY" -x -c "Print :$KEY" "$FLAVOR_PLIST" | sed -n '/<plist version/,/<\\/plist>/p' | sed '1d;$d')
863
+
864
+ "$PLISTBUDDY" -c "Delete :$KEY" "$OUT" >/dev/null 2>&1
865
+ FRAGMENT=$(mktemp /tmp/ann-flavor-plist-fragment.XXXXXX)
866
+ cat > "$FRAGMENT" <<XMLEOF
867
+ <?xml version="1.0" encoding="UTF-8"?>
868
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
869
+ <plist version="1.0">
870
+ <dict>
871
+ <key>$KEY</key>
872
+ $VALUE_XML
873
+ </dict>
874
+ </plist>
875
+ XMLEOF
876
+ "$PLISTBUDDY" -c "Merge \\"$FRAGMENT\\"" "$OUT"
877
+ rm -f "$FRAGMENT"
878
+ done
879
+ SHELL
880
+
881
+ # Find-or-update in place, rather than delete+recreate -- same
882
+ # idempotency rationale as inject_build_info_phase above.
883
+ existing = target.build_phases.find { |p| p.respond_to?(:name) && p.name == '[ann-flavor] Merge Info.plist' }
884
+ phase = existing || target.project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
885
+ phase.name = '[ann-flavor] Merge Info.plist'
886
+ phase.shell_path = '/bin/sh'
887
+ phase.shell_script = script
888
+ phase.show_env_vars_in_log = '0'
889
+ # Declares this phase's output so Xcode's build system understands the
890
+ # dependency (same technique already used for the Firebase-copy phase's
891
+ # output_paths) -- suppresses "input file not found" for INFOPLIST_FILE
892
+ # referencing a path nothing else produces until this phase runs.
893
+ phase.output_paths = ['$(DERIVED_FILE_DIR)/Info-merged.plist']
894
+
895
+ target.build_phases.unshift(phase) unless existing
896
+ end
747
897
  end
748
898
  end
@@ -1,3 +1,3 @@
1
1
  module AnnFlavorCocoapods
2
- VERSION = "0.1.39"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -86,7 +86,7 @@ module AnnFlavorCore
86
86
 
87
87
  IosDefault = Struct.new(
88
88
  :id, :name, :version_name, :version_code, :main_file,
89
- :admob, :icon, :credentials, :firebase, :build_types, :custom, :dart_defines, :info_plist, :sdk,
89
+ :admob, :icon, :credentials, :firebase, :build_types, :custom, :dart_defines, :info_plist, :entitlements, :sdk,
90
90
  keyword_init: true
91
91
  ) do
92
92
  def self.defaults = new(build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
@@ -94,7 +94,7 @@ module AnnFlavorCore
94
94
 
95
95
  IosFlavor = Struct.new(
96
96
  :id, :id_suffix, :name, :name_suffix, :version_name, :version_code,
97
- :main_file, :admob, :icon, :firebase, :stores, :credentials, :build_types, :custom, :dart_defines, :info_plist,
97
+ :main_file, :admob, :icon, :firebase, :stores, :credentials, :build_types, :custom, :dart_defines, :info_plist, :entitlements,
98
98
  keyword_init: true
99
99
  ) do
100
100
  def self.defaults = new(id_suffix: '', name_suffix: '', build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
@@ -160,7 +160,7 @@ module AnnFlavorCore
160
160
  :id_suffix, :name_suffix,
161
161
  :firebase, :auth, :admob,
162
162
  :minify_enabled, :shrink_resources, :lint_check_release_builds,
163
- :ndk_version, :ndk_debug_symbol_level, :ndk_abi_filters, :custom, :dart_defines, :info_plist,
163
+ :ndk_version, :ndk_debug_symbol_level, :ndk_abi_filters, :custom, :dart_defines, :info_plist, :entitlements,
164
164
  keyword_init: true
165
165
  ) do
166
166
  def self.defaults = new(id_suffix: '', name_suffix: '', ndk_abi_filters: [], custom: {}, dart_defines: DartDefines.defaults)
@@ -213,6 +213,7 @@ module AnnFlavorCore
213
213
  :effective_custom,
214
214
  :effective_dart_defines,
215
215
  :effective_info_plist,
216
+ :effective_entitlements,
216
217
  keyword_init: true
217
218
  )
218
219
  end
@@ -163,6 +163,7 @@ module AnnFlavorCore
163
163
  custom: parse_custom_config(h(m, 'custom')),
164
164
  dart_defines: parse_dart_defines(h(m, 'dart_defines')),
165
165
  info_plist: m['info_plist'],
166
+ entitlements: m['entitlements'],
166
167
  sdk: (parse_ios_sdk(h(m, 'sdk')) unless h(m, 'sdk').empty?),
167
168
  )
168
169
  end
@@ -189,6 +190,7 @@ module AnnFlavorCore
189
190
  custom: parse_custom_config(h(m, 'custom')),
190
191
  dart_defines: parse_dart_defines(h(m, 'dart_defines')),
191
192
  info_plist: m['info_plist'],
193
+ entitlements: m['entitlements'],
192
194
  )
193
195
  end
194
196
 
@@ -321,6 +323,7 @@ module AnnFlavorCore
321
323
  custom: parse_custom_config(h(v, 'custom')),
322
324
  dart_defines: parse_dart_defines(h(v, 'dart_defines')),
323
325
  info_plist: v['info_plist'],
326
+ entitlements: v['entitlements'],
324
327
  )
325
328
  end
326
329
  end
@@ -119,6 +119,9 @@ module AnnFlavorCore
119
119
  # in the consuming plugin, not in the core).
120
120
  effective_info_plist: flavor.build_types[build_type]&.info_plist || flavor.info_plist ||
121
121
  defaults.build_types[build_type]&.info_plist || defaults.info_plist,
122
+ # Same whole-value most-specific-wins rule as effective_info_plist above.
123
+ effective_entitlements: flavor.build_types[build_type]&.entitlements || flavor.entitlements ||
124
+ defaults.build_types[build_type]&.entitlements || defaults.entitlements,
122
125
  )
123
126
  end
124
127
 
@@ -21,12 +21,13 @@ module AnnFlavorCore
21
21
  # Fields whose value is a free-form Hash (arbitrary user-declared keys are data,
22
22
  # not schema) -- exempted from unknown-key checking entirely, at any level.
23
23
  #
24
- # info_plist is additionally a union (an inline Hash OR a plain file-path String),
25
- # so even when its value IS a Hash, its keys are raw Info.plist keys the user
26
- # controls, never our own schema fields -- same free-form treatment as
27
- # custom/dart_defines, and deliberately NOT in RECURSION_TARGETS since there is
28
- # no single fixed struct type to recurse into.
29
- FREE_FORM_FIELDS = %i[custom dart_defines info_plist].freeze
24
+ # info_plist and entitlements are additionally a union (an inline Hash OR a
25
+ # plain file-path String), so even when their value IS a Hash, its keys are
26
+ # raw Info.plist/entitlement keys the user controls, never our own schema
27
+ # fields -- same free-form treatment as custom/dart_defines, and deliberately
28
+ # NOT in RECURSION_TARGETS since there is no single fixed struct type to
29
+ # recurse into.
30
+ FREE_FORM_FIELDS = %i[custom dart_defines info_plist entitlements].freeze
30
31
 
31
32
  # (containing_struct, field_name) => target_struct — only the "does this field
32
33
  # recurse, and into what" relationship; the valid-keys-within-that-struct question
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ann-flavor-cocoapods
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.39
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ANN Solutions