Skip to content

Commit 40f9073

Browse files
alt-romesMikolaj
authored andcommitted
Add extraLibDirs to runtime lib search paths of library
Runtime search paths are hard. Here's the current picture to understand why this patch exists: * When linking a shared library, GHC will include in the rpath entries of the shared library all the paths listed in the library dirs section of the installed package info of all packages the shared library depends on. * On darwin, GHC has special logic to inject the library dirs listed in the installed dependent packages info into the rpath section instead of passing the dirs as -rpath flags to the linker. However, only the dirs where used libraries are found are actually injected. The others are ignored. This works around limitations of the darwin loader. * Cabal, in addition, passes directly to the linker (via -optl-Wl,-rpath,...) the library dirs of packages the shared library for the package being built depends on. * In a vanilla cabal installation, this will typically only be the path to the cabal store and the path to the installed GHC's boot libraries store. * When using nix there will a different library dir per installed package. Since these lib dirs are passed directly to the linker as rpaths, we bypass the darwin loader logic and, for very big packages, on darwin, we could end up reaching the load command limit and fail linking. We don't address this situation in this MR. When we specify `extra-lib-dirs` in Cabal, these extra-lib-dirs will be added to the library dirs listed in the installed package info of the library they were specified for. Furthermore, when building a shared library, extra-lib-dirs will be passed as `-L` flags to the linker invocation. However, the same extra-lib-dirs will not be passed as `-rpath` to the linker. The end situation is as follows: 1. The shared library `libA` built for a package `A` will be linked against some libraries `libExtra` found in extra-lib-dirs `extraA`. 2. The RPATH section of `A` will NOT contain `extraA`, because we don't pass -rpath extra-lib-dirs when linking the library, but it will depend on `libExtra`. 3. The installed package info of that package `A` will contain, in the library dirs section, the extra-lib-dirs `extraA` and the path to `libA`. 4. When a package `B` depends on package `A`, it will include in the RPATH section of the shared library `libB` the lib dirs from the installed package info of `A`, i.e. `/path/to/libA` and `extraA`, and depends on `libA` and, transitively, on `libExtra`. The conclusion is: 5. When we load `libB`, we will load `libA`, which is found in `/path/to/libA`, and, transitively, load `libExtra` which is found in `extraA` -- they are both found because both `/path/to/libA` and `extraA` are listed in the RPATH entries. 6. However, if we load `libA` directly we will /NOT/ find `libExtra`, because `extraA` is not included in the RPATH entries. So, ultimately, what this commit fixes, is the failure described in (6), caused by the incorrect behaviour of (2), by specifying `-rpath extra-lib-dirs` when linking the shared library of a package, to include the extra lib dirs in the RPATH entries of that shared library (even though dependents of this library would already get the extra-lib-dirs in their RPATH, the library itself didn't, resulting in cabal#7339 and ghc#19350) Fixes #7339 Fixes ghc#19350
1 parent 9f9b5bd commit 40f9073

File tree

8 files changed

+100
-2
lines changed

8 files changed

+100
-2
lines changed

Cabal/src/Distribution/Simple/GHC.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ buildOrReplLib mReplFlags verbosity numJobs pkg_descr lbi lib clbi = do
935935
ghcOptLinkFrameworks = toNubListR $ PD.frameworks libBi,
936936
ghcOptLinkFrameworkDirs =
937937
toNubListR $ PD.extraFrameworkDirs libBi,
938-
ghcOptRPaths = rpaths
938+
ghcOptRPaths = rpaths <> toNubListR (extraLibDirs libBi)
939939
}
940940
ghcStaticLinkArgs =
941941
mempty {
@@ -1436,7 +1436,7 @@ gbuild verbosity numJobs pkg_descr lbi bm clbi = do
14361436
[tmpDir </> x | x <- cLikeObjs ++ cxxObjs]
14371437
}
14381438
dynLinkerOpts = mempty {
1439-
ghcOptRPaths = rpaths,
1439+
ghcOptRPaths = rpaths <> toNubListR (extraLibDirs bnfo),
14401440
ghcOptInputFiles = toNubListR
14411441
[tmpDir </> x | x <- cLikeObjs ++ cxxObjs]
14421442
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Hello
2+
hello
3+
:q
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <stdio.h>
2+
3+
void hello_world(void) {
4+
printf("hello world!");
5+
}
6+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Hello (hello) where
2+
3+
foreign import ccall "hello_world" hello :: IO ()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cabal-version: >=1.10
2+
name: T7339
3+
version: 1.0
4+
build-type: Simple
5+
6+
library
7+
build-depends: base
8+
exposed-modules: Hello
9+
default-language: Haskell2010
10+
extra-libraries: hello
11+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Setup configure
2+
# Setup build
3+
Preprocessing library for T7339-1.0..
4+
Building library for T7339-1.0..
5+
# Setup register
6+
Registering library for T7339-1.0..
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
-- Test for #19350, #7339 originally by @bgamari
2+
-- =============================================
3+
--
4+
-- The plan
5+
-- ---------
6+
-- We build a C shared library (`libhello`, contained in ./clib) and then build
7+
-- a Haskell library (`T19350-lib`, in ./lib) which depends upon it via `foreign
8+
-- import`. We make sure that the libhello shared object can only be found via
9+
-- the extra-lib-dirs from the package database registration (which we do by
10+
-- moving libhello.so from its original place).
11+
--
12+
-- Finally, we enter GHCi, load the Haskell library, and try to use it to call
13+
-- into libhello.
14+
15+
import System.Directory
16+
import Distribution.System
17+
import Test.Cabal.Prelude
18+
19+
20+
main = setupTest $ do
21+
22+
skipIfWindows
23+
skipUnlessGhcVersion ">= 8.4"
24+
25+
withPackageDb $ do
26+
cwd <- takeDirectory . testCurrentDir <$> getTestEnv
27+
plat <- testPlatform <$> getTestEnv
28+
let libExt = case plat of
29+
Platform _ OSX -> "dylib"
30+
Platform _ Windows -> "dll"
31+
Platform _ _other -> "so"
32+
33+
34+
-- Link a C program against the library
35+
_ <- runProgramM ghcProgram
36+
[ "-fPIC", "-c", "clib/lib.c"
37+
, "-o", "clib/lib.o" ]
38+
Nothing
39+
_ <- runProgramM ghcProgram
40+
[ "-shared", "-no-hs-main", "clib/lib.o"
41+
, "-o", "clib/libhello" <.> libExt ]
42+
Nothing
43+
44+
withDirectory "lib" $ do
45+
setup "configure" ["-v0"
46+
, "--extra-lib-dirs=" ++ (cwd </> "clib")
47+
, "--extra-lib-dirs=" ++ (cwd </> "clib-install")
48+
, "--disable-library-vanilla"
49+
, "--enable-shared"]
50+
setup "build" []
51+
setup "register" ["--inplace"]
52+
53+
-- Move libhello from its original place to ensure it isn't found via RPATH
54+
liftIO $ do
55+
createDirectoryIfMissing False (cwd </> "clib-install")
56+
copyFile (cwd </> "clib/libhello" <.> libExt) ( cwd </> "clib-install/libhello" <.> libExt)
57+
removeFile (cwd </> "clib/libhello" <.> libExt)
58+
59+
pkgDb <- testPackageDbDir <$> getTestEnv
60+
ghciScript <- liftIO $ readFile (cwd </> "T19350.script")
61+
_ <- runProgramM ghcProgram
62+
[ "--interactive"
63+
, "-package", "T7339"
64+
, "-package-db", pkgDb
65+
] (Just ghciScript)
66+
67+
return ()

0 commit comments

Comments
 (0)