From e009027647ba62ec5148e14cd771749999c6e354 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Sat, 23 May 2026 20:14:54 +0100 Subject: [PATCH] Preserve cask choice zeroes - Keep `attributeSetting: 0` in internal API cask artifacts because installer choices need zero as a meaningful value. - Allow `Utils.deep_compact_blank` callers to opt out of dropping zeroes while preserving existing default behaviour. --- Library/Homebrew/api/cask_struct.rb | 9 ++++--- Library/Homebrew/test/api/cask_struct_spec.rb | 26 +++++++++++++++++++ Library/Homebrew/utils.rb | 10 +++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Library/Homebrew/api/cask_struct.rb b/Library/Homebrew/api/cask_struct.rb index eb11a71466cef..d5c2d2511a595 100644 --- a/Library/Homebrew/api/cask_struct.rb +++ b/Library/Homebrew/api/cask_struct.rb @@ -116,12 +116,15 @@ def serialize [prop.to_s, send(prop)] end.to_h - hash["raw_artifacts"] = raw_artifacts.map do |artifact| + hash["raw_artifacts"] = ::Utils.deep_compact_blank(raw_artifacts.map do |artifact| serialize_artifact_args(artifact) - end + end, compact_zero: false) hash = ::Utils.deep_stringify_symbols(hash) - ::Utils.deep_compact_blank(hash) + raw_artifacts = hash["raw_artifacts"] + hash = ::Utils.deep_compact_blank(hash) + hash["raw_artifacts"] = raw_artifacts if raw_artifacts.present? + hash end sig { params(hash: T::Hash[String, T.untyped]).returns(CaskStruct) } diff --git a/Library/Homebrew/test/api/cask_struct_spec.rb b/Library/Homebrew/test/api/cask_struct_spec.rb index e72753c1c3ee3..1aac6971db7df 100644 --- a/Library/Homebrew/test/api/cask_struct_spec.rb +++ b/Library/Homebrew/test/api/cask_struct_spec.rb @@ -138,6 +138,32 @@ .to eq([:preflight, ["foo"], { bar: "baz" }, nil]) end + it "preserves zero values in serialized artifact arguments" do + struct = klass.new( + sha256: "abc123", + version: "1.0.0", + ruby_source_checksum: { sha256: "def456" }, + raw_artifacts: [ + [ + :pkg, + ["Test.pkg"], + { choices: [{ choiceIdentifier: "choice1", choiceAttribute: "selected", attributeSetting: 0 }] }, + nil, + ], + ], + ) + + expect(struct.serialize.fetch("raw_artifacts")) + .to eq([ + [ + ":pkg", + ["Test.pkg"], + { ":choices" => [{ ":choiceIdentifier" => "choice1", ":choiceAttribute" => "selected", + ":attributeSetting" => 0 }] }, + ], + ]) + end + specify "::deserialize_artifact_args", :aggregate_failures do expect(klass.deserialize_artifact_args([:foo])) .to eq([:foo, [], {}, nil]) diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 007fea6a7dbd4..71ccfa0faa603 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -185,21 +185,21 @@ def self.deep_unstringify_symbols(obj) sig { type_parameters(:U) - .params(obj: T.all(T.type_parameter(:U), Object)) + .params(obj: T.all(T.type_parameter(:U), Object), compact_zero: T::Boolean) .returns(T.nilable(T.type_parameter(:U))) } - def self.deep_compact_blank(obj) + def self.deep_compact_blank(obj, compact_zero: true) obj = case obj when Hash - obj.transform_values { |v| deep_compact_blank(v) } + obj.transform_values { |v| deep_compact_blank(v, compact_zero:) } .compact when Array - obj.filter_map { |v| deep_compact_blank(v) } + obj.filter_map { |v| deep_compact_blank(v, compact_zero:) } else obj end - return if obj.blank? || (obj.is_a?(Numeric) && obj.zero?) + return if obj.blank? || (compact_zero && obj.is_a?(Numeric) && obj.zero?) obj end