#!/usr/bin/env python3 """ Build script for NotePad application Creates distributable binaries for Linux and Windows """ import subprocess import sys import os import shutil import tarfile import xml.etree.ElementTree as ET from pathlib import Path def get_version(): """Extract version from .csproj file""" csproj_path = Path("NotePad.csproj") if not csproj_path.exists(): print("⚠️ WARNING: NotePad.csproj not found. Using default version 0.1.0") return "0.1.0" try: tree = ET.parse(csproj_path) root = tree.getroot() version_elem = root.find(".//Version") if version_elem is not None and version_elem.text: return version_elem.text print("⚠️ WARNING: Version not found in .csproj. Using default version 0.1.0") return "0.1.0" except Exception as e: print(f"⚠️ WARNING: Failed to parse .csproj: {e}. Using default version 0.1.0") return "0.1.0" def run_command(cmd, description): """Run a shell command and print status""" print(f"\n{'=' * 60}") print(f"{description}") print(f"{'=' * 60}") print(f"Command: {' '.join(cmd)}\n") result = subprocess.run(cmd, capture_output=False, text=True) if result.returncode != 0: print(f"\n❌ ERROR: {description} failed!") sys.exit(1) print(f"\n✅ {description} completed successfully!") return result def copy_to_publish_dir(runtime_id, platform): """Copy published files to centralized publish directory""" source_dir = Path(f"bin/Release/net8.0/{runtime_id}/publish") dest_dir = Path(f"../publish/{runtime_id}") if dest_dir.exists(): shutil.rmtree(dest_dir) shutil.copytree(source_dir, dest_dir) print(f"\n📦 Copied {platform.upper()} build to: {dest_dir.absolute()}") return dest_dir def build_windows_installer(): """Build Windows installer using Inno Setup""" iscc_path = shutil.which("iscc") if not iscc_path: print("\n⚠️ WARNING: Inno Setup not found. Skipping installer creation.") print(" Install from: https://jrsoftware.org/isinfo.php") print(" Or install via winget: winget install JRSoftware.InnoSetup") return setup_script = Path("setup.iss") if not setup_script.exists(): print(f"\n⚠️ WARNING: {setup_script} not found. Skipping installer creation.") return cmd = [iscc_path, str(setup_script)] try: run_command(cmd, "Creating Windows installer") installer_dir = Path("../publish/installers") if installer_dir.exists(): installers = list(installer_dir.glob("*.exe")) if installers: print(f"\n📦 Installer created: {installers[0].absolute()}") except Exception as e: print(f"\n⚠️ WARNING: Failed to create installer: {e}") def build_linux_tarball(version): """Create tar.gz archive with install script""" print(f"\n{'=' * 60}") print("Creating Linux tar.gz archive") print(f"{'=' * 60}\n") source_dir = Path("../publish/linux-x64") if not source_dir.exists(): print(f"⚠️ WARNING: {source_dir} not found. Skipping tar.gz creation.") return # Create tarball directory tarball_dir = Path("../publish/tarball") tarball_dir.mkdir(parents=True, exist_ok=True) # Create temporary directory for tarball contents temp_dir = tarball_dir / "notepad-temp" if temp_dir.exists(): shutil.rmtree(temp_dir) temp_dir.mkdir() # Copy application files shutil.copytree(source_dir, temp_dir / "notepad") # Copy install scripts for script in ["install.sh", "uninstall.sh"]: script_path = Path(script) if script_path.exists(): shutil.copy(script_path, temp_dir / "notepad" / script) os.chmod(temp_dir / "notepad" / script, 0o755) # Copy img directory (contains icon) img_dir = Path("img") if img_dir.exists(): shutil.copytree(img_dir, temp_dir / "notepad" / "img") # Create tar.gz tarball_path = tarball_dir / f"notepad-{version}-linux-x64.tar.gz" with tarfile.open(tarball_path, "w:gz") as tar: tar.add(temp_dir / "notepad", arcname="notepad") # Clean up temp directory shutil.rmtree(temp_dir) print(f"\n✅ Tar.gz archive created successfully!") print(f"📦 Output: {tarball_path.absolute()}") print(f"\nTo install:") print(f" tar -xzf {tarball_path.name}") print(f" cd notepad") print(f" sudo ./install.sh") def build_linux_appimage(version): """Build Linux AppImage""" build_script = Path("build-appimage.sh") if not build_script.exists(): print(f"\n⚠️ WARNING: {build_script} not found. Skipping AppImage creation.") return try: result = subprocess.run( ["bash", str(build_script), version], capture_output=True, text=True ) print(result.stdout) if result.returncode != 0: print(result.stderr) print("\n⚠️ WARNING: AppImage creation failed.") else: print("\n✅ AppImage created successfully!") except Exception as e: print(f"\n⚠️ WARNING: Failed to create AppImage: {e}") def publish_platform(platform, linux_package='both'): """Publish for a specific platform""" runtime_id = f"{platform}-x64" # Find dotnet executable dotnet_path = shutil.which("dotnet") if not dotnet_path: # Try common locations home = Path.home() common_paths = [ home / ".dotnet" / "dotnet", Path("/usr/bin/dotnet"), Path("/usr/local/bin/dotnet") ] for path in common_paths: if path.exists(): dotnet_path = str(path) break if not dotnet_path: print("❌ ERROR: dotnet not found. Please install .NET SDK.") sys.exit(1) cmd = [ dotnet_path, "publish", "-c", "Release", "-r", runtime_id, "--self-contained", "true", "-p:PublishSingleFile=true", "-p:IncludeNativeLibrariesForSelfExtract=true", "-p:PublishTrimmed=false" # Avalonia doesn't work well with trimming ] run_command(cmd, f"Building for {platform.upper()}") # Copy to centralized publish directory dest_dir = copy_to_publish_dir(runtime_id, platform) # Show the output files if dest_dir.exists(): files = list(dest_dir.glob("*")) for file in files: size_mb = file.stat().st_size / (1024 * 1024) print(f" - {file.name} ({size_mb:.2f} MB)") # Build installer for Windows if platform == 'win': build_windows_installer() # Build Linux packages if platform == 'linux': version = get_version() if linux_package in ['appimage', 'both']: build_linux_appimage(version) if linux_package in ['tarball', 'both']: build_linux_tarball(version) def main(): """Main entry point""" # Parse arguments target = 'both' linux_package = 'both' if len(sys.argv) > 1: target = sys.argv[1].lower() if target not in ['linux', 'windows', 'both', 'all']: print("Usage: python3 publish.py [linux|windows|both] [appimage|tarball|both]") print(" Platform: linux, windows, or both (default: both)") print(" Linux package: appimage, tarball, or both (default: both)") print("") print("Examples:") print(" python3 publish.py # Build everything") print(" python3 publish.py linux # Build Linux with both packages") print(" python3 publish.py linux appimage # Build Linux AppImage only") print(" python3 publish.py linux tarball # Build Linux tarball only") print(" python3 publish.py windows # Build Windows installer") sys.exit(1) if len(sys.argv) > 2: linux_package = sys.argv[2].lower() if linux_package not in ['appimage', 'tarball', 'both']: print("Error: Linux package must be 'appimage', 'tarball', or 'both'") sys.exit(1) # Change to script directory script_dir = Path(__file__).parent os.chdir(script_dir) print(f"🚀 NotePad Build Script") print(f"Target platform(s): {target.upper()}") if target in ['linux', 'both', 'all']: print(f"Linux package(s): {linux_package.upper()}") # Build for requested platform(s) if target in ['linux', 'both', 'all']: publish_platform('linux', linux_package) if target in ['windows', 'both', 'all']: publish_platform('win') print(f"\n{'=' * 60}") print("🎉 All builds completed successfully!") print(f"{'=' * 60}\n") if __name__ == "__main__": main()