From d961b3474cb82be9c969d998d6b92864e72d1cbb Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Thu, 21 Jun 2018 19:27:55 +0900 Subject: [PATCH 1/5] support for non-entry-point-scripts --- poetry/masonry/builders/builder.py | 15 +++++++++++---- poetry/masonry/builders/sdist.py | 5 ++++- poetry/masonry/builders/wheel.py | 25 ++++++++++++++++++++++--- poetry/masonry/utils/helpers.py | 4 ++-- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/poetry/masonry/builders/builder.py b/poetry/masonry/builders/builder.py index cab4fcf4447..7992a578581 100644 --- a/poetry/masonry/builders/builder.py +++ b/poetry/masonry/builders/builder.py @@ -89,6 +89,10 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list ) to_add.append(file) + _, scripts = self.convert_entry_points() + for script in scripts: + to_add.append(Path(script)) + # Include project files self._io.writeln( " - Adding: pyproject.toml", @@ -126,12 +130,16 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list return sorted(to_add) - def convert_entry_points(self): # type: () -> dict + def convert_entry_points(self): # type: () -> (dict, list) result = defaultdict(list) + scripts = [] # Scripts -> Entry points for name, ep in self._poetry.local_config.get("scripts", {}).items(): - result["console_scripts"].append("{} = {}".format(name, ep)) + if isinstance(ep, dict): + scripts.append(ep["path"]) + else: + result["console_scripts"].append("{} = {}".format(name, ep)) # Plugins -> entry points plugins = self._poetry.local_config.get("plugins", {}) @@ -141,8 +149,7 @@ def convert_entry_points(self): # type: () -> dict for groupname in result: result[groupname] = sorted(result[groupname]) - - return dict(result) + return dict(result), scripts @classmethod def convert_author(cls, author): # type: () -> dict diff --git a/poetry/masonry/builders/sdist.py b/poetry/masonry/builders/sdist.py index 9b7cf9f5065..aeecc3f80c7 100644 --- a/poetry/masonry/builders/sdist.py +++ b/poetry/masonry/builders/sdist.py @@ -142,10 +142,13 @@ def build_setup(self): # type: () -> bytes before.append("extras_require = \\\n{}\n".format(pformat(extras))) extra.append("'extras_require': extras_require,") - entry_points = self.convert_entry_points() + entry_points, scripts = self.convert_entry_points() if entry_points: before.append("entry_points = \\\n{}\n".format(pformat(entry_points))) extra.append("'entry_points': entry_points,") + if scripts: + before.append("scripts = \\\n{}\n".format(pformat(scripts))) + extra.append("'scripts': scripts,") if self._package.python_versions != "*": python_requires = self._meta.requires_python diff --git a/poetry/masonry/builders/wheel.py b/poetry/masonry/builders/wheel.py index 24a37c6f021..863ca9fb666 100644 --- a/poetry/masonry/builders/wheel.py +++ b/poetry/masonry/builders/wheel.py @@ -178,6 +178,10 @@ def find_excluded_files(self): # type: () -> list def dist_info(self): # type: () -> str return self.dist_info_name(self._package.name, self._meta.version) + @property + def data_info(self): # type: () -> str + return self.data_info_name(self._package.name, self._meta.version) + @property def wheel_filename(self): # type: () -> str return "{}-{}-{}.whl".format( @@ -197,6 +201,12 @@ def dist_info_name(self, distribution, version): # type: (...) -> str return "{}-{}.dist-info".format(escaped_name, escaped_version) + def data_info_name(self, distribution, version): # type: (...) -> str + escaped_name = re.sub("[^\w\d.]+", "_", distribution, flags=re.UNICODE) + escaped_version = re.sub("[^\w\d.]+", "_", version, flags=re.UNICODE) + + return "{}-{}.data".format(escaped_name, escaped_version) + @property def tag(self): if self._package.build: @@ -217,7 +227,7 @@ def tag(self): return "-".join(tag) - def _add_file(self, full_path, rel_path): + def _add_file(self, full_path, rel_path, executable=False): full_path, rel_path = str(full_path), str(rel_path) if os.sep != "/": # We always want to have /-separated paths in the zip file and in @@ -228,7 +238,7 @@ def _add_file(self, full_path, rel_path): # Normalize permission bits to either 755 (executable) or 644 st_mode = os.stat(full_path).st_mode - new_mode = normalize_file_permissions(st_mode) + new_mode = normalize_file_permissions(st_mode, executable=executable) zinfo.external_attr = (new_mode & 0xFFFF) << 16 # Unix attributes if stat.S_ISDIR(st_mode): @@ -271,7 +281,7 @@ def _write_entry_points(self, fp): """ Write entry_points.txt. """ - entry_points = self.convert_entry_points() + entry_points, scripts = self.convert_entry_points() for group_name in sorted(entry_points): fp.write("[{}]\n".format(group_name)) @@ -280,6 +290,15 @@ def _write_entry_points(self, fp): fp.write("\n") + for script in scripts: + file = Path(script) + full_path = self._path / file + self._add_file( + full_path, + self.data_info + "/scripts/" + script.lstrip("/"), + executable=True, + ) + def _write_wheel_file(self, fp): fp.write( wheel_file_template.format( diff --git a/poetry/masonry/utils/helpers.py b/poetry/masonry/utils/helpers.py index 2455bb811ae..bf835bf47ee 100644 --- a/poetry/masonry/utils/helpers.py +++ b/poetry/masonry/utils/helpers.py @@ -1,4 +1,4 @@ -def normalize_file_permissions(st_mode): +def normalize_file_permissions(st_mode, executable=False): """ Normalizes the permission bits in the st_mode field from stat to 644/755 @@ -8,7 +8,7 @@ def normalize_file_permissions(st_mode): """ # Set 644 permissions, leaving higher bits of st_mode unchanged new_mode = (st_mode | 0o644) & ~0o133 - if st_mode & 0o100: + if st_mode & 0o100 or executable: new_mode |= 0o111 # Executable: 644 -> 755 return new_mode From 733cd231c2b4b38898fccb41da44891559c3d258 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Mon, 25 Jun 2018 15:08:43 +0900 Subject: [PATCH 2/5] reject path-based scripts if their filename doesn't match name --- poetry/masonry/builders/builder.py | 2 ++ tests/masonry/builders/fixtures/complete/bin/my-special-script | 0 tests/masonry/builders/fixtures/complete/pyproject.toml | 1 + tests/masonry/builders/test_sdist.py | 2 ++ 4 files changed, 5 insertions(+) create mode 100644 tests/masonry/builders/fixtures/complete/bin/my-special-script diff --git a/poetry/masonry/builders/builder.py b/poetry/masonry/builders/builder.py index 7992a578581..7e372937810 100644 --- a/poetry/masonry/builders/builder.py +++ b/poetry/masonry/builders/builder.py @@ -137,6 +137,8 @@ def convert_entry_points(self): # type: () -> (dict, list) # Scripts -> Entry points for name, ep in self._poetry.local_config.get("scripts", {}).items(): if isinstance(ep, dict): + if ep["path"].split("/")[-1] != name: + raise ValueError("Path based scripts name must match their file name, {name} does not ({path})".format(name=name, path=ep["path"])) scripts.append(ep["path"]) else: result["console_scripts"].append("{} = {}".format(name, ep)) diff --git a/tests/masonry/builders/fixtures/complete/bin/my-special-script b/tests/masonry/builders/fixtures/complete/bin/my-special-script new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/masonry/builders/fixtures/complete/pyproject.toml b/tests/masonry/builders/fixtures/complete/pyproject.toml index 87a519305d8..ddc5929df22 100644 --- a/tests/masonry/builders/fixtures/complete/pyproject.toml +++ b/tests/masonry/builders/fixtures/complete/pyproject.toml @@ -37,3 +37,4 @@ time = ["pendulum"] [tool.poetry.scripts] my-script = "my_package:main" my-2nd-script = "my_package:main2" +my-special-script = {path = "bin/my-special-script"} diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index 48e0d934a85..98f98519f74 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -121,6 +121,7 @@ def test_make_setup(): "my-script = my_package:main", ] } + assert ns["scripts"] == ["bin/my-special-script"] assert ns["extras_require"] == {"time": ["pendulum>=1.4,<2.0"]} @@ -133,6 +134,7 @@ def test_find_files_to_add(): assert result == [ Path("LICENSE"), Path("README.rst"), + Path("bin/my-special-script"), Path("my_package/__init__.py"), Path("my_package/data1/test.json"), Path("my_package/sub_pkg1/__init__.py"), From 74288586b443ad00b4b19ca7a14d7a334ff1d348 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Fri, 13 Jul 2018 15:29:09 +0900 Subject: [PATCH 3/5] allow path-based-scripts to have a different name --- poetry/masonry/builders/builder.py | 25 ++++++++-------- poetry/masonry/builders/sdist.py | 10 +++++-- poetry/masonry/builders/wheel.py | 11 ++----- .../builders/fixtures/complete/pyproject.toml | 2 +- tests/masonry/builders/test_sdist.py | 29 ++++++++++++------- 5 files changed, 42 insertions(+), 35 deletions(-) diff --git a/poetry/masonry/builders/builder.py b/poetry/masonry/builders/builder.py index e1352828ff8..3d062f7851b 100644 --- a/poetry/masonry/builders/builder.py +++ b/poetry/masonry/builders/builder.py @@ -68,7 +68,6 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list Finds all files to add to the tarball """ excluded = self.find_excluded_files() - src = self._module.path to_add = [] for include in self._module.includes: @@ -91,18 +90,18 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list " - Adding: {}".format(str(file)), verbosity=self._io.VERBOSITY_VERY_VERBOSE, ) - to_add.append(file) + to_add.append((file, file)) _, scripts = self.convert_entry_points() - for script in scripts: - to_add.append(Path(script)) + for name, script in scripts.items(): + to_add.append((Path(script), Path("bin") / name)) # Include project files self._io.writeln( " - Adding: pyproject.toml", verbosity=self._io.VERBOSITY_VERY_VERBOSE, ) - to_add.append(Path("pyproject.toml")) + to_add.append((Path("pyproject.toml"), Path("pyproject.toml"))) # If a license file exists, add it for license_file in self._path.glob("LICENSE*"): @@ -112,7 +111,8 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list ), verbosity=self._io.VERBOSITY_VERY_VERBOSE, ) - to_add.append(license_file.relative_to(self._path)) + file_path = license_file.relative_to(self._path) + to_add.append((file_path, file_path)) # If a README is specificed we need to include it # to avoid errors @@ -125,25 +125,24 @@ def find_files_to_add(self, exclude_build=True): # type: () -> list ), verbosity=self._io.VERBOSITY_VERY_VERBOSE, ) - to_add.append(readme.relative_to(self._path)) + readme_path = readme.relative_to(self._path) + to_add.append((readme_path, readme_path)) # If a build script is specified and explicitely required # we add it to the list of files if self._package.build and not exclude_build: - to_add.append(Path(self._package.build)) + to_add.append((Path(self._package.build), Path(self._package.build))) return sorted(to_add) - def convert_entry_points(self): # type: () -> (dict, list) + def convert_entry_points(self): # type: () -> (dict, dict) result = defaultdict(list) - scripts = [] + scripts = {} # Scripts -> Entry points for name, ep in self._poetry.local_config.get("scripts", {}).items(): if isinstance(ep, dict): - if ep["path"].split("/")[-1] != name: - raise ValueError("Path based scripts name must match their file name, {name} does not ({path})".format(name=name, path=ep["path"])) - scripts.append(ep["path"]) + scripts[name] = ep["path"] else: result["console_scripts"].append("{} = {}".format(name, ep)) diff --git a/poetry/masonry/builders/sdist.py b/poetry/masonry/builders/sdist.py index 750a248bfb3..0c7e209c54a 100644 --- a/poetry/masonry/builders/sdist.py +++ b/poetry/masonry/builders/sdist.py @@ -76,10 +76,10 @@ def build(self, target_dir=None): # type: (Path) -> Path files_to_add = self.find_files_to_add(exclude_build=False) - for relpath in files_to_add: + for relpath, targetpath in files_to_add: path = self._path / relpath tar_info = tar.gettarinfo( - str(path), arcname=pjoin(tar_dir, str(relpath)) + str(path), arcname=pjoin(tar_dir, str(targetpath)) ) tar_info = self.clean_tarinfo(tar_info) @@ -173,7 +173,11 @@ def build_setup(self): # type: () -> bytes before.append("entry_points = \\\n{}\n".format(pformat(entry_points))) extra.append("'entry_points': entry_points,") if scripts: - before.append("scripts = \\\n{}\n".format(pformat(scripts))) + before.append( + "scripts = \\\n{}\n".format( + pformat([f"bin/{name}" for name in scripts]) + ) + ) extra.append("'scripts': scripts,") if self._package.python_versions != "*": diff --git a/poetry/masonry/builders/wheel.py b/poetry/masonry/builders/wheel.py index 3181a58ceaf..2f9e198d728 100644 --- a/poetry/masonry/builders/wheel.py +++ b/poetry/masonry/builders/wheel.py @@ -107,7 +107,6 @@ def _build(self): def _copy_module(self, wheel): excluded = self.find_excluded_files() - src = self._module.path to_add = [] for include in self._module.includes: @@ -288,14 +287,10 @@ def _write_entry_points(self, wheel, fp): fp.write("\n") - for script in scripts: - file = Path(script) - full_path = self._path / file + for name, script in scripts.items(): + full_path = self._original_path / script self._add_file( - wheel, - full_path, - self.data_info + "/scripts/" + script.lstrip("/"), - executable=True, + wheel, full_path, self.data_info + "/scripts/" + name, executable=True ) def _write_wheel_file(self, fp): diff --git a/tests/masonry/builders/fixtures/complete/pyproject.toml b/tests/masonry/builders/fixtures/complete/pyproject.toml index ddc5929df22..b3edfc3c6de 100644 --- a/tests/masonry/builders/fixtures/complete/pyproject.toml +++ b/tests/masonry/builders/fixtures/complete/pyproject.toml @@ -37,4 +37,4 @@ time = ["pendulum"] [tool.poetry.scripts] my-script = "my_package:main" my-2nd-script = "my_package:main2" -my-special-script = {path = "bin/my-special-script"} +different-name = {path = "bin/my-special-script"} diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index 4f3a58b76c9..2468de75081 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -125,7 +125,7 @@ def test_make_setup(): "my-script = my_package:main", ] } - assert ns["scripts"] == ["bin/my-special-script"] + assert ns["scripts"] == ["bin/different-name"] assert ns["extras_require"] == {"time": ["pendulum>=1.4,<2.0"]} @@ -186,15 +186,24 @@ def test_find_files_to_add(): assert sorted(result) == sorted( [ - Path("LICENSE"), - Path("README.rst"), - Path("bin/my-special-script"), - Path("my_package/__init__.py"), - Path("my_package/data1/test.json"), - Path("my_package/sub_pkg1/__init__.py"), - Path("my_package/sub_pkg2/__init__.py"), - Path("my_package/sub_pkg2/data2/data.json"), - Path("pyproject.toml"), + (Path("LICENSE"), Path("LICENSE")), + (Path("README.rst"), Path("README.rst")), + (Path("bin/my-special-script"), Path("bin/different-name")), + (Path("my_package/__init__.py"), Path("my_package/__init__.py")), + (Path("my_package/data1/test.json"), Path("my_package/data1/test.json")), + ( + Path("my_package/sub_pkg1/__init__.py"), + Path("my_package/sub_pkg1/__init__.py"), + ), + ( + Path("my_package/sub_pkg2/__init__.py"), + Path("my_package/sub_pkg2/__init__.py"), + ), + ( + Path("my_package/sub_pkg2/data2/data.json"), + Path("my_package/sub_pkg2/data2/data.json"), + ), + (Path("pyproject.toml"), Path("pyproject.toml")), ] ) From b9382bff8e6632a8ac1cf2e1e9a256ad312251aa Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Sat, 14 Jul 2018 10:35:49 +0900 Subject: [PATCH 4/5] add documentation for path based scripts --- docs/docs/pyproject.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/pyproject.md b/docs/docs/pyproject.md index 2ff6317a36b..9a20474cb8c 100644 --- a/docs/docs/pyproject.md +++ b/docs/docs/pyproject.md @@ -173,6 +173,14 @@ poetry = 'poetry:console.run' Here, we will have the `poetry` script installed which will execute `console.run` in the `poetry` package. +Poetry also supports scripts which are not necessarily Python functions. To include these in your package, +provide the path to the script: + +```toml +[tool.poetry.scripts] +my-script = {path = "path/to/script"} +``` + ## `extras` Poetry supports extras to allow expression of: From 699bed6c46c07273f592f0c86d233c479bbd87ac Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Mon, 16 Jul 2018 09:18:46 +0900 Subject: [PATCH 5/5] removed f-string --- poetry/masonry/builders/sdist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry/masonry/builders/sdist.py b/poetry/masonry/builders/sdist.py index 0c7e209c54a..d6988a173d6 100644 --- a/poetry/masonry/builders/sdist.py +++ b/poetry/masonry/builders/sdist.py @@ -175,7 +175,7 @@ def build_setup(self): # type: () -> bytes if scripts: before.append( "scripts = \\\n{}\n".format( - pformat([f"bin/{name}" for name in scripts]) + pformat(["bin/{name}".format(name=name) for name in scripts]) ) ) extra.append("'scripts': scripts,")