From d7904efb2c949647341d364d2235b85b21fc71b9 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sat, 19 Aug 2023 06:44:38 -0600 Subject: Add LIBLITERAL to support gcc -l:filename 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 #3951 Signed-off-by: Mats Wichmann --- SCons/Defaults.py | 53 +++++++++++++++++++----- SCons/EnvironmentTests.py | 24 ++++++++++- SCons/PathList.py | 21 +++++----- test/Libs/LIBLITERAL.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++ test/Libs/LIBPREFIXES.py | 23 ++++++----- test/Libs/LIBSUFFIXES.py | 33 +++++++-------- 6 files changed, 206 insertions(+), 49 deletions(-) create mode 100644 test/Libs/LIBLITERAL.py diff --git a/SCons/Defaults.py b/SCons/Defaults.py index cabadcc..c0e7afc 100644 --- a/SCons/Defaults.py +++ b/SCons/Defaults.py @@ -36,7 +36,7 @@ import shutil import stat import sys import time -from typing import List +from typing import List, Callable import SCons.Action import SCons.Builder @@ -455,16 +455,34 @@ 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: str, + stripsuffixes: str, + env, + 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 +498,17 @@ 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 $LIBLITERAL is the same as os.pathsep + # (e.g. both ':'), the normal conversion to a PathList will drop the + # $LIBLITERAL 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. + libliteral = env.get('LIBLITERAL') + do_split = not libliteral == 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 libliteral and l.startswith(libliteral): + stripped.append(l) + continue + for stripprefix in stripprefixes: lsp = len(stripprefix) if l[:lsp] == stripprefix: diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py index 2f96679..756e055 100644 --- a/SCons/EnvironmentTests.py +++ b/SCons/EnvironmentTests.py @@ -1489,7 +1489,8 @@ def exists(env): 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 exists(env): 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,24 @@ def exists(env): 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 LIBLITERAL + 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') diff --git a/SCons/PathList.py b/SCons/PathList.py index dab8b2c..33ac7e5 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 @@ class _PathList: 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 @@ class _PathList: 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 @@ class PathListCache: 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 @@ class PathListCache: except KeyError: pass - result = _PathList(pathlist) + result = _PathList(pathlist, split) memo_dict[pathlist] = result diff --git a/test/Libs/LIBLITERAL.py b/test/Libs/LIBLITERAL.py new file mode 100644 index 0000000..e99fa1f --- /dev/null +++ b/test/Libs/LIBLITERAL.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 LIBLITERAL 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', """\ +LIBLITERAL = ":" +env = Environment(LIBLITERAL=LIBLITERAL, 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"{LIBLITERAL}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 e496ca4..2bf3c9e 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 @@ if sys.platform == 'win32': 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 @@ main(int argc, char *argv[]) } """) -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 13baeab..06e4506 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 @@ main(int argc, char *argv[]) } """) -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() -- cgit v0.12