Skip to content
Merged
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
48 changes: 48 additions & 0 deletions installer/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,40 @@ def run_dev() -> int:
).returncode


def generate_build_metadata(args: argparse.Namespace) -> None:
"""Generate version and build info files (mirrors CI steps)."""
print("\n" + "=" * 60)
print("Generating build metadata...")
print("=" * 60 + "\n")

# Generate version file
version_script = ROOT / "scripts" / "generate_version.py"
if version_script.exists():
run_command([sys.executable, str(version_script)], cwd=ROOT)
else:
print(f"Warning: {version_script} not found, skipping version generation")

# Generate build info (_build_info.py with BUILD_TAG)
build_info_script = ROOT / "scripts" / "generate_build_info.py"
if build_info_script.exists():
tag = args.tag
if not tag and args.nightly:
from datetime import datetime, timezone

tag = f"nightly-{datetime.now(timezone.utc).strftime('%Y%m%d')}"

cmd = [sys.executable, str(build_info_script)]
if tag:
cmd.append(tag)
print(f" Build tag: {tag}")
else:
print(" Build tag: None (stable)")

run_command(cmd, cwd=ROOT)
else:
print(f"Warning: {build_info_script} not found, skipping build info generation")


def main() -> int:
"""Run the build process."""
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -441,6 +475,17 @@ def main() -> int:
action="store_true",
help="Skip portable ZIP creation",
)
parser.add_argument(
"--nightly",
action="store_true",
help="Build as nightly (generates nightly-YYYYMMDD build tag)",
)
parser.add_argument(
"--tag",
type=str,
default=None,
help="Custom build tag (e.g. nightly-20260208). Overrides --nightly.",
)

args = parser.parse_args()

Expand Down Expand Up @@ -471,6 +516,9 @@ def main() -> int:
if not args.skip_icons:
check_icons()

# Generate version and build info (same as CI)
generate_build_metadata(args)

# Build with PyInstaller
if not build_pyinstaller():
return 1
Expand Down
12 changes: 11 additions & 1 deletion src/accessiweather/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,16 @@ def do_check():
current_version = getattr(self, "version", "0.0.0")
build_tag = getattr(self, "build_tag", None)
current_nightly_date = parse_nightly_date(build_tag) if build_tag else None
display_version = current_nightly_date if current_nightly_date else current_version

# Safety: if frozen but no build_tag and checking nightly channel,
# skip auto-prompt to avoid infinite update loops
if not build_tag and channel == "nightly":
logger.warning(
"Skipping startup nightly update check: no build_tag available. "
"Use Help > Check for Updates to check manually."
)
return

async def check():
service = UpdateService("AccessiWeather")
Expand All @@ -399,7 +409,7 @@ async def check():
def show_update_notification():
result = wx.MessageBox(
f"A new {channel_label} update is available!\n\n"
f"Current: {current_version}\n"
f"Current: {display_version}\n"
f"Latest: {update_info.version}\n\n"
"Download now?",
"Update Available",
Expand Down
14 changes: 14 additions & 0 deletions src/accessiweather/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ def is_portable_mode() -> bool:

logger.debug(f"Checking portable mode for executable directory: {app_dir}")

# Check for uninstaller (Inno Setup leaves unins*.exe in app directory)
# This reliably detects installed copies regardless of install location
app_dir_path = os.path.dirname(sys.executable)
uninstaller_exists = any(
f.startswith("unins") and f.endswith(".exe")
for f in os.listdir(app_dir_path)
if os.path.isfile(os.path.join(app_dir_path, f))
)
if uninstaller_exists:
logger.debug(
f"Not in portable mode: uninstaller found in {app_dir_path}"
)
return False

# Check if we're running from Program Files (standard installation)
program_files = os.environ.get("PROGRAMFILES", "")
program_files_x86 = os.environ.get("PROGRAMFILES(X86)", "")
Expand Down
6 changes: 4 additions & 2 deletions src/accessiweather/ui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ def _on_check_updates(self) -> None:
current_version = getattr(self.app, "version", "0.0.0")
build_tag = getattr(self.app, "build_tag", None)
current_nightly_date = parse_nightly_date(build_tag) if build_tag else None
# Show nightly date as the display version when running a nightly build
display_version = current_nightly_date if current_nightly_date else current_version

# Show checking status
wx.BeginBusyCursor()
Expand Down Expand Up @@ -460,7 +462,7 @@ async def check():
elif current_nightly_date:
msg = f"You're on the latest nightly ({current_nightly_date})."
else:
msg = f"You're up to date ({current_version})."
msg = f"You're up to date ({display_version})."

wx.CallAfter(
wx.MessageBox,
Expand All @@ -475,7 +477,7 @@ async def check():
def prompt():
result = wx.MessageBox(
f"A new {channel_label} update is available!\n\n"
f"Current: {current_version}\n"
f"Current: {display_version}\n"
f"Latest: {update_info.version}\n\n"
"Download now?",
"Update Available",
Expand Down
Loading