Calling one formula's methods in another

I noticed that several formulae in homebrew/core call the version_suffix method defined in the gcc formula, to derive the name of the installed GCC executable, e.g.:

cc = Formula["gcc"].version_suffix

This breaks when building HEAD, because gcc defines it as follows:

  def version_suffix
    if build.head?
      "HEAD"
    else
      version.to_s.slice(/\d+/)
    end
  end

Since the build reference is resolved from the parent (the formula actually being built), building HEAD formulae would then use the nonexistent gcc-HEAD, resulting in very strange build failures.

I’ll submit PRs to change Formula["gcc"].version_suffix to Formula["gcc"].version.to_s.slice(/\d+/) in the relevant core formulae, but this begs the question: Is it licit for one formula to directly call methods defined in another formula?

I’m pretty sure it’s not desirable, as it’s obviously fragile, and there’s currently no way to mark specific methods as part of a formula’s “interface”.

I would say “no, it’s not really” beyond the methods like opt_prefix that are actually part of the Formula class.

1 Like

This was introduced with the intention to be used in other formulae. If it stops being used in other formulae then the method itself should disappear too, just like how it doesn’t exist for GCC’s versioned formulae.

What if gcc itself is built as HEAD? gcc-HEAD would the correct binary then.

1 Like

And that’s one of the main issues: that intent is not reflected in any visible way.

I’m fairly certain that the majority of Homebrew contributors are like myself: Ruby illiterates whose eyes glaze over at the contents of rubydoc.brew.sh, and who therefore pattern our contributions on existing formulae. We don’t necessarily have the Ruby smarts to distinguish between:

  • methods that are the “public API” of a formula, so can be called with peace of mind, and
  • methods that may disappear tomorrow, when a version bump suddenly makes them unnecessary to the “hosting” formula, or return something different than before, when a contributor refactors the formula.

I wasn’t proposing to change gcc itself (since it works as intended), just the six formulae that depend on version_suffix (and therefore break on HEAD builds). This should also remove the impetus for third-party tap maintainers to use the same function, and we arguably do HEAD builds far more often than homebrew/core.

Of course, it would be better if version_suffix could be fixed to actually return the version of the installed GCC when called from another formula. Sadly, I can’t figure out a fix myself.

The problem really is that the wrong tool was used for the job. Formula[name] is just an alias of Formulary.factory(name), which is designed to load the formula from file and that’s about it. Formulary.resolve(name) would’ve have produced more correct results - build.head? would then be based on the installed receipt/tab.

So version_suffix should then use Formulary.resolve("gcc"), and the current method code moved to a different method for gcc internals? I’ll put the original PRs on hold then, while I test that and submit a PR.

From a functional standpoint, yes. From a API standpoint, I suppose Formulary.resolve was designed with CLI parsing in mind rather than to be used in formulae files - but maybe I’m wrong about that.

The whole build stuff is quirky because the type changes depending on the context. If the formulary loads the formula from the keg or via the resolving stuff, then the build attribute points to the tab. When loaded from the factory the build stuff is taken directly from command line flags.

It also varies within the formula being installed. During the install stage the build attribute of the formula being installed is BuildOptions, while in the post_install and test stages the current formula’s build attribute changes to a tab.

active_spec also changes depending on the Formulary method used, but that one makes a bit more sense.

Personally I consider a formula calling methods on another to be a smell. If there’s a widespread need for e.g. the gcc version_suffix then perhaps it could instead be moved into a proper helper method in Homebrew/brew?

It’s been useful on two occasions in particular when the suffix changed - GCC 5 and GCC 10.

Will the suffix change again in the future? I don’t anticipate it but I should probably not start future telling.

I do think gcc-HEAD should be handled rather than ignored however - albeit I don’t know if brew itself actually handles it properly (e.g. GNU_GCC_VERSIONS).