From f92c362bc7995f98061eb0736c19c60e39a40651 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Tue, 26 Nov 2024 16:42:26 +0000 Subject: [PATCH] Remove `Brewfile.lock.json` generation. This was originally added to faciliate later functionality from other maintainers that never materialized. I've never heard anyone say this was useful and a lot of people have complained that it's useless or annoying. --- .gitignore | 1 - README.md | 4 - cmd/bundle.rb | 5 +- lib/bundle.rb | 1 - lib/bundle/cask_dumper.rb | 8 - lib/bundle/installer.rb | 5 - lib/bundle/locker.rb | 171 ------------------- spec/bundle/commands/install_command_spec.rb | 2 - spec/bundle/locker_spec.rb | 137 --------------- 9 files changed, 2 insertions(+), 332 deletions(-) delete mode 100644 lib/bundle/locker.rb delete mode 100644 spec/bundle/locker_spec.rb diff --git a/.gitignore b/.gitignore index 3045441dd..eab891638 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ .vscode Brewfile -Brewfile.lock.json Gemfile.lock coverage diff --git a/README.md b/README.md index d920448e4..7ad789443 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,6 @@ vscode "GitHub.codespaces" Homebrew is a [rolling release](https://en.wikipedia.org/wiki/Rolling_release) package manager so it does not support installing arbitrary older versions of software. If your software needs specific pinned versions, consider [`whalebrew`](https://github.com/whalebrew/whalebrew) lines in your `Brewfile` to install [Docker](https://www.docker.com) containers. -After a successful `brew bundle` run, it creates a `Brewfile.lock.json` to record the environment. If a future `brew bundle` run fails, you can check the differences between `Brewfile.lock.json` to debug. As it can contain local environment information that varies between systems, it's not worth committing to version control on multi-user repositories. - -Disable generation of the `Brewfile.lock.json` file by setting the environment variable with `export HOMEBREW_BUNDLE_NO_LOCK=1` or by using the command-line argument `brew bundle --no-lock`. - ## New Installers/Checkers/Dumpers `brew bundle` currently supports Homebrew, Homebrew Cask, Mac App Store, Whalebrew and Visual Studio Code. diff --git a/cmd/bundle.rb b/cmd/bundle.rb index f7824af95..ab3d5b1ee 100755 --- a/cmd/bundle.rb +++ b/cmd/bundle.rb @@ -28,8 +28,6 @@ class BundleCmd < AbstractCommand You can skip the installation of dependencies by adding space-separated values to one or more of the following environment variables: `HOMEBREW_BUNDLE_BREW_SKIP`, `HOMEBREW_BUNDLE_CASK_SKIP`, `HOMEBREW_BUNDLE_MAS_SKIP`, `HOMEBREW_BUNDLE_WHALEBREW_SKIP`, `HOMEBREW_BUNDLE_TAP_SKIP`. - `brew bundle` will output a `Brewfile.lock.json` in the same directory as the `Brewfile` if all dependencies are installed successfully. This contains dependency and system status information which can be useful for debugging `brew bundle` failures and replicating a "last known good build" state. You can opt-out of this behaviour by setting the `HOMEBREW_BUNDLE_NO_LOCK` environment variable or passing the `--no-lock` option. You may wish to check this file into the same version control system as your `Brewfile` (or ensure your version control system ignores it if you'd prefer to rely on debugging information from a local machine). - `brew bundle dump`: Write all installed casks/formulae/images/taps into a `Brewfile` in the current directory. @@ -76,7 +74,8 @@ class BundleCmd < AbstractCommand "This is enabled by default if `HOMEBREW_BUNDLE_INSTALL_CLEANUP` is set and " \ "`--global` is passed." switch "--no-lock", - description: "`install` does not output a `Brewfile.lock.json`." + description: "no-op since `Brewfile.lock.json` was removed.", + hidden: true switch "--all", description: "`list` all dependencies." switch "--formula", "--brews", diff --git a/lib/bundle.rb b/lib/bundle.rb index 17f249819..09797d4ef 100644 --- a/lib/bundle.rb +++ b/lib/bundle.rb @@ -24,7 +24,6 @@ require "bundle/dumper" require "bundle/installer" require "bundle/lister" -require "bundle/locker" require "bundle/commands/install" require "bundle/commands/dump" require "bundle/commands/cleanup" diff --git a/lib/bundle/cask_dumper.rb b/lib/bundle/cask_dumper.rb index eec47bd6c..a2196361d 100644 --- a/lib/bundle/cask_dumper.rb +++ b/lib/bundle/cask_dumper.rb @@ -30,14 +30,6 @@ def cask_is_outdated_using_greedy?(cask_name) cask.outdated?(greedy: true) end - def cask_versions - return {} unless Bundle.cask_installed? - - casks.each_with_object({}) do |cask, name_versions| - name_versions[cask.to_s] = cask.version - end - end - def dump(describe: false) casks.map do |cask| description = "# #{cask.desc}\n" if describe && cask.desc.present? diff --git a/lib/bundle/installer.rb b/lib/bundle/installer.rb index 7e41754f4..31015439f 100644 --- a/lib/bundle/installer.rb +++ b/lib/bundle/installer.rb @@ -59,14 +59,9 @@ def install(entries, global: false, file: nil, no_lock: false, no_upgrade: false unless failure.zero? puts Formatter.error "Homebrew Bundle failed! " \ "#{failure} Brewfile #{Bundle::Dsl.pluralize_dependency(failure)} failed to install." - if (lock = Bundle::Locker.lockfile(global:, file:)) && lock.exist? - puts Formatter.error("Check for differences in your #{lock.basename}!") - end return false end - Bundle::Locker.lock(entries, global:, file:, no_lock:) - puts Formatter.success "Homebrew Bundle complete! " \ "#{success} Brewfile #{Bundle::Dsl.pluralize_dependency(success)} now installed." true diff --git a/lib/bundle/locker.rb b/lib/bundle/locker.rb deleted file mode 100644 index 14fc5485c..000000000 --- a/lib/bundle/locker.rb +++ /dev/null @@ -1,171 +0,0 @@ -# frozen_string_literal: true - -require "tap" -require "os" -require "development_tools" -require "env_config" - -module Bundle - module Locker - module_function - - def lockfile(global: false, file: nil) - brew_file_path = Brewfile.path(global:, file:) - lock_file_path = brew_file_path.dirname/"#{brew_file_path.basename}.lock.json" - - # no need to call realpath if the lockfile is not a symlink - # unnecessary call to fs, also breaks tests, which use filenames that are not in fs - lock_file_path = lock_file_path.realpath if lock_file_path.symlink? - - lock_file_path - end - - def write_lockfile?(global: false, file: nil, no_lock: false) - return false if no_lock - return false if ENV["HOMEBREW_BUNDLE_NO_LOCK"] - - # handle the /dev/stdin and /dev/stdout cases - return false if lockfile(global:, file:).parent.to_s == "/dev" - - true - end - - def lock(entries, global: false, file: nil, no_lock: false) - return false unless write_lockfile?(global:, file:, no_lock:) - - lockfile = lockfile(global:, file:) - - lock = JSON.parse(lockfile.read) if lockfile.exist? - lock ||= {} - lock["entries"] ||= {} - lock["system"] ||= {} - - entries.each do |entry| - next if Bundle::Skipper.skip?(entry, silent: true) - - entry_type_key = entry.type.to_s - options = entry.options - lock["entries"][entry_type_key] ||= {} - lock["entries"][entry_type_key][entry.name] = case entry.type - when :brew - brew_list(entry.name) - when :cask - options.delete(:args) if options[:args].blank? - { version: cask_list[entry.name] } - when :mas - options.delete(:id) - mas_list[entry.name] - when :whalebrew - whalebrew_list[entry.name] - when :tap - options.delete(:clone_target) if options[:clone_target].blank? - options.delete(:pin) if options[:pin] == false - { revision: Tap.fetch(entry.name).git_head } - end - - next if options.blank? - - lock["entries"][entry_type_key][entry.name] ||= {} - lock["entries"][entry_type_key][entry.name]["options"] = - options.deep_stringify_keys - end - - if OS.mac? - lock["system"]["macos"] ||= {} - version, hash = system_macos - lock["system"]["macos"][version] = hash - elsif OS.linux? - lock["system"]["linux"] ||= {} - version, hash = system_linux - lock["system"]["linux"][version] = hash - end - - json = JSON.pretty_generate(lock) - begin - Bundle.exchange_uid_if_needed! do - lockfile.unlink if lockfile.exist? - lockfile.write("#{json}\n") - end - rescue Errno::EPERM, Errno::EACCES, Errno::ENOTEMPTY - unless ENV.fetch("HOMEBREW_BUNDLE_NO_LOCKFILE_WRITE_WARNING", false) - opoo "Could not write to #{lockfile}!" - unless Homebrew::EnvConfig.no_env_hints? - puts "Hide this warning by setting HOMEBREW_BUNDLE_NO_LOCKFILE_WRITE_WARNING." - puts "Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`)." - end - end - return false - end - - true - end - - def brew_list(name) - @brew_list ||= begin - # reset and reget all versions from scratch - Bundle::BrewDumper.reset! - {} - end - - return @brew_list[name] if @brew_list.key?(name) - - @brew_list[name] ||= Bundle::BrewDumper.formulae_by_name(name) - &.slice(:version, :bottle) - end - - def cask_list - return {} unless OS.mac? - - @cask_list ||= begin - # reset and reget all versions from scratch - Bundle::CaskDumper.reset! - Bundle::CaskDumper.cask_versions - end - end - - def mas_list - return {} unless OS.mac? - - @mas_list ||= `mas list`.lines - .each_with_object({}) do |line, name_id_versions| - line = line.split - id = line.shift - version = line.pop.delete("()") - name = line.join(" ") - name_id_versions[name] = { - id:, - version:, - } - end - end - - def whalebrew_list - @whalebrew_list ||= Bundle::WhalebrewDumper.images.each_with_object({}) do |image, name_versions| - _, version = `docker image inspect #{image} --format '{{ index .RepoDigests 0 }}'`.split(":") - name_versions[image] = version.chomp - end - end - - def system - { - "HOMEBREW_VERSION" => HOMEBREW_VERSION, - "HOMEBREW_PREFIX" => HOMEBREW_PREFIX.to_s, - "Homebrew/homebrew-core" => Homebrew::EnvConfig.no_install_from_api? ? CoreTap.instance.git_head : "api", - } - end - - def system_macos - [MacOS.version.to_sym.to_s, system.merge({ - "CLT" => MacOS::CLT.version.to_s, - "Xcode" => MacOS::Xcode.version.to_s, - "macOS" => MacOS.full_version.to_s, - })] - end - - def system_linux - [OS::Linux.os_version, system.merge({ - "GCC" => DevelopmentTools.gcc_version("gcc"), - })] - end - end -end diff --git a/spec/bundle/commands/install_command_spec.rb b/spec/bundle/commands/install_command_spec.rb index 0975a2f06..70970a770 100644 --- a/spec/bundle/commands/install_command_spec.rb +++ b/spec/bundle/commands/install_command_spec.rb @@ -5,7 +5,6 @@ describe Bundle::Commands::Install do before do allow_any_instance_of(IO).to receive(:puts) - allow(Bundle::Locker).to receive(:write_lockfile?).and_return(false) end context "when a Brewfile is not found" do @@ -66,7 +65,6 @@ allow(Bundle::TapInstaller).to receive_messages(preinstall: true, install: false) allow(Bundle::WhalebrewInstaller).to receive_messages(preinstall: true, install: false) allow(Bundle::VscodeExtensionInstaller).to receive_messages(preinstall: true, install: false) - allow(Bundle::Locker).to receive(:lockfile).and_return(Pathname(__dir__)) allow_any_instance_of(Pathname).to receive(:read).and_return(brewfile_contents) expect { described_class.run }.to raise_error(SystemExit) diff --git a/spec/bundle/locker_spec.rb b/spec/bundle/locker_spec.rb deleted file mode 100644 index d18be63f9..000000000 --- a/spec/bundle/locker_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" -require "cask" - -describe Bundle::Locker do - subject(:locker) { described_class } - - describe ".lockfile" do - it "returns a Pathname" do - allow(Bundle::Brewfile).to receive(:path).and_return(Pathname("Brewfile")) - expect(locker.lockfile.class).to be Pathname - end - - it "correctly matches the Brewfile name in the lockfile name" do - allow(Bundle::Brewfile).to receive(:path).and_return(Pathname("Personal.brewfile")) - expect(locker.lockfile).to eq Pathname.new("Personal.brewfile.lock.json") - end - end - - describe ".write_lockfile?" do - it "returns false if `no_lock` is true" do - expect(locker.write_lockfile?(no_lock: true)).to be false - end - - it "returns false if HOMEBREW_BUNDLE_NO_LOCK is set" do - ENV["HOMEBREW_BUNDLE_NO_LOCK"] = "1" - expect(locker.write_lockfile?).to be false - end - - it "returns false if it would write to /dev" do - allow(Bundle::Brewfile).to receive(:path).and_return(Pathname("/dev/stdin")) - expect(locker.write_lockfile?).to be false - end - - it "returns true otherwise" do - ENV["HOMEBREW_BUNDLE_NO_LOCK"] = nil - allow(Bundle::Brewfile).to receive(:path).and_return(Pathname("Brewfile")) - expect(locker.write_lockfile?).to be true - end - end - - describe ".whalebrew_list" do - before do - allow(Bundle::WhalebrewDumper).to receive(:images).and_return(["whalebrew/wget"]) - allow(locker).to receive(:`) - .with("docker image inspect whalebrew/wget --format '{{ index .RepoDigests 0 }}'") - .and_return("whalebrew/wget@sha256:abcd1234") - end - - it "returns a hash of the name and layer checksum" do - expect(locker.whalebrew_list).to eq({ "whalebrew/wget" => "abcd1234" }) - end - end - - describe ".lock" do - describe "writes Brewfile.lock.json" do - let(:lockfile) { Pathname("Brewfile.json.lock") } - let(:brew_options) { { start_service: true, restart_service: true } } - let(:entries) do - [ - Bundle::Dsl::Entry.new(:brew, "mysql", brew_options), - Bundle::Dsl::Entry.new(:cask, "adoptopenjdk8"), - Bundle::Dsl::Entry.new(:mas, "Xcode", id: 497_799_835), - Bundle::Dsl::Entry.new(:tap, "homebrew/homebrew-cask-versions"), - Bundle::Dsl::Entry.new(:whalebrew, "whalebrew/wget"), - ] - end - - before do - allow(locker).to receive(:lockfile).and_return(lockfile) - allow(brew_options).to receive(:deep_stringify_keys) - .and_return("start_service" => true, "restart_service" => true) - allow(Bundle::BrewDumper).to receive(:formulae_by_full_name).with("mysql").and_return({ - name: "mysql", - version: "8.0.18", - bottle: { - stable: {}, - }, - }) - allow(locker).to receive(:`).with("whalebrew list").and_return("COMMAND IMAGE\nwget whalebrew/wget") - allow(locker).to receive(:`) - .with("docker image inspect whalebrew/wget --format '{{ index .RepoDigests 0 }}'") - .and_return("whalebrew/wget@sha256:abcd1234") - allow(Bundle::WhalebrewDumper).to receive(:images).and_return(["whalebrew/wget"]) - end - - context "when on macOS" do - before do - allow(OS).to receive(:mac?).and_return(true) - allow(Bundle).to receive(:cask_installed?).and_return(true) - - adoptopenjdk8 = instance_double(Cask::Cask, to_s: "adoptopenjdk8", version: "8,232:b09") - allow(Cask::Caskroom).to receive(:casks).and_return([adoptopenjdk8]) - allow(locker).to receive(:`).with("mas list").and_return("497799835 Xcode (11.2)") - end - - it "returns true" do - expect(lockfile).to receive(:write) - expect(locker.lock(entries)).to be true - end - - it "returns false on a permission error" do - expect(lockfile).to receive(:write).and_raise(Errno::EPERM) - allow(locker).to receive(:opoo) - allow(locker).to receive(:puts) - expect(locker.lock(entries)).to be false - end - - it "outputs warning on a permission error" do - expect(lockfile).to receive(:write).and_raise(Errno::EPERM) - expect(locker).to receive(:opoo) - allow(locker).to receive(:puts) - locker.lock(entries) - end - - it "outputs environment variable hint on a permission error" do - expect(lockfile).to receive(:write).and_raise(Errno::EPERM) - allow(locker).to receive(:opoo) - expect(locker).to receive(:puts).twice - locker.lock(entries) - end - end - - context "when on Linux" do - before do - allow(OS).to receive_messages(mac?: false, linux?: true) - end - - it "returns true" do - expect(lockfile).to receive(:write) - expect(locker.lock(entries)).to be true - end - end - end - end -end