2026-02-14 14:15:52 +00:00
|
|
|
#!/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
|
2026-02-14 22:24:29 +00:00
|
|
|
import xml.etree.ElementTree as ET
|
2026-02-14 14:15:52 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
2026-02-14 22:24:29 +00:00
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
|
2026-02-14 14:15:52 +00:00
|
|
|
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}")
|
|
|
|
|
|
|
|
|
|
|
2026-02-14 22:24:29 +00:00
|
|
|
def build_linux_tarball(version):
|
2026-02-14 14:15:52 +00:00
|
|
|
"""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)
|
|
|
|
|
|
2026-02-14 22:24:29 +00:00
|
|
|
# Copy img directory (contains icon)
|
|
|
|
|
img_dir = Path("img")
|
|
|
|
|
if img_dir.exists():
|
|
|
|
|
shutil.copytree(img_dir, temp_dir / "notepad" / "img")
|
|
|
|
|
|
2026-02-14 14:15:52 +00:00
|
|
|
# Create tar.gz
|
2026-02-14 22:24:29 +00:00
|
|
|
tarball_path = tarball_dir / f"notepad-{version}-linux-x64.tar.gz"
|
2026-02-14 14:15:52 +00:00
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|
2026-02-14 22:24:29 +00:00
|
|
|
def build_linux_appimage(version):
|
2026-02-14 14:15:52 +00:00
|
|
|
"""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(
|
2026-02-14 22:24:29 +00:00
|
|
|
["bash", str(build_script), version],
|
2026-02-14 14:15:52 +00:00
|
|
|
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':
|
2026-02-14 22:24:29 +00:00
|
|
|
version = get_version()
|
2026-02-14 14:15:52 +00:00
|
|
|
if linux_package in ['appimage', 'both']:
|
2026-02-14 22:24:29 +00:00
|
|
|
build_linux_appimage(version)
|
2026-02-14 14:15:52 +00:00
|
|
|
if linux_package in ['tarball', 'both']:
|
2026-02-14 22:24:29 +00:00
|
|
|
build_linux_tarball(version)
|
2026-02-14 14:15:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|