| # 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 |