haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.62k stars 697 forks source link

Support standalone foreign libraries on Linux #4827

Open TravisWhitaker opened 7 years ago

TravisWhitaker commented 7 years ago

If option: standalone is used in a foreign-library stanza on Linux, Cabal reports "We cannot build standalone libraries on Linux." However, it is possible to do so; all that's necessary is that the package's dependencies (including the RTS) are built with -fPIC. Although a default GHC build doesn't do this, it's not too difficult to build a GHC that uses position-independent boot libraries (especially on Nix). I've used this technique combined with the ghc-options functionality in stack (to ensure non-boot Haskell dependencies are built with -fPIC) to build Haskell code into C libraries that are shipped to other teams in a commercial setting.

In most settings I've dealt with it's all but necessary to statically link Haskell foreign libraries against Haskell dependencies; the whole point of doing this is to wrap up all the Haskell goodies as cleanly as possible for consumers working in other environments. Supporting standalone libraries on Linux, combined with a solution to #4042, would allow me to eliminate a lot of custom build machinery.

I haven't looked at the plumbing around the foreign-library stanza, but I'd imagine it wouldn't be too difficult to do the right thing on Linux assuming the boot libraries available to the GHC in use are position-independent. If they aren't position-independent (and they won't be by default), it'd be nice for cabal-install to detect this somehow and fail before the linker shows the user something much scarier looking.

The foreign-library feature is really exciting. It makes it a lot easier to use Haskell as a part of larger systems.

23Skidoo commented 7 years ago

/cc @dcoutts @edsko

sboosali commented 6 years ago

all that's necessary is that the package's dependencies (including the RTS) are built with -fPIC. Although a default GHC build doesn't do this, it's not too difficult to build a GHC that uses position-independent boot libraries (especially on Nix).

@TravisWhitaker

Can you share your nix script? Is it enough to override mkDerivation to add -fPIC to configureFlags?

Also, iiuc, this:

https://ro-che.info/articles/2017-07-26-haskell-library-in-c-project

says it's still necessary to manually sort the libHS<...>.a by dependence; until we can reuse whatever cabal-install function doee that sorting (as in the Simple build-type?).

btw, my motivation is to export haskell programs as Emacs dynamic modules:

https://github.com/jkitchin/emacs-modules/blob/master/README.org

and I think it's important to make them standalone if we want to distribute, for example, an Emacs mode written in Haskell as any other normal Emacs package. (But I'm not familiar with it so I might be wrong; either way, I'd try your GHC/haskellPackages configuration).

TravisWhitaker commented 6 years ago

@sboosali All you need to get a GHC is something like:

ghc822pic = haskell.compiler.ghc822.overrideAttrs (a: rec
    {
        picConfigString = ''
            SRC_HC_OPTS += -fPIC
            GhcRtsHcOpts += -fPIC
            GhcLibHcOpts += -fPIC
        '';

        preConfigure = a.preConfigure + ''
            echo "${picConfigString}" >> mk/build.mk
        '';
    });

In fact, now that I look at this again, setting it on SRC_HC_OPTS might be overkill; we don't care if GHC itself is position-independent.

In order to actually build the statically linked shared libraries, I used to use stack with ghc-options and a hacky Makefile that essentially automated what Roman was doing in the link you posted, but with Cabal 2 you can now use the foreign-library stanza. This PR partially addresses the issue of deciding which RTS to link against. I find it's convenient to define a whole Haskell package set that's built with -fPIC, like so:

    picHaskell = (callPackage (../development/haskell-modules)
    {
        ghc = ghc822pic; # from above
        haskellLib = pkgs.haskell.lib;
        buildHaskellPackages = pkgs.buildPackages.haskell.packages.ghc822;
    }).override
    {
        overrides = self: super:
        {
            mkDerivation = args: super.mkDerivation (args //
            {
                configureFlags = (args.configureFlags or []) ++ ["--ghc-option=-fPIC"];
            });
        };
    };

This way you don't have to worry about making sure that the closure of your dependencies is also position independent. If you build a foreign-library with that Cabal patch(really only necessary if you want to make sure -threaded is handled correctly) against this package set with this GHC, you should be all set. Curious to hear about how you make out.

sboosali commented 6 years ago

Thanks! I'll try it out.

btw, this says that foreign-library won't use the threaded runtime HSrts_thr:

https://www.vex.net/~trebla/haskell/so.xhtml

On Tue, Apr 3, 2018, 6:07 PM Travis Whitaker notifications@github.com wrote:

@sboosali https://github.com/sboosali All you need to get a GHC is something like:

ghc822pic = haskell.compiler.ghc822.overrideAttrs (a: rec { picConfigString = '' SRC_HC_OPTS += -fPIC GhcRtsHcOpts += -fPIC GhcLibHcOpts += -fPIC '';

    preConfigure = a.preConfigure + ''            echo "${picConfigString}" >> mk/build.mk        '';
});

In fact, now that I look at this again, setting it on SRC_HC_OPTS might be overkill; we don't care if GHC itself is position-independent.

In order to actually build the statically linked shared libraries, I used to use stack with ghc-options and a hacky Makefile that essentially automated what Roman was doing in the link you posted, but with Cabal 2 you can now use the foreign-library stanza. This PR https://github.com/haskell/cabal/pull/5118 partially addresses the issue of deciding which RTS to link against. I find it's convenient to define a whole Haskell package set that's built with -fPIC, like so:

picHaskell = (callPackage (../development/haskell-modules)
{
    ghc = ghc822pic; # from above
    haskellLib = pkgs.haskell.lib;
    buildHaskellPackages = pkgs.buildPackages.haskell.packages.ghc822;
}).override
{
    overrides = self: super:
    {
        mkDerivation = args: super.mkDerivation (args //
        {
            configureFlags = (args.configureFlags or []) ++ ["--ghc-option=-fPIC"];
        });
    };
};

This way you don't have to worry about making sure that the closure of your dependencies is also position independent. If you build a foreign-library with that Cabal patch(really only necessary if you want to make sure -threaded is handled correctly) against this package set with this GHC, you should be all set. Curious to hear about how you make out.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/haskell/cabal/issues/4827#issuecomment-378447193, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNoMQO-m3LILmDA6P1_SWYaSGZMSU-kks5tlBzegaJpZM4P7Li6 .

sboosali commented 6 years ago

(Oh, nvm, that's what you said that the patch lets you pick between)

On Tue, Apr 3, 2018, 6:11 PM Spiros Boosalis samboosalis@gmail.com wrote:

Thanks! I'll try it out.

btw, this says that foreign-library won't use the threaded runtime HSrts_thr:

https://www.vex.net/~trebla/haskell/so.xhtml

On Tue, Apr 3, 2018, 6:07 PM Travis Whitaker notifications@github.com wrote:

@sboosali https://github.com/sboosali All you need to get a GHC is something like:

ghc822pic = haskell.compiler.ghc822.overrideAttrs (a: rec { picConfigString = '' SRC_HC_OPTS += -fPIC GhcRtsHcOpts += -fPIC GhcLibHcOpts += -fPIC '';

    preConfigure = a.preConfigure + ''            echo "${picConfigString}" >> mk/build.mk        '';
});

In fact, now that I look at this again, setting it on SRC_HC_OPTS might be overkill; we don't care if GHC itself is position-independent.

In order to actually build the statically linked shared libraries, I used to use stack with ghc-options and a hacky Makefile that essentially automated what Roman was doing in the link you posted, but with Cabal 2 you can now use the foreign-library stanza. This PR https://github.com/haskell/cabal/pull/5118 partially addresses the issue of deciding which RTS to link against. I find it's convenient to define a whole Haskell package set that's built with -fPIC, like so:

picHaskell = (callPackage (../development/haskell-modules)
{
    ghc = ghc822pic; # from above
    haskellLib = pkgs.haskell.lib;
    buildHaskellPackages = pkgs.buildPackages.haskell.packages.ghc822;
}).override
{
    overrides = self: super:
    {
        mkDerivation = args: super.mkDerivation (args //
        {
            configureFlags = (args.configureFlags or []) ++ ["--ghc-option=-fPIC"];
        });
    };
};

This way you don't have to worry about making sure that the closure of your dependencies is also position independent. If you build a foreign-library with that Cabal patch(really only necessary if you want to make sure -threaded is handled correctly) against this package set with this GHC, you should be all set. Curious to hear about how you make out.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/haskell/cabal/issues/4827#issuecomment-378447193, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNoMQO-m3LILmDA6P1_SWYaSGZMSU-kks5tlBzegaJpZM4P7Li6 .

cormacrelf commented 5 years ago

I don’t think there’s an existing issue tracking the “not implemented yet” native-static option in foreign-library stanzas, but it’s worth a mention as it’s very similar. It would be a valuable feature, especially given that Haskell binaries are often pretty big. My use case at the moment is linking to Pandoc from a Rust codebase. libHSpandoc.dylib is 37MB without even counting its dependencies, so you really want to engage LTO there.

sboosali commented 5 years ago

got it (and thanks for the link to the docs!)

On Sun, Dec 16, 2018, 09:50 Cormac Relf <notifications@github.com wrote:

I don’t think there’s an existing issue tracking the “not implemented yet” native-static option in foreign-library stanzas https://www.haskell.org/cabal/release/latest/doc/API/Cabal/Distribution-Types-ForeignLibType.html, but it’s worth a mention as it’s very similar. It would be a valuable feature, especially given that Haskell binaries are often pretty big. My use case at the moment is linking to Pandoc from a Rust codebase. libHSpandoc.dylib is 37MB without even counting its dependencies, so you really want to engage LTO there.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/haskell/cabal/issues/4827#issuecomment-447662385, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNoMYg-SoAnQNJfqsJ5PWu5cMelx0xGks5u5ofQgaJpZM4P7Li6 .

KaneTW commented 4 years ago

In case people want to build a static foreign library: Compile GHC 8.8 (8.6 has some stupid bugs that prevent it from compiling) with -fPIC and -fexternal-dynamic-refs. Copy libHSrts.a to libHSrts-ghc8.8.1.a (for example) and build normally with -static on the foreign library and -fPIC and -fexternal-dynamic-refs on all packages.

Easiest hack I've found is to edit default_PIC in main/DynFlags.hs to always enable PIC and external dynamic refs.