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()
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()
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)
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()
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)
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()
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())
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()
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])
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)
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())
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
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()
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()))