Some checks failed
Tests & Quality Checks / Test on Python 3.11 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.11-1 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12-1 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.10 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.11-2 (push) Has been cancelled
Tests & Quality Checks / Test on Python 3.12-2 (push) Has been cancelled
Tests & Quality Checks / Build Artifacts (push) Has been cancelled
Tests & Quality Checks / Build Artifacts-1 (push) Has been cancelled
- Added support for multiple brands in release scripts, allowing for branded artifacts. - Introduced brand configuration management with JSON files for each brand. - Created a new `brand_config.py` script to handle brand-specific logic and asset resolution. - Updated `create_release.ps1` and `create_release.sh` scripts to utilize brand configurations and generate release manifests. - Added unit tests for brand configuration loading and release manifest generation. - Introduced `agravity` brand with its specific configuration in `agravity.json`.
236 lines
7.8 KiB
Python
236 lines
7.8 KiB
Python
"""Brand-aware build configuration helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BrandConfig:
|
|
"""Packaging metadata for a branded build."""
|
|
|
|
brand_id: str
|
|
display_name: str
|
|
asset_prefix: str
|
|
exe_name: str
|
|
manufacturer: str
|
|
install_dir_name: str
|
|
shortcut_description: str
|
|
bundle_identifier: str
|
|
config_dir_name: str
|
|
msi_upgrade_code: str
|
|
update_channel: str
|
|
icon_ico: Path
|
|
icon_icns: Path
|
|
dialog_bmp: Path
|
|
banner_bmp: Path
|
|
license_rtf: Path
|
|
|
|
def windows_installer_name(self, version: str) -> str:
|
|
return f"{self.asset_prefix}-{version}-win-x64.msi"
|
|
|
|
def macos_installer_name(self, version: str) -> str:
|
|
return f"{self.asset_prefix}-{version}-macos-universal.dmg"
|
|
|
|
@property
|
|
def app_bundle_name(self) -> str:
|
|
return f"{self.asset_prefix}.app"
|
|
|
|
|
|
DEFAULT_BRAND_VALUES: dict[str, Any] = {
|
|
"brand_id": "webdrop_bridge",
|
|
"display_name": "WebDrop Bridge",
|
|
"asset_prefix": "WebDropBridge",
|
|
"exe_name": "WebDropBridge",
|
|
"manufacturer": "HIM-Tools",
|
|
"install_dir_name": "WebDrop Bridge",
|
|
"shortcut_description": "Web Drag-and-Drop Bridge",
|
|
"bundle_identifier": "de.him_tools.webdrop-bridge",
|
|
"config_dir_name": "webdrop_bridge",
|
|
"msi_upgrade_code": "12345678-1234-1234-1234-123456789012",
|
|
"update_channel": "stable",
|
|
"icon_ico": "resources/icons/app.ico",
|
|
"icon_icns": "resources/icons/app.icns",
|
|
"dialog_bmp": "resources/icons/background.bmp",
|
|
"banner_bmp": "resources/icons/banner.bmp",
|
|
"license_rtf": "resources/license.rtf",
|
|
}
|
|
|
|
|
|
def project_root() -> Path:
|
|
return Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def brands_dir(root: Path | None = None) -> Path:
|
|
base = root or project_root()
|
|
return base / "build" / "brands"
|
|
|
|
|
|
def load_brand_config(
|
|
brand: str | None = None,
|
|
*,
|
|
root: Path | None = None,
|
|
manifest_path: Path | None = None,
|
|
) -> BrandConfig:
|
|
"""Load a brand manifest with defaults and asset fallbacks."""
|
|
base = root or project_root()
|
|
values = dict(DEFAULT_BRAND_VALUES)
|
|
|
|
if manifest_path is None and brand:
|
|
manifest_path = brands_dir(base) / f"{brand}.json"
|
|
|
|
if manifest_path and manifest_path.exists():
|
|
values.update(json.loads(manifest_path.read_text(encoding="utf-8")))
|
|
elif manifest_path and not manifest_path.exists():
|
|
raise FileNotFoundError(f"Brand manifest not found: {manifest_path}")
|
|
|
|
def resolve_asset(key: str) -> Path:
|
|
candidate = base / str(values.get(key, DEFAULT_BRAND_VALUES[key]))
|
|
if candidate.exists():
|
|
return candidate
|
|
return base / str(DEFAULT_BRAND_VALUES[key])
|
|
|
|
return BrandConfig(
|
|
brand_id=str(values["brand_id"]),
|
|
display_name=str(values["display_name"]),
|
|
asset_prefix=str(values["asset_prefix"]),
|
|
exe_name=str(values["exe_name"]),
|
|
manufacturer=str(values["manufacturer"]),
|
|
install_dir_name=str(values["install_dir_name"]),
|
|
shortcut_description=str(values["shortcut_description"]),
|
|
bundle_identifier=str(values["bundle_identifier"]),
|
|
config_dir_name=str(values["config_dir_name"]),
|
|
msi_upgrade_code=str(values["msi_upgrade_code"]),
|
|
update_channel=str(values.get("update_channel", "stable")),
|
|
icon_ico=resolve_asset("icon_ico"),
|
|
icon_icns=resolve_asset("icon_icns"),
|
|
dialog_bmp=resolve_asset("dialog_bmp"),
|
|
banner_bmp=resolve_asset("banner_bmp"),
|
|
license_rtf=resolve_asset("license_rtf"),
|
|
)
|
|
|
|
|
|
def generate_release_manifest(
|
|
version: str,
|
|
brands: list[str],
|
|
*,
|
|
output_path: Path,
|
|
root: Path | None = None,
|
|
) -> Path:
|
|
"""Generate a shared release-manifest.json from local build outputs."""
|
|
base = root or project_root()
|
|
manifest: dict[str, Any] = {
|
|
"version": version,
|
|
"channel": "stable",
|
|
"brands": {},
|
|
}
|
|
|
|
for brand_name in brands:
|
|
brand = load_brand_config(brand_name, root=base)
|
|
manifest["channel"] = brand.update_channel
|
|
entries: dict[str, dict[str, str]] = {}
|
|
|
|
windows_dir = base / "build" / "dist" / "windows" / brand.brand_id
|
|
windows_installer = windows_dir / brand.windows_installer_name(version)
|
|
windows_checksum = windows_dir / f"{windows_installer.name}.sha256"
|
|
if windows_installer.exists():
|
|
entries["windows-x64"] = {
|
|
"installer": windows_installer.name,
|
|
"checksum": windows_checksum.name if windows_checksum.exists() else "",
|
|
}
|
|
|
|
macos_dir = base / "build" / "dist" / "macos" / brand.brand_id
|
|
macos_installer = macos_dir / brand.macos_installer_name(version)
|
|
macos_checksum = macos_dir / f"{macos_installer.name}.sha256"
|
|
if macos_installer.exists():
|
|
entries["macos-universal"] = {
|
|
"installer": macos_installer.name,
|
|
"checksum": macos_checksum.name if macos_checksum.exists() else "",
|
|
}
|
|
|
|
if entries:
|
|
manifest["brands"][brand.brand_id] = entries
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
return output_path
|
|
|
|
|
|
def cli_env(args: argparse.Namespace) -> int:
|
|
brand = load_brand_config(args.brand)
|
|
assignments = {
|
|
"WEBDROP_BRAND_ID": brand.brand_id,
|
|
"WEBDROP_APP_DISPLAY_NAME": brand.display_name,
|
|
"WEBDROP_ASSET_PREFIX": brand.asset_prefix,
|
|
"WEBDROP_EXE_NAME": brand.exe_name,
|
|
"WEBDROP_BUNDLE_ID": brand.bundle_identifier,
|
|
"WEBDROP_CONFIG_DIR_NAME": brand.config_dir_name,
|
|
"WEBDROP_ICON_ICO": str(brand.icon_ico),
|
|
"WEBDROP_ICON_ICNS": str(brand.icon_icns),
|
|
}
|
|
for key, value in assignments.items():
|
|
print(f'export {key}="{value}"')
|
|
return 0
|
|
|
|
|
|
def cli_manifest(args: argparse.Namespace) -> int:
|
|
output = generate_release_manifest(
|
|
args.version,
|
|
args.brands,
|
|
output_path=Path(args.output).resolve(),
|
|
)
|
|
print(output)
|
|
return 0
|
|
|
|
|
|
def cli_show(args: argparse.Namespace) -> int:
|
|
brand = load_brand_config(args.brand)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"brand_id": brand.brand_id,
|
|
"display_name": brand.display_name,
|
|
"asset_prefix": brand.asset_prefix,
|
|
"exe_name": brand.exe_name,
|
|
"manufacturer": brand.manufacturer,
|
|
"install_dir_name": brand.install_dir_name,
|
|
"shortcut_description": brand.shortcut_description,
|
|
"bundle_identifier": brand.bundle_identifier,
|
|
"config_dir_name": brand.config_dir_name,
|
|
"msi_upgrade_code": brand.msi_upgrade_code,
|
|
"update_channel": brand.update_channel,
|
|
},
|
|
indent=2,
|
|
)
|
|
)
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Brand-aware build configuration")
|
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
|
|
env_parser = subparsers.add_parser("env")
|
|
env_parser.add_argument("--brand", required=True)
|
|
env_parser.set_defaults(func=cli_env)
|
|
|
|
manifest_parser = subparsers.add_parser("release-manifest")
|
|
manifest_parser.add_argument("--version", required=True)
|
|
manifest_parser.add_argument("--output", required=True)
|
|
manifest_parser.add_argument("--brands", nargs="+", required=True)
|
|
manifest_parser.set_defaults(func=cli_manifest)
|
|
|
|
show_parser = subparsers.add_parser("show")
|
|
show_parser.add_argument("--brand", required=True)
|
|
show_parser.set_defaults(func=cli_show)
|
|
|
|
args = parser.parse_args()
|
|
return args.func(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|