Homebrew / brew

🍺 The missing package manager for macOS (or Linux)
https://brew.sh
BSD 2-Clause "Simplified" License
40.75k stars 9.56k forks source link

Feature Request: Preventing formulae updates from breaking their dependencies #60

Closed ahundt closed 8 years ago

ahundt commented 8 years ago

Key problems

  1. Package A is updated from 1.0 to 2.0, breaking package B which depends on A 1.0.
    • breakage is not always detected
      • Example: ceres depends on an exact version of eigen (a simple bottle refresh could fix it)
    • breakage can't always be fixed
      • B may not support A 2.0 until its next release a month from now
  2. Package B,C depend on different versions of package A. Package D depends on both B, C and sometimes A directly. (other variations on this theme exist)
  3. Options sometimes break invisibly
  4. Cross tap dependencies may change

    Solutions with most backing

These are reasonably easy, well liked ideas that will mitigate (but may not completely solve) the above

This is the full list of proposals (not definite upcoming features). Check marks indicate they are implemented

these are from the ongoing conversation beyond the initial request posed above

A: depends on boost (latest)
B: depends on boost155 (newest feasible version)
C: depends on A, B, sometimes various boost versions

I keep having libraries I depend on get broken because those dependencies' own dependencies are changed out from under them. In particular, this happens most frequently when flags other than the default are needed.

Two examples: https://github.com/Homebrew/homebrew-science/issues/3454 https://github.com/Homebrew/homebrew-science/pull/3512

Perhaps there is a way to avoid these problems that also meets the crucial criteria of the homebrew team being willing to put it into practice? :-)

Thanks for your thoughts and considering my request!

tdsmith commented 8 years ago

In particular, this happens most frequently when flags other than the default are needed.

This is a difficult problem because we are unable to test all combinations of all formulas with all options. Formulas with fewer options are inherently less fragile.

We do test the default configurations where we have tests available. If the broken formulas lack tests, adding a test do... block that tests the default configuration will help the bot detect breakage and prevent us from pulling an incompatible update in the future.

Of course, we do not want to break packages. I think there is not a generic solution but maybe you have some ideas.

ilovezfs commented 8 years ago

As peculiar as it may sound, this is basically by design. If you need something more stable, I'd suggest maintaining your own tap with the working set of tools pegged to a particular version and backing up older bottles and build-time resources that you need. brew deps and brew uses --recursive are the commands to understand the scope of what you'd need to capture in such a tap.

Note that you can also generate your own bottles using install --build-bottle + brew bottle. The test-bot command is helpful for this, but it roughly does this:

#!/bin/bash
set -e
brew fetch --retry --build-bottle "$1"
brew install --only-dependencies --verbose --build-bottle "$1"
brew install --verbose --build-bottle "$1"
set +e
brew audit "$1" --strict --online
brew style "$1"
set -e
brew bottle --verbose --rb "$1"
brew bottle --merge --write --no-commit "./$1"*rb
brew uninstall -f "$1"
brew install "./$1"*gz
mv "$1"*gz ~/Library/Caches/Homebrew

Cf. https://github.com/Homebrew/homebrew-bundle/issues/162

(Another option is to use virtualization and/or file-system level snapshotting).

apjanke commented 8 years ago

Yeah, adding tests will help in some cases. And minimizing options in the first place will help.

TL;DR: Add test do blocks to all your formulae so test-bot can test them when upgrading their dependencies. Probably would have caught those two breaks before they happened.

Details:

The Homebrew/homebrew-science#3454 ceres-solver breakage looks like it was a dylib version linkage problem. It didn't get caught first time around because ceres-solver did not have a test do. Adding a test do, even a basic one like --version, will generally be enough to catch dylib linkage problems for direct dependencies, because when we test a version bump PR, that includes testing all the bottled dependents. (At least the ones with direct dependencies.) For this kind of breakage, making sure all formulae have tests, even if they're trivial tests, will help. brew audit now requires a test do for all formulae, so we're making progress there.

Homebrew/homebrew-science#3512 PCL doesn't have a test do in its formula either, so it's not being tested when its dependencies change. I can't tell from skimming that issue whether it was a dylib version problem or a different issue, but a test might have caught it.

I'm not sure what happens in test_bot when testing a PR for foo and there's a testable bar with depends_on "foo" => "with-some-option". For dependencies besides the tested formula, it will install the dependency with the option. But I'm not sure what happens when the dep is the formula under test. It probably should reinstall it with that option. It probably doesn't do it 100% correctly now: our dependency resolution isn't perfect, and it's something we're working to get more robust in the longer term.

The full solution of testing all combinations of options is prohibitively expensive. (Testing N options takes N or 2^N runs, depending on how thorough you are.)

We could do a smaller version where we just test the option combinations which are used in depends_on xxx => "with_foo" dependencies in the central taps. (And maybe we effectively already do, depending on how the test-bot dependency resolution works.) But even that could blow up the test bots: we're already pushing run time limits with larger formulae that have lots of dependencies, and that's where some of the dependencies with options happen. Could be helped by having test-bot examine the dependents and consolidate dependents that need options toward the end of the list, to avoid redundant builds. Calculating that could get hairy once indirect dependencies are considered, though.

We could also detect dylib versions by interrogating files with otool and storing that data for bottles some where. But that would introduce a whole new data set for brew to manage, and would be mostly redundant with test do blocks, which we want to encourage, and which can test more things. So I don't think we'd want to do that.

The best immediate approach is probably "make sure all your formulae have tests". I think that would have caught both of the issues you're referencing here.

Open to other suggestions. I may have blinders on for this issue since I'm so used to looking at test-bot throughput limitations.

(EDIT: Note that this ability is currently limited because we're not using uses --recursive in the test-bot, for performance reasons. Open issue for it: https://github.com/Homebrew/legacy-homebrew/issues/50256)

ahundt commented 8 years ago

@tdsmith Maybe it is possible to compile once with the default and once with all options? I know mutually exclusive options won't work there but it would probably eliminate a large subset of these problems while only requiring 1 additional continuous integration run.

ilovezfs commented 8 years ago

A pull request implementing a brew install --maximal-configuration foo would be welcome for review.

ilovezfs commented 8 years ago

@ahundt FYI https://github.com/Homebrew/legacy-homebrew/issues/49338#issuecomment-186445849 ;)

tdsmith commented 8 years ago

A pull request implementing a brew install --maximal-configuration foo would be welcome for review.

Would it? What's the goal here?

Reopened for now; I'm not sure the discussion is over.

tdsmith commented 8 years ago

while only requiring 1 additional continuous integration run.

I'm not sure doubling the CI load is acceptable either; we haven't found a way to move our CI infrastructure into the cloud and right now we aren't willing to invest in the capital costs or the maintenance burden of additional CI builders.

We could maybe do some heuristics about whether it looks like dylib names are likely to change, but that seems less effective than adding tests to formulas.

apjanke commented 8 years ago

We would probably have to alter the formula DSL to express option conflicts, too, or explicitly specify maximal_configuration option sets in the formulae themselves. IIRC, there's a lot of formulae that have options or optional dependencies which conflict with each other (like openssl vs libressl). I'm not sure we'd want to do that.

ahundt commented 8 years ago

maybe an "advanced build" command where the rb script can specify a second reasonable configuration to test with extra options set? that's something I'd know enough to submit pull requests for as an occasional, very minor contributor.

@ilovezfs thanks for the link, I'd be sad if there were no options because I'd be back to compiling from source all the time... I'm using stuff like cuda and image tools. For one data point I'd say about 90% of the time I type brew install commands I run use options. I'm probably an unusual case though.

DomT4 commented 8 years ago

Maybe it is possible to compile once with the default and once with all options?

There's no way this'll fly on Homebrew's current CI.

It's an outlier of an example but doing Qt5 per option (which is the safest way to handle possible conflicting options) would take ~10 hours of CI time, per VM. You're talking 30 hours of CI time total, for a single formula. Doing it in a way where we do an initial run and then one with all options afterwards (potentially risking conflict) would still take upwards of ~4 hours per CI time, per VM.

Short of someone providing Homebrew with a large enough donation to enable us to buy, run and maintain a significantly larger number of hardware we're limited on what we can reasonably do. We tried to farm out some of the CI load to Travis but have ended up pulling back on that for various reasons. CI is a struggle for us, and it chews up a lot of hours behind-the-scenes keeping it going.

The answer here to an extent is that people need to be careful which options are added and only add the ones with community demand and value. Every option added to an official Homebrew formulae is more or less a promise that we'll keep trying to support it, as removing options is always treated as more or less a last resort.

brew install --maximal-configuration foo

For reasons stated by others above and myself before I still consider this a "fairly bad idea" :tm:.

sjackman commented 8 years ago

Options in Homebrew are inherently fragile, because the options are not tested by the test-bot, and the interactions between options are definitely not tested. Using any option basically puts you into uncharted territory. Within the current constraints of not testing options with CI, the only way to avoid breakage is not to use options. If there's an option that you use frequently, you could open an issue to discuss making that option the default behaviour for the formula, and then it would be tested by CI.

DomT4 commented 8 years ago

Increasingly we're asking the question if an option is popular/valuable enough to merit inclusion is it popular/valuable to merit being turned on by default. We haven't come up with a firm answer yet, but on a case-by-case basis it's worth considering.

ahundt commented 8 years ago

@sjackman an "advanced build" option could reduce the surface area that is untested while keeping compile times lower than the full combinatorial explosion of possibilities.

Or, perhaps more options should generally be included by default? I'd be happy to add some pull requests adding :recommended tags (or stronger) to the options I personally require that aren't on by default in homebrew-science and other formulae repositories for libraries like opencv, vtk, pcl, eigen, etc. Just have to look up how to do it right... wish I knew ruby better... :-)

MikeMcQuaid commented 8 years ago

Options in Homebrew are inherently fragile, because the options are not tested by the test-bot, and the interactions between options are definitely not tested. Using any option basically puts you into uncharted territory.

This is a good point and options should be considered "dangerous" by anyone relying on CI to test formulae. Really, I think on most formulae they add more user pain than they save...

CC @Homebrew/science here. This is a reason brew audit demands a test do block. Without one our reverse dependency testing is ineffective.

Or, perhaps more options should generally be included by default?

I'm fairly keen to keep formulae's default options on the stuff that's useful for 99% of users. This has made me remember that we should be making a note with our new analytics of what options are used for formulae so we can consider making commonly used ones the default.

Just have to look up how to do it right... wish I knew ruby better... :-)

You'll learn. I'd never used Ruby before I started working on Homebrew and now I do Ruby development full-time :wink:

sjackman commented 8 years ago

CC @Homebrew/science here. This is a reason brew audit demands a test do block. Without one our reverse dependency testing is ineffective.

Yes tests are absolutely necessary. We insist on tests now, but there was a time when we didn't, and some formula haven't changed since that time. Currently 486/581 (84%) have tests.

You'll learn. I'd never used Ruby before I started working on Homebrew

Ditto. Homebrew was my first Ruby project as well. I quite like the language now.

sjackman commented 8 years ago

Without one our reverse dependency testing is ineffective.

I've wondered how this system works. If app depends on lib, and lib is rebuilt changing the dylib name (or soname), and the test-bot notices that app breaks, does it build a new bottle for app, and does brew pull --bottle change the bottles for both lib and app? I haven't seen breakage due to a changed dylib name in a long time, so I figured that there's some system in place, but I've never seen brew pull --bottle modify more than just the one formula being updated.

UniqMartin commented 8 years ago

@sjackman A classic example are poppler version bumps (e.g. Homebrew/legacy-homebrew#50249). Tests for some dependent formulae will fail and if the OP doesn't notice/understand themselves, we ask them to bump the revision of those formulae, so they are rebuilt and rebottled in the same PR job.

sjackman commented 8 years ago

Ah, I see. Thanks for the explanation. Do you modify the original formula and bump the revisions of the dependents all in the same commit?

DomT4 commented 8 years ago

Ideally one commit per formula bump, with the following style:

* poppler 0.43.0
* diff-pdf: revision for poppler
* pdf2htmlex: revision for poppler

And so on.

UniqMartin commented 8 years ago

Do you modify the original formula and bump the revisions of the dependents all in the same commit?

We haven't been entirely consistent with this, unfortunately. I think the preferred style is to have one commit per formula. But you can also find instances of

in the recent Git history of homebrew/core.

apjanke commented 8 years ago

@ahundt: Do you have a list of some other homebrew/science formulae which tend to get broken dependencies like this? It would be helpful to have some more concrete examples. And we can make sure they are testable so they're at least taking advantage of Homebrew's current QA process.

skystrife commented 8 years ago

I think icu4c is another concrete example of this problem. See https://github.com/Homebrew/homebrew-php/issues/2544#issuecomment-208770839.

While having the repos all separate is great for separation of concerns, it's a little troublesome to update a library in homebrew-core that other formulae depend on from other repos. In icu4c's case, we've got dependants in homebrew-php, homebrew-games, homebrew-science, and homebrew-core itself.

Dealing with the homebrew-core dependents is easy: just ensure that the pull request also includes revision bumps for all formulae that have an explicit (non-optional) dependency on icu4c. The existing CI infrastructure works well to tell you if there are any formulae in homebrew-core that need to have their bottles remade (provided, of course, that they actually have tests defined. Let's just assume that they do for now, for the sake of argument).

Dealing with the other repos' dependents is harder. CI currently does detect the breaks in the other repos. While we can immediately issue pull requests on those repos as soon as the version bump is merged into homebrew-core (which is what I did for this icu4c version bump), there is still going to be a window of time where packages in the other repos will break because of the dependency version bump. (Only one of the two pull requests in the other repos has merged, so currently there are packages that are broken because they are looking for the older version of icu4c in homebrew-games and homebrew-science.)

I'm not sure about the right way to solve this. One option would be to somehow coordinate between the repositories so that all of the library-version-bump-related PRs are merged (nearly) simultaneously. This seems hard to do, though, since unpredictable CI errors do happen (something builds just fine locally, but not on e.g. a different OS X version).

Another option would be to have the formulae that list icu4c as a dependency specify a specific version that they target. In theory, couldn't brew then detect that installing a newer icu4c formula would break the existing formulae installed on the system that have not been updated and either spout a warning or just outright bail from updating icu4c until they're ready?

apjanke commented 8 years ago

Do you modify the original formula and bump the revisions of the dependents all in the same commit?

The one vs many commits is more a stylistic issue. Regardless of how many commits it is, for formulae in the same repo, all the commits go in the same PR which is merged all-or-nothing, so they're tested and committed as a unit, and users will pick up all of them in the same brew update, so there's never a time when they are out of sync for a given Homebrew installation.

But for formulae in separate repos, skystrife hits it on the head:

While having the repos all separate is great for separation of concerns, it's a little troublesome to update a library in homebrew-core that other formulae depend on from other repos. ... While we can immediately issue pull requests on those repos as soon as the version bump is merged into homebrew-core (which is what I did for this icu4c version bump), there is still going to be a window of time where packages in the other repos will break because of the dependency version bump.

We do not have a mechanism for linking PRs across repos, testing them together, or making sure they get tested and deployed as a unit. So the current mechanism actually requires this out-of-sync window, because the PRs for dependent repos' revision bumps can only be run in CI after the version bumps to core are committed. (And since we only have 1 CI worker per OS, the PRs for multiple dependent repos must be serialized. And wait on whatever other CI traffic there is.)

I don't know how to solve this currently. I think that "coordinating between repositories" would require adding some special metadata in PRs that brew test-bot recognized and used to test them as a unit. It's brew test-bot, not Jenkins code, which pulls in the formula PRs for testing, so that could be solved with custom Homebrew code, and not require GitHub and Jenkins to support it. But probably non-trivial.

The limiting factor here is again whether the dependent formulae are testable. Right now, that means having a test do block. So, no test do in a dependent, and we still wouldn't see the breakage reported in a test run. I don't have numbers to support this, but I suspect most of the dependency-version breakage users are actually encountering is probably due to lack of testing, and not repo synchronization. The sync breakage window is maybe an hour or so. The breakage window for a dependent that is missing a test do is until somebody notices it's broken, reports it, and gets a revision-bump PR through. And unfortunately it's probably going to be a user noticing it in this case.

Perhaps we could also add an automatic dylib linkage test that's done regardless of whether a test do is defined: install the dependents, check each with brew linkage, and consider broken link references to be a test failure. That way all dependents, regardless of their test do definition, will have dylib version breakage caught.

Both of these are probably necessary to get to a "perfect" state, because they're independent sources of possible breakage. (Plus restoring --recursive to brew uses, or flattening all dependencies.)

couldn't brew then detect that installing a newer icu4c formula would break the existing formulae

IIRC, brew actually does something like this already: it checks for dylib linkage, and if the old version of icu4c has installed formulae still linked to its versioned dylibs, it will not be uninstalled until those dependencies have been uninstalled. For Homebrew installations with existing formula installations, it's the removal of the old version, not installation of the new version, which breaks things.

This doesn't cover the case of keeping bottles in sync, though: the case that breaks more often is when you do a fresh installation of icu4c and some dependents. In that case, there's no prior installation for brew to hold on to for dylib linkage reasons.

apjanke commented 8 years ago

That brings up another question, @ahundt: Are you seeing this breakage on systems where these things are already installed, and a brew upgrade breaks them? Or is it that sometimes when newly installing things, you end up with a broken installation?

derrabus commented 8 years ago

@apjanke: If on a fresh system after the icu4c 56.1 bump you installed the php56-intl package, you received icu4c 56.1 as well as binaries for php56-intl that were compiled against icu4c 55.1. So yes, this applies to newly installing packages as well.

MikeMcQuaid commented 8 years ago

Just a heads up that I have a massive brain dump that's going to be coming here when I write it up. Basically: yes, this is a problem and I have a proposed solution.

ahundt commented 8 years ago

@apjanke it is a combination of both. let me think... I can't guarantee the examples will be perfect.

Another particular case is opencv2 vs opencv3 & 3.1. opencv3.1 broke with certain flags, I don't think that one was the fault of any dependencies, there it may have been best to stay on 3.0 until the bug was fixed (it is now working). https://github.com/Homebrew/homebrew-science/issues/3147 Thank goodness 2 and 3 have been permitted to both live in homebrew-science because I need to switch between them frequently.

Testing of features based on flags could potentially broadly fit as well, though that gets a bit more complex. Here a python flag broke for opencv: https://github.com/Homebrew/homebrew-science/issues/2724

There are a few similar examples in linuxbrew, but based on earlier discussions that's considered out of scope here so I won't elaborate on those items.

We do not have a mechanism for linking PRs across repos, testing them together, or making sure they get tested and deployed as a unit. So the current mechanism actually requires this out-of-sync window, because the PRs for dependent repos' revision bumps can only be run in CI after the version bumps to core are committed.

I've encountered this myself and I've had to manually update or revert some repos that I've written my own build scripts for in https://github.com/ahundt/homebrew-robotics.

@mikemcquaid looking forward to that proposed solution! awesome to hear!

MikeMcQuaid commented 8 years ago

I need to write up a better in-depth proposal but here's what I propose we start doing moving forward:

  1. @DomT4's cleanup of homebrew/versions goes ahead as planned but we do not accept new versions in there for any versions above those in core.
  2. We start moving libraries and system services (not end-user applications or GUIs) that have dependencies in the Homebrew org to being versioned on the next major upgrade. e.g. if Boost 2.0.0 was released tomorrow we'd make a boost20 formula which is merged immediately without trying to upgrade anything that depends on boost.
  3. After the boost20 formula is merged in a PR we create a new PR to change all dependencies that can be build with boost20 from boost and rename the old formula to boost15 and give boost20 the boost alias. Anything that doesn't build with boost20 doesn't block anything.
  4. At some point in the future if we're on e.g. boost30 or something (having had boost21 etc.) we remove any formulae from core that still depend on boost15 and remove the boost15 formula itself.

This may need to have some revisions applied and bottles rebuild in the process but this would be no worse than any current upgrade and paves the way for this to be smoother in future. This also may not even need to be done depending on how the linkage is found at runtime.

Thoughts? Concerns? I realise this is a big departure from Homebrew's previous versions support but I think we need to change due to concerns raised in this issue and also to provide people who want it a more stable base to say things like e.g. install mysql 5.7 today but don't upgrade anyone to mysql 5.8 in Brewfiles.

apjanke commented 8 years ago

Appealing.

Versioning the widely-used libraries in core like this is probably a good idea. That would fix the cross-tap decoupling synchronization issue without requiring special GitHub/Jenkins customization. And even within a single repo, it would be useful: by avoiding a single "flag day" where everything has to cut over, it would let the upgrades happen without waiting for every last dependent to fix compatibility, so users get the new stuff without waiting for the slowest-dev-cycle application. And it would let us safely split the revision bump updates in to multiple PRs, avoiding the worst 5+ hour test-bot scenarios, and making it easier for multiple authors to work on doing the upgrade work and testing (since PRs have a single owner/committer).

I think I like this versioning approach better than my idea of adding cross-PR linkage at the PR and test-bot level, because it builds the associations in to Homebrew and formulae themselves, and not just into the GitHub-based workflow. Plus it would support a wider range of valid configurations.

Some packages are kind of both libraries and applications, in that they install commands. mysql is probably a good example. How would we handle side-by-side installation of those? Would we version-suffix the command names, and switch those around when the new mysql58 becomes the "main" installation? To do this, I think we do need to support side-by-side installation of all versioned variants of a formula.

A big change here is that these formulae would only be available in versioned variants, instead of having the versioned variants supplement a main formula. For users who install it for direct use, e.g. with brew install mysql, a brew upgrade might no longer get them to the latest version of it, because that mysql would have been resolved to mysql57 at install time. And that's probably not what they want. (That is, we need to remember whether the user did brew install mysql or brew install mysql57, and alias resolution lose that info, I think.)

"Virtual packages" – a formula that contains no files itself, but just has dependencies or references – might fix this. (I think you and some other people have been down on them before as added complexity, but with this versioning change, the complexity might be justified.) mysql could be a virtual package instead of an alias, and it would contain or depends_on the "current" versioned mysqlNN variant. That way formulae could specify exact version dependencies, but a user could still request "the latest mysql" and have that keep up with new versioned formulae across update; upgrade cycles.

Can a formula depend_on an alias? We should preserve back-compatibility for formulae in taps outside the central Homebrew ones.

ahundt commented 8 years ago

In addition to myself I've encountered a few others that have run into these exact problems. Perhaps adding multi revision support in single formulae, and particularly revision dependencies directly to formulas would help?

Forgiving my likely botched ruby syntax but perhaps versions could be something like the following or another cleaner idea:

url "https://github.com/jhu-cisst/cisst.git", :tag => "1.0.5", :version => "1.0.5"
url1.0 "https://github.com/jhu-cisst/cisst.git", :tag => "1.0", :version => "1.0.0"

dependencies could be something like:

depends_on "opencv",  :version => "3.0"

I generally deal with packages with a deep tree of dependencies which substantially increases the chance of breakage as it grows. I sometimes also work with ROS (robot operating system) infrastructure which is in the same situation. I personally know at least 4 other users that have encountered these issues and I've seen posts from several others I don't personally know.

Here are some posts where these types of problems are mentioned by others:

VTK dep issue that led to this feature request

This is the original issue for which I created this, there are comments about this type of problem by other users there.

Breakage when tools depend on brew (rosdep)

I created the issue, other brew users are discussing how to handle the problem. For example:

@mikepurvis april 17 I'm curious to know how other rolling release systems like Arch deal with this kind of thing. Homebrew is always on the bleeding edge of everything, which basically guarantees some level of brokenness, at least as far as new major versions with breaking changes. That said, a disproportionate number of issues seem to come up around bottled versions of things with baked-in paths to their dependencies, that then go stale when those depended-upon packages move on— we've seen this with Python, VTK, PCL, CMake, and probably others. It feels like there's a fundamental issue there with Homebrew, that it should have a dependency type for "rebottle when this dependency updates".

In any case, if upstream Homebrew can't commit to a satisfactory level of stability across the rather large base of packages upon which we depend, then I don't see much choice other than to maintain at least some of the system in taps. Random assorted thoughts on this direction:

Unless we are also committed to bottling stuff, the cost to users of building big dependencies like VTK and PCL locally is pretty big— my understanding is that bottling is not hard; you just have to set it up. Certain dependencies (CMake, Python) are not realistic to maintain in taps due to the weirdness of a system with multiple versions of these things. It may be possible to build some light Travis-based tooling to provide notification of upstream formulae changes, so we can hopefully mitigate a bit the issue of stuff falling behind. Maintaining any of this will be nightmare without regular CI builds across the supported OS X versions. I know OSRF had expressed interest last year in doing some of this around ros-install-osx, but it didn't seem to go anywhere.

comments on duplicating formula for versions

Steven Peters Apr 16

It is indeed very easy to set up your own homebrew tap to host your own custom packages. It's harder, though, to put version constraints on dependencies (for example requiring an older version of boost). The current state-of-the-art is to create a duplicate formula (such as boost155) typically hosted in homebrew/duplicates. I don't consider this a very elegant approach.

It is true that they allow customizable builds (such as --double-precision for bullet), and you can declare these options in your dependencies (gazebo requests that double-precision option from bullet). Using that example, if you install gazebo and bullet is not installed, it will install bullet with the requested options. If bullet is already installed, however, it won't check whether it has the desired configuration, it will just use it.

Don't get me wrong, I love homebrew very much. I just think it's not trying to do everything that debian does, so it's important to keep that in mind.

apjanke commented 8 years ago

We can also have test-bot do dylib checking now for all dependents, not just those with a test do block. Here's a PR to do so: #107.

In my test run, it catches breakage in some of icu4c's dependents which was not previously caught. If we merge this, that will show up at the PR stage, before it gets pushed.


==> brew linkage --test gptfdisk
No broken dylib links
==> brew linkage --test libfolia
Broken dylib links:
  /usr/local/opt/icu4c/lib/libicudata.56.1.dylib
  /usr/local/opt/icu4c/lib/libicui18n.56.dylib
  /usr/local/opt/icu4c/lib/libicuio.56.dylib
  /usr/local/opt/icu4c/lib/libicuuc.56.dylib
==> FAILED
==> brew linkage --test ucto
Broken dylib links:
  /usr/local/opt/icu4c/lib/libicudata.56.1.dylib
  /usr/local/opt/icu4c/lib/libicui18n.56.dylib
  /usr/local/opt/icu4c/lib/libicuio.56.dylib
  /usr/local/opt/icu4c/lib/libicuuc.56.dylib
==> FAILED

It doesn't fix the cross-repo decoupling: bumping the revisions for these would need to be manually managed across multiple PRs. And the main PR would never go green. But at least now you'd have a programmatically-generated list of the things to bump, and it doesn't require test do to make it happen.

Indirect dependencies across taps are also not picked up since we're not doing uses --recursive, and the main PR would never be iterated to include the revision bumps for the intermediate dependencies. Most indirect dependencies within a single repo will be caught, because iterating the PR to revision bump direct dependents with breakage will expand the list of formulae changed in the PR, pulling in more formulae as direct dependents of the PR's scope, and eventually getting to a transitive closure, as long as each formulae actually has a dylib link to its dependent, not just a command other runtime dependency.

MikeMcQuaid commented 8 years ago

Sorry, submitted too early:

Some packages are kind of both libraries and applications, in that they install commands. mysql is probably a good example. How would we handle side-by-side installation of those? Would we version-suffix the command names, and switch those around when the new mysql58 becomes the "main" installation? To do this, I think we do need to support side-by-side installation of all versioned variants of a formula.

I think side-by-side installation is a good goal but keg-only gives us a bit of a hammer we can use to get around it in the short-term. For mysql I see us suffixing the binary names and data directories but probably not e.g. ports.

A big change here is that these formulae would only be available in versioned variants, instead of having the versioned variants supplement a main formula. For users who install it for direct use, e.g. with brew install mysql, a brew upgrade might no longer get them to the latest version of it, because that mysql would have been resolved to mysql57 at install time. And that's probably not what they want. (That is, we need to remember whether the user did brew install mysql or brew install mysql57, and alias resolution lose that info, I think.)

Yeh, we'd probably need a little be of syntactical sugar on top to handle this case of someone installing from an alias. I think this is the only code that needs to be written.

"Virtual packages" – a formula that contains no files itself, but just has dependencies or references – might fix this. (I think you and some other people have been down on them before as added complexity, but with this versioning change, the complexity might be justified.) mysql could be a virtual package instead of an alias, and it would contain or depends_on the "current" versioned mysqlNN variant. That way formulae could specify exact version dependencies, but a user could still request "the latest mysql" and have that keep up with new versioned formulae across update; upgrade cycles.

I kinda hate virtual packages. They smell bad to me as a formula file but we could introduce some other element that would allow them. Aliases may be sufficient here but we may want to introduce another directory for versioned aliases whilst relying on the same underlying technology.

Can a formula depend_on an alias? We should preserve back-compatibility for formulae in taps outside the central Homebrew ones.

They can, yes.

In any case, if upstream Homebrew can't commit to a satisfactory level of stability across the rather large base of packages upon which we depend, then I don't see much choice other than to maintain at least some of the system in taps.

@mikepurvis Homebrew relies on internal and upstream stability rather than external stability. That is:

We do not provided guaranteed stable versions so you can "sit on" a particular version of software indefinitely. I'd like us to provide that guarantee for libraries and some service applications based on major/minor (not patch) semver but if you need more than that: I doubt we'll be able to provide it, unfortunately.

If bullet is already installed, however, it won't check whether it has the desired configuration, it will just use it.

@scpeters This is not accurate, it will be reinstalled if you request an option it was not built with.

mikepurvis commented 8 years ago

There's one issue I've seen a number of times that I'm not certain would be caught by additional homebrew test blocks— that is where a build captures paths from things on which it depends, and then generates a pkgconfig or CMake find module with those paths baked-in. All of this works at build/test time; the errors emerge when something then depends on that package, and finds that paths from the supplied XX_INCLUDE_DIRS variables (or whatever) no longer exist. Example:

https://github.com/PointCloudLibrary/pcl/blob/456bf44205ac7ed09b2754e2062d08a1a7fbed53/PCLConfig.cmake.in#L457

On the one hand, this is an anti-pattern, and the ideal would be to fix it upstream, but I feel like it happens often enough with ROS stuff that it would be great if Homebrew had some way of stamping bottles for "I have a hard dependency on ver X of Y; when that changes, I need to have my revision bumped."

scpeters commented 8 years ago

@mikemcquaid you are right! I apologize for the uninformed statement. I thought I had seen that behavior once in the past, but it obviously works correctly now, which is the important thing.

I just tested a corollary though: if I install bullet --with-double-precision and then install efl (which depends on bullet without specified options), it will not complain. I don't know if it is actually a problem in this case, but I imagine it could be (which is often the hallmark of a distraction, not an important problem). I just wanted to mention it.

MikeMcQuaid commented 8 years ago

@mikepurvis That would be broken with a sufficient test do as that's going to be testing the new version without the old one present for all bottled reverse dependencies.

scpeters commented 8 years ago

Regarding @mikemcquaid's proposal about versioning, it would be nice to see versioning become an official feature and not just part of a tap. I've maintained multiple versions of several formulae in the osrf/simulation tap for a while, so here's some thoughts I've built up:

MikeMcQuaid commented 8 years ago

If there is a downstream package that can handle multiple versions of a formula, how could this be encoded (minimum version, maximum version, list of valid versions)?

I explicitly don't think we should support this. It's got no real purpose and makes it incredibly hard to support software.

Should versioned formulae inherit from a parent formula? Could this reduce duplicate formula code?

This would reduce duplication but would massively hard readability. I think it's worth just duplicating formulae for versions, as we do currently.

scpeters commented 8 years ago

Let me say it a different way then: if you have a formula that can handle multiple versions of a dependency (say anything greater than boost 1.53) and there is another package that requires a specific version (say boost 1.55), would they fight over which version of boost to install? Boost 1.55 is fine for both, but there's currently no way to encode that.

MikeMcQuaid commented 8 years ago

@scpeters In that case the formula should depend on whatever is the newest version of boost it supports.

scpeters commented 8 years ago

@mikemcquaid thanks for the clarification. What would happen when trying to install both packages at the same time? Would the older version of boost be installed as keg-only for the sake of the package that requires it?

Also, if there was a 3rd package that depended on packages that have varying support for the latest versions of boost, they might run into a conflict.

A: depends on boost (latest)
B: depends on boost155 (newest feasible version)
C: depends on A, B

Would the same version of boost be used when installing C? Seems like if the boost versions are different that there could be linking errors.

MikeMcQuaid commented 8 years ago

@scpeters They would both be installed either side-by-side or keg-only depending on conflicts and if we're able to resolve them.

In that case it'd be up to the author of C to resolve the issue. Currently that formula would not work at all or have the same problem with homebrew/versions. I'd rather focus on known issues with formulae rather than speculative issues; we can always iterated and improve any approach we take.

sjackman commented 8 years ago

Since boost155 is installed :keg_only, I don't seen any problem with the above situation. C shouldn't see an issue. It has no direct dependency on boost.

scpeters commented 8 years ago

Thanks for the responses. I spend a big part of my time maintaining software for Ubuntu/debian, which define distributions with specific versions of each package, which is a different model than homebrew, which generally uses the latest version of everything. In my head, the example about packages A,B,C seemed relevant, but maybe it's more relevant to the Ubuntu/debian model.

I'll see if I can think of a concrete example for which it would impact homebrew.

MikeMcQuaid commented 8 years ago

@scpeters At this point it's probably worth critiquing implementations rather than theory so let's hold off until we have that.

ahundt commented 8 years ago

Sorry for my lack of responses, I've been traveling the last two weeks. I like the ideas that have been discussed, is there something that can be put into effect?

@mikemcquaid I've actually encountered the "theoretical" problem mentioned by @scpeters in real use cases. Particularly with libraries that support OpenCV 2.x vs OpenCV 3.x, vtk 5 vs 6 and 6 vs 7, Qt 4.x vs Qt 5.x, and a variety of boost versions, particularly around the introduction of new boost libraries (boost.geometry in my case) between 1.4x and 1.5x, and compilation of libraries during the transition between cmake versions 2.8.x with x<10 to 3.x. I don't think this situation is uncommon as different libraries take different amounts of time to support newer versions of their dependencies, particularly widely used ones.

Here is a specific comment on the effects of version transitions from another user: https://github.com/PointCloudLibrary/pcl/issues/1563#issuecomment-208452435

ahundt commented 8 years ago

Regarding https://github.com/Homebrew/brew/issues/62 where I'd sometimes like to remap dependencies, could there be a command line command like brew install mypackage --override-dep boost ahundt/boost155. If we can't automate the solution perhaps something like that may allow workarounds to be effective with less effort.

apjanke commented 8 years ago

A couple of the smaller ones are now in effect.

Don't know about the others. And this thread has gotten long enough that I've kind of lost track of all the items which were proposed.

MikeMcQuaid commented 8 years ago

Regarding #62 where I'd sometimes like to remap dependencies, could there be a command line command like brew install mypackage --override-dep boost ahundt/boost155. If we can't automate the solution perhaps something like that may allow workarounds to be effective with less effort.

I can 100% guarantee we won't support something like that that will make things extremely hard to debug.

I don't think this situation is uncommon as different libraries take different amounts of time to support newer versions of their dependencies, particularly widely used ones.

I think in this case it's on the packager to avoid such conflicts.

ahundt commented 8 years ago

@apjanke I tried to add a summary of proposals and additional problems to the very top of this. here is the tl;dr

Key problems

  1. Package A is updated from 1.0 to 2.0, breaking package B which depends on A 1.0.
    • breakage is not always detected
      • Example: ceres depends on an exact version of eigen (a simple bottle refresh could fix it)
    • breakage can't always be fixed
      • B may not support A 2.0 until its next release a month from now
  2. Package B,C depend on different versions of package A. Package D depends on both B, C and sometimes A. (other variations on this theme exist)
  3. Options sometimes break invisibly
  4. Cross tap dependencies may change

Solutions with most backing

These are reasonably easy, well liked ideas that will mitigate (but may not completely solve) the above