blob: b87338b532b40ff8c9df1669022c829380fe8cb2 [file] [log] [blame]
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import pathlib
import typing
from graph import all_headers
from graph import calculate_rdeps
from graph import Header
from graph import IncludeDir
from graph import Target
from platforms import Os
if typing.TYPE_CHECKING:
# To fix circular dependency.
from compiler import Compiler
IGNORED_MODULES = [
# This is a builtin module with feature requirements.
'opencl_c',
# This is a mac module with feature requirements that should be disabled.
'_stddef',
]
# When any of the following directory names are in the path, it's treated as a
# sysroot directory.
SYSROOT_DIRS = {
'android_toolchain',
'debian_bullseye_amd64-sysroot',
'debian_bullseye_arm64-sysroot',
'debian_bullseye_armhf-sysroot',
'debian_bullseye_i386-sysroot',
'fuchsia-sdk',
'MacOSX.platform',
'win_toolchain',
}
# It doesn't matter if these don't work on all platforms.
# It'll just print a warning saying it failed to compile.
# This contains a list of files that aren't depended on by libc++, but we still
# want to precompile.
SYSROOT_PRECOMPILED_HEADERS = [
'fcntl.h',
'getopt.h',
'linux/types.h',
'sys/ioctl.h',
'syscall.h',
]
def fix_graph(graph: dict[str, Header],
compiler: 'Compiler') -> dict[pathlib.Path, str]:
"""Applies manual augmentation of the header graph."""
def force_textual(key: str):
if key in graph:
graph[key].textual = True
def add_dep(frm, to, check=True):
if check:
assert to not in frm.deps
if to not in frm.deps:
frm.deps.append(to)
def skip_module(name):
found = False
for hdr in graph.values():
while True:
if hdr.root_module == name:
hdr.textual = True
found = True
if hdr.next is None:
break
hdr = hdr.next
assert found
# We made the assumption that the deps of something we couldn't compile is
# the intersection of the deps of all users of it.
# This does not hold true for stddef.h because of __need_size_t
add_dep(graph['stddef.h'].next, graph['__stddef_size_t.h'], check=False)
if compiler.os in [Os.Android, Os.Win, Os.Fuchsia]:
# include_next behaves differently in module builds and non-module builds.
# Because of this, module builds include libcxx's wchar.h instead of
# the sysroot's wchar.h
add_dep(graph['__mbstate_t.h'], graph['wchar.h'])
# This makes the libcxx/wchar.h included by mbstate_t.h act more like
# sysroot/wchar.h by preventing it from defining functions.
graph['__mbstate_t.h'].kwargs['defines'].append(
'_LIBCPP_WCHAR_H_HAS_CONST_OVERLOADS')
elif compiler.os.is_apple:
# This is shadowed by the builtin iso646, so we don't need to build it.
graph['iso646.h'].next.textual = True
rdeps = calculate_rdeps(all_headers(graph))
sysroot = graph['assert.h'].abs.parent
for header in all_headers(graph):
header.direct_deps = header.calculate_direct_deps(graph, sysroot=sysroot)
if compiler.os.is_apple:
# See https://github.com/llvm/llvm-project/issues/154675
# Darwin defines the symbol "echo" in curses.h
# Although curses.h is not included, the symbol is part of the module and
# thus we get an error when attempting to use the symbol "echo" after
# including *any* part of the module Darwin.
skip_module("Darwin")
# This module isn't intended to be used - it's intended to catch
# misconfigured sysroots.
skip_module("_c_standard_library_obsolete")
else:
for header in all_headers(graph):
if header.include_dir != IncludeDir.Sysroot:
continue
parts = set(pathlib.Path(header.rel).parts)
# We want non-textual, but we don't need to do so if the header including
# you via include_next is non-textual.
if header.prev is not None:
header.textual = not header.prev.textual
# Anything not to be included by the user directly that was only included
# once can be marked as textual. Unfortunately since .d files calculate
# *transitive* dependencies this is not particularly effective.
elif (len(rdeps[header]) < 2
and parts.intersection(['asm', 'asm-generic', 'bits'])):
header.textual = True
elif '#pragma once' in (header.content or ''):
header.textual = False
elif 'bits' in parts:
header.textual = True
# Assert is inherently textual.
graph['assert.h'].textual = True
force_textual('asm-generic/unistd.h')
force_textual('asm-generic/bitsperlong.h')
if compiler.os == Os.Android:
graph['android/legacy_stdlib_inlines.h'].textual = True
graph['android/legacy_threads_inlines.h'].textual = True
graph['android/legacy_unistd_inlines.h'].textual = True
graph['bits/threads_inlines.h'].textual = True
graph['asm-generic/posix_types.h'].textual = True
graph['asm/posix_types.h'].textual = True
# sys/syscall.h includes asm/unistd.h, which includes
# asm/unistd_<platform>.h, which defines some macros.
# It then includes bits/glibc-syscalls.h which uses said macros, so both
# must be non-textual.
for k in graph:
if k.startswith('asm/unistd'):
graph[k].textual = True
graph['bits/glibc-syscalls.h'].textual = True
elif compiler.os == Os.Linux:
# See https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/bits/local_lim.h.html#56
# if linux/limits.h is non-textual, then limits.h undefs the limits.h
# defined in the linux/limits.h module.
# Thus, limits.h exports an undef.
# if it's textual, limits.h undefs something it defined itself.
graph['linux/limits.h'].textual = True
# This is not included on arm32
graph['asm-generic/types.h'].textual = True
# On chromeos, x86_64-linux-gnu/foo.h will be either moved to foo.h or to
# x86_64-cros-gnu.
# So we just mark them all as textual so they don't appear in the modulemap.
for hdr in graph.values():
if '-linux-gnu' in str(hdr.abs):
hdr.textual = True
# Windows has multiple include directories contained with the sysroot.
if compiler.os == Os.Win:
graph['math.h'].kwargs['defines'].append('_USE_MATH_DEFINES')
return {
graph['corecrt.h'].abs.parent.parent: '$windows_kits',
graph['eh.h'].abs.parent: '$msvc',
}
else:
return {sysroot: '$sysroot'}
def should_compile(target: Target) -> bool:
"""Decides whether a target should be compiled or not.
If this returns true, the target should be compiled.
If this returns false, the target *may* be compiled (eg. if a target that
should be compiled depends on this).
"""
for header in target.headers:
# For now, we only precompile the transitive dependencies of libcxx, and
# nothing else in the sysroot.
if header.include_dir == IncludeDir.LibCxx:
return True
return False