diff --git a/CHANGES.txt b/CHANGES.txt
index 97dd04fb6a..b87d81f4fa 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -210,7 +210,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Eliminate more http: references (mostly in comments/docstrings where
they really weren't harmful). A few links labeled dead with no alt.
- Add JDK 21 LTS support
-
+ - Add a LIBLITERALPREFIX variable which can be set to the linker's
+ prefix for considering a library argument unmodified (e.g. for the
+ GNU linker, the ':' in '-l:libfoo.a'). Fixes Github issue #3951.
From Jonathon Reinhart:
- Fix another instance of `int main()` in CheckLib() causing failures
diff --git a/RELEASE.txt b/RELEASE.txt
index 92896d3072..546bba7661 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -23,6 +23,9 @@ NEW FUNCTIONALITY
- MSVC: If necessary, automatically define VSCMD_SKIP_SENDTELEMETRY for VS2019 and later
on arm64 hosts when using an arm (32-bit) build of python to prevent a powershell
error pop-up window (powershell dll not found).
+- Add a LIBLITERALPREFIX variable which can be set to the linker's
+ prefix for considering a library argument unmodified (e.g. for the
+ GNU linker, the ':' in '-l:libfoo.a'). Fixes Github issue #3951.
DEPRECATED FUNCTIONALITY
------------------------
diff --git a/SCons/Defaults.py b/SCons/Defaults.py
index cabadcc679..32e9da3f25 100644
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -36,7 +36,7 @@
import stat
import sys
import time
-from typing import List
+from typing import List, Callable
import SCons.Action
import SCons.Builder
@@ -455,16 +455,35 @@ def _concat_ixes(prefix, items_iter, suffix, env):
return result
-def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
+def _stripixes(
+ prefix: str,
+ items,
+ suffix: str,
+ stripprefixes: List[str],
+ stripsuffixes: List[str],
+ env,
+ literal_prefix: str = "",
+ c: Callable[[list], list] = None,
+) -> list:
+ """Returns a list with text added to items after first stripping them.
+
+ A companion to :func:`_concat_ixes`, used by tools (like the GNU
+ linker) that need to turn something like ``libfoo.a`` into ``-lfoo``.
+ *stripprefixes* and *stripsuffixes* are stripped from *items*.
+ Calls function *c* to postprocess the result.
+
+ Args:
+ prefix: string to prepend to elements
+ items: string or iterable to transform
+ suffix: string to append to elements
+ stripprefixes: prefix string(s) to strip from elements
+ stripsuffixes: suffix string(s) to strip from elements
+ env: construction environment for variable interpolation
+ c: optional function to perform a transformation on the list.
+ The default is `None`, which will select :func:`_concat_ixes`.
"""
- This is a wrapper around _concat()/_concat_ixes() that checks for
- the existence of prefixes or suffixes on list items and strips them
- where it finds them. This is used by tools (like the GNU linker)
- that need to turn something like 'libfoo.a' into '-lfoo'.
- """
-
- if not itms:
- return itms
+ if not items:
+ return items
if not callable(c):
env_c = env['_concat']
@@ -480,8 +499,16 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
stripprefixes = list(map(env.subst, flatten(stripprefixes)))
stripsuffixes = list(map(env.subst, flatten(stripsuffixes)))
+ # This is a little funky: if literal_prefix is the same as os.pathsep
+ # (e.g. both ':'), the normal conversion to a PathList will drop the
+ # literal_prefix prefix. Tell it not to split in that case, which *should*
+ # be okay because if we come through here, we're normally processing
+ # library names and won't have strings like "path:secondpath:thirdpath"
+ # which is why PathList() otherwise wants to split strings.
+ do_split = not literal_prefix == os.pathsep
+
stripped = []
- for l in SCons.PathList.PathList(itms).subst_path(env, None, None):
+ for l in SCons.PathList.PathList(items, do_split).subst_path(env, None, None):
if isinstance(l, SCons.Node.FS.File):
stripped.append(l)
continue
@@ -489,6 +516,10 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
if not is_String(l):
l = str(l)
+ if literal_prefix and l.startswith(literal_prefix):
+ stripped.append(l)
+ continue
+
for stripprefix in stripprefixes:
lsp = len(stripprefix)
if l[:lsp] == stripprefix:
diff --git a/SCons/Defaults.xml b/SCons/Defaults.xml
index b892f2ef9f..f1ba3860d3 100644
--- a/SCons/Defaults.xml
+++ b/SCons/Defaults.xml
@@ -250,7 +250,7 @@ as the result will be non-portable
and the directories will not be searched by the dependency scanner.
&cv-CPPPATH; should be a list of path strings,
or a single string, not a pathname list joined by
-Python's os.sep.
+Python's os.pathsep.
@@ -473,7 +473,7 @@ when the &cv-link-_LIBDIRFLAGS; variable is automatically generated.
An automatically-generated construction variable
containing the linker command-line options
for specifying libraries to be linked with the resulting target.
-The value of &cv-link-_LIBFLAGS; is created
+The value of &cv-_LIBFLAGS; is created
by respectively prepending and appending &cv-link-LIBLINKPREFIX;
and &cv-link-LIBLINKSUFFIX;
to each filename in &cv-link-LIBS;.
@@ -510,7 +510,7 @@ The list of directories that will be searched for libraries
specified by the &cv-link-LIBS; &consvar;.
&cv-LIBPATH; should be a list of path strings,
or a single string, not a pathname list joined by
-Python's os.sep.
+Python's os.pathsep.
+For each &Builder; call that causes linking with libraries,
+&SCons; will add the libraries in the setting of &cv-LIBS;
+in effect at that moment to the dependecy graph
+as dependencies of the target being generated.
+
+
+
+The library list will transformed to command line
+arguments through the automatically-generated
+&cv-link-_LIBFLAGS; &consvar;
which is constructed by
respectively prepending and appending the values of the
-&cv-LIBLINKPREFIX; and &cv-LIBLINKSUFFIX; &consvars;
-to each library name in &cv-LIBS;.
-Library name strings should not include a
-path component, instead the compiler will be
-directed to look for libraries in the paths
-specified by &cv-link-LIBPATH;.
+&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; &consvars;
+to each library name.
-Any command lines you define that need
-the &cv-LIBS; library list should
-include &cv-_LIBFLAGS;:
+Any command lines you define yourself that need
+the libraries from &cv-LIBS; should include &cv-_LIBFLAGS;
+(as well as &cv-link-_LIBDIRFLAGS;)
+rather than &cv-LIBS;.
+For example:
env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE")
+
+
+
+
-If you add a
-File
-object to the
-&cv-LIBS;
-list, the name of that file will be added to
-&cv-_LIBFLAGS;,
-and thus to the link line, as-is, without
-&cv-LIBLINKPREFIX;
-or
-&cv-LIBLINKSUFFIX;.
-For example:
+If the linker supports command line syntax directing
+that the argument specifying a library should be
+searched for literally (without modification),
+&cv-LIBLITERALPREFIX; can be set to that indicator.
+For example, the GNU linker follows this rule:
+
+-l:foo searches the library path
+for a filename called foo,
+without converting it to
+libfoo.so or
+libfoo.a.
+
+If &cv-LIBLITERALPREFIX; is set,
+&SCons; will not transform a string-valued entry in
+&cv-link-LIBS; that starts with that string.
+The entry will still be surrounded with
+&cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX;
+on the command line.
+This is useful, for example,
+in directing that a static library
+be used when both a static and dynamic library are available
+and linker policy is to prefer dynamic libraries.
+Compared to the example in &cv-link-LIBS;,
-
-env.Append(LIBS=File('/tmp/mylib.so'))
+env.Append(LIBS=":libmylib.a")
-
-In all cases, scons will add dependencies from the executable program to
-all the libraries in this list.
+will let the linker select that specific (static)
+library name if found in the library search path.
+This differs from using a
+File object
+to specify the static library,
+as the latter bypasses the library search path entirely.
diff --git a/SCons/Environment.py b/SCons/Environment.py
index 6327d86206..64d38b0768 100644
--- a/SCons/Environment.py
+++ b/SCons/Environment.py
@@ -37,6 +37,7 @@
import shlex
from collections import UserDict, deque
from subprocess import PIPE, DEVNULL
+from typing import Optional
import SCons.Action
import SCons.Builder
@@ -679,7 +680,7 @@ def gvars(self):
def lvars(self):
return {}
- def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: bool=False):
+ def subst(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None):
"""Recursively interpolates construction variables from the
Environment into the specified string, returning the expanded
result. Construction variables are specified by a $ prefix
@@ -705,9 +706,11 @@ def subst_kw(self, kw, raw: int=0, target=None, source=None):
nkw[k] = v
return nkw
- def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: bool=False):
- """Calls through to SCons.Subst.scons_subst_list(). See
- the documentation for that function."""
+ def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, executor=None, overrides: Optional[dict] = None):
+ """Calls through to SCons.Subst.scons_subst_list().
+
+ See the documentation for that function.
+ """
gvars = self.gvars()
lvars = self.lvars()
lvars['__env__'] = self
@@ -716,9 +719,10 @@ def subst_list(self, string, raw: int=0, target=None, source=None, conv=None, ex
return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv, overrides=overrides)
def subst_path(self, path, target=None, source=None):
- """Substitute a path list, turning EntryProxies into Nodes
- and leaving Nodes (and other objects) as-is."""
+ """Substitute a path list.
+ Turns EntryProxies into Nodes, leaving Nodes (and other objects) as-is.
+ """
if not is_List(path):
path = [path]
diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py
index 2f96679793..c40439ae07 100644
--- a/SCons/EnvironmentTests.py
+++ b/SCons/EnvironmentTests.py
@@ -1489,7 +1489,8 @@ def copy2(self, src, dst) -> None:
SCons.CacheDir.CacheDir.copy_from_cache = save_copy_from_cache
SCons.CacheDir.CacheDir.copy_to_cache = save_copy_to_cache
- def test_concat(self) -> None:
+ # function is in Defaults.py, tested here to use TestEnvironment
+ def test__concat(self) -> None:
"""Test _concat()"""
e1 = self.TestEnvironment(PRE='pre', SUF='suf', STR='a b', LIST=['a', 'b'])
s = e1.subst
@@ -1507,7 +1508,8 @@ def test_concat(self) -> None:
assert x == '$( preasuf prebsuf $)', x
- def test_concat_nested(self) -> None:
+ # function is in Defaults.py, tested here to use TestEnvironment
+ def test__concat_nested(self) -> None:
"""Test _concat() on a nested substitution strings."""
e = self.TestEnvironment(PRE='pre', SUF='suf',
L1=['a', 'b'],
@@ -1522,6 +1524,49 @@ def test_concat_nested(self) -> None:
x = e.subst('$( ${_concat(PRE, L1, SUF, __env__)} $)')
assert x == 'preasuf prebsuf precsuf predsuf precsuf predsuf', x
+
+ # function is in Defaults.py, tested here to use TestEnvironment
+ def test__stripixes(self) -> None:
+ """Test _stripixes()"""
+ # LIBPREFIXES and LIBSUFFIXES are stripped, except if an entry
+ # begins with LIBLITERALPREFIX. Check this with and without that
+ # argument being passed, and whether or not LIBLITERALPREFIX is
+ # explicitly set.
+ e = self.TestEnvironment(
+ PRE='pre',
+ SUF='suf',
+ LIST=['xxx-a', 'b.yyy', 'zzxxx-c.yyy'],
+ LIBPREFIXES=['xxx-'],
+ LIBSUFFIXES=['.yyy'],
+ )
+
+ # e['LIBLITERALPREFIX'] = ''
+ with self.subTest():
+ x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)')
+ self.assertEqual('preasuf prebsuf prezzxxx-csuf', x)
+
+ with self.subTest():
+ x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__)} $)')
+ self.assertEqual('preasuf prebsuf prezzxxx-csuf', x)
+
+ # add it to the env:
+ e['LIBLITERALPREFIX'] = 'zz'
+
+ with self.subTest():
+ x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)')
+ self.assertEqual('preasuf prebsuf prezzxxx-c.yyysuf', x)
+
+ with self.subTest():
+ x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__)} $)')
+ self.assertEqual('preasuf prebsuf prezzxxx-csuf', x)
+
+ # And special case: LIBLITERALPREFIX is the same as os.pathsep:
+ e['LIBLITERALPREFIX'] = os.pathsep
+ with self.subTest():
+ x = e.subst('$( ${_stripixes(PRE, LIST, SUF, LIBPREFIXES, LIBSUFFIXES,__env__, LIBLITERALPREFIX)} $)')
+ self.assertEqual('preasuf prebsuf prezzxxx-csuf', x)
+
+
def test_gvars(self) -> None:
"""Test the Environment gvars() method"""
env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z')
diff --git a/SCons/PathList.py b/SCons/PathList.py
index dab8b2ce41..33ac7e58be 100644
--- a/SCons/PathList.py
+++ b/SCons/PathList.py
@@ -64,10 +64,9 @@ def node_conv(obj):
return result
class _PathList:
- """
- An actual PathList object.
- """
- def __init__(self, pathlist) -> None:
+ """An actual PathList object."""
+
+ def __init__(self, pathlist, split=True) -> None:
"""
Initializes a PathList object, canonicalizing the input and
pre-processing it for quicker substitution later.
@@ -94,7 +93,10 @@ def __init__(self, pathlist) -> None:
over and over for each target.
"""
if SCons.Util.is_String(pathlist):
- pathlist = pathlist.split(os.pathsep)
+ if split:
+ pathlist = pathlist.split(os.pathsep)
+ else: # no splitting, but still need a list
+ pathlist = [pathlist]
elif not SCons.Util.is_Sequence(pathlist):
pathlist = [pathlist]
@@ -141,8 +143,7 @@ def subst_path(self, env, target, source):
class PathListCache:
- """
- A class to handle caching of PathList lookups.
+ """A class to handle caching of PathList lookups.
This class gets instantiated once and then deleted from the namespace,
so it's used as a Singleton (although we don't enforce that in the
@@ -161,7 +162,7 @@ class PathListCache:
The main type of duplication we're trying to catch will come from
looking up the same path list from two different clones of the
same construction environment. That is, given
-
+
env2 = env1.Clone()
both env1 and env2 will have the same CPPPATH value, and we can
@@ -189,7 +190,7 @@ def _PathList_key(self, pathlist):
return pathlist
@SCons.Memoize.CountDictCall(_PathList_key)
- def PathList(self, pathlist):
+ def PathList(self, pathlist, split=True):
"""
Returns the cached _PathList object for the specified pathlist,
creating and caching a new object as necessary.
@@ -206,7 +207,7 @@ def PathList(self, pathlist):
except KeyError:
pass
- result = _PathList(pathlist)
+ result = _PathList(pathlist, split)
memo_dict[pathlist] = result
diff --git a/SCons/Platform/Platform.xml b/SCons/Platform/Platform.xml
index c449fa50da..dc9ed795d4 100644
--- a/SCons/Platform/Platform.xml
+++ b/SCons/Platform/Platform.xml
@@ -49,7 +49,8 @@ to reflect the names of the libraries they create.
-A list of all legal prefixes for library file names.
+A list of all legal prefixes for library file names
+on the current platform.
When searching for library dependencies,
SCons will look for files with these prefixes,
the base library name,
@@ -75,6 +76,7 @@ to reflect the names of the libraries they create.
A list of all legal suffixes for library file names.
+on the current platform.
When searching for library dependencies,
SCons will look for files with prefixes from the &cv-link-LIBPREFIXES; list,
the base library name,
@@ -129,7 +131,7 @@ else:
platform argument to &f-link-Environment;).
- Should be considered immutable.
+ Should be considered immutable.
&cv-HOST_OS; is not currently used by &SCons;,
but the option is reserved to do so in future
@@ -177,7 +179,7 @@ else:
and x86_64 for 64-bit hosts.
- Should be considered immutable.
+ Should be considered immutable.
&cv-HOST_ARCH; is not currently used by other platforms,
but the option is reserved to do so in future
@@ -305,7 +307,7 @@ an alternate command line so the invoked tool will make
use of the contents of the temporary file.
If you need to replace the default tempfile object,
the callable should take into account the settings of
-&cv-link-MAXLINELENGTH;,
+&cv-link-MAXLINELENGTH;,
&cv-link-TEMPFILEPREFIX;,
&cv-link-TEMPFILESUFFIX;,
&cv-link-TEMPFILEARGJOIN;,
@@ -367,7 +369,7 @@ The directory to create the long-lines temporary file in.
-The default argument escape function is
+The default argument escape function is
SCons.Subst.quote_spaces.
If you need to apply extra operations on a command argument
(to fix Windows slashes, normalize paths, etc.)
diff --git a/SCons/Platform/cygwin.py b/SCons/Platform/cygwin.py
index c62a668b37..2353763d7c 100644
--- a/SCons/Platform/cygwin.py
+++ b/SCons/Platform/cygwin.py
@@ -47,8 +47,9 @@ def generate(env) -> None:
env['PROGSUFFIX'] = '.exe'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
- env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX' ]
- env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX' ]
+ env['LIBPREFIXES'] = ['$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX']
+ env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX']
+ env['LIBLITERAPPREFIX'] = ':'
env['TEMPFILE'] = TempFileMunge
env['TEMPFILEPREFIX'] = '@'
env['MAXLINELENGTH'] = 2048
diff --git a/SCons/Platform/os2.py b/SCons/Platform/os2.py
index 7394aa8995..72bb034024 100644
--- a/SCons/Platform/os2.py
+++ b/SCons/Platform/os2.py
@@ -43,8 +43,9 @@ def generate(env) -> None:
env['LIBSUFFIX'] = '.lib'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
- env['LIBPREFIXES'] = '$LIBPREFIX'
- env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
+ env['LIBPREFIXES'] = ['$LIBPREFIX']
+ env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX']
+ env['LIBLITERAPPREFIX'] = ''
env['HOST_OS'] = 'os2'
env['HOST_ARCH'] = win32.get_architecture().arch
diff --git a/SCons/Platform/posix.py b/SCons/Platform/posix.py
index 55b00b4db1..b655b77d51 100644
--- a/SCons/Platform/posix.py
+++ b/SCons/Platform/posix.py
@@ -93,8 +93,9 @@ def generate(env) -> None:
env['LIBSUFFIX'] = '.a'
env['SHLIBPREFIX'] = '$LIBPREFIX'
env['SHLIBSUFFIX'] = '.so'
- env['LIBPREFIXES'] = [ '$LIBPREFIX' ]
- env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
+ env['LIBPREFIXES'] = ['$LIBPREFIX']
+ env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX']
+ env['LIBLITERALPREFIX'] = ''
env['HOST_OS'] = 'posix'
env['HOST_ARCH'] = platform.machine()
env['PSPAWN'] = pspawn
diff --git a/SCons/Platform/win32.py b/SCons/Platform/win32.py
index da9e5740f3..b145823616 100644
--- a/SCons/Platform/win32.py
+++ b/SCons/Platform/win32.py
@@ -420,8 +420,9 @@ def generate(env):
env['LIBSUFFIX'] = '.lib'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
- env['LIBPREFIXES'] = [ '$LIBPREFIX' ]
- env['LIBSUFFIXES'] = [ '$LIBSUFFIX' ]
+ env['LIBPREFIXES'] = ['$LIBPREFIX']
+ env['LIBSUFFIXES'] = ['$LIBSUFFIX']
+ env['LIBLITERALPREFIX'] = ''
env['PSPAWN'] = piped_spawn
env['SPAWN'] = spawn
env['SHELL'] = cmd_interp
diff --git a/SCons/Subst.py b/SCons/Subst.py
index 4046ca6b53..b04ebe50cd 100644
--- a/SCons/Subst.py
+++ b/SCons/Subst.py
@@ -26,6 +26,7 @@
import collections
import re
from inspect import signature, Parameter
+from typing import Optional
import SCons.Errors
from SCons.Util import is_String, is_Sequence
@@ -448,11 +449,12 @@ def substitute(self, args, lvars):
This serves as a wrapper for splitting up a string into
separate tokens.
"""
+ def sub_match(match):
+ return self.conv(self.expand(match.group(1), lvars))
+
if is_String(args) and not isinstance(args, CmdStringHolder):
args = str(args) # In case it's a UserString.
try:
- def sub_match(match):
- return self.conv(self.expand(match.group(1), lvars))
result = _dollar_exps.sub(sub_match, args)
except TypeError:
# If the internal conversion routine doesn't return
@@ -805,7 +807,7 @@ def _remove_list(list):
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
-def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: bool=False):
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None):
"""Expand a string or list containing construction variable
substitutions.
@@ -887,7 +889,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
return result
-def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None,overrides: bool=False):
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None, overrides: Optional[dict] = None):
"""Substitute construction variables in a string (or list or other
object) and separate the arguments into a command list.
diff --git a/SCons/Tool/dmd.py b/SCons/Tool/dmd.py
index 553670016b..2ac84d0cf2 100644
--- a/SCons/Tool/dmd.py
+++ b/SCons/Tool/dmd.py
@@ -138,7 +138,7 @@ def generate(env) -> None:
env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
- env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
+ env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}'
env['DLIBDIRPREFIX'] = '-L-L'
env['DLIBDIRSUFFIX'] = ''
diff --git a/SCons/Tool/gnulink.py b/SCons/Tool/gnulink.py
index 159aa972cb..c636c11d23 100644
--- a/SCons/Tool/gnulink.py
+++ b/SCons/Tool/gnulink.py
@@ -52,6 +52,9 @@ def generate(env) -> None:
env['RPATHSUFFIX'] = ''
env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'
+ env['LIBLITERALPREFIX'] = ':'
+
+
def exists(env):
# TODO: sync with link.smart_link() to choose a linker
diff --git a/SCons/Tool/ldc.py b/SCons/Tool/ldc.py
index bd0767e7f5..6ee022aa34 100644
--- a/SCons/Tool/ldc.py
+++ b/SCons/Tool/ldc.py
@@ -73,7 +73,7 @@ def generate(env) -> None:
env['_DDEBUGFLAGS'] = '${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)}'
env['_DI_FLAGS'] = "${DI_FILE_DIR and DI_FILE_DIR_PREFIX+DI_FILE_DIR+DI_FILE_DIR_SUFFFIX}"
-
+
env['_DFLAGS'] = '${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)}'
env['SHDC'] = '$DC'
@@ -96,10 +96,10 @@ def generate(env) -> None:
env['DFLAGPREFIX'] = '-'
env['DFLAGSUFFIX'] = ''
env['DFILESUFFIX'] = '.d'
-
+
env['DI_FILE_DIR'] = ''
env['DI_FILE_SUFFIX'] = '.di'
-
+
env['DI_FILE_DIR_PREFIX'] = '-Hd='
env['DI_FILE_DIR_SUFFFIX'] = ''
@@ -115,7 +115,7 @@ def generate(env) -> None:
env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
# env['_DLIBFLAGS'] = '${_concat(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, __env__, RDirs, TARGET, SOURCE)}'
- env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
+ env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}'
env['DLIBDIRPREFIX'] = '-L-L'
env['DLIBDIRSUFFIX'] = ''
diff --git a/SCons/Tool/link.py b/SCons/Tool/link.py
index e879ae8623..cd8b2f88b5 100644
--- a/SCons/Tool/link.py
+++ b/SCons/Tool/link.py
@@ -55,7 +55,7 @@ def generate(env) -> None:
env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LIBDIRPREFIX'] = '-L'
env['LIBDIRSUFFIX'] = ''
- env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
+ env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}'
env['LIBLINKPREFIX'] = '-l'
env['LIBLINKSUFFIX'] = ''
diff --git a/test/Libs/LIBLITERALPREFIX.py b/test/Libs/LIBLITERALPREFIX.py
new file mode 100644
index 0000000000..ebd32b6ae5
--- /dev/null
+++ b/test/Libs/LIBLITERALPREFIX.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+#
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+Test LIBLITERALPREFIX behavior.
+
+Build a static library and shared library of the same name,
+and make sure the requested syntax selects the static one.
+Depends on a live compiler to build libraries and executable,
+and actually runs the executable.
+"""
+
+import os
+import sys
+
+import TestSCons
+from TestSCons import lib_, _lib
+
+test = TestSCons.TestSCons()
+
+if sys.platform == 'win32':
+ import SCons.Tool.MSCommon as msc
+ if msc.msvc_exists():
+ test.skip_test("Functionality not available for msvc, skipping test.\n")
+
+test.write('SConstruct', """\
+LIBLITERALPREFIX = ":"
+env = Environment(LIBLITERALPREFIX=LIBLITERALPREFIX, LIBPATH=".")
+lib = env.Library(target='foo', source='foo.c')
+shlib = env.SharedLibrary(target='foo', source='shfoo.c')
+env.Program(target='prog', source=['prog.c'], LIBS=f"{LIBLITERALPREFIX}libfoo.a")
+""")
+
+test.write('shfoo.c', r"""
+#include
+
+void
+foo(void)
+{
+ printf("shared libfoo\n");
+}
+""")
+
+test.write('foo.c', r"""
+#include
+
+void
+foo(void)
+{
+ printf("static libfoo\n");
+}
+""")
+
+test.write('prog.c', r"""
+#include
+
+void foo(void);
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ foo();
+ printf("prog.c\n");
+ return 0;
+}
+""")
+
+test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall)
+test.fail_test(not os.path.exists(test.workpath(f"{lib_}foo{_lib}")))
+
+test.run(program=test.workpath('prog'), stdout="static libfoo\nprog.c\n")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/Libs/LIBPREFIXES.py b/test/Libs/LIBPREFIXES.py
index e496ca4baa..2bf3c9e205 100644
--- a/test/Libs/LIBPREFIXES.py
+++ b/test/Libs/LIBPREFIXES.py
@@ -23,6 +23,13 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+Test LIBPREFIXES.
+
+Depends on a live compiler to build library and executable,
+and actually runs the executable.
+"""
+
import os
import sys
@@ -36,11 +43,10 @@
test = TestSCons.TestSCons()
-test.write('SConstruct', """
-env = Environment(LIBPREFIX = 'xxx-',
- LIBPREFIXES = ['xxx-'])
-lib = env.Library(target = 'foo', source = 'foo.c')
-env.Program(target = 'prog', source = ['prog.c', lib])
+test.write('SConstruct', """\
+env = Environment(LIBPREFIX='xxx-', LIBPREFIXES=['xxx-'])
+lib = env.Library(target='foo', source='foo.c')
+env.Program(target='prog', source=['prog.c', lib])
""")
test.write('foo.c', r"""
@@ -67,13 +73,10 @@
}
""")
-test.run(arguments = '.',
- stderr=TestSCons.noisy_ar,
- match=TestSCons.match_re_dotall)
-
+test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall)
test.fail_test(not os.path.exists(test.workpath('xxx-foo' + _lib)))
-test.run(program = test.workpath('prog'), stdout = "foo.c\nprog.c\n")
+test.run(program=test.workpath('prog'), stdout="foo.c\nprog.c\n")
test.pass_test()
diff --git a/test/Libs/LIBSUFFIXES.py b/test/Libs/LIBSUFFIXES.py
index 13baeab9ff..06e4506ac4 100644
--- a/test/Libs/LIBSUFFIXES.py
+++ b/test/Libs/LIBSUFFIXES.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# MIT License
+#
+# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -20,29 +22,31 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Test LIBSUFFIXES.
+
+Depends on a live compiler to build library and executable,
+and actually runs the executable.
+"""
import os
import sys
+
import TestSCons
+from TestSCons import lib_
if sys.platform == 'win32':
- lib_ = ''
import SCons.Tool.MSCommon as msc
if not msc.msvc_exists():
lib_ = 'lib'
-else:
- lib_ = 'lib'
test = TestSCons.TestSCons()
-test.write('SConstruct', """
-env = Environment(LIBSUFFIX = '.xxx',
- LIBSUFFIXES = ['.xxx'])
-lib = env.Library(target = 'foo', source = 'foo.c')
-env.Program(target = 'prog', source = ['prog.c', lib])
+test.write('SConstruct', """\
+env = Environment(LIBSUFFIX='.xxx', LIBSUFFIXES=['.xxx'])
+lib = env.Library(target='foo', source='foo.c')
+env.Program(target='prog', source=['prog.c', lib])
""")
test.write('foo.c', r"""
@@ -69,13 +73,10 @@
}
""")
-test.run(arguments = '.',
- stderr=TestSCons.noisy_ar,
- match=TestSCons.match_re_dotall)
-
+test.run(arguments='.', stderr=TestSCons.noisy_ar, match=TestSCons.match_re_dotall)
test.fail_test(not os.path.exists(test.workpath(lib_ + 'foo.xxx')))
-test.run(program = test.workpath('prog'), stdout = "foo.c\nprog.c\n")
+test.run(program=test.workpath('prog'), stdout="foo.c\nprog.c\n")
test.pass_test()
diff --git a/test/Libs/Library.py b/test/Libs/Library.py
index 50fe68bf7d..04ba37dff1 100644
--- a/test/Libs/Library.py
+++ b/test/Libs/Library.py
@@ -1,6 +1,8 @@
#!/usr/bin/env python
#
-# __COPYRIGHT__
+# MIT License
+#
+# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -20,9 +22,7 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import TestSCons