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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 46 additions & 16 deletions spsvalidator/packaging/build_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,49 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"

python -m pip install -e ".[dev]"
python -m pip install pyinstaller
pyinstaller --noconfirm --windowed \
--name spsvalidator \
--icon src/spsvalidator/web/static/img/icon.png \
--paths src \
--collect-all packtools \
--collect-all webview \
--collect-data spsvalidator \
--hidden-import pkg_resources \
--hidden-import requests \
--hidden-import tenacity \
--hidden-import langdetect \
--copy-metadata setuptools \
src/spsvalidator/main.py
echo "Use linuxdeploy/appimagetool to convert dist/spsvalidator into AppImage."
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
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-gtk-3.0 gir1.2-webkit-6.0" >&2
exit 1
fi

python -m pip install -e .
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 output so the binary can use dist/spsvalidator.
rm -rf build/spsvalidator dist/spsvalidator

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."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

É realmente necessário informar o usuário sobre isso de converter para AppImage?
No futuro pode ser interessante adotarmos o AppImage para disponibilizar os executáveis.

32 changes: 0 additions & 32 deletions spsvalidator/packaging/generate_build_info.sh

This file was deleted.

3 changes: 0 additions & 3 deletions spsvalidator/src/spsvalidator/build_info.py

This file was deleted.

69 changes: 54 additions & 15 deletions spsvalidator/src/spsvalidator/build_metadata.py
Original file line number Diff line number Diff line change
@@ -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)
83 changes: 83 additions & 0 deletions spsvalidator/src/spsvalidator/spsvalidator_linux.spec
Original file line number Diff line number Diff line change
@@ -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],
)
8 changes: 5 additions & 3 deletions spsvalidator/src/spsvalidator/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import tomllib
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path


Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions spsvalidator/src/spsvalidator/web/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion spsvalidator/tests/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
7 changes: 6 additions & 1 deletion spsvalidator/tests/test_service_and_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down