Skip to content

Instantly share code, notes, and snippets.

@benhoskings
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benhoskings/6242871f7df92472f47f to your computer and use it in GitHub Desktop.
Save benhoskings/6242871f7df92472f47f to your computer and use it in GitHub Desktop.

For the package management you're talking about, it sounds like there are two cases:

  • Accept any version (1)
  • Require a specific version (2)
  • Require the latest version (3)

Babushka can already do (1) and (2). The third is fairly easy to add, but to do so in babushka core isn't really feasible until I refactor Babushka::BrewHelper.

For (1), you don't need to do anything: that's the standard behaviour. For example:

dep 'postgres.bin' do
  installs 'postgresql'
  provides 'psql'
end

This dep is met if there is any psql in the path, in fact this is the met? block for deps templated on 'bin':

met? {
  in_path?(provides)
}

For (2), the usage is the same, but the check is restricted to a specific version. in_path? understands how to extract versions from the arguments, and how to query the binaries it finds to see if they match. For example, this dep will only be met if the version matches exactly:

dep 'postgres.bin' do
  installs 'postgresql'
  provides 'psql 9.3.4'
end

You can also use whatever operators you like:

provides 'psql == 9.3.4' # The above is shorthand for this

provides 'psql >= 9.3.4'

provides 'psql ~> 9.3.4' # Same behaviour as rubygems' ~> operator

You could also parameterise it like so:

dep 'postgres.bin', :version do
  installs 'postgresql'
  provides "psql >= #{version}"
end

Or customise it like this (specifying a custom met? block overrides the one in the template, which means provides is unused in this dep):

dep 'postgres.bin' do
  installs 'postgresql'
  def version
    MagicalAPI.get('version')
  end
  met? {
    in_path?("postgres ~> #{version}")
  }
end

Achieving (3) would end up looking like (1) -- we do want to lock to a version like, but instead of supplying it ourselves like (2) we want to query it from the package manager. This is the quick-and-dirty replacement for the 'bin' template that will do the job:

meta :latest_brew do
  accepts_value_for :installs, :basename, :choose_with => :via
  accepts_list_for :provides, :basename, :choose_with => :via

  def latest pkg_name
    # Make sure we're checking against the latest version
    Babushka.host.pkg_helper.update_pkg_lists_if_required
    shell!("brew info #{pkg_name}").val_for(pkg_name).scan(/(?<=stable )\S+/).first
  end

  template {
    requires_when_unmet Babushka.host.pkg_helper.manager_dep

    met? {
      provides.all? {|pkg_name|
        # This would also work, but it's a dirty round-trip through a
        # string that we can easily avoid.
        # in_path?("#{pkg_name} == #{latest(pkg_name)}")
        in_path?(Babushka::VersionOf(pkg_name, latest(installs)))
      }
    }

    meet {
      log_shell "Unlinking existing #{installs}", "brew unlink #{installs}" if which(installs)
      Babushka.host.pkg_helper.handle_install! installs
      log_shell "Linking latest #{installs}", "brew switch #{installs} #{latest(installs)}" unless which(installs)
    }
  }
end

There's more to it than the original, though, because we need to interact meaningfully with the package manager instead of just blindly telling it to install things, like the simple case.

  • The 'installs' word in the DSL now only accepts a single value instead of a list, because we need a single package's version to test against.
  • Also, the met? block assembles the version check based on what #latest retrieves from the package manager instead of a user-supplied argument.
  • There's a bit of a dance in meet to account for homebrew's finicky behaviour around linking and switching.

I'd rather this logic was integrated into Babushka::BrewHelper, but that would need some preparatory refactoring. In the meantime, it's nice to contain hacks like this inside a dep template, because it makes them completely self-contained within your dep source, and easy to upgrade from later.

Anyhow, with the above template, you can use a dep identical to that in (1):

dep 'postgres.latest_bin' do
  installs 'postgresql'
  provides 'psql'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment