blob: 11ce2e0d8766e2a5830c6f7eca7d1a8499698cc1 [file] [log] [blame]
# Copyright 2015 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A type used to represent a toolchain and its setting overrides."""
import collections
import copy
import json
import os
from chromite.lib import osutils
_ToolchainTuple = collections.namedtuple(
"_ToolchainTuple", ("target", "setting_overrides")
)
_DEFAULT_TOOLCHAIN_KEY = "default"
class MismatchedToolchainConfigsError(Exception):
"""We have no defined resolution for conflicting toolchain configs."""
class ToolchainList:
"""Represents a list of toolchains."""
def __init__(self, overlays) -> None:
"""Construct an instance.
Args:
overlays: list of overlay directories to add toolchains from.
"""
if overlays is None:
raise ValueError("Must specify overlays.")
self._toolchains = []
self._auto_default_toolchain = None
for overlay_path in overlays:
self._AddToolchainsFromOverlayDir(overlay_path)
def _AddToolchainsFromOverlayDir(self, overlay_dir) -> None:
"""Add toolchains to |self| from the given overlay.
Does not include overlays that this overlay depends on.
Args:
overlay_dir: absolute path to an overlay directory.
"""
config_path = os.path.join(overlay_dir, "toolchain.conf")
if not os.path.exists(config_path):
# Not all overlays define toolchains.
return
first_target = True
default_target = None
config_lines = osutils.ReadFile(config_path).splitlines()
for line in config_lines:
# Split by hash sign so that comments are ignored.
# Then split the line to get the tuple and its options.
line_pieces = line.split("#", 1)[0].split(None, 1)
if not line_pieces:
continue
target = line_pieces[0]
settings = (
json.loads(line_pieces[1]) if len(line_pieces) > 1 else {}
)
if first_target:
if settings.get("default", True):
default_target = target
first_target = False
self._AddToolchain(target, setting_overrides=settings)
if default_target:
self._auto_default_toolchain = default_target
def _AddToolchain(self, target, setting_overrides=None) -> None:
"""Add a toolchain to |self|.
Args:
target: string target (e.g. 'x86_64-cros-linux-gnu').
setting_overrides: dictionary of setting overrides for this
toolchain.
"""
if setting_overrides is None:
setting_overrides = {}
self._toolchains.append(
_ToolchainTuple(target=target, setting_overrides=setting_overrides)
)
def GetMergedToolchainSettings(self):
"""Returns a dictionary of merged toolchain settings."""
targets = {}
toolchains = copy.deepcopy(self._toolchains)
if not toolchains:
return targets
have_default = any(
setting_overrides.get(_DEFAULT_TOOLCHAIN_KEY, False)
for target, setting_overrides in toolchains
)
if not have_default:
assert self._auto_default_toolchain, "No toolchains!?"
default_toolchain = _ToolchainTuple(
self._auto_default_toolchain, {_DEFAULT_TOOLCHAIN_KEY: True}
)
toolchains.insert(0, default_toolchain)
# We might get toolchain setting overrides from a couple different
# overlays. Merge all these overrides together, disallowing conflicts.
for toolchain in toolchains:
targets.setdefault(toolchain.target, {})
existing_overrides = targets[toolchain.target]
for key, value in toolchain.setting_overrides.items():
if (
key in existing_overrides
and existing_overrides[key] != value
):
raise MismatchedToolchainConfigsError(
"For toolchain %s, found %s to be set to both %r and "
"%r."
% (
toolchain.target,
key,
existing_overrides[key],
value,
)
)
existing_overrides[key] = value
# Now that we've merged all the setting overrides, apply them to
# defaults.
# TODO(b/236161656): Fix.
# pylint: disable-next=consider-using-dict-items
for target in targets.keys():
settings = {
"sdk": True,
"crossdev": "",
"default": False,
"have-binpkg": True,
}
settings.update(targets[target])
targets[target] = settings
return targets