Skip to content

formula: add rpath helper method#11187

Merged
carlocab merged 1 commit intoHomebrew:masterfrom
carlocab:cmake-rpath
Apr 20, 2021
Merged

formula: add rpath helper method#11187
carlocab merged 1 commit intoHomebrew:masterfrom
carlocab:cmake-rpath

Conversation

@carlocab
Copy link
Copy Markdown
Member

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes? Here's an example.
  • Have you successfully run brew style with your changes locally?
  • Have you successfully run brew typecheck with your changes locally?
  • Have you successfully run brew tests with your changes locally?

About 40 formulae set CMAKE_INSTALL_RPATH to lib or opt_lib, but
this breaks bottle relocatability.

The correct solution is to use @loader_path/../lib, but this is macOS
specific, so it requires some OS-specific logic. Rather than replicating
this logic over many formulae, we may as well define a helper method for
it.

See Homebrew/homebrew-core#75458.

About 40 formulae set `CMAKE_INSTALL_RPATH` to `lib` or `opt_lib`, but
this breaks bottle relocatability.

The correct solution is to use `@loader_path/../lib`, but this is macOS
specific, so it requires some OS-specific logic. Rather than replicating
this logic over many formulae, we may as well define a helper method for
it.

See Homebrew/homebrew-core#75458.
@BrewTestBot
Copy link
Copy Markdown
Contributor

Review period will end on 2021-04-20 at 08:30:16 UTC.

@iMichka
Copy link
Copy Markdown
Member

iMichka commented Apr 19, 2021

Is this cmake specific or more general? If it's cmake specific I would like to propose to use cmake_rpath. Else rpath is fine.

@carlocab
Copy link
Copy Markdown
Member Author

It's likely to be used with cmake the most, but it could be used more generally (say, passed to the linker).

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me but can you define a def rpath in formula.rb (either with the Linux or macOS version) so it'll work on a "generic" OS.

@carlocab
Copy link
Copy Markdown
Member Author

There is a def rpath in formula.rb 😅

@MikeMcQuaid
Copy link
Copy Markdown
Member

There is a def rpath in formula.rb 😅

I cannot read 🤦🏻. :shipit: !

@carlocab carlocab enabled auto-merge April 19, 2021 11:58
@BrewTestBot BrewTestBot removed the waiting for feedback Merging is blocked until sufficient time has passed for review label Apr 20, 2021
@BrewTestBot
Copy link
Copy Markdown
Contributor

Review period ended.

@carlocab carlocab merged commit d89eda0 into Homebrew:master Apr 20, 2021
@carlocab carlocab deleted the cmake-rpath branch April 20, 2021 09:08
@sjackman
Copy link
Copy Markdown
Contributor

I didn't see this PR until now. For the future, I'm very interested in getting a review request on PRs that affect Linux bottles and their ability to be relocated.

end

def rpath
"'$ORIGIN/../lib'"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The single quotes here seem very suspicious to me. I've verified that the RPATH of abseil does get set correctly to $ORIGIN/../lib without single quotes, but I strongly suspect that it should be up to the caller to add the single quotes when needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop it if it's not needed. I was just concerned about the shell eating the $, so I put that in there. I don't quite know how much the shell interprets things when you make system calls in the install method, however.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form system "foo bar baz" does go through the shell and shell expansion, but the preferred form system "foo", "bar", "baz" does not go through the shell and shell expansion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it hurt though? I think waiting for the caller to make sure the $ is not interpreted by the shell is a footgun. If there's a reason to give callers that, then we can do so, but I'm not sure there is.

@sjackman
Copy link
Copy Markdown
Contributor

An RPATH of $ORIGIN/../lib is only correct for files installed in a top-level directory such as bin or libexec. rpath should take two arguments: the location of the executable (or perhaps its directory), the desired library directory, and compute the correct relative path from the executable's directory to that of the library.

@sjackman
Copy link
Copy Markdown
Contributor

Thank you for this work, Carlo. I like the general idea. Here's an alternative. keg_relocate rather than replacing the absolute path #{HOMEBREW_PREFIX} with @@HOMEBREW_PREFX@@ could instead compute the relative path from the executable to each library directory specified in RPATH, and replace each element of RPATH with $ORIGIN and a relative path to the library directory.

@sjackman
Copy link
Copy Markdown
Contributor

For the rpath API, I'd suggest something like lib.rpath bin for executables that are being installed in bin and are looking for libraries installed in lib.

@carlocab
Copy link
Copy Markdown
Member Author

Thanks for the comments, @sjackman! I'll remember to request a review from you in the future for PRs like this.

While I do like the added flexibility you suggest, I don't see where it could be used at the moment. This PR was designed to make it easy to pass a value to CMAKE_INSTALL_RPATH, which doesn't really give you much control over what it's applied to.

I was contemplating a slightly less sophisticated change to this, which, with a little bit more verbosity might let you do something similar to what you suggest, I think: Homebrew/homebrew-core#75566 (comment)

Note that this value of RPATH isn't necessarily just for executables looking for libraries: it's also for libraries looking for other libraries. (See the llvm formula for an instance where this is needed.)

@sjackman
Copy link
Copy Markdown
Contributor

Note that this value of RPATH isn't necessarily just for executables looking for libraries: it's also for libraries looking for other libraries.

Good point! In that case particular case, although $ORIGIN/../lib works, it should probably instead be simply $ORIGIN since the libraries are found in the same directory.

@carlocab
Copy link
Copy Markdown
Member Author

carlocab commented Apr 20, 2021

Well the idea is that it should work for both libraries and executables with no intervention :)

@sjackman
Copy link
Copy Markdown
Contributor

My biggest concern is that Formula.rpath makes assumptions and hides complexity that's not evident from its name. It works only for files installed in a directory subdirectory of prefix (like bin, lib, and libexec) and looking for libraries in lib. Although that's certainly the most common case, it's not the only case. The desired functionality is more similar to the existing function Pathname.make_relative_symlink. I'd suggest making a function something like Pathname.make_relative_rpath.

@carlocab
Copy link
Copy Markdown
Member Author

Are there any formulae that could make use of the added flexibility? I agree it makes a big assumption, but it's an assumption that standard build systems make. It's also an assumption that I believe is true for each of the ~40 cases I intend to use it in.

@sjackman
Copy link
Copy Markdown
Contributor

sjackman commented Apr 21, 2021

Are there any formulae that could make use of the added flexibility?

Homebrew/homebrew-core#75566 (comment)
You mentioned csound, which has libraries installed in Frameworks.

@sjackman
Copy link
Copy Markdown
Contributor

@sjackman
Copy link
Copy Markdown
Contributor

If standardese has libraries installed in libexec/lib that uses RPATH to find other libraries also installed in libexec/lib, then RPATH=@loader_path/../lib would work for those libraries, although RPATH=@loader_path would be simpler.

If it has executables installed in bin that uses RPATH to find libraries installed in libexec/lib, then RPATH=@loader_path/../lib would not work for those executables. It needs RPATH=@loader_path/../libexec/lib

@sjackman
Copy link
Copy Markdown
Contributor

sjackman commented Apr 21, 2021

f = Formula["hello"]

# The RPATH for an executable installed in bin looking for a library installed in lib.
p "@loader_path/#{f.lib.relative_path_from f.bin}"
# "@loader_path/../lib"

# The RPATH for an executable installed in bin looking for a library installed in Frameworks.
p "@loader_path/#{f.frameworks.relative_path_from f.bin}"
# "@loader_path/../Frameworks"

# The RPATH for a library installed in lib looking for a library installed in lib.
p "@loader_path/#{f.lib.relative_path_from f.lib}"
# "@loader_path/."

# The RPATH for an executable installed in bin looking for a library installed in libexec/lib.
p "@loader_path/#{(f.libexec/"lib").relative_path_from f.bin}"
# "@loader_path/../libexec/lib"

# The RPATH for an executable installed in libexec/bin looking for a library installed in lib.
p "@loader_path/#{f.lib.relative_path_from f.libexec/"bin"}"
# "@loader_path/../../lib"

A few different cases to consider.

@sjackman
Copy link
Copy Markdown
Contributor

Perhaps something like…

class Pathname
  def relative_rpath_from base_directory
    "@loader_path/#{self.relative_path_from base_directory}"
  end
end

@sjackman
Copy link
Copy Markdown
Contributor

$ otool -l /usr/local/Cellar/standardese/0.5.1/bin/standardese | grep -A2 LC_RPATH
          cmd LC_RPATH
      cmdsize 56
         path /usr/local/opt/standardese/libexec/lib (offset 12)

The RPATH of the executable bin/standardese needs to be set to @loader_path/../libexec/lib.

@carlocab
Copy link
Copy Markdown
Member Author

carlocab commented Apr 21, 2021

Homebrew/homebrew-core#75566 (comment)
You mentioned csound, which has libraries installed in Frameworks.

It does, but I don't think we'll be using an rpath helper in csound, as it requires a number of additional hacks to make relocatable.

https://github.com/Homebrew/homebrew-core/blob/bf3cc1388d68d829bfbeb7f9cdfb71ac57b4692c/Formula/standardese.rb#L24
standardese.rb has libraries installed in libexec/lib.

I know. I put them there in Homebrew/homebrew-core@8916862, because I wasn't sure if putting them in lib was the right move. 😄 I have since come to the realisation that I was mistaken. (This was shortly after I opened Homebrew/homebrew-core#75458 and started contemplating the fix in this PR.) I am moving the libraries to lib when I fix standardese.

@sjackman
Copy link
Copy Markdown
Contributor

After looking over the 37 formulae that use CMAKE_INSTALL_RPATH and finding only two exceptions csound and standardese, and you plan to fix the latter, I'm fine with the simpler Formula.rpath API that you've already implemented. I commented too quickly before fully understanding the situation. Sorry for that.

It does make me a tad nervous that if the executable is installed in a subdirectory like say libexec/tools/foobar then it will not be able to find its libraries installed in lib through the RPATH of @loader_path/../lib. If that case were to come up, it'll likely fail tests like brew linkage (though not on Linux where HOMEBREW_PREFIX/lib is in the library search path), and we can cross that bridge when we get there.

Thanks for improving bottle relocation, Carlo!

@carlocab
Copy link
Copy Markdown
Member Author

No apologies necessary! Always happy to discuss changes to make sure they're the right ones to make.

I actually really like the idea of a more flexible rpath API that computes relative paths between binary objects in order to determine the correct RPATH. Unfortunately, I'm not aware of a standard build system that allows you to take advantage of such flexibility. We might one day build it into brew's relocation code, but I don't think we've found need to just yet.

I agree with your concern about breaking stuff though: I'll be careful, and double check the built bottles for stray binary objects living somewhere the current definition of rpath doesn't account for.

@kthchew kthchew mentioned this pull request May 2, 2021
7 tasks
@github-actions github-actions bot added the outdated PR was locked due to age label May 27, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated PR was locked due to age

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants