From fd934cc3af4c558d8e48654c6ea1db7acac1fbba Mon Sep 17 00:00:00 2001 From: Samuel Veiga Rangel Date: Tue, 30 Jun 2026 12:07:18 -0300 Subject: [PATCH 1/5] fix script build linux --- spsvalidator/packaging/build_linux.sh | 53 ++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) mode change 100644 => 100755 spsvalidator/packaging/build_linux.sh diff --git a/spsvalidator/packaging/build_linux.sh b/spsvalidator/packaging/build_linux.sh old mode 100644 new mode 100755 index b235689..5af9a7b --- a/spsvalidator/packaging/build_linux.sh +++ b/spsvalidator/packaging/build_linux.sh @@ -4,19 +4,62 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" -python -m pip install -e ".[dev]" +VENV_DIR="${ROOT_DIR}/.venv" +_ensure_build_venv() { + if [[ -d "$VENV_DIR" ]] && ! "$VENV_DIR/bin/python" -c "import gi" >/dev/null 2>&1; then + rm -rf "$VENV_DIR" + fi + if [[ ! -d "$VENV_DIR" ]]; then + python3 -m venv --system-site-packages "$VENV_DIR" + fi +} + +if [[ -z "${VIRTUAL_ENV:-}" ]]; then + _ensure_build_venv + # shellcheck source=/dev/null + source "$VENV_DIR/bin/activate" +fi + +if ! python -c "import gi" >/dev/null 2>&1; then + echo "Missing GTK bindings (gi). Install system packages and rebuild:" >&2 + echo " sudo apt install python3-gi python3-gi-cairo gir1.2-webkit-6.0" >&2 + exit 1 +fi + +python -m pip install -e . python -m pip install pyinstaller -pyinstaller --noconfirm --windowed \ + +# Remove old onedir output so the onefile binary can use dist/spsvalidator. +rm -rf build/spsvalidator dist/spsvalidator spsvalidator.spec + +pyinstaller --noconfirm --clean --onefile --windowed \ --name spsvalidator \ --icon src/spsvalidator/web/static/img/icon.png \ --paths src \ - --collect-all packtools \ - --collect-all webview \ + --collect-data packtools \ --collect-data spsvalidator \ --hidden-import pkg_resources \ --hidden-import requests \ --hidden-import tenacity \ --hidden-import langdetect \ + --hidden-import gi \ + --hidden-import gi.repository \ + --hidden-import gi.repository.Gtk \ + --hidden-import gi.repository.WebKit2 \ + --hidden-import webview.platforms.gtk \ + --exclude-module webview.platforms.qt \ + --exclude-module webview.platforms.android \ + --exclude-module webview.platforms.cocoa \ + --exclude-module webview.platforms.winforms \ + --exclude-module webview.platforms.edgechromium \ + --exclude-module qtpy \ + --exclude-module PyQt6 \ + --exclude-module PySide6 \ + --exclude-module black \ + --exclude-module pytest \ + --exclude-module isort \ --copy-metadata setuptools \ src/spsvalidator/main.py -echo "Use linuxdeploy/appimagetool to convert dist/spsvalidator into AppImage." + +echo "Linux executable generated at dist/spsvalidator." +echo "Use linuxdeploy/appimagetool to convert it into AppImage." From 39d3bced02820b0e4fb4a760dbcb49bb026e992a Mon Sep 17 00:00:00 2001 From: Samuel Veiga Rangel Date: Tue, 30 Jun 2026 15:05:53 -0300 Subject: [PATCH 2/5] =?UTF-8?q?Add=20fun=C3=A7=C3=B5es=20para=20obter=20in?= =?UTF-8?q?forma=C3=A7=C3=B5es=20do=20sistema=20em=20que=20foi=20executado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/spsvalidator/build_metadata.py | 69 +++++++++++++++---- spsvalidator/src/spsvalidator/version.py | 8 ++- spsvalidator/src/spsvalidator/web/i18n.py | 6 +- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/spsvalidator/src/spsvalidator/build_metadata.py b/spsvalidator/src/spsvalidator/build_metadata.py index 36864fa..bca5344 100644 --- a/spsvalidator/src/spsvalidator/build_metadata.py +++ b/spsvalidator/src/spsvalidator/build_metadata.py @@ -1,24 +1,63 @@ from __future__ import annotations import platform +import sys +from pathlib import Path -from spsvalidator import build_info +def _linux_distro_name() -> str: + os_release = Path("/etc/os-release") + if not os_release.is_file(): + return "Linux" -def get_footer_build_label(language: str, translations: dict[str, str]) -> str: - if ( - build_info.BUILD_MACOS_VERSION != "development" - and build_info.BUILD_PLATFORM == "macOS" - ): - return translations["footer_built_for_macos"].format( - version=build_info.BUILD_MACOS_VERSION - ) - runtime_platform = platform.system() - if runtime_platform == "Darwin": + values: dict[str, str] = {} + for line in os_release.read_text(encoding="utf-8").splitlines(): + if "=" not in line: + continue + key, _, value = line.partition("=") + values[key] = value.strip().strip('"') + + return values.get("PRETTY_NAME") or values.get("NAME") or "Linux" + + +def _runtime_platform_label() -> str: + system = platform.system() + if system == "Darwin": + return "macOS" + if system == "Windows": + return "Windows" + if system == "Linux": + return "Linux" + return system + + +def _runtime_version_label() -> str: + system = platform.system() + if system == "Darwin": mac_version = platform.mac_ver()[0] build_number = platform.mac_ver()[2] - version_label = mac_version if build_number: - version_label = f"{mac_version} ({build_number})" - return translations["footer_built_for_macos"].format(version=version_label) - return translations["footer_dev_build"].format(platform=runtime_platform) + return f"{mac_version} ({build_number})" + return mac_version or "unknown" + + if system == "Windows": + version, _, build, _ = platform.win32_ver() + label = version or platform.release() + if build: + return f"{label} (build {build})" + return label or "unknown" + + if system == "Linux": + return f"{_linux_distro_name()} (kernel {platform.release()})" + + return platform.platform() + + +def get_footer_build_label(language: str, translations: dict[str, str]) -> str: + platform_label = _runtime_platform_label() + if getattr(sys, "frozen", False): + return translations["footer_built_for"].format( + platform=platform_label, + version=_runtime_version_label(), + ) + return translations["footer_dev_build"].format(platform=platform_label) diff --git a/spsvalidator/src/spsvalidator/version.py b/spsvalidator/src/spsvalidator/version.py index b9d68d5..3c17a6d 100644 --- a/spsvalidator/src/spsvalidator/version.py +++ b/spsvalidator/src/spsvalidator/version.py @@ -1,6 +1,7 @@ from __future__ import annotations import tomllib +from importlib.metadata import PackageNotFoundError, version from pathlib import Path @@ -10,9 +11,10 @@ def _load_version() -> str: with pyproject_path.open("rb") as file_pointer: data = tomllib.load(file_pointer) return str(data["project"]["version"]) - from spsvalidator import build_info - - return build_info.APP_VERSION + try: + return version("spsvalidator") + except PackageNotFoundError: + return "0.0.0" __version__ = _load_version() diff --git a/spsvalidator/src/spsvalidator/web/i18n.py b/spsvalidator/src/spsvalidator/web/i18n.py index 4298d15..602b9d4 100644 --- a/spsvalidator/src/spsvalidator/web/i18n.py +++ b/spsvalidator/src/spsvalidator/web/i18n.py @@ -35,7 +35,7 @@ "validation_issues": "Issues de validação", "no_exceptions": "Sem exceptions.", "detail": "Detalhe", - "footer_built_for_macos": "Compilado para macOS {version}", + "footer_built_for": "Compilado para {platform} {version}", "footer_dev_build": "Build de desenvolvimento ({platform})", "status_valid": "valid", "status_invalid": "invalid", @@ -69,7 +69,7 @@ "validation_issues": "Validation issues", "no_exceptions": "No exceptions.", "detail": "Detail", - "footer_built_for_macos": "Built for macOS {version}", + "footer_built_for": "Built for {platform} {version}", "footer_dev_build": "Development build ({platform})", "status_valid": "valid", "status_invalid": "invalid", @@ -103,7 +103,7 @@ "validation_issues": "Issues de validación", "no_exceptions": "Sin exceptions.", "detail": "Detalle", - "footer_built_for_macos": "Compilado para macOS {version}", + "footer_built_for": "Compilado para {platform} {version}", "footer_dev_build": "Build de desarrollo ({platform})", "status_valid": "valid", "status_invalid": "invalid", From 698e8d6d857475cc68b76d28d8ba69c2b7b3638a Mon Sep 17 00:00:00 2001 From: Samuel Veiga Rangel Date: Tue, 30 Jun 2026 15:06:02 -0300 Subject: [PATCH 3/5] remove --- spsvalidator/packaging/generate_build_info.sh | 32 ------------------- spsvalidator/src/spsvalidator/build_info.py | 3 -- 2 files changed, 35 deletions(-) delete mode 100755 spsvalidator/packaging/generate_build_info.sh delete mode 100644 spsvalidator/src/spsvalidator/build_info.py diff --git a/spsvalidator/packaging/generate_build_info.sh b/spsvalidator/packaging/generate_build_info.sh deleted file mode 100755 index 7d4bf01..0000000 --- a/spsvalidator/packaging/generate_build_info.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -TARGET="$ROOT_DIR/src/spsvalidator/build_info.py" -cd "$ROOT_DIR" - -APP_VERSION="$(python - <<'PY' -import tomllib -from pathlib import Path - -with Path("pyproject.toml").open("rb") as file_pointer: - data = tomllib.load(file_pointer) -print(data["project"]["version"]) -PY -)" - -if [[ "$(uname -s)" == "Darwin" ]]; then - MACOS_VERSION="$(sw_vers -productVersion)" - MACOS_BUILD="$(sw_vers -buildVersion)" - cat > "$TARGET" < "$TARGET" < Date: Tue, 30 Jun 2026 15:06:10 -0300 Subject: [PATCH 4/5] add testes --- spsvalidator/tests/test_i18n.py | 2 +- spsvalidator/tests/test_service_and_web.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spsvalidator/tests/test_i18n.py b/spsvalidator/tests/test_i18n.py index b7b1ab4..f6dac10 100644 --- a/spsvalidator/tests/test_i18n.py +++ b/spsvalidator/tests/test_i18n.py @@ -11,4 +11,4 @@ def test_translations_available_for_all_languages(): for language in ("pt", "en", "es"): translations = get_translations(language) assert translations["validate_package"] - assert translations["footer_built_for_macos"] + assert translations["footer_built_for"] diff --git a/spsvalidator/tests/test_service_and_web.py b/spsvalidator/tests/test_service_and_web.py index dd8ffba..0873d58 100644 --- a/spsvalidator/tests/test_service_and_web.py +++ b/spsvalidator/tests/test_service_and_web.py @@ -132,7 +132,12 @@ def test_set_language_switches_ui_text(tmp_path): home = client.get("/", headers={"Cookie": "lang=en"}) html = home.get_data(as_text=True) assert "SPS package validation" in html - assert "Built for macOS" in html or "Development build" in html + assert ( + "Built for macOS" in html + or "Built for Linux" in html + or "Built for Windows" in html + or "Development build" in html + ) def test_validate_route_processes_upload(monkeypatch, tmp_path): From ca93290dc8feed190d0b511105b32f1e1ff9bbae Mon Sep 17 00:00:00 2001 From: Samuel Veiga Rangel Date: Wed, 1 Jul 2026 10:58:38 -0300 Subject: [PATCH 5/5] - Add spec - Altera script para funcionar com interfaces GTK e QT --- .gitignore | 2 +- spsvalidator/packaging/build_linux.sh | 51 +++++------- .../src/spsvalidator/spsvalidator_linux.spec | 83 +++++++++++++++++++ 3 files changed, 103 insertions(+), 33 deletions(-) mode change 100755 => 100644 spsvalidator/packaging/build_linux.sh create mode 100644 spsvalidator/src/spsvalidator/spsvalidator_linux.spec diff --git a/.gitignore b/.gitignore index 83972fa..4aaf63b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec + # Installer logs pip-log.txt diff --git a/spsvalidator/packaging/build_linux.sh b/spsvalidator/packaging/build_linux.sh old mode 100755 new mode 100644 index 5af9a7b..f5f7673 --- a/spsvalidator/packaging/build_linux.sh +++ b/spsvalidator/packaging/build_linux.sh @@ -4,6 +4,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" +SPEC_FILE="${ROOT_DIR}/src/spsvalidator/spsvalidator_linux.spec" + VENV_DIR="${ROOT_DIR}/.venv" _ensure_build_venv() { if [[ -d "$VENV_DIR" ]] && ! "$VENV_DIR/bin/python" -c "import gi" >/dev/null 2>&1; then @@ -22,44 +24,29 @@ fi if ! python -c "import gi" >/dev/null 2>&1; then echo "Missing GTK bindings (gi). Install system packages and rebuild:" >&2 - echo " sudo apt install python3-gi python3-gi-cairo gir1.2-webkit-6.0" >&2 + echo " sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit-6.0" >&2 exit 1 fi python -m pip install -e . -python -m pip install pyinstaller +python -m pip install "pywebview[qt]" pyinstaller + +if ! python -c "import webview.platforms.qt" >/dev/null 2>&1; then + echo "Missing Qt backend for pywebview. Install system packages and rebuild:" >&2 + echo " sudo apt install python3-pyqt6 python3-pyqt6.qtwebengine" >&2 + exit 1 +fi + +if [[ ! -f "$SPEC_FILE" ]]; then + echo "Spec file not found: $SPEC_FILE" >&2 + exit 1 +fi -# Remove old onedir output so the onefile binary can use dist/spsvalidator. -rm -rf build/spsvalidator dist/spsvalidator spsvalidator.spec +# Remove old output so the binary can use dist/spsvalidator. +rm -rf build/spsvalidator dist/spsvalidator -pyinstaller --noconfirm --clean --onefile --windowed \ - --name spsvalidator \ - --icon src/spsvalidator/web/static/img/icon.png \ - --paths src \ - --collect-data packtools \ - --collect-data spsvalidator \ - --hidden-import pkg_resources \ - --hidden-import requests \ - --hidden-import tenacity \ - --hidden-import langdetect \ - --hidden-import gi \ - --hidden-import gi.repository \ - --hidden-import gi.repository.Gtk \ - --hidden-import gi.repository.WebKit2 \ - --hidden-import webview.platforms.gtk \ - --exclude-module webview.platforms.qt \ - --exclude-module webview.platforms.android \ - --exclude-module webview.platforms.cocoa \ - --exclude-module webview.platforms.winforms \ - --exclude-module webview.platforms.edgechromium \ - --exclude-module qtpy \ - --exclude-module PyQt6 \ - --exclude-module PySide6 \ - --exclude-module black \ - --exclude-module pytest \ - --exclude-module isort \ - --copy-metadata setuptools \ - src/spsvalidator/main.py +pyinstaller --noconfirm --clean "$SPEC_FILE" echo "Linux executable generated at dist/spsvalidator." +echo "GTK and Qt backends are bundled; runtime still needs system GTK or Qt libraries." echo "Use linuxdeploy/appimagetool to convert it into AppImage." diff --git a/spsvalidator/src/spsvalidator/spsvalidator_linux.spec b/spsvalidator/src/spsvalidator/spsvalidator_linux.spec new file mode 100644 index 0000000..50fb1c6 --- /dev/null +++ b/spsvalidator/src/spsvalidator/spsvalidator_linux.spec @@ -0,0 +1,83 @@ +# -*- mode: python ; coding: utf-8 -*- +import os + +from PyInstaller.utils.hooks import collect_all, collect_data_files, copy_metadata + +project_root = os.path.abspath(os.path.join(SPECPATH, "..", "..")) +main_script = os.path.join(project_root, "src", "spsvalidator", "main.py") +src_path = os.path.join(project_root, "src") +icon_path = os.path.join( + project_root, "src", "spsvalidator", "web", "static", "img", "icon.png" +) + +datas = [] +binaries = [] +hiddenimports = [ + "pkg_resources", + "requests", + "tenacity", + "langdetect", + "gi", + "gi.repository", + "gi.repository.Gtk", + "gi.repository.WebKit2", + "webview.platforms.gtk", + "webview.platforms.qt", +] +datas += collect_data_files("packtools") +datas += collect_data_files("spsvalidator") +datas += copy_metadata("setuptools") +tmp_ret = collect_all("webview") +datas += tmp_ret[0] +binaries += tmp_ret[1] +hiddenimports += tmp_ret[2] + +a = Analysis( + [main_script], + pathex=[src_path], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={ + "gi": { + "icons": ["hicolor"], + "themes": [], + } + }, + runtime_hooks=[], + excludes=[ + "webview.platforms.android", + "webview.platforms.cocoa", + "webview.platforms.winforms", + "webview.platforms.edgechromium", + "black", + "pytest", + "isort", + ], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="spsvalidator", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=[icon_path], +)