premake / premake-core

Premake
https://premake.github.io/
BSD 3-Clause "New" or "Revised" License
3.14k stars 612 forks source link

Whole-archive linking option and Clang equivalent? #1170

Open aschneid1 opened 5 years ago

aschneid1 commented 5 years ago

Is there a way to link a static library project with "whole-archive" and the clang equivalent ("force-load")?

I have several project targets that all compile several of the same CPP files (which takes a while). If I could instead compile these common files to a static lib and link that, it would work fantastically, but unfortunately both clang and gcc remove several symbols from the static lib project. Using the whole-archive and force-load option fixes this, but there doesn't appear to be a way to link a library with these options.

The only related issue I could find was this: https://github.com/premake/premake-core/pull/662, which refers to this commit: https://github.com/premake/premake-core/pull/662/commits/2e3370e6a37b7359bf22cfd88d5664908e24b047

Am I out of luck?

catb0t commented 5 years ago

the linker has decided that your symbols are unused and optimises them out. the UNIX linker ld has a --no-as-needed option to disable exactly this (https://linux.die.net/man/1/ld, search for "as-needed") but that doesn't work on Windows so perhaps Premake could include a cross-platform flag?

https://stackoverflow.com/questions/14116420/how-to-force-gcc-to-link-an-unused-static-library

aschneid1 commented 5 years ago

It looks like as-needed is specifically for dynamic libraries, while the whole-archive and force-load options work for static (although that may be wrong).

In either case, applying -Wl options to specific libraries doesn't seem possible with premake at the moment.

For what it's worth, I hacked around my problem by just specifying the object directory for each project to be the same. That way if I compile (for example) ProjectTitle, it generates all of its object files. If I then compile ProjectTitleTests, it finds object files (if using the same configuration) that were compiled by the ProjectTitle project and links those in. It's not ideal, but it works. I can just place this

    objdir("!./config/build/" .. _ACTION .. "/obj/%{cfg.buildcfg}")

in each project to ensure they share the same object directory (in this case, the current directory/config/build/[vs2017,gmake,etc.]/obj/[release,debug,etc.]).

ratzlaff commented 5 years ago

In either case, applying -Wl options to specific libraries doesn't seem possible with premake at the moment

Does this not work for your situation?

project "blah"
  filter { "system:linux" }
    linkoptions { "-Wl," .. option }
aschneid1 commented 5 years ago

Not easily, but I managed -- it just wasn't pretty. We have 4 configurations: balanced, debug, profile, and release.

Targeting the correctly built library for the current configuration is finicky. I had to do something like:

function linkStaticLib()
    -- A hack to be able to link StaticLib with whole-archive
    -- Should ideally just be links{"StaticLib"}, then it would just...work
    libdirs{"./config/build/staticlib"}
    dependson{"StaticLibProject"}

    -- GCC Options
    filter { "toolset:gcc", "configurations:Balacned" }
        linkoptions{"-Wl,--whole-archive libStaticLibBalanced.a -Wl,--no-whole-archive"}
    filter { "toolset:gcc", "configurations:Release" }
        linkoptions{"-Wl,--whole-archive libStaticLibRelease.a -Wl,--no-whole-archive"}
    filter { "toolset:gcc", "configurations:Debug" }
        linkoptions{"-Wl,--whole-archive libStaticLibDebug.a -Wl,--no-whole-archive"}       
    filter { "toolset:gcc", "configurations:Profile" }
        linkoptions{"-Wl,--whole-archive libStaticLibProfile.a -Wl,--no-whole-archive"}

    -- Clang options
    filter { "toolset:clang", "configurations:Balanced" }
        linkoptions{"-Wl,-force-load,libStaticLibBalanced.a"}
    filter { "toolset:clang", "configurations:Release" }
        linkoptions{"-Wl,-force-load,libStaticLibRelease.a"}
    filter { "toolset:clang", "configurations:Debug" }
        linkoptions{"-Wl,-force-load,libStaticLibDebug.a"}       
    filter { "toolset:clang", "configurations:Profile" }
        linkoptions{"-Wl,-force-load,libStaticLibProfile.a"}
    filter{}
end

That works, but it's not super clear, and isn't very maintainable. Ideally, we would have something more like linksWithOption{"StaticLib", "force-load"} that would correctly build the project StaticLib for the current configuration, link it, and wrap it in -Wl,-force-load or -Wl-whole-archive/no-whole-archive options.

tdesveauxPKFX commented 5 years ago

To shorten your workaround, you could use tokens and have something like this

   -- GCC Options
    filter { "toolset:gcc" }
        linkoptions{"-Wl,--whole-archive libStaticLib%{cfg.buildcfg}.a -Wl,--no-whole-archive"}

    -- Clang options
    filter { "toolset:clang" }
        linkoptions{"-Wl,-force-load,libStaticLib%{cfg.buildcfg}.a"}
    filter{}

%{cfg.buildcfg} will be substituted with the configuration name (so Balanced, Release, etc).

Anyway, this is a workaround and we should have a proper way to do this. I think we could expand the link mode with a :whole which pull the lib in a whole group inside the static group (I don't think while linking is a thing for dynamic libs). I will see if I find the time to look into it.

aschneid1 commented 5 years ago

%{cfg.buildcfg} will be substituted with the configuration name (so Balanced, Release, etc).

Derp. Way better idea than dumping all of my obj files into the same directory (causes conflicts if files from two projects happen to have the same name).

Thanks!

anonymouss commented 4 years ago

probably should be -force_load not -force-load for Clang?

lsem commented 4 years ago

On OSX try -all_load and -noall_load linker options.

otavepto commented 2 months ago

Hey, sorry to revive this old one. I assume this is the part of the code responsible for wrapping libs with -Bstatic ... -Bdynamic https://github.com/premake/premake-core/blob/d842e671c7bc7e09f2eeaafd199fd01e48b87ee7/src/tools/gcc.lua#L590-L591

If possible, maybe something like mylib:static_whole would help, so later you can add

-- ...
-- ...
local static_whole_syslibs = {"-Wl,--whole-archive -Wl,-Bstatic"}
-- ...
-- ...
elseif endswith(name, ":static_whole") then
  name = string.sub(name, 0, -14)
  table.insert(static_whole_syslibs, "-l" .. name)
-- ...
-- ...
if #static_whole_syslibs > 1 then
    table.insert(static_whole_syslibs, "-Wl,-Bdynamic -Wl,--no-whole-archive")
    move(static_whole_syslibs, result)
end

I can submit a PR if that's accepted. Edit: For anyone looking for a quick and dirty solution, here's what I came up with. Just add it to the top of your .lua script

-- add "-Wl,--whole-archive -Wl,-Bstatic -lmylib -Wl,-Bdynamic -Wl,--no-whole-archive"
-- via: links { 'mylib:static_whole' }
premake.override(premake.tools.gcc, "getlinks", function(originalFn, cfg, systemonly, nogroups)
    -- source:    
    -- premake.tools.gcc.getlinks(cfg, systemonly, nogroups)
    -- https://github.com/premake/premake-core/blob/d842e671c7bc7e09f2eeaafd199fd01e48b87ee7/src/tools/gcc.lua#L568C15-L568C22

    local result = originalFn(cfg, systemonly, nogroups)
    local static_whole_syslibs = {"-Wl,--whole-archive -Wl,-Bstatic"}

    local endswith = function(s, ptrn)
        return ptrn == string.sub(s, -string.len(ptrn))
    end

    local idx_to_remove = {}
    for idx, name in ipairs(result) do
        if endswith(name, ":static_whole") then
            name = string.sub(name, 0, -14)
            table.insert(static_whole_syslibs, name) -- it already includes '-l'
            table.insert(idx_to_remove, idx)
        end
    end

    -- remove from the end to avoid trouble with table indexes shifting
    for iii = #idx_to_remove, 1, -1 do
        table.remove(result, idx_to_remove[iii])
    end

    local move = function(a1, a2)
        local t = #a2
        for i = 1, #a1 do a2[t + i] = a1[i] end
    end

    local new_result = {}
    if #static_whole_syslibs > 1 then
        table.insert(static_whole_syslibs, "-Wl,-Bdynamic -Wl,--no-whole-archive")
        move(static_whole_syslibs, new_result)
    end

    -- https://stackoverflow.com/a/71719579
    -- because of the way linux handles linking, the order on the commnadline becomes important
    -- static whole libs are added first since they'll retain all symbols,
    -- non-static/whole libs are appended afterwards, in case one of the static/whole libs
    -- mentions one of the symbols provided later by the non-static/whole libs
    -- otherwise symbols from the dynamic/non-whole libs might be removed during linking, resulting in linking failure
    move(result, new_result)
    return new_result
end)