diff --git a/docs/cli.md b/docs/cli.md index 83640a066cb..b8140be51ef 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -548,8 +548,22 @@ Note that, at the moment, only pure python wheels are supported. ### Options * `--format (-f)`: Limit the format to either `wheel` or `sdist`. +* `--local-version (-l)`: Add or replace a local version label to the build. * `--output (-o)`: Set output directory for build artifacts. Default is `dist`. +{{% note %}} +When using `--local-version`, the identifier must be [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers) +compliant. This is useful for adding build numbers, platform specificities etc. for private packages. +{{% /note %}} + +{{% warning %}} +Local version identifiers SHOULD NOT be used when publishing upstream projects to a public index server, but MAY be +used to identify private builds created directly from the project source. + +See [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers) for more information. +{{% /warning %}} + + ## publish This command publishes the package, previously built with the [`build`](#build) command, to the remote repository. diff --git a/src/poetry/console/commands/build.py b/src/poetry/console/commands/build.py index 07ca01762ba..640c68d57e9 100644 --- a/src/poetry/console/commands/build.py +++ b/src/poetry/console/commands/build.py @@ -14,6 +14,12 @@ class BuildCommand(EnvCommand): options = [ option("format", "f", "Limit the format to either sdist or wheel.", flag=False), + option( + "local-version", + "l", + "Add or replace a local version label to the build.", + flag=False, + ), option( "output", "o", @@ -45,6 +51,11 @@ def _build( else: raise ValueError(f"Invalid format: {fmt}") + if local_version_label := self.option("local-version"): + self.poetry.package.version = self.poetry.package.version.replace( + local=local_version_label + ) + for builder in builders: builder(self.poetry, executable=executable).build(target_dir) diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index 80fe49c1c58..1c7a0ede03e 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -41,8 +41,13 @@ def tmp_tester( return command_tester_factory("build", tmp_poetry) -def get_package_glob(poetry: Poetry) -> str: - return f"{poetry.package.name.replace('-', '_')}-{poetry.package.version}*" +def get_package_glob(poetry: Poetry, local_version: str | None = None) -> str: + version = poetry.package.version + + if local_version: + version = version.replace(local=local_version) + + return f"{poetry.package.name.replace('-', '_')}-{version}*" def test_build_format_is_not_valid(tmp_tester: CommandTester) -> None: @@ -62,6 +67,21 @@ def test_build_creates_packages_in_dist_directory_if_no_output_is_specified( assert all(archive.exists() for archive in build_artifacts) +def test_build_with_local_version_label( + tmp_tester: CommandTester, tmp_project_path: Path, tmp_poetry: Poetry +) -> None: + local_version_label = "local-version" + tmp_tester.execute(f"--local-version {local_version_label}") + build_artifacts = tuple( + (tmp_project_path / "dist").glob( + get_package_glob(tmp_poetry, local_version=local_version_label) + ) + ) + + assert len(build_artifacts) > 0 + assert all(archive.exists() for archive in build_artifacts) + + def test_build_not_possible_in_non_package_mode( fixture_dir: FixtureDirGetter, command_tester_factory: CommandTesterFactory,