Skip to content

Commit

Permalink
Extended poetry include with plugin settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Nealium authored and thmahe committed Jun 22, 2024
1 parent 1df6c44 commit ecb7d4d
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 10 deletions.
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Are listed in this sections all options available to configure `poetry-pyinstall
* `version` (string)
* Version of PyInstaller to use during build
* Does not support version constraint
* `exclude-include` (boolean)
* Exclude poetry include. Default: `False`
* `scripts` (dictionary)
* Where key is the program name and value a path to script or a `PyInstallerTarget` spec
* Example: `prog-name = "my_package/script.py"`
Expand All @@ -40,6 +42,10 @@ Are listed in this sections all options available to configure `poetry-pyinstall
* `all` (list): Collect all submodules, data files, and binaries for specified package(s) or module(s)
* `copy-metadata` (list) : list of packages for which metadata must be copied
* `recursive-copy-metadata` (list) : list of packages for which metadata must be copied (including dependencies)
* `include` (dictionary) :
* Data file(s) to include. `{source: destination}`
* `package` (dictionary) :
* File(s) to include with executable. `{source: destination}`

`PyinstallerTarget` spec:
* `source` (string): Path to your program entrypoint
Expand Down Expand Up @@ -174,3 +180,82 @@ $ pip install my-package[with-deps]
```

Bundled binaries must be built with all dependencies installed in build environment.

## Packaging additional files

This plugin by default supports `tool.poetry.include`, but it can be disabled
for more control. You can also add files *next* to the executable by using the
`package` setting

### Example
```toml
[tool.poetry]
name = "my_project"
include = [
{ path = "README.md", format = ["sdist"] },
]

[tool.poetry-pyinstaller-plugin]
# Disable [tool.poetry.include] and use plugin settings instead
exclude-include = true

[tool.poetry-pyinstaller-plugin.scripts]
hello-world = "my_package/main.py"

[tool.poetry-pyinstaller-plugin.package]
# 1-1 next to executable
"README.md" = "."

# renaming next to executable
"user/README.md" = "USER_README.md"

# directory next to executable
"docs" = "."

[tool.poetry-pyinstaller-plugin.include]
# loose files in bundle
"icons/*" = "."

# entire directory in bundle
"images/*" = "element_images"
```

Expected directory structure:
```text
.
├── build ...................... PyInstaller intermediate build directory
├── dist ....................... Result of `poetry build` command
│ ├── pyinstaller ............. PyInstaller output
│ │ ├── .specs/ ............ Specs files
│ │ └── hello-world/
│ │ ├── docs/ ............ Packaged Docs
│ │ │ ├── how_to.md
│ │ │ └── if_breaks.md
│ │ ├── my_project_internal/ ............ Onedir bundle
│ │ │ ├── main_icon.svg ............... Included icon
│ │ │ ├── extra_icon.svg .............. Included icon
│ │ │ └── element_images/ ............. Included images
│ │ │ ├── footer_icon.svg
│ │ │ └── header_icon.svg
│ │ ├── my_project.exe ............ Bundled program
│ │ ├── README.md ................. Packaged Readme
│ │ └── USER_README.md. ........... Packaged User Readme
│ ├── my_package-0.0.0-py3-none-manylinux_2_35_x86_64.whl ... Wheel with bundled binaries
│ └── my_package-0.0.0.tar.gz ............................... Source archive, includes README.md
├── docs/
│ ├── how_to.md
│ └── if_breaks.md
├── user/
│ └── README.md
├── icons/
│ ├── main_icon.svg
│ └── extra_icon.svg
├── images/
│ ├── footer_icon.md
│ └── header_icon.svg
├── pyproject.toml
├── README.py
└── my_package
├── __init__.py
└── main.py
```
67 changes: 58 additions & 9 deletions poetry_pyinstaller_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
from importlib import reload
from pathlib import Path
from typing import List, Dict, Optional
from shutil import copytree, copy
from errno import ENOTDIR, EINVAL


# Reload logging after PyInstaller import (conflicts with poetry logging)
reload(logging)
Expand Down Expand Up @@ -110,10 +113,12 @@ def _validate_type(self, type: str):
def build(self,
venv: VirtualEnv,
platform: str,
include_config: List,
collect_config: Dict,
copy_metadata: List,
recursive_copy_metadata: List
recursive_copy_metadata: List,
poetry_include_config: List,
include_config: Dict,
package_config: Dict
):
self._platform = platform
work_path = Path("build", platform)
Expand Down Expand Up @@ -142,11 +147,17 @@ def build(self,

args += collect_args

if include_config:

if include_config or poetry_include_config:
include_args = []
sep = ";" if "win" in platform else ":"

for item in include_config:
for source, target in include_config.items():
if source and target:
include_args.append("--add-data")
include_args.append(f"{Path(source).resolve()}{sep}{target}")

for item in poetry_include_config:
path = item if isinstance(item, str) else item.get("path")
if path:
include_args.append("--add-data")
Expand Down Expand Up @@ -194,6 +205,20 @@ def build(self,

venv.run(str(Path(venv.script_dirs[0]) / "pyinstaller"), *args)

if package_config:
package_path = Path("dist", "pyinstaller", platform, self.prog)
for source, target in package_config.items():
destination = Path(package_path / (target if target != "." else source))
try:
copytree(source, destination)
except OSError as exc: # python >2.5 or is file
if exc.errno in (ENOTDIR, EINVAL, EEXIST):
copy(source, destination)
else:
raise



def bundle_wheel(self, io):
wheels = glob.glob("*-py3-none-any.whl", root_dir="dist")
for wheel in wheels:
Expand Down Expand Up @@ -256,13 +281,35 @@ def collect_opt_block(self) -> Dict:
raise RuntimeError("Error while retrieving pyproject.toml data.")

@property
def include_opt_block(self) -> List:
def poetry_include_opt_block(self) -> List:
"""
Get include config
Get poetry include config
"""
data = self._app.poetry.pyproject.data
if data:
return data.get("tool", {}).get("poetry", {}).get("include", [])
if not data.get("tool", {}).get("poetry-pyinstaller-plugin", {}).get("exclude-include", False):
return data.get("tool", {}).get("poetry", {}).get("include", [])
return []
raise RuntimeError("Error while retrieving pyproject.toml data.")

@property
def include_opt_block(self) -> Dict:
"""
Get pyinstaller include config
"""
data = self._app.poetry.pyproject.data
if data:
return data.get("tool", {}).get("poetry-pyinstaller-plugin", {}).get("include", {})
raise RuntimeError("Error while retrieving pyproject.toml data.")

@property
def package_opt_block(self) -> Dict:
"""
Get package config
"""
data = self._app.poetry.pyproject.data
if data:
return data.get("tool", {}).get("poetry-pyinstaller-plugin", {}).get("package", {})
raise RuntimeError("Error while retrieving pyproject.toml data.")

@property
Expand Down Expand Up @@ -426,10 +473,12 @@ def _build_binaries(self, event: ConsoleCommandEvent, event_name: str, dispatche
for t in self._targets:
io.write_line(f" - Building <info>{t.prog}</info> <debug>{t.type.name}{' BUNDLED' if t.bundled else ''}{' NOUPX' if t.noupx else ''}</debug>")
t.build(venv=venv, platform=platform,
include_config=self.include_opt_block,
collect_config=self.collect_opt_block,
copy_metadata=self.copy_metadata_opt_block,
recursive_copy_metadata=self.recursive_copy_metadata_opt_block
recursive_copy_metadata=self.recursive_copy_metadata_opt_block,
poetry_include_config=self.poetry_include_opt_block,
include_config=self.include_opt_block,
package_config=self.package_opt_block,
)
io.write_line(f" - Built <success>{t.prog}</success> -> <success>'{Path('dist', 'pyinstaller', platform, t.prog)}'</success>")

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "poetry-pyinstaller-plugin"
version = "1.1.11"
version = "1.1.12"
description = "Poetry plugin to build and/or bundle executable binaries with PyInstaller"
authors = ["Thomas Mahé <[email protected]>"]
license = "MIT"
Expand Down

0 comments on commit ecb7d4d

Please sign in to comment.