diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14ea580..a49dcb6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.2.2 + rev: v0.3.4 hooks: # Run the linter. - id: ruff @@ -14,7 +14,7 @@ repos: # Run the formatter. - id: ruff-format - repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 + rev: v3.15.2 hooks: - id: pyupgrade args: [ --py38-plus ] diff --git a/docs/guide.md b/docs/guide.md index 11dbbb2..7096b54 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -74,7 +74,15 @@ If you would like to provide a local `PyApp` source, you can do so by providing either the path to the local `.tar.gz` source or to a local folder using the `-p`/`--pyapp-source` argument. The local source will then be used instead of the latest release from GitHub. -It will be copied into the `build` folder before packaging. +It will be copied into the `build` folder before packaging in the `pyapp-local` folder. +Running only `box package` when a local folder is present will always use this local folder +and not download any releases. +An information about this will be printed. + +To re-copy the local source into the `build` folder, +the `box` project needs to be cleaned first. +Then run `box package -p LOCAL_SOURCE` again, +where `LOCAL_SOURCE` is the path to the local source as described above. ## Cleaning your project diff --git a/src/box/packager.py b/src/box/packager.py index 9391ac4..ccd7164 100644 --- a/src/box/packager.py +++ b/src/box/packager.py @@ -107,6 +107,8 @@ def _get_pyapp(self, local_source: Union[Path, str] = None): :raises: `click.ClickException` if no pyapp source code is found """ tar_name = Path("pyapp-source.tar.gz") + local_source_destination = "pyapp-local" + local_source_exists = False if isinstance(local_source, str): local_source = Path(local_source) @@ -116,9 +118,16 @@ def _get_pyapp(self, local_source: Union[Path, str] = None): if local_source.suffix == ".gz" and local_source.is_file(): shutil.copy(local_source, tar_name) elif local_source.is_dir(): - shutil.copytree( - local_source, self._build_dir.joinpath(local_source.name) - ) + if Path(local_source_destination).is_dir(): + fmt.warning( + "Local source folder already copied. " + "If you want to copy again, please clean the project first." + ) + else: + shutil.copytree( + local_source, + self._build_dir.joinpath(local_source_destination), + ) else: raise click.ClickException( "Error: invalid local pyapp source code. " @@ -126,14 +135,17 @@ def _get_pyapp(self, local_source: Union[Path, str] = None): ) else: # no local source - if not tar_name.is_file(): + if Path(local_source_destination).is_dir(): + local_source_exists = True + fmt.info("Using existing local pyapp source.") + elif not tar_name.is_file(): urllib.request.urlretrieve(PYAPP_SOURCE, tar_name) - if not tar_name.is_file(): - raise click.ClickException( - "Error: no pyapp source code found. " - "Please check your internet connection and try again." - ) + if not tar_name.is_file(): + raise click.ClickException( + "Error: no pyapp source code found. " + "Please check your internet connection and try again." + ) # check if pyapp source code is already extracted all_pyapp_folders = [] @@ -142,30 +154,46 @@ def _get_pyapp(self, local_source: Union[Path, str] = None): all_pyapp_folders.append(file) # extract the source code if we didn't just copy a local folder - if not local_source or local_source.suffix == ".gz": - with tarfile.open(tar_name, "r:gz") as tar: - tarfile_members = tar.getmembers() - - # only extract if the folder in archive does not already exist - folder_exists = False - new_folder = tarfile_members[0].name - for folder in all_pyapp_folders: - if folder.name == new_folder: - folder_exists = True - break - - # extract the source with tarfile package - if not folder_exists: - tar.extractall() - if "pyapp-" in new_folder: - all_pyapp_folders.append(Path(new_folder)) + if not local_source_exists: + if not local_source or local_source.suffix == ".gz": + with tarfile.open(tar_name, "r:gz") as tar: + tarfile_members = tar.getmembers() + + # only extract if not existing and no local source! + folder_exists = False + new_folder = tarfile_members[0].name + for folder in all_pyapp_folders: + if ( + folder.name == new_folder + or folder.name == local_source_destination + ): + folder_exists = True + break + + # extract the source with tarfile package + if not folder_exists: + tar.extractall() + if "pyapp-" in new_folder: + all_pyapp_folders.append(Path(new_folder)) + + # if local source, rename the extracted folder + if local_source: + shutil.move( + new_folder, + local_source_destination, + ) # find the name of the pyapp folder and return it if len(all_pyapp_folders) == 1: self._pyapp_path = all_pyapp_folders[0].absolute() elif len(all_pyapp_folders) > 1: all_pyapp_folders.sort(key=lambda x: x.stem) - self._pyapp_path = all_pyapp_folders[-1].absolute() + if Path(local_source_destination).is_dir(): + self._pyapp_path = self._build_dir.joinpath( + local_source_destination + ) + else: + self._pyapp_path = all_pyapp_folders[-1].absolute() fmt.warning( "Multiple pyapp versions were. " f"Using {self._pyapp_path.name}. " diff --git a/tests/cli/test_cli_packager.py b/tests/cli/test_cli_packager.py index 8eca61c..0b1b980 100644 --- a/tests/cli/test_cli_packager.py +++ b/tests/cli/test_cli_packager.py @@ -83,7 +83,32 @@ def test_package_project_local_pyapp(rye_project, mocker, data_dir, pyapp_source assert result.exit_code == 0 urllib_mock.assert_not_called() - # assert rye_project.joinpath("build/pyapp-v0.14.0/source.txt").is_file() + assert rye_project.joinpath("build/pyapp-local/source.txt").is_file() + + +def test_package_project_do_not_copy_local_folder_twice(rye_project, data_dir, mocker): + """Re-copying local folder echos a warning and does nothing.""" + pyapp_source_name = "pyapp-v0.14.0" + + mocker.patch("box.packager.PackageApp._package_pyapp") + mocker.patch("box.packager.PackageApp.binary_name", return_value="pyapp") + + # create dist folder and package + dist_folder = rye_project.joinpath("dist") + dist_folder.mkdir() + dist_folder.joinpath(f"{rye_project.name.replace('-', '_')}-v0.1.0.tar.gz").touch() + + runner = CliRunner() + result = runner.invoke(cli, ["package", "-p", data_dir.joinpath(pyapp_source_name)]) + result2 = runner.invoke( + cli, ["package", "-p", data_dir.joinpath(pyapp_source_name)] + ) + + assert result.exit_code == 0 + + assert rye_project.joinpath("build/pyapp-local/source.txt").is_file() + + assert "Local source folder already copied." in result2.output def test_cargo_not_found(rye_project, mocker): diff --git a/tests/func/test_packager.py b/tests/func/test_packager.py index 428106f..6fa9681 100644 --- a/tests/func/test_packager.py +++ b/tests/func/test_packager.py @@ -176,6 +176,25 @@ def test_get_pyapp_wrong_no_pyapp_folder(rye_project, mocker): assert "Error: no pyapp source code folder found." in e.value.args[0] +def test_get_pyapp_use_local_folder(rye_project, mocker): + """Use local source code if it already exists provided.""" + urlretrieve_mock = mocker.patch.object(urllib.request, "urlretrieve") + tar_mock = mocker.patch("tarfile.open") + + # create a fake source code file - tarfile is mocked + rye_project.joinpath("build/").mkdir() + local_source = rye_project.joinpath("build/pyapp-local") + local_source.mkdir(parents=True) + + packager = PackageApp() + packager._get_pyapp() + + urlretrieve_mock.assert_not_called() + tar_mock.assert_not_called() + + assert local_source == packager._pyapp_path + + def test_get_pyapp_local_wrong_file(rye_project): """Raise an error if local file is not a .tar.gz.""" rye_project.joinpath("build/").mkdir()