def test_raises_when_no_such_ref(self, mock_repo, mock_clone, mock_fetch,
                                     mock_checkout, tmp_path):
        num_times_called = 0

        def clone_side_effect(url, dst_dir, *args):
            nonlocal num_times_called
            if num_times_called == 0:
                num_times_called += 1
                raise VersionControlError("Failed to clone")
            elif num_times_called == 1:
                num_times_called += 1
                dst_dir.mkdir()
            else:
                assert False

        fs_root = pathlib.Path(tmp_path, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git#lajdhalk234")

        mock_clone.side_effect = clone_side_effect
        mock_fetch.side_effect = None
        mock_checkout.side_effect = VersionControlError("Failed to checkout")

        with pytest.raises(VersionControlError, match="Failed to checkout"):
            lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
            lib_refs.fetch()
    def test_fetch_performs_checkout_if_ref_is_hash(self, mock_get_repo,
                                                    mock_clone, mock_fetch,
                                                    mock_checkout, tmp_path):
        num_times_called = 0

        def clone_side_effect(url, dst_dir, *args):
            nonlocal num_times_called
            if num_times_called == 0:
                num_times_called += 1
                raise VersionControlError("Failed to clone")
            elif num_times_called == 1:
                num_times_called += 1
                dst_dir.mkdir()
            else:
                assert False

        fs_root = pathlib.Path(tmp_path, "foo")
        lib = make_mbed_lib_reference(fs_root,
                                      ref_url="https://git#398bc1a63370")
        mock_clone.side_effect = clone_side_effect

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.fetch()

        mock_clone.assert_called_with(lib.get_git_reference().repo_url,
                                      lib.source_code_path)
        mock_fetch.assert_called_once_with(None, lib.get_git_reference().ref)
        mock_checkout.assert_called_once_with(None, "FETCH_HEAD")
    def test_does_not_resolve_references_in_ignore_paths(self, mock_get_repo, mock_checkout, mock_clone, tmp_path):
        fs_root = pathlib.Path(tmp_path, "mbed-os")
        make_mbed_lib_reference(fs_root, ref_url="https://git#lajdhalk234")

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.fetch()

        mock_clone.assert_not_called()
Exemple #4
0
    def test_does_not_perform_checkout_if_no_git_ref_exists(self, mock_init, mock_checkout, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git", resolved=True)

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.checkout(force=False)

        mock_checkout.assert_not_called()
Exemple #5
0
    def test_performs_checkout_if_git_ref_exists(self, mock_init, mock_checkout, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        lib = make_mbed_lib_reference(fs_root, ref_url="https://git#lajdhalk234", resolved=True)

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.checkout(force=False)

        mock_checkout.assert_called_once_with(mock_init.return_value, lib.get_git_reference().ref, force=False)
    def test_fetches_only_requested_ref(self, mock_repo, tmp_path):
        fs_root = pathlib.Path(tmp_path, "foo")
        fake_ref = "28eeee2b4c169739192600b92e7970dbbcabd8d0"
        make_mbed_lib_reference(fs_root, ref_url=f"https://git#{fake_ref}", resolved=True)

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.checkout(force=False)

        mock_repo().git.fetch.assert_called_once_with("origin", fake_ref)
Exemple #7
0
    def test_resolve_does_not_perform_checkout_if_no_git_ref_exists(self, mock_init, mock_checkout, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git")
        mock_clone.side_effect = lambda url, dst_dir: dst_dir.mkdir()

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.resolve()

        mock_checkout.assert_not_called()
Exemple #8
0
    def test_resolve_performs_checkout_if_git_ref_exists(self, mock_init, mock_checkout, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        lib = make_mbed_lib_reference(fs_root, ref_url="https://git#lajdhalk234")
        mock_clone.side_effect = lambda url, dst_dir: dst_dir.mkdir()

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.resolve()

        mock_checkout.assert_called_once_with(None, lib.get_git_reference().ref)
    def test_does_perform_checkout_of_default_repo_branch_if_no_git_ref_exists(
        self, mock_get_repo, mock_checkout, mock_get_default_branch, mock_clone, tmp_path
    ):
        fs_root = pathlib.Path(tmp_path, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git", resolved=True)

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.checkout(force=False)

        mock_checkout.assert_called_once_with(mock_get_repo(), mock_get_default_branch(), force=False)
Exemple #10
0
    def test_fetch_does_not_perform_checkout_if_no_git_ref_exists(
            self, mock_get_repo, mock_checkout, mock_clone, tmp_path):
        fs_root = pathlib.Path(tmp_path, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git")
        mock_clone.side_effect = lambda url, dst_dir: dst_dir.mkdir()

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.fetch()

        mock_checkout.assert_not_called()
Exemple #11
0
    def test_hydrates_top_level_library_references(self, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        lib = make_mbed_lib_reference(fs_root, ref_url="https://git")
        mock_clone.side_effect = lambda url, dst_dir: dst_dir.mkdir()

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.resolve()

        mock_clone.assert_called_once_with(lib.get_git_reference().repo_url, lib.source_code_path)
        self.assertTrue(lib.is_resolved())
Exemple #12
0
def initialise_project(path: pathlib.Path, create_only: bool) -> None:
    """Create a new Mbed project, optionally fetching and adding mbed-os.

    Args:
        path: Path to the project folder. Created if it doesn't exist.
        create_only: Flag which suppreses fetching mbed-os. If the value is `False`, fetch mbed-os from the remote.
    """
    program = MbedProgram.from_new(path)
    if not create_only:
        libs = LibraryReferences(root=program.root, ignore_paths=["mbed-os"])
        libs.fetch()
    def test_doesnt_fetch_for_branch_or_tag(self, mock_clone, mock_fetch,
                                            mock_checkout, tmp_path):
        fs_root = pathlib.Path(tmp_path, "foo")
        make_mbed_lib_reference(fs_root, ref_url="https://git#lajdhalk234")

        mock_clone.side_effect = lambda url, dst_dir, *args: dst_dir.mkdir()

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.fetch()

        mock_fetch.assert_not_called()
        mock_checkout.assert_not_called()
Exemple #14
0
    def __init__(self, program_files: MbedProgramFiles,
                 mbed_os: MbedOS) -> None:
        """Initialise the program attributes.

        Args:
            program_files: Object holding paths to a set of files that define an Mbed program.
            mbed_os: An instance of `MbedOS` holding paths to locations in the local copy of the Mbed OS source.
        """
        self.files = program_files
        self.root = self.files.mbed_os_ref.parent
        self.mbed_os = mbed_os
        self.lib_references = LibraryReferences(
            root=self.root, ignore_paths=[self.mbed_os.root])
Exemple #15
0
def get_known_libs(path: pathlib.Path) -> List:
    """List all resolved library dependencies.

    This function will not resolve dependencies. This will only generate a list of resolved dependencies.

    Args:
        path: Path to the Mbed project.

    Returns:
        A list of known dependencies.
    """
    libs = LibraryReferences(path, ignore_paths=["mbed-os"])
    return list(sorted(libs.iter_resolved()))
    def test_performs_checkout_if_git_ref_exists(self, mock_get_repo,
                                                 mock_checkout, mock_fetch,
                                                 mock_clone, tmp_path):
        fs_root = pathlib.Path(tmp_path, "foo")
        lib = make_mbed_lib_reference(fs_root,
                                      ref_url="https://git#lajdhalk234",
                                      resolved=True)

        lib_refs = LibraryReferences(fs_root, ignore_paths=["mbed-os"])
        lib_refs.checkout(force=False)

        mock_fetch.assert_called_once_with(mock_get_repo(),
                                           lib.get_git_reference().ref)
        mock_checkout.assert_called_once_with(mock_get_repo.return_value,
                                              "FETCH_HEAD",
                                              force=False)
Exemple #17
0
    def test_hydrates_recursive_dependencies(self, mock_clone, fs):
        fs_root = pathlib.Path(fs, "foo")
        lib = make_mbed_lib_reference(fs_root, ref_url="https://git")
        # Create a lib reference without touching the fs at this point, we want to mock the effects of a recursive
        # reference lookup and we need to assert the reference was resolved.
        lib2 = MbedLibReference(
            reference_file=(lib.source_code_path / "lib2.lib"), source_code_path=(lib.source_code_path / "lib2")
        )
        # Here we mock the effects of a recursive reference lookup. We create a new lib reference as a side effect of
        # the first call to the mock. Then we create the src dir, thus resolving the lib, on the second call.
        mock_clone.side_effect = lambda url, dst_dir: (
            make_mbed_lib_reference(pathlib.Path(dst_dir), name=lib2.reference_file.name, ref_url="https://valid2"),
            lib2.source_code_path.mkdir(),
        )

        lib_refs = LibraryReferences(fs_root, ignore_paths=[fs_root / "mbed-os"])
        lib_refs.resolve()

        self.assertTrue(lib.is_resolved())
        self.assertTrue(lib2.is_resolved())
Exemple #18
0
def import_project(url: str,
                   dst_path: Any = None,
                   recursive: bool = False) -> pathlib.Path:
    """Clones an Mbed project from a remote repository.

    Args:
        url: URL of the repository to clone.
        dst_path: Destination path for the repository.
        recursive: Recursively clone all project dependencies.

    Returns:
        The path the project was cloned to.
    """
    git_data = parse_url(url)
    url = git_data["url"]
    if not dst_path:
        dst_path = pathlib.Path(git_data["dst_path"])

    git_utils.clone(url, dst_path)
    if recursive:
        libs = LibraryReferences(root=dst_path, ignore_paths=["mbed-os"])
        libs.fetch()

    return dst_path
Exemple #19
0
def deploy_project(path: pathlib.Path, force: bool = False) -> None:
    """Deploy a specific revision of the current Mbed project.

    This function also resolves and syncs all library dependencies to the revision specified in the library reference
    files.

    Args:
        path: Path to the Mbed project.
        force: Force overwrite uncommitted changes. If False, the deploy will fail if there are uncommitted local
               changes.
    """
    libs = LibraryReferences(path, ignore_paths=["mbed-os"])
    libs.checkout(force=force)
    if list(libs.iter_unresolved()):
        logger.info(
            "Unresolved libraries detected, downloading library source code.")
        libs.fetch()
Exemple #20
0
class MbedProgram:
    """Represents an Mbed program.

    An `MbedProgram` consists of:
        * A git repository
        * A copy of, or reference to, `MbedOS`
        * A set of `MbedProgramFiles`
        * A collection of references to external libraries, defined in .lib files located in the program source tree
    """
    def __init__(self, program_files: MbedProgramFiles,
                 mbed_os: MbedOS) -> None:
        """Initialise the program attributes.

        Args:
            program_files: Object holding paths to a set of files that define an Mbed program.
            mbed_os: An instance of `MbedOS` holding paths to locations in the local copy of the Mbed OS source.
        """
        self.files = program_files
        self.root = self.files.mbed_os_ref.parent
        self.mbed_os = mbed_os
        self.lib_references = LibraryReferences(
            root=self.root, ignore_paths=[self.mbed_os.root])

    @classmethod
    def from_url(cls,
                 url: str,
                 dst_path: Path,
                 check_mbed_os: bool = True) -> "MbedProgram":
        """Fetch an Mbed program from a remote URL.

        Args:
            url: URL of the remote program repository.
            dst_path: Destination path for the cloned program.

        Raises:
            ExistingProgram: `dst_path` already contains an Mbed program.
        """
        if _tree_contains_program(dst_path):
            raise ExistingProgram(
                f"The destination path '{dst_path}' already contains an Mbed program. Please set the destination path "
                "to an empty directory.")
        logger.info(f"Cloning Mbed program from URL '{url}'.")
        git_utils.clone(url, dst_path)

        program_files = MbedProgramFiles.from_existing(dst_path)
        if not program_files.mbed_os_ref.exists():
            raise ProgramNotFound(
                "This repository does not contain a valid Mbed program at the top level. "
                "Cloned programs must contain an mbed-os.lib file containing the URL to the Mbed OS repository. It is "
                "possible you have cloned a repository containing multiple mbed-programs. If this is the case, you "
                "should cd to a directory containing a program before performing any other operations."
            )

        try:
            mbed_os = MbedOS.from_existing(dst_path / MBED_OS_DIR_NAME,
                                           check_mbed_os)
        except ValueError as mbed_err:
            raise MbedOSNotFound(f"{mbed_err}")

        return cls(program_files, mbed_os)

    @classmethod
    def from_new(cls, dir_path: Path) -> "MbedProgram":
        """Create an MbedProgram from an empty directory.

        Creates the directory if it doesn't exist.

        Args:
            dir_path: Directory in which to create the program.

        Raises:
            ExistingProgram: An existing program was found in the path.
        """
        if _tree_contains_program(dir_path):
            raise ExistingProgram(
                f"An existing Mbed program was found in the directory tree {dir_path}. It is not possible to nest Mbed "
                "programs. Please ensure there is no mbed-os.lib file in the cwd hierarchy."
            )

        logger.info(f"Creating Mbed program at path '{dir_path.resolve()}'")
        dir_path.mkdir(exist_ok=True)
        program_files = MbedProgramFiles.from_new(dir_path)
        logger.info(
            f"Creating git repository for the Mbed program '{dir_path}'")
        mbed_os = MbedOS.from_new(dir_path / MBED_OS_DIR_NAME)
        return cls(program_files, mbed_os)

    @classmethod
    def from_existing(cls,
                      dir_path: Path,
                      check_mbed_os: bool = True) -> "MbedProgram":
        """Create an MbedProgram from an existing program directory.

        Args:
            dir_path: Directory containing an Mbed program.
            check_mbed_os: If True causes an exception to be raised if the Mbed OS source directory does not
                           exist.

        Raises:
            ProgramNotFound: An existing program was not found in the path.
        """
        program_root = _find_program_root(dir_path)
        logger.info(f"Found existing Mbed program at path '{program_root}'")
        program = MbedProgramFiles.from_existing(program_root)

        try:
            mbed_os = MbedOS.from_existing(program_root / MBED_OS_DIR_NAME,
                                           check_mbed_os)
        except ValueError as mbed_os_err:
            raise MbedOSNotFound(
                f"Mbed OS was not found due to the following error: {mbed_os_err}"
                "\nYou may need to resolve the mbed-os.lib reference. You can do this by performing a `checkout`."
            )

        return cls(program, mbed_os)

    def resolve_libraries(self) -> None:
        """Resolve all external dependencies defined in .lib files."""
        self.lib_references.resolve()

    def checkout_libraries(self, force: bool = False) -> None:
        """Check out all resolved libraries to revisions specified in .lib files."""
        self.lib_references.checkout(force)

    def list_known_library_dependencies(self) -> List[MbedLibReference]:
        """Returns a list of all known library dependencies."""
        return sorted([lib for lib in self.lib_references.iter_all()])

    def has_unresolved_libraries(self) -> bool:
        """Checks if any unresolved library dependencies exist in the program tree."""
        return bool(list(self.lib_references.iter_unresolved()))