| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | # typed: true | 
					
						
							|  |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-13 12:19:15 +01:00
										 |  |  | require "bundle_version" | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | require "cask/cask" | 
					
						
							|  |  |  | require "cask/installer" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Homebrew | 
					
						
							|  |  |  |   # Check unversioned casks for updates by extracting their | 
					
						
							|  |  |  |   # contents and guessing the version from contained files. | 
					
						
							|  |  |  |   # | 
					
						
							|  |  |  |   # @api private | 
					
						
							|  |  |  |   class UnversionedCaskChecker | 
					
						
							|  |  |  |     extend T::Sig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig {  returns(Cask::Cask) } | 
					
						
							|  |  |  |     attr_reader :cask | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { params(cask: Cask::Cask).void } | 
					
						
							|  |  |  |     def initialize(cask) | 
					
						
							|  |  |  |       @cask = cask | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(Cask::Installer) } | 
					
						
							|  |  |  |     def installer | 
					
						
							|  |  |  |       @installer ||= Cask::Installer.new(cask, verify_download_integrity: false) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(T::Array[Cask::Artifact::App]) } | 
					
						
							|  |  |  |     def apps | 
					
						
							|  |  |  |       @apps ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::App) } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-07 17:38:47 +10:00
										 |  |  |     sig { returns(T::Array[Cask::Artifact::Qlplugin]) } | 
					
						
							|  |  |  |     def qlplugins | 
					
						
							|  |  |  |       @qlplugins ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::Qlplugin) } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 14:26:38 +11:00
										 |  |  |     sig { returns(T::Array[Cask::Artifact::Installer]) } | 
					
						
							|  |  |  |     def installers | 
					
						
							|  |  |  |       @installers ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::Installer) } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |     sig { returns(T::Array[Cask::Artifact::Pkg]) } | 
					
						
							|  |  |  |     def pkgs | 
					
						
							|  |  |  |       @pkgs ||= @cask.artifacts.select { |a| a.is_a?(Cask::Artifact::Pkg) } | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(T::Boolean) } | 
					
						
							|  |  |  |     def single_app_cask? | 
					
						
							|  |  |  |       apps.count == 1
 | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 16:09:09 +10:00
										 |  |  |     sig { returns(T::Boolean) } | 
					
						
							|  |  |  |     def single_qlplugin_cask? | 
					
						
							|  |  |  |       qlplugins.count == 1
 | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |     sig { returns(T::Boolean) } | 
					
						
							|  |  |  |     def single_pkg_cask? | 
					
						
							|  |  |  |       pkgs.count == 1
 | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |     # Filter paths to `Info.plist` files so that ones belonging | 
					
						
							|  |  |  |     # to e.g. nested `.app`s are ignored. | 
					
						
							|  |  |  |     sig { params(paths: T::Array[Pathname]).returns(T::Array[Pathname]) } | 
					
						
							|  |  |  |     def top_level_info_plists(paths) | 
					
						
							|  |  |  |       # Go from `./Contents/Info.plist` to `./`. | 
					
						
							|  |  |  |       top_level_paths = paths.map { |path| path.parent.parent } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       paths.reject do |path| | 
					
						
							|  |  |  |         top_level_paths.any? do |_other_top_level_path| | 
					
						
							|  |  |  |           path.ascend.drop(3).any? { |parent_path| top_level_paths.include?(parent_path) } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-04 03:00:34 +02:00
										 |  |  |     sig { returns(T::Hash[String, BundleVersion]) } | 
					
						
							|  |  |  |     def all_versions | 
					
						
							|  |  |  |       versions = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       parse_info_plist = proc do |info_plist_path| | 
					
						
							|  |  |  |         plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", info_plist_path]).plist | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         id = plist["CFBundleIdentifier"] | 
					
						
							|  |  |  |         version = BundleVersion.from_info_plist_content(plist) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         versions[id] = version if id && version | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       Dir.mktmpdir do |dir| | 
					
						
							|  |  |  |         dir = Pathname(dir) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         installer.extract_primary_container(to: dir) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 14:26:38 +11:00
										 |  |  |         info_plist_paths = apps.concat(qlplugins, installers).flat_map do |artifact| | 
					
						
							|  |  |  |           source = artifact.is_a?(Cask::Artifact::Installer) ? artifact.path : artifact.source.basename | 
					
						
							|  |  |  |           top_level_info_plists(Pathname.glob(dir/"**"/source/"Contents"/"Info.plist")).sort | 
					
						
							| 
									
										
										
										
											2021-04-04 03:00:34 +02:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         info_plist_paths.each(&parse_info_plist) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pkg_paths = pkgs.flat_map do |pkg| | 
					
						
							|  |  |  |           Pathname.glob(dir/"**"/pkg.path.basename).sort | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pkg_paths.each do |pkg_path| | 
					
						
							|  |  |  |           Dir.mktmpdir do |extract_dir| | 
					
						
							|  |  |  |             extract_dir = Pathname(extract_dir) | 
					
						
							|  |  |  |             FileUtils.rmdir extract_dir | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             system_command! "pkgutil", args: ["--expand-full", pkg_path, extract_dir] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             top_level_info_plist_paths = top_level_info_plists(Pathname.glob(extract_dir/"**/Contents/Info.plist")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             top_level_info_plist_paths.each(&parse_info_plist) | 
					
						
							|  |  |  |           ensure | 
					
						
							|  |  |  |             Cask::Utils.gain_permissions_remove(extract_dir) | 
					
						
							|  |  |  |             extract_dir.mkpath | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         nil | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       versions | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |     sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |     def guess_cask_version | 
					
						
							| 
									
										
										
										
											2021-07-22 19:36:50 +10:00
										 |  |  |       if apps.empty? && pkgs.empty? && qlplugins.empty? | 
					
						
							|  |  |  |         opoo "Cask #{cask} does not contain any apps, qlplugins or PKG installers." | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |         return | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       Dir.mktmpdir do |dir| | 
					
						
							|  |  |  |         dir = Pathname(dir) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 01:47:37 +01:00
										 |  |  |         installer.then do |i| | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |           i.extract_primary_container(to: dir) | 
					
						
							|  |  |  |         rescue ErrorDuringExecution => e | 
					
						
							|  |  |  |           onoe e | 
					
						
							|  |  |  |           return nil | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         info_plist_paths = apps.flat_map do |app| | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |           top_level_info_plists(Pathname.glob(dir/"**"/app.source.basename/"Contents"/"Info.plist")).sort | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         info_plist_paths.each do |info_plist_path| | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |           if (version = BundleVersion.from_info_plist(info_plist_path)) | 
					
						
							|  |  |  |             return version.nice_version | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pkg_paths = pkgs.flat_map do |pkg| | 
					
						
							| 
									
										
										
										
											2020-12-10 19:09:46 +01:00
										 |  |  |           Pathname.glob(dir/"**"/pkg.path.basename).sort | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pkg_paths.each do |pkg_path| | 
					
						
							|  |  |  |           packages = | 
					
						
							|  |  |  |             system_command!("installer", args: ["-plist", "-pkginfo", "-pkg", pkg_path]) | 
					
						
							|  |  |  |             .plist | 
					
						
							|  |  |  |             .map { |package| package.fetch("Package") } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           Dir.mktmpdir do |extract_dir| | 
					
						
							|  |  |  |             extract_dir = Pathname(extract_dir) | 
					
						
							|  |  |  |             FileUtils.rmdir extract_dir | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             begin | 
					
						
							|  |  |  |               system_command! "pkgutil", args: ["--expand-full", pkg_path, extract_dir] | 
					
						
							|  |  |  |             rescue ErrorDuringExecution => e | 
					
						
							|  |  |  |               onoe "Failed to extract #{pkg_path.basename}: #{e}" | 
					
						
							|  |  |  |               next | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |             top_level_info_plist_paths = top_level_info_plists(Pathname.glob(extract_dir/"**/Contents/Info.plist")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             unique_info_plist_versions = | 
					
						
							|  |  |  |               top_level_info_plist_paths.map { |i| BundleVersion.from_info_plist(i)&.nice_version } | 
					
						
							|  |  |  |                                         .compact.uniq | 
					
						
							|  |  |  |             return unique_info_plist_versions.first if unique_info_plist_versions.count == 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |             package_info_path = extract_dir/"PackageInfo" | 
					
						
							|  |  |  |             if package_info_path.exist? | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |               if (version = BundleVersion.from_package_info(package_info_path)) | 
					
						
							|  |  |  |                 return version.nice_version | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  |               end | 
					
						
							|  |  |  |             elsif packages.count == 1
 | 
					
						
							|  |  |  |               onoe "#{pkg_path.basename} does not contain a `PackageInfo` file." | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |             distribution_path = extract_dir/"Distribution" | 
					
						
							|  |  |  |             if distribution_path.exist? | 
					
						
							| 
									
										
										
										
											2021-06-25 18:46:12 -04:00
										 |  |  |               require "rexml/document" | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:46:12 -04:00
										 |  |  |               xml = REXML::Document.new(distribution_path.read) | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:46:12 -04:00
										 |  |  |               product = xml.get_elements("//installer-gui-script//product").first | 
					
						
							|  |  |  |               product_version = product["version"] if product | 
					
						
							|  |  |  |               return product_version if product_version.present? | 
					
						
							| 
									
										
										
										
											2020-12-14 18:43:31 +01:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             opoo "#{pkg_path.basename} contains multiple packages: #{packages}" if packages.count != 1
 | 
					
						
							| 
									
										
										
										
											2020-12-07 16:31:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             $stderr.puts Pathname.glob(extract_dir/"**/*") | 
					
						
							|  |  |  |                                  .map { |path| | 
					
						
							|  |  |  |                                    regex = %r{\A(.*?\.(app|qlgenerator|saver|plugin|kext|bundle|osax))/.*\Z} | 
					
						
							|  |  |  |                                    path.to_s.sub(regex, '\1') | 
					
						
							|  |  |  |                                  }.uniq | 
					
						
							|  |  |  |           ensure | 
					
						
							|  |  |  |             Cask::Utils.gain_permissions_remove(extract_dir) | 
					
						
							|  |  |  |             extract_dir.mkpath | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         nil | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |