diff --git a/docs/docs/cli.md b/docs/docs/cli.md index bf207e8dc42..32e4c51a8ec 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -320,6 +320,7 @@ Note that, at the moment, only pure python wheels are supported. ### Options * `--format (-f)`: Limit the format to either `wheel` or `sdist`. +* `--lock (-l)`: Add all pinned dependencies from `poetry.lock` to `pyproject.toml` of source and wheels archive ## publish diff --git a/poetry/console/commands/build.py b/poetry/console/commands/build.py index 72b7319fcd5..2da32607a1f 100644 --- a/poetry/console/commands/build.py +++ b/poetry/console/commands/build.py @@ -9,7 +9,13 @@ class BuildCommand(EnvCommand): description = "Builds a package, as a tarball and a wheel by default." options = [ - option("format", "f", "Limit the format to either sdist or wheel.", flag=False) + option("format", "f", "Limit the format to either sdist or wheel.", flag=False), + option( + "lock", + "l", + "Add all dependencies as locked dependencies in the distributed pyproject.toml", + flag=True, + ), ] loggers = [ @@ -19,12 +25,14 @@ class BuildCommand(EnvCommand): ] def handle(self): - from poetry.core.masonry import Builder + from poetry.core.masonry import builder fmt = "all" if self.option("format"): fmt = self.option("format") + lock = self.option("lock") + package = self.poetry.package self.line( "Building {} ({})".format( @@ -32,5 +40,27 @@ def handle(self): ) ) - builder = Builder(self.poetry) + original_content = self.poetry.file.read() + if lock: + section = "dependencies" + + content = self.poetry.file.read() + poetry_content = content["tool"]["poetry"] + if section not in poetry_content: + poetry_content[section] = {} + + for dependency_package in self.poetry.locker.lock_data["package"]: + name = dependency_package["name"] + version = dependency_package["version"] + poetry_content[section][name] = version + + self._write_pyproject_toml(content) + + builder = builder.Builder(self.poetry) builder.build(fmt, executable=self.env.python) + + if lock: + self._write_pyproject_toml(original_content) + + def _write_pyproject_toml(self, content): + self.poetry.file.write(content) diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py new file mode 100644 index 00000000000..13ef85faf45 --- /dev/null +++ b/tests/console/commands/test_build.py @@ -0,0 +1,83 @@ +import pytest + +from poetry.console.commands.build import BuildCommand +from poetry.core.masonry import builder +from poetry.utils._compat import Path + + +@pytest.fixture +def source_dir(tmp_path): # type: (Path) -> Path + yield Path(tmp_path.as_posix()) + + +@pytest.fixture +def tester(command_tester_factory): + return command_tester_factory("build") + + +class MockedBuilder: + def __init__(self, *args, **kw): + pass + + def build(self, *args, **kw): + pass + + +@pytest.fixture +def poetry_with_lock_file(project_factory, fixture_dir): + source = fixture_dir("lock") + pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8") + poetry_lock_content = (source / "poetry.lock").read_text(encoding="utf-8") + return project_factory( + pyproject_content=pyproject_content, poetry_lock_content=poetry_lock_content, + ) + + +def test_build_without_lock(command_tester_factory, poetry_with_lock_file, monkeypatch): + content_of_written_pyproject_toml = [] + monkeypatch.setattr( + BuildCommand, + "_write_pyproject_toml", + lambda _, content: content_of_written_pyproject_toml.append(content), + ) + monkeypatch.setattr(builder, "Builder", MockedBuilder) + + tester = command_tester_factory("build", poetry=poetry_with_lock_file) + tester.execute() + + # pyproject.toml is not changed + assert len(content_of_written_pyproject_toml) == 0 + + +def test_build_with_lock(command_tester_factory, poetry_with_lock_file, monkeypatch): + content_of_written_pyproject_toml = [] + monkeypatch.setattr( + BuildCommand, + "_write_pyproject_toml", + lambda _, content: content_of_written_pyproject_toml.append(content), + ) + monkeypatch.setattr(builder, "Builder", MockedBuilder) + + tester = command_tester_factory("build", poetry=poetry_with_lock_file) + tester.execute("--lock") + + assert len( + content_of_written_pyproject_toml[0]["tool"]["poetry"]["dependencies"] + ) > len(content_of_written_pyproject_toml[1]["tool"]["poetry"]["dependencies"]) + assert content_of_written_pyproject_toml[0]["tool"]["poetry"]["dependencies"] == { + "python": "^3.8", + "sampleproject": ">=1.3.1", + "certifi": "2020.6.20", + "chardet": "3.0.4", + "docker": "4.3.1", + "idna": "2.10", + "pywin32": "227", + "requests": "2.24.0", + "six": "1.15.0", + "urllib3": "1.25.10", + "websocket-client": "0.57.0", + } + assert content_of_written_pyproject_toml[1]["tool"]["poetry"]["dependencies"] == { + "python": "^3.8", + "sampleproject": ">=1.3.1", + } diff --git a/tests/fixtures/lock/poetry.lock b/tests/fixtures/lock/poetry.lock new file mode 100644 index 00000000000..22cd049e68f --- /dev/null +++ b/tests/fixtures/lock/poetry.lock @@ -0,0 +1,152 @@ +[[package]] +name = "certifi" +version = "2020.6.20" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "docker" +version = "4.3.1" +description = "A Python library for the Docker Engine API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +six = ">=1.4.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.24.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.25.10" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +name = "websocket-client" +version = "0.57.0" +description = "WebSocket client for Python. hybi13 is supported." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +six = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "bb4c2f3c089b802c1930b6acbeed04711d93e9cdfd9a003eb17518a6d9f350c6" + +[metadata.files] +certifi = [ + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +docker = [ + {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, + {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] +requests = [ + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, +] +websocket-client = [ + {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, + {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, +] diff --git a/tests/fixtures/lock/pyproject.toml b/tests/fixtures/lock/pyproject.toml new file mode 100644 index 00000000000..377aa676be9 --- /dev/null +++ b/tests/fixtures/lock/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "foobar" +version = "0.1.0" +description = "" +authors = ["Poetry Developer "] + +[tool.poetry.dependencies] +python = "^3.8" +sampleproject = ">=1.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"