mbed-os/tools/python/mbed_tools/project/_internal/libraries.py

134 lines
4.7 KiB
Python

#
# Copyright (c) 2020-2021 Arm Limited and Contributors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
"""Objects for library reference handling."""
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Generator, List
from mbed_tools.project._internal import git_utils
from mbed_tools.project.exceptions import VersionControlError
logger = logging.getLogger(__name__)
@dataclass(frozen=True, order=True)
class MbedLibReference:
"""Metadata associated with an Mbed library.
An Mbed library is an external dependency of an MbedProgram. The MbedProgram is made aware of the library
dependency by the presence of a .lib file in the project tree, which we refer to as a library reference file. The
library reference file contains a URI where the dependency's source code can be fetched.
Attributes:
reference_file: Path to the .lib reference file for this library.
source_code_path: Path to the source code if it exists in the local project.
"""
reference_file: Path
source_code_path: Path
def is_resolved(self) -> bool:
"""Determines if the source code for this library is present in the source tree."""
return self.source_code_path.exists() and self.source_code_path.is_dir()
def get_git_reference(self) -> git_utils.GitReference:
"""Get the source code location from the library reference file.
Returns:
Data structure containing the contents of the library reference file.
"""
raw_ref = self.reference_file.read_text().strip()
url, sep, ref = raw_ref.partition("#")
if url.endswith("/"):
url = url[:-1]
return git_utils.GitReference(repo_url=url, ref=ref)
@dataclass
class LibraryReferences:
"""Manages library references in an MbedProgram."""
root: Path
ignore_paths: List[str]
def fetch(self) -> None:
"""Recursively clone all dependencies defined in .lib files."""
for lib in self.iter_unresolved():
git_ref = lib.get_git_reference()
logger.info(f"Resolving library reference {git_ref.repo_url}.")
_clone_at_ref(git_ref.repo_url, lib.source_code_path, git_ref.ref)
# Check if we find any new references after cloning dependencies.
if list(self.iter_unresolved()):
self.fetch()
def checkout(self, force: bool) -> None:
"""Check out all resolved libs to revision specified in .lib files."""
for lib in self.iter_resolved():
repo = git_utils.get_repo(lib.source_code_path)
git_ref = lib.get_git_reference()
if not git_ref.ref:
git_ref.ref = git_utils.get_default_branch(repo)
git_utils.fetch(repo, git_ref.ref)
git_utils.checkout(repo, "FETCH_HEAD", force=force)
def iter_all(self) -> Generator[MbedLibReference, None, None]:
"""Iterate all library references in the tree.
Yields:
Iterator to library reference.
"""
for lib in self.root.rglob("*.lib"):
if not self._in_ignore_path(lib):
yield MbedLibReference(lib, lib.with_suffix(""))
def iter_unresolved(self) -> Generator[MbedLibReference, None, None]:
"""Iterate all unresolved library references in the tree.
Yields:
Iterator to library reference.
"""
for lib in self.iter_all():
if not lib.is_resolved():
yield lib
def iter_resolved(self) -> Generator[MbedLibReference, None, None]:
"""Iterate all resolved library references in the tree.
Yields:
Iterator to library reference.
"""
for lib in self.iter_all():
if lib.is_resolved():
yield lib
def _in_ignore_path(self, lib_reference_path: Path) -> bool:
"""Check if a library reference is in a path we want to ignore."""
return any(p in lib_reference_path.parts for p in self.ignore_paths)
def _clone_at_ref(url: str, path: Path, ref: str) -> None:
if ref:
logger.info(f"Checking out revision {ref} for library {url}.")
try:
git_utils.clone(url, path, ref)
except VersionControlError:
# We weren't able to clone. Try again without the ref.
repo = git_utils.clone(url, path)
# We couldn't clone the ref and had to fall back to cloning
# just the default branch. Fetch the ref before checkout, so
# that we have it available locally.
logger.warning(f"No tag or branch with name {ref}. Fetching full repository.")
git_utils.fetch(repo, ref)
git_utils.checkout(repo, "FETCH_HEAD")
else:
git_utils.clone(url, path)