Add brand-specific update channel and environment configuration
- Updated `brand_config.py` to include `WEBDROP_UPDATE_CHANNEL` in the environment variables. - Enhanced `build_macos.sh` to create a bundled `.env` file with brand-specific defaults, including the update channel. - Implemented a method in `build_windows.py` to create a bundled `.env` file for Windows builds, incorporating brand-specific runtime defaults. - Modified `config.py` to ensure the application can locate the `.env` file in various installation scenarios. - Added unit tests in `test_config.py` to verify the loading of the bootstrap `.env` from the PyInstaller runtime directory. - Generated new WiX object and script files for the Windows installer, including application shortcuts and registry entries.
This commit is contained in:
parent
de6e9838e5
commit
eab1009d8c
9 changed files with 3083 additions and 2886 deletions
1
build/WebDropBridge.generated.wixobj
Normal file
1
build/WebDropBridge.generated.wixobj
Normal file
File diff suppressed because one or more lines are too long
88
build/WebDropBridge.generated.wxs
Normal file
88
build/WebDropBridge.generated.wxs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:ui="http://schemas.microsoft.com/wix/2010/ui"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
<Product Id="*" Name="Agravity Bridge" Language="1033" Version="0.8.4"
|
||||
Manufacturer="agravity"
|
||||
UpgradeCode="4a7c80da-6170-4d88-8efc-3f30636f6392">
|
||||
|
||||
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Platform="x64" />
|
||||
<Media Id="1" Cabinet="AgravityBridge.cab" EmbedCab="yes" />
|
||||
|
||||
<!-- Required property for WixUI_InstallDir dialog set -->
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
|
||||
<!-- Application Icon -->
|
||||
<Icon Id="AppIcon.ico" SourceFile="C:\Development\VS Code Projects\webdrop_bridge\resources\icons\app.ico" />
|
||||
|
||||
<!-- Custom branding for InstallDir dialog set -->
|
||||
<WixVariable Id="WixUIDialogBmp" Value="C:\Development\VS Code Projects\webdrop_bridge\resources\icons\background.bmp" />
|
||||
<WixVariable Id="WixUIBannerBmp" Value="C:\Development\VS Code Projects\webdrop_bridge\resources\icons\banner.bmp" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="C:\Development\VS Code Projects\webdrop_bridge\resources\license.rtf" />
|
||||
|
||||
<!-- Installation UI dialogs -->
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
<UIRef Id="WixUI_ErrorProgressText" />
|
||||
|
||||
<!-- Close running application before installation -->
|
||||
<util:CloseApplication
|
||||
Target="AgravityBridge.exe"
|
||||
CloseMessage="yes"
|
||||
RebootPrompt="no"
|
||||
ElevatedCloseMessage="no" />
|
||||
|
||||
<Feature Id="ProductFeature" Title="Agravity Bridge" Level="1">
|
||||
<ComponentGroupRef Id="AppFiles" />
|
||||
<ComponentRef Id="ProgramMenuShortcut" />
|
||||
<ComponentRef Id="DesktopShortcut" />
|
||||
</Feature>
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Agravity Bridge" />
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="Agravity Bridge"/>
|
||||
</Directory>
|
||||
<Directory Id="DesktopFolder" />
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ProgramMenuShortcut" Guid="*">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="Agravity Bridge"
|
||||
Description="Agravity drag-and-drop bridge"
|
||||
Target="[INSTALLFOLDER]AgravityBridge.exe"
|
||||
Icon="AppIcon.ico"
|
||||
IconIndex="0"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="ApplicationProgramsFolderRemove"
|
||||
On="uninstall" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\AgravityBridge"
|
||||
Name="installed"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="DesktopFolder">
|
||||
<Component Id="DesktopShortcut" Guid="*">
|
||||
<Shortcut Id="DesktopApplicationShortcut"
|
||||
Name="Agravity Bridge"
|
||||
Description="Agravity drag-and-drop bridge"
|
||||
Target="[INSTALLFOLDER]AgravityBridge.exe"
|
||||
Icon="AppIcon.ico"
|
||||
IconIndex="0"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\AgravityBridge"
|
||||
Name="DesktopShortcut"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</Product>
|
||||
</Wix>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -269,6 +269,7 @@ def cli_env(args: argparse.Namespace) -> int:
|
|||
"WEBDROP_EXE_NAME": brand.exe_name,
|
||||
"WEBDROP_BUNDLE_ID": brand.bundle_identifier,
|
||||
"WEBDROP_CONFIG_DIR_NAME": brand.config_dir_name,
|
||||
"WEBDROP_UPDATE_CHANNEL": brand.update_channel,
|
||||
"WEBDROP_ICON_ICO": str(brand.icon_ico),
|
||||
"WEBDROP_ICON_ICNS": str(brand.icon_icns),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,8 +200,21 @@ build_executable() {
|
|||
log_info "Building macOS executable with PyInstaller..."
|
||||
echo ""
|
||||
|
||||
# Create bundled runtime .env with brand defaults so first launch
|
||||
# uses brand-specific app name and config directory.
|
||||
BUNDLED_ENV_FILE="$TEMP_BUILD/.env"
|
||||
cp "$ENV_FILE" "$BUNDLED_ENV_FILE"
|
||||
{
|
||||
echo ""
|
||||
echo "# Brand-specific defaults added during packaging"
|
||||
echo "APP_NAME=\"$WEBDROP_APP_DISPLAY_NAME\""
|
||||
echo "BRAND_ID=\"$WEBDROP_BRAND_ID\""
|
||||
echo "APP_CONFIG_DIR_NAME=\"$WEBDROP_CONFIG_DIR_NAME\""
|
||||
echo "UPDATE_CHANNEL=\"$WEBDROP_UPDATE_CHANNEL\""
|
||||
} >> "$BUNDLED_ENV_FILE"
|
||||
|
||||
# Export env file for spec file to pick up
|
||||
export WEBDROP_ENV_FILE="$ENV_FILE"
|
||||
export WEBDROP_ENV_FILE="$BUNDLED_ENV_FILE"
|
||||
export WEBDROP_VERSION="$VERSION"
|
||||
export WEBDROP_BUNDLE_ID="$BUNDLE_IDENTIFIER"
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import argparse
|
|||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
# Import shared version utilities
|
||||
from brand_config import load_brand_config
|
||||
from sync_version import get_current_version, do_sync_version
|
||||
|
|
@ -95,6 +97,44 @@ class WindowsBuilder:
|
|||
shutil.rmtree(path)
|
||||
print(f" Removed {path}")
|
||||
|
||||
@staticmethod
|
||||
def _format_env_value(value: str) -> str:
|
||||
"""Format env values safely for .env files."""
|
||||
if any(ch in value for ch in [" ", "#", '"', "'", "\t"]):
|
||||
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
||||
return f'"{escaped}"'
|
||||
return value
|
||||
|
||||
def _create_bundled_env_file(self) -> Path:
|
||||
"""Create a bundled .env file with brand-specific runtime defaults."""
|
||||
values = dotenv_values(self.env_file)
|
||||
overrides = {
|
||||
"APP_NAME": self.brand.display_name,
|
||||
"BRAND_ID": self.brand.brand_id,
|
||||
"APP_CONFIG_DIR_NAME": self.brand.config_dir_name,
|
||||
"UPDATE_CHANNEL": self.brand.update_channel,
|
||||
}
|
||||
|
||||
output_env = self.temp_dir / ".env"
|
||||
output_env.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
lines: list[str] = []
|
||||
for key, raw_value in values.items():
|
||||
if key in overrides:
|
||||
continue
|
||||
if raw_value is None:
|
||||
lines.append(key)
|
||||
else:
|
||||
lines.append(f"{key}={self._format_env_value(str(raw_value))}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("# Brand-specific defaults added during packaging")
|
||||
for key, value in overrides.items():
|
||||
lines.append(f"{key}={self._format_env_value(value)}")
|
||||
|
||||
output_env.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return output_env
|
||||
|
||||
def build_executable(self) -> bool:
|
||||
"""Build executable using PyInstaller."""
|
||||
print("\n🔨 Building Windows executable with PyInstaller...")
|
||||
|
|
@ -119,7 +159,7 @@ class WindowsBuilder:
|
|||
|
||||
# Set environment variable for spec file to use
|
||||
env = os.environ.copy()
|
||||
env["WEBDROP_ENV_FILE"] = str(self.env_file)
|
||||
env["WEBDROP_ENV_FILE"] = str(self._create_bundled_env_file())
|
||||
env["WEBDROP_BRAND_ID"] = self.brand.brand_id
|
||||
env["WEBDROP_APP_DISPLAY_NAME"] = self.brand.display_name
|
||||
env["WEBDROP_ASSET_PREFIX"] = self.brand.asset_prefix
|
||||
|
|
|
|||
|
|
@ -408,7 +408,15 @@ class Config:
|
|||
candidate_paths.append(Path(env_file).resolve())
|
||||
else:
|
||||
if getattr(sys, "frozen", False):
|
||||
candidate_paths.append(Path(sys.executable).resolve().parent / ".env")
|
||||
exe_dir = Path(sys.executable).resolve().parent
|
||||
# One-folder fallback: some packagers place data files in _internal.
|
||||
candidate_paths.append(exe_dir / ".env")
|
||||
candidate_paths.append(exe_dir / "_internal" / ".env")
|
||||
|
||||
# PyInstaller runtime extraction directory (one-file and one-folder).
|
||||
meipass = getattr(sys, "_MEIPASS", None)
|
||||
if meipass:
|
||||
candidate_paths.append(Path(meipass).resolve() / ".env")
|
||||
|
||||
candidate_paths.append(Path.cwd() / ".env")
|
||||
candidate_paths.append(Path(__file__).resolve().parents[2] / ".env")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Unit tests for configuration system."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -232,3 +233,22 @@ class TestConfigValidation:
|
|||
|
||||
assert config_path.parts[-2:] == ("agravity_bridge", "config.json")
|
||||
assert log_path.parts[-2:] == ("logs", "agravity_bridge.log")
|
||||
|
||||
|
||||
class TestBootstrapEnvLoading:
|
||||
"""Test bootstrap .env loading behavior for packaged builds."""
|
||||
|
||||
def test_load_bootstrap_env_reads_meipass_dotenv(self, tmp_path, monkeypatch):
|
||||
"""Packaged app should load .env from PyInstaller runtime directory."""
|
||||
meipass_dir = tmp_path / "runtime"
|
||||
meipass_dir.mkdir(parents=True)
|
||||
env_path = meipass_dir / ".env"
|
||||
env_path.write_text("APP_NAME=Agravity Bridge\n", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(sys, "frozen", True, raising=False)
|
||||
monkeypatch.setattr(sys, "_MEIPASS", str(meipass_dir), raising=False)
|
||||
|
||||
loaded_path = Config.load_bootstrap_env()
|
||||
|
||||
assert loaded_path == env_path
|
||||
assert os.getenv("APP_NAME") == "Agravity Bridge"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue