Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support linker specifying "unmodified library names" #3951

Closed
mwichmann opened this issue Jun 7, 2021 · 7 comments · Fixed by #4429
Closed

support linker specifying "unmodified library names" #3951

mwichmann opened this issue Jun 7, 2021 · 7 comments · Fixed by #4429
Milestone

Comments

@mwichmann
Copy link
Collaborator

mwichmann commented Jun 7, 2021

Issue created after discussion on libera.chat irc channel #scons.

The GNU linker now has syntax which allows specifying a library filename which will not be modified before searching for it. This is a lightly edited copy of the text from a recent manpage:

-l namespec, --library=namespec
Add the archive or object file specified by namespec to the list of files to link. This option may be used any number of times. If namespec is of the form :filename, ld will search the library path for a file called filename, otherwise it will search the library path for a file called libnamespec.a. ... on ELF and SunOS systems, ld will search a directory for a library called libnamespec.so before searching for one called libnamespec.a. (By convention, a .so extension indicates a shared library.) Note that this behavior does not apply to :filename, which always specifies a file called filename.

Seems like SCons should understand this :foo syntax in handling LIBS. The alternative of trying to specify a specific library name/path which is taken without modification is to add it to LINKFLAGS instead of LIBS, but that loses the library ordering of the command line, so usually isn't a desirable approach. The other approach of using File('libname') to specify the library is okay for a specific project-local library, but I believe will not respect the defined library search path as the colon syntax does.

A little research shows this was added in binutils 2.18 (which was 2007-ish):

Changes in 2.18:
...
* -l:foo now searches the library path for a filename called foo, 
  without converting it to libfoo.a or libfoo.so.
@mwichmann
Copy link
Collaborator Author

I suppose the more general purpose way to handle this is a new construction var that describes a no-modify prefix - the code could just look for a libname that starts with : and not transform it, but that wires in gcc syntax...

@bdbaddog
Copy link
Contributor

bdbaddog commented Jun 8, 2021

Related (slightly) to #1161

@mwichmann
Copy link
Collaborator Author

mwichmann commented Jun 10, 2021

These issues are related: #2743 #2925 #2912

@mwichmann
Copy link
Collaborator Author

mwichmann commented Aug 15, 2023

Seems like this can be implemented in _stripixes - simply telling it not to strip if the magic prefix is found. I'm presuming we'd want to parameterize that (construction variable). The following works for very simple tests - it's not a complete patch as we'd need to set up initialization of the "literal" prefix and documentation.

diff --git a/SCons/Defaults.py b/SCons/Defaults.py
index cabadcc67..534c57575 100644
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -477,6 +477,7 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
         else:
             c = _concat_ixes
 
+    libliteral = env.get('LIBLITERAL')
     stripprefixes = list(map(env.subst, flatten(stripprefixes)))
     stripsuffixes = list(map(env.subst, flatten(stripsuffixes)))
 
@@ -489,6 +490,11 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
         if not is_String(l):
             l = str(l)
 
+        # if special prefix is found, do not strip the name
+        if libliteral and l.startswith(libliteral):
+            stripped.append(l)
+            continue
+
         for stripprefix in stripprefixes:
             lsp = len(stripprefix)
             if l[:lsp] == stripprefix:

Verified only with a trivial unittest (pending a decision of whether this is something we even want to do):

diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py
index e7cb99f34..2fa917d2a 100644
--- a/SCons/EnvironmentTests.py
+++ b/SCons/EnvironmentTests.py
@@ -1522,6 +1522,21 @@ def exists(env):
         x = e.subst('$( ${_concat(PRE, L1, SUF, __env__)} $)')
         assert x == 'preasuf prebsuf precsuf predsuf precsuf predsuf', x
 
+
+    def test__stripixes(self) -> None:
+        """Test _stripixes()"""
+        e = self.TestEnvironment(
+            PRE='PRE',
+            SUF='SUF',
+            LIST=['xxx-a', 'b.yyy', 'zzxxx-c.yyy'],
+            LIBPREFIXES=['xxx-'],
+            LIBSUFFIXES=['.yyy'],
+            LIBLITERAL='zz',
+        )
+        x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES, __env__)} $)')
+        self.assertEqual(x, 'PREaSUF PREbSUF PREzzxxx-c.yyySUF')
+
+
     def test_gvars(self) -> None:
         """Test the Environment gvars() method"""
         env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z')

@mwichmann mwichmann added the has patch Issue has attached Patch label Aug 15, 2023
@mwichmann
Copy link
Collaborator Author

Sadly, while the change works fine at the _stripixes level, but it doesn't actually work at the E2E level, so there's something else going on.

@mwichmann
Copy link
Collaborator Author

The "something else" is a call to PathList, which, for string-valued members, does a split on os.pathsep. Which just happens to be the same magic character as the literal-lib introducer.

mwichmann added a commit to mwichmann/scons that referenced this issue Oct 11, 2023
This is done in a general way. using a construction var, although
at the moment only the GNU linker is known to handle things this way.

Had to do something funky, or it won't work when os.pathsep and
LIBLITRAL are the same, as they are on Linux (i.e. ':'). That's because
SCons.PathList.PathList is called and it does:

class _Pathlist:
    def __init__(self, pathlist) -> None:
        if SCons.Util.is_String(pathlist):
            pathlist = pathlist.split(os.pathsep)

Splitting has the effect of turning ":libm.a" into ((0, ''), (0, 'libm.a')]
That is, the ':' is lost as part of the library specifier - so need to
try to avoid that.

Fixes SCons#3951

Signed-off-by: Mats Wichmann <[email protected]>
mwichmann added a commit to mwichmann/scons that referenced this issue Oct 28, 2023
This is done in a general way. using a construction var, although
at the moment only the GNU linker is known to handle things this way.

Had to do something funky, or it won't work when os.pathsep and
LIBLITRAL are the same, as they are on Linux (i.e. ':'). That's because
SCons.PathList.PathList is called and it does:

class _Pathlist:
    def __init__(self, pathlist) -> None:
        if SCons.Util.is_String(pathlist):
            pathlist = pathlist.split(os.pathsep)

Splitting has the effect of turning ":libm.a" into ((0, ''), (0, 'libm.a')]
That is, the ':' is lost as part of the library specifier - so need to
try to avoid that.

Fixes SCons#3951

Signed-off-by: Mats Wichmann <[email protected]>
@mwichmann mwichmann added this to the 4.6 milestone Oct 30, 2023
@augfab
Copy link

augfab commented Oct 31, 2023

Thanks a lot for the fix. I'm looking forward to version 4.6 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants