diff --git a/poetry/core/masonry/builders/sdist.py b/poetry/core/masonry/builders/sdist.py index 479174509..825d8c5b7 100644 --- a/poetry/core/masonry/builders/sdist.py +++ b/poetry/core/masonry/builders/sdist.py @@ -89,7 +89,7 @@ def build(self, target_dir=None): # type: (Path) -> Path else: tar.addfile(tar_info) # Symlinks & ? - if self._poetry.package.build_config.get("generate-setup-file", True): + if self._poetry.package.build_should_generate_setup(): setup = self.build_setup() tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) tar_info.size = len(setup) diff --git a/poetry/core/masonry/builders/wheel.py b/poetry/core/masonry/builders/wheel.py index da7129c9f..0031e510b 100644 --- a/poetry/core/masonry/builders/wheel.py +++ b/poetry/core/masonry/builders/wheel.py @@ -77,8 +77,13 @@ def build(self): with zipfile.ZipFile( os.fdopen(fd, "w+b"), mode="w", compression=zipfile.ZIP_DEFLATED ) as zip_file: - self._copy_module(zip_file) - self._build(zip_file) + if not self._poetry.package.build_should_generate_setup(): + self._build(zip_file) + self._copy_module(zip_file) + else: + self._copy_module(zip_file) + self._build(zip_file) + self._write_metadata(zip_file) self._write_record(zip_file) @@ -91,44 +96,59 @@ def build(self): def _build(self, wheel): if self._package.build_script: - with SdistBuilder(poetry=self._poetry).setup_py() as setup: - # We need to place ourselves in the temporary - # directory in order to build the package + if not self._poetry.package.build_should_generate_setup(): + # Since we have a build script but no setup.py generation is required, + # we assume that the build script will build and copy the files + # directly. + # That way they will be picked up when adding files to the wheel. current_path = os.getcwd() try: os.chdir(str(self._path)) - self._run_build_command(setup) + self._run_build_script(self._package.build_script) finally: os.chdir(current_path) + else: + with SdistBuilder(poetry=self._poetry).setup_py() as setup: + # We need to place ourselves in the temporary + # directory in order to build the package + current_path = os.getcwd() + try: + os.chdir(str(self._path)) + self._run_build_command(setup) + finally: + os.chdir(current_path) - build_dir = self._path / "build" - lib = list(build_dir.glob("lib.*")) - if not lib: - # The result of building the extensions - # does not exist, this may due to conditional - # builds, so we assume that it's okay - return + build_dir = self._path / "build" + lib = list(build_dir.glob("lib.*")) + if not lib: + # The result of building the extensions + # does not exist, this may due to conditional + # builds, so we assume that it's okay + return - lib = lib[0] + lib = lib[0] - for pkg in lib.glob("**/*"): - if pkg.is_dir() or self.is_excluded(pkg): - continue + for pkg in lib.glob("**/*"): + if pkg.is_dir() or self.is_excluded(pkg): + continue - rel_path = str(pkg.relative_to(lib)) + rel_path = str(pkg.relative_to(lib)) - if rel_path in wheel.namelist(): - continue + if rel_path in wheel.namelist(): + continue - logger.debug(" - Adding: {}".format(rel_path)) + logger.debug(" - Adding: {}".format(rel_path)) - self._add_file(wheel, pkg, rel_path) + self._add_file(wheel, pkg, rel_path) def _run_build_command(self, setup): subprocess.check_call( [sys.executable, str(setup), "build", "-b", str(self._path / "build")] ) + def _run_build_script(self, build_script): + subprocess.check_call([sys.executable, build_script]) + def _copy_module(self, wheel): # type: (zipfile.ZipFile) -> None to_add = self.find_files_to_add() diff --git a/poetry/core/packages/project_package.py b/poetry/core/packages/project_package.py index d90aebbf1..bcc5799e0 100644 --- a/poetry/core/packages/project_package.py +++ b/poetry/core/packages/project_package.py @@ -58,3 +58,6 @@ def urls(self): urls.update(self.custom_urls) return urls + + def build_should_generate_setup(self): # type: () -> bool + return self.build_config.get("generate-setup-file", True) diff --git a/tests/masonry/builders/fixtures/extended_with_no_setup/README.rst b/tests/masonry/builders/fixtures/extended_with_no_setup/README.rst new file mode 100644 index 000000000..a7508bd51 --- /dev/null +++ b/tests/masonry/builders/fixtures/extended_with_no_setup/README.rst @@ -0,0 +1,2 @@ +Module 1 +======== diff --git a/tests/masonry/builders/fixtures/extended_with_no_setup/build.py b/tests/masonry/builders/fixtures/extended_with_no_setup/build.py new file mode 100644 index 000000000..0a8cf0b09 --- /dev/null +++ b/tests/masonry/builders/fixtures/extended_with_no_setup/build.py @@ -0,0 +1,27 @@ +import os +import shutil +from distutils.command.build_ext import build_ext +from distutils.core import Distribution, Extension + +extensions = [Extension("extended.extended", ["extended/extended.c"])] + + +def build(): + distribution = Distribution({"name": "extended", "ext_modules": extensions}) + distribution.package_dir = "extended" + + cmd = build_ext(distribution) + cmd.ensure_finalized() + cmd.run() + + # Copy built extensions back to the project + for output in cmd.get_outputs(): + relative_extension = os.path.relpath(output, cmd.build_lib) + shutil.copyfile(output, relative_extension) + mode = os.stat(relative_extension).st_mode + mode |= (mode & 0o444) >> 2 + os.chmod(relative_extension, mode) + + +if __name__ == "__main__": + build() diff --git a/tests/masonry/builders/fixtures/extended_with_no_setup/extended/__init__.py b/tests/masonry/builders/fixtures/extended_with_no_setup/extended/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/masonry/builders/fixtures/extended_with_no_setup/extended/extended.c b/tests/masonry/builders/fixtures/extended_with_no_setup/extended/extended.c new file mode 100644 index 000000000..25a028eb1 --- /dev/null +++ b/tests/masonry/builders/fixtures/extended_with_no_setup/extended/extended.c @@ -0,0 +1,58 @@ +#include + + +static PyObject *hello(PyObject *self) { + return PyUnicode_FromString("Hello"); +} + + +static PyMethodDef module_methods[] = { + { + "hello", + (PyCFunction) hello, + NULL, + PyDoc_STR("Say hello.") + }, + {NULL} +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "extended", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL, +}; +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit_extended(void) +#else +init_extended(void) +#endif +{ + PyObject *module; + +#if PY_MAJOR_VERSION >= 3 + module = PyModule_Create(&moduledef); +#else + module = Py_InitModule3("extended", module_methods, NULL); +#endif + + if (module == NULL) +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/tests/masonry/builders/fixtures/extended_with_no_setup/pyproject.toml b/tests/masonry/builders/fixtures/extended_with_no_setup/pyproject.toml new file mode 100644 index 000000000..c5d5467cc --- /dev/null +++ b/tests/masonry/builders/fixtures/extended_with_no_setup/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "extended" +version = "0.1" +description = "Some description." +authors = [ + "Sébastien Eustace " +] +license = "MIT" + +readme = "README.rst" + +homepage = "https://python-poetry.org/" + +[tool.poetry.build] +script = "build.py" +generate-setup-file = false diff --git a/tests/masonry/builders/test_complete.py b/tests/masonry/builders/test_complete.py index 71155787c..5a3bb71b3 100644 --- a/tests/masonry/builders/test_complete.py +++ b/tests/masonry/builders/test_complete.py @@ -95,6 +95,63 @@ def test_wheel_c_extension(): zip.close() +@pytest.mark.skipif( + sys.platform == "win32" + and sys.version_info <= (3, 6) + or platform.python_implementation().lower() == "pypy", + reason="Disable test on Windows for Python <=3.6 and for PyPy", +) +def test_wheel_c_extension_with_no_setup(): + module_path = fixtures_dir / "extended_with_no_setup" + builder = CompleteBuilder(Factory().create_poetry(module_path)) + builder.build() + + sdist = fixtures_dir / "extended_with_no_setup" / "dist" / "extended-0.1.tar.gz" + + assert sdist.exists() + + with tarfile.open(str(sdist), "r") as tar: + assert "extended-0.1/build.py" in tar.getnames() + assert "extended-0.1/extended/extended.c" in tar.getnames() + + whl = list((module_path / "dist").glob("extended-0.1-cp*-cp*-*.whl"))[0] + + assert whl.exists() + + zip = zipfile.ZipFile(str(whl)) + + has_compiled_extension = False + for name in zip.namelist(): + if name.startswith("extended/extended") and name.endswith((".so", ".pyd")): + has_compiled_extension = True + + assert has_compiled_extension + + try: + wheel_data = decode(zip.read("extended-0.1.dist-info/WHEEL")) + + assert ( + re.match( + """(?m)^\ +Wheel-Version: 1.0 +Generator: poetry {} +Root-Is-Purelib: false +Tag: cp[23]\\d-cp[23]\\dm?u?-.+ +$""".format( + __version__ + ), + wheel_data, + ) + is not None + ) + + records = decode(zip.read("extended-0.1.dist-info/RECORD")) + + assert re.search(r"\s+extended/extended.*\.(so|pyd)", records) is not None + finally: + zip.close() + + @pytest.mark.skipif( sys.platform == "win32" and sys.version_info <= (3, 6)