ktemkin / gnuradio-for-mac-without-macports

GNURadio bundled as an app for Mac OS X (does not require MacPorts)
GNU General Public License v3.0
324 stars 45 forks source link

Automagically Correct soname and Other Library References in .dylib #13

Open cfriedt opened 7 years ago

cfriedt commented 7 years ago

See

https://github.com/conda/conda-build/issues/279#issuecomment-67241554

cfriedt commented 7 years ago

From here,

A couple of things to account for:

To correct the "soname":

install_name_tool -id "/desired/soname/with/full/path.dylib" file

To correct any referenced libraries:

install_name_tool \
    -change "liboriginal.0.dylib" \
    "/full/path/to/candidate/liboriginal.0.dylib" \
    "file"
cfriedt commented 7 years ago

There are two opportunities for us to do this:

  1. upon install
  2. every time the program starts

We can, of course, also do both. We MUST do both.

The first option is tricky because it would mean we would need to install each package to a temporary DESTDIR before installing to /, which is not something we do now.

The second options is nice because it would allow users to simply copy the .app file from one computer to another, and the library references would be updated each time. A challenge there, is that it is fairly straightforward to handle library-prefix cases when there is a known prefix pattern such as '/' or '@rpath/', but it is more challenging when going from /Users/Bob/Applications/GNURadio.app/.. to /Applications/GNURadio.app/.. or /Volumes/OtherDisk/Applications/GNURadio.app/..

However, it seems apparent that handling install names using a common method for all installed libraries would definitely be beneficial as it would reduce the complexity of patches required for each package on its own.

cfriedt commented 7 years ago

Hmm... so as it turns out, it's really freaking complicated to build huge projects like this for Mac OS X. The "legacy" method of linking code on mac is by using -lfoo (where the directory containing libfoo.dylib is specified with -L /path/to/libfoo/).

The "modern" way of linking code on a mac is by using frameworks

Is the better option, then to create a "framework" for every group of .dylibs?

The tricky part, with GNURadio, is that it is really just a python script that dynamically loads other libraries. Python itself should not be packaged with GNURadio, and neither should X11 (there is no need), so really, there is no single executable contained within the .app from which all other .dylibs could be made relative to a @loader_path or @executable_path

I haven't even seen @loader_path in use inside the majority of the frameworks that I have inspected, which is a bit infuriating. Am I thinking of a plugin instead of a framework?? Why then are frameworks routinely copied into .app/Contents/Resources directories??

cfriedt commented 7 years ago

I would really be interested in following some tutorials about how complex projects are organized in terms of .app, .framework, .dylib, @loader_path, @rpath, and @executable_path, etc

cfriedt commented 7 years ago

Possibly, the whole thing is caused by Apple's SIP (System Integrity Protection) explicitly disallowing the use of "relative paths" in applications. I assume it means install names like libfoo.dylib without any prefix (yes, even @loader_path/../lib/libfoo.dylib sounds as though it is non-relative).

Not surprisingly, it sounds as though Apple did not foresee managed code dynamically loading native code as a valid use case.

As a result, (and apparently the interpretation of @loader_path as being non-relative), the acceptible solution is to copy the python executable into the bin directory of the app in question. Seems hackish, but I guess if it works...

https://bitbucket.org/fenics-project/fenics-developer-tools/issues/8/fenics-160-binary-for-os-x-does-not-work

cfriedt commented 7 years ago

My plan of attack here is roughly the following:

  1. identify all changes that hard-code library-names into .dylibs
  2. rather than using hard-coded library names, use @loader_path
    1. this will likely break some linking stages of the build, because binaries will expect .dylibs to be installed (or in the process of being installed)
    2. come up with clever hacks / work-arounds to get all build stages to build
    3. We will interpret APP_DIR/Contents/MacOS/usr/lib as @loader_path
  3. modify run-grc to copy the python2.7 binary into the path

Tagging @dholl for feedback

cfriedt commented 7 years ago

This could come in handy.

https://opensource.apple.com/source/cctools/cctools-576/misc/install_name_tool.c

I'm currently writing a tool to fix broken dylibs in bash, but it's likely a much better idea to do it in C or C++ for speed.

dholl commented 7 years ago

hehe --- I think we're on the same trajectory.

I had just started looking into this yesterday before seeing your message, since I was able to get a working (? to be verified) build of gnuradio-companion on my system with the Python 2.7 that's native to OS X Sierra, and with an APP_DIR set to "$HOME/Applications/GNURadio.app".

  1. identify all changes that hard-code library-names into .dylibs

Changes in the current build process? This was my initial hunch to try in my own pursuit. But then I was wondering if there was a faster way to prototype a strategy based on @loader_path... which is what you hit on next. :)

  1. rather than using hard-coded library names, use @loader_path

And for the likely very complicated subparts i. and ii. that you mention, I started wondering if there's a faster way to prototype "What if we did make all the changes to the build-process to produce both executables and dynamic libraries with all the correct @loader_path relative references, would the end result still work?"

  1. modify run-grc to copy the python2.7 binary into the path

Is this to work around Apple's "System Integrity Protection"? I was poking at this, since my own DYLD_LIBRARY_PATH isn't being passed through to python. I tried copying /usr/bin/python and /usr/bin/python2.7 but that didn't work -- since those appear to only be Apple's wrappers to invoke the real python based on $VERSIONER_PYTHON_VERSION.

Only after copying /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python did I manage to pass $DYLD_LIBRARY_PATH.

Here's a quick transcript of my journey where I happen to use $DYLD_LIBRARY_PATH for other unrelated, pursuits:

zero(ttys004):...orts/scripts> echo $DYLD_LIBRARY_PATH 
/Users/dholl/.pkg/autotools/lib:/Users/dholl/.pkg/iverilog/lib:/Users/dholl/.pkg/mercurial/lib:/Users/dholl/.pkg/portaudio/lib:/Users/dholl/.pkg/protobuf/lib:/Users/dholl/.pkg/tokyocabinet/lib
zero(ttys004):...orts/scripts> python -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.py", line 23, in __getitem__
    raise KeyError(key)
KeyError: 'DYLD_LIBRARY_PATH'
zsh: exit 1     python -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
zero(ttys004):...orts/scripts> which python
/usr/bin/python
zero(ttys004):...orts/scripts> cp /usr/bin/python .
zero(ttys004):...orts/scripts> ./python -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.py", line 23, in __getitem__
    raise KeyError(key)
KeyError: 'DYLD_LIBRARY_PATH'
zsh: exit 1     ./python -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
zero(ttys004):...orts/scripts> rm ./python
zero(ttys004):...orts/scripts> which python2.7
/usr/bin/python2.7
zero(ttys004):...orts/scripts> python2.7 -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.py", line 23, in __getitem__
    raise KeyError(key)
KeyError: 'DYLD_LIBRARY_PATH'
zsh: exit 1     python2.7 -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
zero(ttys004):...orts/scripts> cp /usr/bin/python2.7 .
zero(ttys004):...orts/scripts> ls -l python2.7
-rwxr-xr-x  1 dholl  staff  43152 May 21 22:45 python2.7
zero(ttys004):...orts/scripts> ./python2.7 -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.py", line 23, in __getitem__
    raise KeyError(key)
KeyError: 'DYLD_LIBRARY_PATH'
zsh: exit 1     ./python2.7 -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"
zero(ttys004):...orts/scripts> rm ./python2.7 
zero(ttys004):...orts/scripts> cp /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python .
zero(ttys004):...orts/scripts> ./Python -c "import os ; print os.environ['DYLD_LIBRARY_PATH']"                           
/Users/dholl/.pkg/autotools/lib:/Users/dholl/.pkg/iverilog/lib:/Users/dholl/.pkg/mercurial/lib:/Users/dholl/.pkg/portaudio/lib:/Users/dholl/.pkg/protobuf/lib:/Users/dholl/.pkg/tokyocabinet/lib

So we could search for /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python to copy over, but then I started wondering: Do we really need to copy it if we can fix dynamic linking with @loader_path relative paths?

To summarize: I hypothesize that we don't need to copy the python2.7 binary if we can rewrite all Mach-O files to use @loader_path. :)

So, I'm presently daydreaming of creating a script of a similar mentality to find-broken-dylibs.sh but to be run only once after the whole GNURadio.app has been built with hard-coded paths, just prior to packaging into a .dmg disk image. I'd envision this script to identify all Mach-O files (both executables and shared libraries), and to replace the hard-coded paths with relative paths based off @loader_path.

The reason I figure to do this at the very end, after all files have been built + linked, so that we don't have to meddle with every separate project that gets downloaded and compiled in build.sh.

This could come in handy.

https://opensource.apple.com/source/cctools/cctools-576/misc/install_name_tool.c

Nifty find! I noticed that this source doesn't fully match the precompiled binary that ships with Xcode. (such as -rpath) I'm a little wary of getting into banging on raw binary formats, but maybe this source code is current enough? Otherwise, my own notional idea was to just invoke the precompiled install_name_tool and accept the performance hit of spawning additional processes.

I'm currently writing a tool to fix broken dylibs in bash, but it's likely a much better idea to do it in C or C++ for speed.

Regarding a scripting language vs C/C++, I was leaning toward scripting to just repeatedly invoke

Running a cheesy test on my laptop, the following shell script takes about 8 minutes to traverse the entire $APP_DIR:

#!/bin/sh
set -e
set -u
find /Users/dholl/Applications/GNURadio.app -type f '!' -name '*.a' -exec sh -c 'exec otool -h "$1" < /dev/null > /dev/null 2>&1' unused_arg0 {} \; -print0 > print0.txt
# Rationale:
#   -type f
#       We look for real files, and ignore symbolic links
#   '!' -name '*.a'
#       We ignore .a files (object archives for static linking)
#   -exec sh -c 'exec otool -h "$1" < /dev/null > /dev/null 2>&1' unused_arg0 {} \;
#       Use "otool -h ..." to test each file for a Mach-O header.
#   -print0
#       For each passing file, print it with null-termination

# Now 
xargs -0 file < print0.txt > file.txt
xargs -0 otool -L < print0.txt > otool-L.txt

Granted that this script doesn't contain the string manipulation nor install_name_tool invocations, but compared to the length of time spent in the rest of build.sh, I wouldn't mind waiting an additional 10-15 minutes for such a brute-force scan-and-fix hack. :)

So my inclination is to try a proof-of-concept repair script in python first. (I also considered sh, but I wager that the string manipulation may start getting a little tedious with the quoting to handle paths safely.)

Thoughts? Do we both conjure separate scan-n-fix implementations, and then compare notes?

dholl commented 7 years ago

I found a more recent version of install_name_tool that seems a little more complete: https://github.com/opensource-apple/cctools/blob/master/misc/install_name_tool.c

dholl commented 7 years ago

Trying an experiment... I'm deep in $APP_DIR:

zero(ttys003):.../gnuradio/gr> pwd
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/share/gnuradio/python/site-packages/gnuradio/gr

and I'm tinkering with _runtime_swig.so:

zero(ttys003):.../gnuradio/gr> otool -D _runtime_swig.so
_runtime_swig.so:

huh... no "id" assigned to _runtime_swig.so?

zero(ttys003):.../gnuradio/gr> otool -L _runtime_swig.so
_runtime_swig.so:
    /System/Library/Frameworks/Python.framework/Versions/2.7/Python (compatibility version 2.7.0, current version 2.7.10)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.51.1)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1349.65.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libgnuradio-runtime.3.7.10.1.dylib (compatibility version 3.7.10, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libgnuradio-pmt.3.7.10.1.dylib (compatibility version 3.7.10, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libthrift-0.10.0.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_date_time.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_program_options.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_system.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_regex.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_thread.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_chrono.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_atomic.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libvolk.1.3.dylib (compatibility version 1.3.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/liborc-0.4.dylib (compatibility version 26.0.0, current version 26.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.5.0)

I wanted to experiment by rewriting the shared libraries using @loader_path, and this worked:

install_name_tool \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libgnuradio-runtime.3.7.10.1.dylib \
        @loader_path/../../../../../../lib/libgnuradio-runtime.3.7.10.1.dylib \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libgnuradio-pmt.3.7.10.1.dylib     \
        @loader_path/../../../../../../lib/libgnuradio-pmt.3.7.10.1.dylib     \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libthrift-0.10.0.dylib             \
        @loader_path/../../../../../../lib/libthrift-0.10.0.dylib             \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_date_time.dylib           \
        @loader_path/../../../../../../lib/libboost_date_time.dylib           \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_program_options.dylib     \
        @loader_path/../../../../../../lib/libboost_program_options.dylib     \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib          \
        @loader_path/../../../../../../lib/libboost_filesystem.dylib          \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_system.dylib              \
        @loader_path/../../../../../../lib/libboost_system.dylib              \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_regex.dylib               \
        @loader_path/../../../../../../lib/libboost_regex.dylib               \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_thread.dylib              \
        @loader_path/../../../../../../lib/libboost_thread.dylib              \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_chrono.dylib              \
        @loader_path/../../../../../../lib/libboost_chrono.dylib              \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_atomic.dylib              \
        @loader_path/../../../../../../lib/libboost_atomic.dylib              \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libvolk.1.3.dylib                  \
        @loader_path/../../../../../../lib/libvolk.1.3.dylib                  \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/liborc-0.4.dylib                   \
        @loader_path/../../../../../../lib/liborc-0.4.dylib                   \
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/share/gnuradio/python/site-packages/gnuradio/gr/_runtime_swig.so

(I left off any changes to -id since otool -D _runtime_swig.so returned nothing.)

Now otool reports the correct @loader_path-based library locations:

zero(ttys003):.../gnuradio/gr> otool -D _runtime_swig.so
_runtime_swig.so:
zero(ttys003):.../gnuradio/gr> otool -L _runtime_swig.so
_runtime_swig.so:
    /System/Library/Frameworks/Python.framework/Versions/2.7/Python (compatibility version 2.7.0, current version 2.7.10)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.51.1)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1349.65.0)
    @loader_path/../../../../../../lib/libgnuradio-runtime.3.7.10.1.dylib (compatibility version 3.7.10, current version 0.0.0)
    @loader_path/../../../../../../lib/libgnuradio-pmt.3.7.10.1.dylib (compatibility version 3.7.10, current version 0.0.0)
    @loader_path/../../../../../../lib/libthrift-0.10.0.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_date_time.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_program_options.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_filesystem.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_system.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_regex.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_thread.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_chrono.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libboost_atomic.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/../../../../../../lib/libvolk.1.3.dylib (compatibility version 1.3.0, current version 0.0.0)
    @loader_path/../../../../../../lib/liborc-0.4.dylib (compatibility version 26.0.0, current version 26.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.5.0)

And gnuradio-companion still runs. :)

Then I tried an experiment with libboost_filesystem.dylib:

zero(ttys003):.../gnuradio/gr> otool -D /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib:
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib
zero(ttys003):.../gnuradio/gr> otool -L /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib:
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_system.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.5.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.51.1)

This library has an id reported by "otool -D ...". So here, I'll rewrite the names as:

install_name_tool \
    -change /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_system.dylib \
        @loader_path/libboost_system.dylib                                                  \
    -id libboost_filesystem.dylib                                                               \
    /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib

Now otool reports this id and list of references:

zero(ttys003):.../gnuradio/gr> otool -D /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib:
libboost_filesystem.dylib
zero(ttys003):.../gnuradio/gr> otool -L /Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib
/Users/dholl/Applications/GNURadio.app/Contents/MacOS/usr/lib/libboost_filesystem.dylib:
    libboost_filesystem.dylib (compatibility version 0.0.0, current version 0.0.0)
    @loader_path/libboost_system.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.5.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.51.1)

and gnuradio-companion still runs. :)

So I figure that if I were to write a script it would need to:

  1. Given a specified base directory, such as /Users/dholl/Applications/GNURadio.app
  2. Find all Mach-O binary files (executables and libraries)
  3. If a binary has an id (otool -D ...), strip any directory prefix from the id, or maybe replace it with @rpath/libwhatever.dylib --- I think this id is only using at link time (ld) after compilation --- not during dyld.
  4. For every referenced library (otool -L ...) within a binary, if that reference falls within the base dir (/Users/dholl/Applications/GNURadio.app), then rewrite it with @loader_path/....

(Note: I'd perform step 3 before step 4, since it appears that otool -L reports the library id again at the top of its list of referenced libraries.)

I think I have enough to mock up a quick search-n-destroy script. :)

So if we fix up all the binaries (executables and libraries) in this fashion, then would the whole $APP_DIR might be relocatable? Or is there a change that gnuradio or some dependency may have hard-coded a path during compilation? (hmm, how about I just make this script, try it on my APP_DIR, and see if it works or not... --- It may take me a day or two before I'll get a moment to implement this test.)

cfriedt commented 7 years ago

I have this so far.

It makes the "anchor" at $APP_DIR/usr/bin, but I think i was wrong in choosing @loader_path as the base of the install_name.

Read this in its entirety just yesterday

https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/dyld.1.html

The part about @rpath indicates that I should be using @rpath rather than @loader_path. It should be possible to push another rpath to the python binary using install_name_tool and the -add_rpath option.

Will give that a shot soon..

cfriedt commented 7 years ago

wild. actually works!

After replacing @loader_path with @rpath, I reran the script that I wrote (after a fresh build), then copied python2.7 into $APP_DIR/Contents/MacOS/usr/bin, and then ran

install_name_tool -add_rpath $APP_DIR/Contents/MacOS/usr/bin python2.7

and finally launched GNURadio with $APP_DIR/Contents/MacOS/usr/bin/run-grc and it worked.

I will need to rewrite run-grc to not use hardcoded absolute paths, and will also need to make it install / augment python2.7.

cfriedt commented 7 years ago

@dholl , this branch is shaping up really well. I'll rebase soon off of master from your previous PR. Just thought I would give you a hint at what I would like to do in the near future.

In order to tie-in a few different issues (namely #3, #5 and this issue), I plan on using native code to launch gnuradio-companion rather than a series of bash scripts. That is mainly because it is subject to less variability in terms of available command-line programs, and also because e.g. C++11 provides a much finer-grain of functionality and expressibility for manipulating sets of objects, strings, files, etc.

Eventually, much like the GIMP, it would be incredible to ditch XQuartz entirely and create a native Mac OS, Cocoa-based UI and widget set, and I see that as being something much easier to achieve if our primary executable uses native code.

At that point, tracking the next version of GNURadio would be a bit more trivial and there would be far fewer dependencies to build in this project. We would actually be aiming more at committing that code directly to GNURadio from a separate, tracking, gnuradio repository.

Does that sound interesting to you?

dholl commented 7 years ago

If you don't mind, I'd love to clean-up run-grc.sh a little. :)

GIMP does use a shell script to launch their binary. ;) GIMP.app/Contents/Info.plist sets CFBundleExecutable to GIMP, and GIMP.app/Contents/MacOS/GIMP is a shell script that deduces the location of GIMP.app based on "$0". I abbreviated the excerpt below to highlight the major pieces:

#!/bin/sh
tmp="$0"
tmp="$(dirname "$tmp")"
tmp="$(dirname "$tmp")"
bundle="$(dirname "$tmp")"
bundle_contents="$bundle"/Contents
bundle_res="$bundle_contents"/Resources

if test -f "$bundle_res/environment.sh"; then
  # source environment.sh
  . "$bundle_res/environment.sh"
fi

# Strip out the argument added by the OS.
if [ "x${1#"-psn_"}" != "x${1}" ]; then
  shift 1
fi

exec "$bundle_contents/MacOS/$name-bin" "$@"

If you're game for it, I'd love to fix up run-grc.sh and submit a PR to you. :) (Especially note the final exec of the main GIMP application with double quotes around "$@", and the lack of backgrounding &.)

It might also be fun to explore out how to tweak GNURadio.app's Info.plist how to open .grc files from Finder.

Getting back to your idea of creating a binary launcher and native widget set, since gnuradio-companion is a python application that appears to use the gtk library, I'd speculate our efforts might be more fruitful trying a pygtk-specific bundling for Mac, such as alluded to here: https://wiki.gnome.org/Projects/GTK+/OSX/Bundling#Bundling_PyGTK_apps

If we get pygtk talking directly to OS X without X11/XQuartz, that could reduce the launch latency.

So, I would be game to help explore the Mac-specific voodoo for pygtk and gnuradio-companion. And if we happen to dig up a few fixes to share back with the gnuradio folks, then that's all the better. :)

dholl commented 7 years ago

For edification, here's a good discussion on finding a script's directory: https://stackoverflow.com/questions/29832037/how-to-get-script-directory-in-posix-sh

cfriedt commented 7 years ago

I've already done that based on "$0" (posix), but feel free to make suggestions.

I don't mind in the mean time sticking to shell scripts for a progress bar, if that's possible, it's just obviously way less efficient than native code.

dholl commented 7 years ago

I've already done that based on "$0" (posix), but feel free to make suggestions.

I don't mind in the mean time sticking to shell scripts for a progress bar, if that's possible, it's just obviously way less efficient than native code.

@cfriedt When you have a moment, check out PR #35 :)

ihnorton commented 6 years ago

Possibly of interest: https://github.com/auriamg/macdylibbundler