From c727d0da49e49d9a4e26f2ddfceef92d607b6b87 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 25 May 2023 15:16:37 -0700 Subject: [PATCH] Get include directories for CC-like compilers if possible This also lets us avoid unnecessarily passing default include directories on the command line. --- CHANGES.md | 1 + bfg9000/tools/cc/compiler.py | 52 +++++++++++++++++++++++------ bfg9000/tools/cc/linker.py | 3 ++ doc/about/changes.md | 1 + test/unit/tools/cc/test_compiler.py | 50 +++++++++++++++++++++------ test/unit/tools/cc/test_linker.py | 17 +++++++++- 6 files changed, 102 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 40bb49ed..433abcc2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Bug fixes - When using the `PKG_CONFIG_PATH` specified by mopack, add it to any existing `PKG_CONFIG_PATH` from the environment +- Don't explicitly include default `#include` paths on CC-like compilers --- diff --git a/bfg9000/tools/cc/compiler.py b/bfg9000/tools/cc/compiler.py index 615bcde0..a69a546e 100644 --- a/bfg9000/tools/cc/compiler.py +++ b/bfg9000/tools/cc/compiler.py @@ -1,11 +1,12 @@ from itertools import chain -from ... import options as opts, safe_str +from ... import options as opts, safe_str, shell from .flags import optimize_flags from ..common import BuildCommand from ...file_types import ObjectFile, PrecompiledHeader -from ...iterutils import iterate -from ...path import Path +from ...iterutils import default_sentinel, iterate +from ...objutils import memoize_method +from ...path import abspath, Path from ...versioning import SpecifierSet @@ -22,8 +23,37 @@ def needs_libs(self): def needs_package_options(self): return True + @memoize_method + def _search_dirs(self, cpath=default_sentinel, strict=False): + try: + extra_env = ({'CPATH': cpath or ''} + if cpath is not default_sentinel else None) + output = self.env.execute( + (self.command + self._always_flags + self.global_flags + + ['-E', '-Wp,-v', '/dev/null']), + extra_env=extra_env, stdout=shell.Mode.pipe, + stderr=shell.Mode.stdout + ) + + found = False + dirs = [] + for i in output.splitlines(): # pragma: no branch + if i == '#include <...> search starts here:': + found = True + elif i == 'End of search list.': + break + elif i[0] != ' ': + found = False + elif found: + dirs.append(abspath(i[1:])) + return dirs + except (OSError, shell.CalledProcessError): + if strict: + raise + return self.env.variables.getpaths('CPATH') + def search_dirs(self, strict=False): - return self.env.variables.getpaths('CPATH') + return self._search_dirs(strict=strict) def _call(self, cmd, input, output, deps=None, flags=None): result = list(chain( @@ -49,12 +79,14 @@ def _always_flags(self): return flags def _include_dir(self, directory, allow_system): - is_default = directory.path in self.env.host_platform.include_dirs - - # Don't include default directories as system dirs (e.g. /usr/include). - # Doing so would break GCC 6 when #including stdlib.h: - # . - if allow_system and directory.system and not is_default: + default_dirs = self._search_dirs(None) + if directory.path in default_dirs: + # Don't include default directories (e.g. /usr/include). Including + # them as system dirs would break GCC 6 when #including stdlib.h: + # . Including + # them as regular directories isn't right either. + return [] + elif allow_system and directory.system: return ['-isystem', directory.path] else: return ['-I' + directory.path] diff --git a/bfg9000/tools/cc/linker.py b/bfg9000/tools/cc/linker.py index bacf1459..9857fd7c 100644 --- a/bfg9000/tools/cc/linker.py +++ b/bfg9000/tools/cc/linker.py @@ -9,6 +9,7 @@ from ...builtins.copy_file import CopyFile from ...file_types import * from ...iterutils import first, iterate, listify, recursive_walk, uniques +from ...objutils import memoize_method from ...path import abspath, Path from ...versioning import SpecifierSet from ...packages import Framework @@ -71,6 +72,7 @@ def _has_link_macros(self): # and only define the macros if it does. return self.env.target_platform.has_import_library + @memoize_method def sysroot(self, strict=False): try: # XXX: clang doesn't support -print-sysroot. @@ -83,6 +85,7 @@ def sysroot(self, strict=False): raise return '' if self.env.target_platform.family == 'windows' else '/' + @memoize_method def search_dirs(self, strict=False): try: output = self.env.execute( diff --git a/doc/about/changes.md b/doc/about/changes.md index 3fbec9ff..70f96a85 100644 --- a/doc/about/changes.md +++ b/doc/about/changes.md @@ -7,6 +7,7 @@ in progress ### Bug fixes - When using the `PKG_CONFIG_PATH` specified by mopack, add it to any existing `PKG_CONFIG_PATH` from the environment +- Don't explicitly include default `#include` paths on CC-like compilers --- diff --git a/test/unit/tools/cc/test_compiler.py b/test/unit/tools/cc/test_compiler.py index 4b07d1c1..9731f2ef 100644 --- a/test/unit/tools/cc/test_compiler.py +++ b/test/unit/tools/cc/test_compiler.py @@ -1,3 +1,4 @@ +from textwrap import dedent from unittest import mock from ... import * @@ -7,7 +8,7 @@ from bfg9000.file_types import (HeaderDirectory, HeaderFile, ObjectFile, PrecompiledHeader, SourceFile) from bfg9000.tools.cc import CcBuilder -from bfg9000.path import Path, Root +from bfg9000.path import abspath, Path, Root class TestCcCompiler(CrossPlatformTestCase): @@ -19,6 +20,7 @@ def setUp(self): mock.patch('bfg9000.shell.execute', mock_execute): self.compiler = CcBuilder(self.env, known_langs['c++'], ['c++'], True, 'version').compiler + self.compiler._search_dirs._reset(self.compiler) def test_call(self): extra = self.compiler._always_flags @@ -49,24 +51,50 @@ def test_output_file(self): self.assertEqual(self.compiler.output_file('file', None), ObjectFile(Path('file.o'), fmt, 'c++')) + def test_search_dirs(self): + def mock_execute(*args, **kwargs): + return dedent("""\ + /bad/include + #include <...> search starts here: + /usr/include + /path/to/include + End of search list. + """) + + dirs = [abspath('/usr/include'), abspath('/path/to/include')] + with mock.patch('bfg9000.shell.execute', mock_execute): + self.assertEqual(self.compiler.search_dirs(), dirs) + self.assertEqual(self.compiler.search_dirs(True), dirs) + + def test_search_dirs_broken(self): + def mock_execute(*args, **kwargs): + raise OSError() + + with mock.patch('bfg9000.shell.execute', mock_execute): + self.assertEqual(self.compiler.search_dirs(), []) + with self.assertRaises(OSError): + self.compiler.search_dirs(True) + def test_flags_empty(self): self.assertEqual(self.compiler.flags(opts.option_list()), []) def test_flags_include_dir(self): - p = self.Path('/path/to/include') - self.assertEqual(self.compiler.flags(opts.option_list( - opts.include_dir(HeaderDirectory(p)) - )), ['-I' + p]) + search_dirs = 'bfg9000.tools.cc.compiler.CcBaseCompiler._search_dirs' + with mock.patch(search_dirs, lambda *args, **kwargs: []): + p = self.Path('/path/to/include') + self.assertEqual(self.compiler.flags(opts.option_list( + opts.include_dir(HeaderDirectory(p)) + )), ['-I' + p]) - self.assertEqual(self.compiler.flags(opts.option_list( - opts.include_dir(HeaderDirectory(p, system=True)) - )), ['-isystem', p]) + self.assertEqual(self.compiler.flags(opts.option_list( + opts.include_dir(HeaderDirectory(p, system=True)) + )), ['-isystem', p]) - if self.env.target_platform.genus == 'linux': - p = self.Path('/usr/include') + p = self.Path('/usr/include') + with mock.patch(search_dirs, lambda *args, **kwargs: [p]): self.assertEqual(self.compiler.flags(opts.option_list( opts.include_dir(HeaderDirectory(p, system=True)) - )), ['-I' + p]) + )), []) def test_flags_define(self): self.assertEqual(self.compiler.flags(opts.option_list( diff --git a/test/unit/tools/cc/test_linker.py b/test/unit/tools/cc/test_linker.py index 63678738..ad895505 100644 --- a/test/unit/tools/cc/test_linker.py +++ b/test/unit/tools/cc/test_linker.py @@ -1,3 +1,4 @@ +from textwrap import dedent from unittest import mock from ... import * @@ -9,7 +10,7 @@ from bfg9000.file_types import * from bfg9000.tools.cc import CcBuilder from bfg9000.packages import Framework -from bfg9000.path import InstallRoot, Path, Root +from bfg9000.path import abspath, InstallRoot, Path, Root class TestCcLinker(CrossPlatformTestCase): @@ -29,6 +30,8 @@ def _get_output_file(self): def setUp(self): self.linker = self._get_linker('c++') + self.linker.sysroot._reset(self.linker) + self.linker.search_dirs._reset(self.linker) def test_call(self): extra = self.linker._always_flags @@ -68,6 +71,18 @@ def mock_execute(*args, **kwargs): self.linker.sysroot(True) def test_search_dirs(self): + def mock_execute(*args, **kwargs): + return dedent("""\ + install: /usr/lib/gcc + libraries: =/usr/lib{}/path/to/lib + """.format(os.pathsep)) + + dirs = [abspath('/usr/lib'), abspath('/path/to/lib')] + with mock.patch('bfg9000.shell.execute', mock_execute): + self.assertEqual(self.linker.search_dirs(), dirs) + self.assertEqual(self.linker.search_dirs(True), dirs) + + def test_search_dirs_broken(self): def mock_execute(*args, **kwargs): raise OSError()