8000 Fix cross-platform Python packaging and CLI entry points by Napreth · Pull Request #831 · bkerler/edl · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/python-packaging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Python packaging

on:
push:
branches: ["master"]
pull_request:
branches: ["master"]

permissions:
contents: read

jobs:
packaging:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: python -m pip install build setuptools wheel
- name: Build distributions and verify installed entry points
run: python -m unittest discover -s tests -v
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
include README.md
include LICENSE
include fastpwn
include edl.py
recursive-include Loaders *
include edlclient/Windows/*.dll
include edlclient/Config/nvitems.xml
Expand Down
417 changes: 2 additions & 415 deletions edl.py

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion edlclient/Tools/Config

This file was deleted.

1 change: 0 additions & 1 deletion edlclient/Tools/Library

This file was deleted.

22 changes: 12 additions & 10 deletions edlclient/Tools/fhloaderparse.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
from os import walk
from shutil import copyfile
from struct import unpack
try:
from Config.qualcomm_config import vendor
from Library.loader_db import loader_utils
from Library.utils import elf
except ModuleNotFoundError:
from edlclient.Config.qualcomm_config import vendor
from edlclient.Library.loader_db import loader_utils
from edlclient.Library.utils import elf
# Allow direct execution from a source checkout as well as installed entry points.
if __package__ in (None, ""):
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

from edlclient.Config.qualcomm_config import vendor
from edlclient.Library.loader_db import loader_utils
from edlclient.Library.utils import elf

current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))

Expand Down Expand Up @@ -283,7 +282,9 @@ def is_duplicate(loaderdb, sign_info):
return False


def main(argv):
def main(argv=None):
if argv is None:
argv = sys.argv
file_list = []
path = ""
if len(argv) < 3:
Expand Down Expand Up @@ -502,4 +503,5 @@ class loaderinfo:
rt.close()


main(sys.argv)
if __name__ == '__main__':
main()
27 changes: 12 additions & 15 deletions edlclient/Tools/qc_diag.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@
from binascii import hexlify, unhexlify


current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parent_dir = os.path.dirname(os.path.dirname(current_dir))
sys.path.insert(0, parent_dir)
try:
from Library.utils import print_progress, read_object, write_object, LogBase
from Library.Connection.usblib import usb_class
from Library.Connection.seriallib import serial_class
from Library.hdlc import hdlc
from Config.usb_ids import default_diag_vid_pid
except Exception:
from edlclient.Library.utils import print_progress, read_object, write_object, LogBase
from edlclient.Library.Connection.usblib import usb_class
from edlclient.Library.Connection.seriallib import serial_class
from edlclient.Library.hdlc import hdlc
from edlclient.Config.usb_ids import default_diag_vid_pid
# Allow direct execution from a source checkout as well as installed entry points.
if __package__ in (None, ""):
root_dir = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
sys.path.insert(0, root_dir)

from edlclient.Library.utils import print_progress, read_object, write_object, LogBase
from edlclient.Library.Connection.usblib import usb_class
from edlclient.Library.Connection.seriallib import serial_class
from edlclient.Library.hdlc import hdlc
from edlclient.Config.usb_ids import default_diag_vid_pid

qcerror = {
1: "None",
Expand Down
6 changes: 3 additions & 3 deletions edlclient/Tools/sierrakeygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ def openlock(self):
return False


def main(args):
def main(args=None):
version = "1.5"
info = 'Sierra Wireless Generator ' + version + ' (c) B. Kerler 2019-2021'
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=info)
Expand Down Expand Up @@ -630,7 +630,7 @@ def main(args):
help='run selftest',
default=False, action='store_true')

args = parser.parse_args()
args = parser.parse_args(args)

openlock = args.openlock
openmep = args.openmep
Expand Down Expand Up @@ -692,4 +692,4 @@ def main(args):


if __name__ == '__main__':
main(sys.argv)
main()
1 change: 0 additions & 1 deletion edlclient/edl.py

This file was deleted.

417 changes: 417 additions & 0 deletions edlclient/edl.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ include-package-data = true
zip-safe = false

[ tool.setuptools.packages.find ]
include = ["edlclient", "edlclient.*"]
namespaces = false
136 changes: 136 additions & 0 deletions tests/test_packaging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Packaging regression checks; install build, setuptools and wheel first.

Run with: python -m unittest discover -s tests -v
"""
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
import venv
import zipfile


ROOT = Path(__file__).resolve().parents[1]
CLI_ARGS = {
"edl": ["--help"],
"qc_diag": ["--help"],
"sierrakeygen": ["--help"],
"boottodwnload": ["--help"],
"enableadb": ["--help"],
"fhloaderparse": [],
"beagle_to_loader": [],
}


def run(*command, cwd):
env = os.environ.copy()
env.pop("PYTHONPATH", None)
env.pop("PYTHONHOME", None)
env["PYTHONIOENCODING"] = "utf-8"
result = subprocess.run(
command, cwd=cwd, env=env, capture_output=True, text=True,
encoding="utf-8", errors="replace", timeout=180,
)
if result.returncode:
raise AssertionError(
f"Command failed ({result.returncode}): {command}\n"
f"{result.stdout}\n{result.stderr}"
)
return result.stdout


class PackagingTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory()
cls.addClassCleanup(cls.temp.cleanup)
cls.work = Path(cls.temp.name)
cls.wheels = []
# Exercise both a direct wheel and the default sdist -> wheel release build.
for name, flags in (("direct", ["--wheel"]), ("sdist", [])):
output = cls.work / name
run(sys.executable, "-m", "build", "--no-isolation", *flags,
"--outdir", str(output), cwd=ROOT)
cls.wheels.append(next(output.glob("*.whl")))
cls.python, cls.scripts = cls.create_environment("installed")
cls.source_python, _ = cls.create_environment("source")

@classmethod
def create_environment(cls, name):
environment = cls.work / name
venv.EnvBuilder(with_pip=True).create(environment)
scripts = environment / ("Scripts" if os.name == "nt" else "bin")
python = scripts / ("python.exe" if os.name == "nt" else "python")
# Use the existing requirements file to exercise package structure
# independently of dependency metadata.
run(str(python), "-m", "pip", "install", "-r",
str(ROOT / "requirements.txt"), cwd=cls.work)
return python, scripts

def test_wheel_contents(self):
for wheel in self.wheels:
with self.subTest(wheel=wheel.parent.name), zipfile.ZipFile(wheel) as archive:
names = archive.namelist()
for required in (
"edlclient/edl.py", "edlclient/Config/nvitems.xml",
"edlclient/Windows/libusb-1.0.dll",
"edlclient/Windows/libusb32-1.0.dll",
):
self.assertIn(required, names)
for name in names:
if name.endswith(".py"):
self.assertTrue(name.startswith("edlclient/"), name)
self.assertNotIn("edlclient/Tools/Library/", name)
self.assertNotIn("edlclient/Tools/Config/", name)
compile(archive.read(name), name, "exec")

def test_installed_entry_points(self):
for wheel in self.wheels:
with self.subTest(wheel=wheel.parent.name):
python, scripts = self.python, self.scripts
run(str(python), "-m", "pip", "install", "--no-deps", "--no-index",
"--force-reinstall", str(wheel), cwd=self.work)
output = run(str(python), "-c", "\n".join((
"import importlib.metadata, inspect, pathlib, sys",
"sys.argv = ['entry-point-test', '--invalid-import-argument']",
"dist = importlib.metadata.distribution('edlclient')",
"for entry in dist.entry_points:",
" if entry.group == 'console_scripts':",
" function = entry.load()",
" inspect.signature(function).bind()",
" module = pathlib.Path(inspect.getfile(function)).resolve()",
" assert pathlib.Path(sys.prefix).resolve() in module.parents, module",
" print(entry.name)",
)), cwd=self.work)
self.assertEqual(set(output.splitlines()) & CLI_ARGS.keys(), CLI_ARGS.keys())
for command, args in CLI_ARGS.items():
with self.subTest(command=command):
executable = scripts / (command + (".exe" if os.name == "nt" else ""))
output = run(str(executable), *args, cwd=self.work)
self.assertIn("usage:", output.lower())
self.assertIn("3", run(str(scripts / ("edl.exe" if os.name == "nt" else "edl")),
"--version", cwd=self.work))

def test_source_launchers(self):
# The source checks must never fall back to an installed application.
run(str(self.source_python), "-c",
"import importlib.util; assert importlib.util.find_spec('edlclient') is None",
cwd=self.work)
for script in ("edl.py", "qc_diag.py", "boottodwnload.py", "enableadb.py", "fhloaderparse.py"):
with self.subTest(script=script):
output = run(str(self.source_python), str(ROOT / script),
*CLI_ARGS[Path(script).stem], cwd=self.work)
self.assertIn("usage:", output.lower())
# These tools used to depend on the Tools/Library and Tools/Config links.
for command, args in CLI_ARGS.items():
script = ROOT / "edlclient" / "Tools" / (command + ".py")
if script.exists():
with self.subTest(script=str(script)):
output = run(str(self.source_python), str(script), *args, cwd=self.work)
self.assertIn("usage:", output.lower())


if __name__ == "__main__":
unittest.main()
0