88require "github_packages"
99require "github_releases"
1010require "extend/hash/deep_merge"
11+ require "bottle_transition"
1112
1213module Homebrew
1314 module DevCmd
@@ -108,6 +109,8 @@ def run
108109 end
109110 end
110111
112+ check_transition_bottles! ( bottles_hash )
113+
111114 if github_releases? ( bottles_hash )
112115 github_releases = GitHubReleases . new
113116 github_releases . upload_bottles ( bottles_hash )
@@ -122,6 +125,44 @@ def run
122125 end
123126 end
124127
128+ # Verify transition bottles before upload.
129+ sig { params ( bottles_hash : T ::Hash [ String , T . untyped ] ) . void }
130+ def check_transition_bottles! ( bottles_hash )
131+ # Reload the bottle block written by `brew bottle --merge`.
132+ Formulary . clear_cache
133+ transition = BottleTransition . new
134+ bottles_hash . each_value do |bottle_hash |
135+ formula_path = HOMEBREW_REPOSITORY /bottle_hash . fetch ( "formula" ) . fetch ( "path" )
136+ formula = Formulary . factory ( formula_path )
137+ next unless transition . required? ( formula )
138+
139+ spec = formula . bottle_specification
140+ transition . check! ( formula , tags : spec . collector . tags )
141+ bottle = bottle_hash . fetch ( "bottle" )
142+ tags = bottle . fetch ( "tags" ) . keys . map { |tag | Utils ::Bottles . tag ( tag . to_sym ) }
143+ if !args . keep_old? && tags . exclude? ( BottleTransition . tag ) && tags . exclude? ( Utils ::Bottles . tag ( :all ) )
144+ raise UsageError , <<~EOS
145+ #{ formula . full_name } #{ formula . pkg_version } : upload set is missing
146+ #{ BottleTransition . tag } or `all` bottle artifacts.
147+ Restore the matching bottle artifacts and JSON files before retrying.
148+ EOS
149+ end
150+
151+ root_url = bottle . fetch ( "root_url" )
152+ metadata_matches = bottle_hash . fetch ( "formula" ) . fetch ( "pkg_version" ) == formula . pkg_version . to_s &&
153+ bottle . fetch ( "rebuild" , 0 ) . to_i == spec . rebuild &&
154+ ( GitHubPackages . root_url_if_match ( root_url ) || root_url ) == spec . root_url &&
155+ bottle . fetch ( "tags" ) . all? do |tag , tag_hash |
156+ tag_spec = spec . collector . specification_for ( Utils ::Bottles . tag ( tag . to_sym ) ,
157+ no_older_versions : true )
158+ tag_spec && tag_spec . checksum . hexdigest == tag_hash . fetch ( "sha256" )
159+ end
160+ next if metadata_matches
161+
162+ raise UsageError , "#{ formula . full_name } : bottle metadata does not match the committed formula."
163+ end
164+ end
165+
125166 private
126167
127168 sig { params ( bottles_hash : T ::Hash [ String , T . untyped ] ) . void }
@@ -154,12 +195,28 @@ def github_packages?(bottles_hash)
154195 end , T . nilable ( T ::Boolean ) )
155196 end
156197
198+ public
199+
200+ # Merge compatible bottle metadata.
157201 sig { params ( json_files : T ::Array [ String ] , args : T . untyped ) . returns ( T ::Hash [ String , T . untyped ] ) }
158202 def bottles_hash_from_json_files ( json_files , args )
159203 puts "Reading JSON files: #{ json_files . join ( ", " ) } " if args . verbose?
160204
161205 bottles_hash = json_files . reduce ( { } ) do |hash , json_file |
162- hash . deep_merge ( JSON . parse ( File . read ( json_file ) ) )
206+ incoming = JSON . parse ( File . read ( json_file ) )
207+ incoming . each do |name , bottle_hash |
208+ bottle = bottle_hash . fetch ( "bottle" )
209+ bottle [ "root_url" ] = GitHubPackages . root_url_if_match ( bottle [ "root_url" ] ) || bottle [ "root_url" ]
210+ previous = hash [ name ]
211+ next unless previous
212+ next if previous . fetch ( "formula" ) . slice ( "path" , "pkg_version" ) ==
213+ bottle_hash . fetch ( "formula" ) . slice ( "path" , "pkg_version" ) &&
214+ previous . fetch ( "bottle" ) . slice ( "root_url" , "rebuild" ) ==
215+ bottle_hash . fetch ( "bottle" ) . slice ( "root_url" , "rebuild" )
216+
217+ raise UsageError , "Inconsistent bottle metadata for #{ name } in #{ json_file } ."
218+ end
219+ hash . deep_merge ( incoming )
163220 end
164221
165222 if args . root_url
0 commit comments