Packaging: the wheel carries the PWA, so a release install serves the UI (v2.7.10)
- pyproject declared build-backend = "setuptools.backends._legacy:_Backend", which does not exist: `python -m build`, `pip install .` and even `pip install -e .` died with "Backend ... is not available". It is setuptools.build_meta now. - the wheel and sdist carried only the Python packages, and _default_web_dir() resolved <site-packages>/../frontend, so an installed server started fine but answered every static request with 404. setup.py's build_py now copies the checkout's frontend/ into simtrace_analyser/web/ inside the build (the PWA's tests excluded), and _default_web_dir() prefers that packaged copy, falling back to <repo>/frontend for source and editable runs. The source directory is resolved from setup.py (so an sdist build works) and a missing frontend/ fails the build loudly instead of shipping a package without its UI. - MANIFEST.in grafts frontend/ (with the READMEs, the udev rule and the four launch scripts) into the sdist, so a wheel built from the sdist has the PWA. - README (EN/RU): an install-from-a-release section (pipx/pip with the wheel, the direct release URL for pip) and the --web-dir default documented as the bundled PWA, or <repo>/frontend from source. - tests/test_packaging.py pins the packaged-first resolution (including the empty-directory fallback), the source checkout default, the real build backend, the manifest graft and setup.py's bundling. - verified: a fresh venv install from the wheel and from the sdist serve GET / (200, byte-identical index.html, PWA v2.7.10) out of site-packages/simtrace_analyser/web; an editable install still resolves <repo>/frontend. 547 Python + 80 Node tests.
This commit is contained in:
1 parent
4a03f83459
commit
69b200db11
11 files changed
+196
-16
No files matched your search
@@ -0,0 +1,3 @@
|
||||
graft frontend
|
||||
include README.md README_RUS.md 70-simtrace2-pysniff.rules
|
||||
include start.sh start.bat sniff-start.sh sniff-start.bat
|
||||
@@ -108,12 +108,34 @@ pip install -e .
|
||||
# or: simtrace2-pysniff [opts]
|
||||
```
|
||||
|
||||
### From a release (no git checkout)
|
||||
|
||||
The release artifacts bundle the PWA, so an installed server serves the same UI
|
||||
as a clone. Download the wheel from the
|
||||
[latest release](https://github.com/anttro/simtrace-analyser/releases/latest)
|
||||
and install it:
|
||||
|
||||
```sh
|
||||
pipx install ./simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
# or: pip install ./simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
|
||||
# then run (the UI is served at http://127.0.0.1:8081/):
|
||||
simtrace-analyser-server --capture gsmtap
|
||||
```
|
||||
|
||||
`pip` can also fetch the wheel straight from the release page:
|
||||
|
||||
```sh
|
||||
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### Analysis server + PWA (`simtrace-analyser-server`)
|
||||
|
||||
Stores captured APDU traffic in SQLite and serves it over HTTP, together with
|
||||
the bundled **SIMtrace Analyser** PWA (`frontend/`) on the same origin. Three
|
||||
the bundled **SIMtrace Analyser** PWA (`frontend/` in a source checkout,
|
||||
bundled into the package on install) on the same origin. Three
|
||||
capture modes:
|
||||
|
||||
```sh
|
||||
@@ -283,13 +305,14 @@ Server options:
|
||||
--db FILE SQLite database path (default: ~/.simtrace-analyser/sessions.db)
|
||||
--capture MODE gsmtap | direct | disabled (default: gsmtap)
|
||||
--gsmtap-port PORT UDP port for the GSMTAP listener (default: 4729)
|
||||
--web-dir PATH PWA static files directory (default: <repo>/frontend)
|
||||
--web-dir PATH PWA static files directory (default: the PWA bundled in
|
||||
the installed package, or <repo>/frontend from source)
|
||||
```
|
||||
|
||||
Then open **http://127.0.0.1:8081/** in a browser — the PWA is served by the
|
||||
server, so the UI and the API share an origin and no CORS/Private-Network-Access
|
||||
setup is involved. Use `--web-dir PATH` to serve a different PWA directory
|
||||
(default: `<repo>/frontend`).
|
||||
(default: the bundled PWA, or `<repo>/frontend` in a source checkout).
|
||||
|
||||
### Hosted PWA (landing page)
|
||||
|
||||
|
||||
+27
-3
@@ -113,12 +113,34 @@ pip install -e .
|
||||
# или: simtrace2-pysniff [opts]
|
||||
```
|
||||
|
||||
### Из релиза (без клонирования репозитория)
|
||||
|
||||
Релизные артефакты содержат PWA внутри пакета, поэтому установленный сервер
|
||||
отдаёт тот же интерфейс, что и при запуске из клона. Скачайте wheel со
|
||||
страницы [последнего релиза](https://github.com/anttro/simtrace-analyser/releases/latest)
|
||||
и установите его:
|
||||
|
||||
```sh
|
||||
pipx install ./simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
# или: pip install ./simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
|
||||
# затем запуск (интерфейс отдаётся на http://127.0.0.1:8081/):
|
||||
simtrace-analyser-server --capture gsmtap
|
||||
```
|
||||
|
||||
`pip` также может скачать wheel прямо со страницы релиза:
|
||||
|
||||
```sh
|
||||
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.10/simtrace_analyser-2.7.10-py3-none-any.whl
|
||||
```
|
||||
|
||||
## Запуск
|
||||
|
||||
### Анализирующий сервер + PWA (`simtrace-analyser-server`)
|
||||
|
||||
Сохраняет захваченный APDU-трафик в SQLite и отдаёт его по HTTP вместе
|
||||
со встроенной PWA **SIMtrace Analyser** (`frontend/`) на том же origin.
|
||||
со встроенной PWA **SIMtrace Analyser** (`frontend/` в исходном клоне, при
|
||||
установке — внутри пакета) на том же origin.
|
||||
Три режима захвата:
|
||||
|
||||
```sh
|
||||
@@ -289,13 +311,15 @@ TS 51.011), поэтому FID, повторно используемый в р
|
||||
--db FILE путь к базе SQLite (по умолчанию: ~/.simtrace-analyser/sessions.db)
|
||||
--capture MODE gsmtap | direct | disabled (по умолчанию: gsmtap)
|
||||
--gsmtap-port PORT UDP-порт листенера GSMTAP (по умолчанию: 4729)
|
||||
--web-dir PATH каталог статических файлов PWA (по умолчанию: <repo>/frontend)
|
||||
--web-dir PATH каталог статических файлов PWA (по умолчанию: PWA внутри
|
||||
установленного пакета или <repo>/frontend при запуске из исходников)
|
||||
```
|
||||
|
||||
Затем откройте **http://127.0.0.1:8081/** в браузере — PWA отдаётся
|
||||
сервером, поэтому UI и API используют один origin, и настройка
|
||||
CORS/Private-Network-Access не требуется. Используйте `--web-dir PATH`,
|
||||
чтобы отдавать другой каталог PWA (по умолчанию: `<repo>/frontend`).
|
||||
чтобы отдавать другой каталог PWA (по умолчанию: PWA внутри пакета или
|
||||
`<repo>/frontend` при запуске из исходников).
|
||||
|
||||
### Размещённая PWA (лендинг)
|
||||
|
||||
|
||||
+1
-1
@@ -3389,7 +3389,7 @@ function onSearchChange() {
|
||||
}
|
||||
|
||||
// ──────────────────── Versioning ────────────────────
|
||||
const PWA_VERSION = '2.7.9';
|
||||
const PWA_VERSION = '2.7.10';
|
||||
const MIN_SERVER_VERSION = '2.3.0';
|
||||
|
||||
function parseVersion(v) {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simtrace-analyser-v91';
|
||||
const CACHE = 'simtrace-analyser-v92';
|
||||
|
||||
const PRECACHE = [
|
||||
'index.html',
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=64"]
|
||||
build-backend = "setuptools.backends._legacy:_Backend"
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "simtrace-analyser"
|
||||
version = "2.7.9"
|
||||
version = "2.7.10"
|
||||
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
|
||||
readme = {file = "README.md", content-type = "text/markdown"}
|
||||
license = {text = "GPL-2.0-or-later"}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Bundle the PWA into the installed package.
|
||||
|
||||
The metadata lives in pyproject.toml; this file only adds the build step that
|
||||
copies ``frontend/`` into ``simtrace_analyser/web/`` inside the build tree, so
|
||||
a wheel (or an install from it) serves the same UI as a source checkout.
|
||||
``setup.py`` is part of the sdist, and the source directory is resolved
|
||||
relative to this file, so a wheel built from the unpacked sdist gets the PWA
|
||||
too.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools.command.build_py import build_py
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
WEB_SOURCE = os.path.join(HERE, 'frontend')
|
||||
WEB_TARGET = os.path.join('simtrace_analyser', 'web')
|
||||
|
||||
|
||||
class BuildPy(build_py):
|
||||
def run(self):
|
||||
super().run()
|
||||
if not os.path.isdir(WEB_SOURCE):
|
||||
raise RuntimeError(
|
||||
'the PWA source directory is missing: %s (a build must not '
|
||||
'ship a package without its user interface)' % WEB_SOURCE)
|
||||
target = os.path.join(self.build_lib, WEB_TARGET)
|
||||
shutil.copytree(
|
||||
WEB_SOURCE, target, dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns('tests', '__pycache__', '*.pyc'))
|
||||
|
||||
|
||||
setup(cmdclass={'build_py': BuildPy})
|
||||
@@ -16,10 +16,20 @@ from .capture import CaptureManager, GsmtapListener, DirectSniffer
|
||||
from .version import __version__
|
||||
|
||||
|
||||
def _pick_web_dir(package_dir, repo_root):
|
||||
"""The installed package's bundled PWA first, then the source checkout."""
|
||||
packaged = os.path.join(package_dir, 'web')
|
||||
if os.path.isfile(os.path.join(packaged, 'index.html')):
|
||||
return packaged
|
||||
return os.path.join(repo_root, 'frontend')
|
||||
|
||||
|
||||
def _default_web_dir():
|
||||
# <repo>/frontend, whether run from source or an editable install.
|
||||
# __file__ is <repo>/simtrace_analyser/__main__.py → up two levels.
|
||||
return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'frontend')
|
||||
# A wheel/sdist install carries simtrace_analyser/web (copied there by
|
||||
# setup.py); an editable install and a plain source run fall through to
|
||||
# <repo>/frontend. __file__ is <repo>/simtrace_analyser/__main__.py.
|
||||
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
return _pick_web_dir(pkg_dir, os.path.dirname(pkg_dir))
|
||||
|
||||
|
||||
def main():
|
||||
@@ -42,7 +52,9 @@ def main():
|
||||
p.add_argument('--log-requests', action='store_true', default=False,
|
||||
help='Log every HTTP request (off by default)')
|
||||
p.add_argument('--web-dir', default=_default_web_dir(), metavar='PATH',
|
||||
help='Directory with the simtrace-analyser PWA static files to serve (default: <repo>/frontend)')
|
||||
help='Directory with the simtrace-analyser PWA static files to serve '
|
||||
'(default: the PWA bundled in the installed package, '
|
||||
'or <repo>/frontend in a source checkout)')
|
||||
args = p.parse_args()
|
||||
|
||||
db = Database(args.db)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Single source of truth for the SIMtrace Analyser version."""
|
||||
|
||||
__version__ = '2.7.9'
|
||||
__version__ = '2.7.10'
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Tests for the distributable packaging (bundled PWA, backend, manifest)."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from simtrace_analyser.__main__ import _default_web_dir, _pick_web_dir
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def _touch(path):
|
||||
with open(path, 'w') as f:
|
||||
f.write('')
|
||||
|
||||
|
||||
class TestPickWebDir(unittest.TestCase):
|
||||
def test_prefers_packaged_pwa(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
pkg = os.path.join(tmp, 'simtrace_analyser')
|
||||
repo = os.path.join(tmp, 'repo')
|
||||
os.makedirs(os.path.join(pkg, 'web'))
|
||||
os.makedirs(os.path.join(repo, 'frontend'))
|
||||
_touch(os.path.join(pkg, 'web', 'index.html'))
|
||||
_touch(os.path.join(repo, 'frontend', 'index.html'))
|
||||
self.assertEqual(_pick_web_dir(pkg, repo),
|
||||
os.path.join(pkg, 'web'))
|
||||
|
||||
def test_falls_back_when_packaged_dir_is_missing(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
pkg = os.path.join(tmp, 'simtrace_analyser')
|
||||
repo = os.path.join(tmp, 'repo')
|
||||
os.makedirs(pkg)
|
||||
os.makedirs(os.path.join(repo, 'frontend'))
|
||||
_touch(os.path.join(repo, 'frontend', 'index.html'))
|
||||
self.assertEqual(_pick_web_dir(pkg, repo),
|
||||
os.path.join(repo, 'frontend'))
|
||||
|
||||
def test_falls_back_when_packaged_dir_has_no_index(self):
|
||||
# A stale/empty simtrace_analyser/web directory must not shadow the
|
||||
# source checkout (e.g. an editable install of a half-built tree).
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
pkg = os.path.join(tmp, 'simtrace_analyser')
|
||||
repo = os.path.join(tmp, 'repo')
|
||||
os.makedirs(os.path.join(pkg, 'web'))
|
||||
os.makedirs(os.path.join(repo, 'frontend'))
|
||||
_touch(os.path.join(repo, 'frontend', 'index.html'))
|
||||
self.assertEqual(_pick_web_dir(pkg, repo),
|
||||
os.path.join(repo, 'frontend'))
|
||||
|
||||
|
||||
class TestDefaultWebDir(unittest.TestCase):
|
||||
def test_source_checkout(self):
|
||||
web = _default_web_dir()
|
||||
self.assertTrue(os.path.isfile(os.path.join(web, 'index.html')),
|
||||
'no index.html in %s' % web)
|
||||
|
||||
|
||||
class TestDistributionMetadata(unittest.TestCase):
|
||||
def _read(self, name):
|
||||
with open(os.path.join(REPO, name)) as f:
|
||||
return f.read()
|
||||
|
||||
def test_build_backend_exists(self):
|
||||
pyproject = self._read('pyproject.toml')
|
||||
self.assertIn('build-backend = "setuptools.build_meta"', pyproject)
|
||||
self.assertNotIn('backends._legacy', pyproject)
|
||||
|
||||
def test_manifest_ships_the_pwa(self):
|
||||
# The sdist carries frontend/, so a wheel built from it has the PWA.
|
||||
self.assertIn('graft frontend', self._read('MANIFEST.in'))
|
||||
|
||||
def test_setup_py_bundles_the_pwa(self):
|
||||
setup_py = self._read('setup.py')
|
||||
self.assertIn('frontend', setup_py)
|
||||
self.assertIn('copytree', setup_py)
|
||||
self.assertIn("'web'", setup_py)
|
||||
self.assertIn("'build_py'", setup_py)
|
||||
self.assertIn('ignore_patterns', setup_py)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
|
||||
class TestVersion(unittest.TestCase):
|
||||
def test_package_version(self):
|
||||
from simtrace2_pysniff import __version__
|
||||
self.assertEqual(__version__, '2.7.9')
|
||||
self.assertEqual(__version__, '2.7.10')
|
||||
|
||||
def test_status_includes_version(self):
|
||||
import os
|
||||
|
||||
Reference in new issue
Block a user