Delete decky_builder.py

This commit is contained in:
jazir5 2024-12-28 16:36:05 -08:00 committed by GitHub
parent 5be5761019
commit dd07d9550c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,830 +0,0 @@
import os
import subprocess
import shutil
import argparse
from pathlib import Path
import sys
import time
import PyInstaller
import atexit
import requests
import psutil
import re
class DeckyBuilder:
def __init__(self, release: str = None):
self.release = release or self.prompt_for_version()
self.root_dir = Path(__file__).resolve().parent
self.app_dir = self.root_dir / "app"
self.src_dir = self.root_dir / "src"
self.dist_dir = self.root_dir / "dist"
self.homebrew_dir = self.dist_dir / "homebrew"
self.temp_files = [] # Track temporary files for cleanup
atexit.register(self.cleanup) # Register cleanup on exit
# Setup user homebrew directory
self.user_home = Path.home()
self.user_homebrew_dir = self.user_home / "homebrew"
self.homebrew_folders = [
"data",
"logs",
"plugins",
"services",
"settings",
"themes"
]
def cleanup(self):
"""Clean up temporary files and directories"""
try:
# Clean up any temporary files we created
for temp_file in self.temp_files:
if os.path.exists(temp_file):
try:
if os.path.isfile(temp_file):
os.remove(temp_file)
elif os.path.isdir(temp_file):
shutil.rmtree(temp_file, ignore_errors=True)
except Exception as e:
print(f"Warning: Failed to remove temporary file {temp_file}: {e}")
# Clean up PyInstaller temp files
for dir_name in ['build', 'dist']:
dir_path = self.root_dir / dir_name
if dir_path.exists():
try:
shutil.rmtree(dir_path, ignore_errors=True)
except Exception as e:
print(f"Warning: Failed to remove {dir_name} directory: {e}")
# Clean up PyInstaller spec files
for spec_file in self.root_dir.glob("*.spec"):
try:
os.remove(spec_file)
except Exception as e:
print(f"Warning: Failed to remove spec file {spec_file}: {e}")
except Exception as e:
print(f"Warning: Error during cleanup: {e}")
def safe_remove_directory(self, path):
"""Safely remove a directory with retries for Windows"""
max_retries = 3
retry_delay = 1 # seconds
for attempt in range(max_retries):
try:
if path.exists():
# On Windows, sometimes we need to remove .git directory separately
git_dir = path / '.git'
if git_dir.exists():
for item in git_dir.glob('**/*'):
if item.is_file():
try:
item.chmod(0o777) # Give full permissions
item.unlink()
except:
pass
shutil.rmtree(path, ignore_errors=True)
return
except Exception as e:
print(f"Attempt {attempt + 1} failed to remove {path}: {str(e)}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
continue
else:
print(f"Warning: Could not fully remove {path}. Continuing anyway...")
def setup_directories(self):
"""Setup directory structure"""
print("Setting up directories...")
# Clean up any existing directories
if self.app_dir.exists():
self.safe_remove_directory(self.app_dir)
if self.src_dir.exists():
self.safe_remove_directory(self.src_dir)
if self.homebrew_dir.exists():
self.safe_remove_directory(self.homebrew_dir)
# Create fresh directories
self.src_dir.mkdir(parents=True, exist_ok=True)
self.homebrew_dir.mkdir(parents=True, exist_ok=True)
def setup_homebrew(self):
"""Setup homebrew directory structure"""
print("Setting up homebrew directory structure...")
# Create dist directory
(self.homebrew_dir / "dist").mkdir(parents=True, exist_ok=True)
# Setup homebrew directory structure for both temp and user directories
print("Setting up homebrew directory structure...")
for directory in [self.homebrew_dir, self.user_homebrew_dir]:
if not directory.exists():
directory.mkdir(parents=True)
for folder in self.homebrew_folders:
folder_path = directory / folder
if not folder_path.exists():
folder_path.mkdir(parents=True)
def clone_repository(self):
"""Clone Decky Loader repository and checkout specific version"""
print(f"\nCloning Decky Loader repository version: {self.release}")
# Clean up existing directory
if os.path.exists(self.app_dir):
print("Removing existing repository...")
self.safe_remove_directory(self.app_dir)
try:
# Clone the repository
subprocess.run([
'git', 'clone', '--no-checkout', # Don't checkout anything yet
'https://github.com/SteamDeckHomebrew/decky-loader.git',
str(self.app_dir)
], check=True)
os.chdir(self.app_dir)
# Fetch all refs
subprocess.run(['git', 'fetch', '--all', '--tags'], check=True)
# Try to checkout the exact version first
try:
subprocess.run(['git', 'checkout', self.release], check=True)
except subprocess.CalledProcessError:
# If exact version fails, try to find the commit for pre-releases
if '-pre' in self.release:
# Get all tags and their commit hashes
result = subprocess.run(
['git', 'ls-remote', '--tags', 'origin'],
capture_output=True, text=True, check=True
)
# Find the commit hash for our version
for line in result.stdout.splitlines():
commit_hash, ref = line.split('\t')
ref = ref.replace('refs/tags/', '')
ref = ref.replace('^{}', '') # Remove annotated tag suffix
if ref == self.release:
print(f"Found commit {commit_hash} for version {self.release}")
subprocess.run(['git', 'checkout', commit_hash], check=True)
break
else:
raise Exception(f"Could not find commit for version {self.release}")
else:
raise
print(f"Successfully checked out version: {self.release}")
# Create version files in key locations with the requested version
version_files = [
'.loader.version',
'frontend/.loader.version',
'backend/.loader.version',
'backend/decky_loader/.loader.version'
]
for version_file in version_files:
file_path = os.path.join(self.app_dir, version_file)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w') as f:
f.write(self.release) # Use the requested version
except subprocess.CalledProcessError as e:
raise Exception(f"Failed to clone/checkout repository: {str(e)}")
finally:
os.chdir(self.root_dir)
def build_frontend(self):
"""Build frontend files"""
print("Building frontend...")
batch_file = None
original_dir = os.getcwd()
try:
frontend_dir = self.app_dir / "frontend"
if not frontend_dir.exists():
raise Exception(f"Frontend directory not found at {frontend_dir}")
print(f"Changing to frontend directory: {frontend_dir}")
os.chdir(frontend_dir)
# Create .loader.version file with the release tag
version_file = frontend_dir / ".loader.version"
with open(version_file, "w") as f:
f.write(self.release)
self.temp_files.append(str(version_file))
# Create a batch file to run the commands
batch_file = frontend_dir / "build_frontend.bat"
with open(batch_file, "w") as f:
f.write("@echo off\n")
f.write("call pnpm install\n")
f.write("if %errorlevel% neq 0 exit /b %errorlevel%\n")
f.write("call pnpm run build\n")
f.write("if %errorlevel% neq 0 exit /b %errorlevel%\n")
self.temp_files.append(str(batch_file))
print("Running build commands...")
result = subprocess.run([str(batch_file)], check=True, capture_output=True, text=True, shell=True)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Command failed: {e.cmd}")
print(f"Output: {e.output}")
print(f"Error: {e.stderr}")
raise Exception(f"Error building frontend: Command failed - {str(e)}")
except Exception as e:
print(f"Error building frontend: {str(e)}")
raise
finally:
# Always return to original directory
os.chdir(original_dir)
def prepare_backend(self):
"""Prepare backend files for building."""
print("Preparing backend files...")
print("Copying files according to Dockerfile structure...")
# Create src directory if it doesn't exist
os.makedirs(self.src_dir, exist_ok=True)
# Copy backend files from app/backend/decky_loader to src/decky_loader
print("Copying backend files...")
shutil.copytree(os.path.join(self.app_dir, "backend", "decky_loader"),
os.path.join(self.src_dir, "decky_loader"),
dirs_exist_ok=True)
# Copy static, locales, and plugin directories to maintain decky_loader structure
os.makedirs(os.path.join(self.src_dir, "decky_loader"), exist_ok=True)
shutil.copytree(os.path.join(self.app_dir, "backend", "decky_loader", "static"),
os.path.join(self.src_dir, "decky_loader", "static"),
dirs_exist_ok=True)
shutil.copytree(os.path.join(self.app_dir, "backend", "decky_loader", "locales"),
os.path.join(self.src_dir, "decky_loader", "locales"),
dirs_exist_ok=True)
shutil.copytree(os.path.join(self.app_dir, "backend", "decky_loader", "plugin"),
os.path.join(self.src_dir, "decky_loader", "plugin"),
dirs_exist_ok=True)
# Create legacy directory
os.makedirs(os.path.join(self.src_dir, "src", "legacy"), exist_ok=True)
# Copy main.py to src directory
shutil.copy2(os.path.join(self.app_dir, "backend", "main.py"),
os.path.join(self.src_dir, "main.py"))
# Create version file in the src directory
version_file = os.path.join(self.src_dir, ".loader.version")
with open(version_file, "w") as f:
f.write(self.release)
print("Backend preparation completed successfully!")
return True
def install_requirements(self):
"""Install Python requirements"""
print("Installing Python requirements...")
try:
# Try both requirements.txt and pyproject.toml
requirements_file = self.app_dir / "backend" / "requirements.txt"
pyproject_file = self.app_dir / "backend" / "pyproject.toml"
if requirements_file.exists():
subprocess.run([
sys.executable, "-m", "pip", "install", "--user", "-r", str(requirements_file)
], check=True)
elif pyproject_file.exists():
# Install core dependencies directly instead of using poetry
dependencies = [
"aiohttp>=3.8.1",
"psutil>=5.9.0",
"fastapi>=0.78.0",
"uvicorn>=0.17.6",
"python-multipart>=0.0.5",
"watchdog>=2.1.7",
"requests>=2.27.1",
"setuptools>=60.0.0",
"wheel>=0.37.1",
"winregistry>=1.1.1; platform_system == 'Windows'",
"pywin32>=303; platform_system == 'Windows'"
]
# Install each dependency
for dep in dependencies:
try:
subprocess.run([
sys.executable, "-m", "pip", "install", "--user", dep
], check=True)
except subprocess.CalledProcessError as e:
print(f"Warning: Failed to install {dep}: {str(e)}")
continue
else:
print("Warning: No requirements.txt or pyproject.toml found")
except Exception as e:
print(f"Error installing requirements: {str(e)}")
raise
def add_defender_exclusion(self, path):
"""Add Windows Defender exclusion for a path"""
try:
subprocess.run([
"powershell",
"-Command",
f"Add-MpPreference -ExclusionPath '{path}'"
], check=True, capture_output=True)
return True
except:
print("Warning: Could not add Windows Defender exclusion. You may need to run as administrator or manually add an exclusion.")
return False
def remove_defender_exclusion(self, path):
"""Remove Windows Defender exclusion for a path"""
try:
subprocess.run([
"powershell",
"-Command",
f"Remove-MpPreference -ExclusionPath '{path}'"
], check=True, capture_output=True)
except:
print("Warning: Could not remove Windows Defender exclusion.")
def build_executables(self):
"""Build executables using PyInstaller"""
print("\nBuilding executables...")
# Read version from .loader.version
version_file = os.path.join(self.app_dir, '.loader.version')
if not os.path.exists(version_file):
raise Exception("Version file not found. Run clone_repository first.")
with open(version_file, 'r') as f:
version = f.read().strip()
# Normalize version for Python packaging
# Convert v3.0.5-pre1 to 3.0.5rc1
py_version = version.lstrip('v') # Remove v prefix
if '-pre' in py_version:
py_version = py_version.replace('-pre', 'rc')
print(f"Building version: {version} (Python package version: {py_version})")
original_dir = os.getcwd()
backend_dir = os.path.join(self.app_dir, "backend")
dist_dir = os.path.join(backend_dir, "dist")
# Add Windows Defender exclusion for build directories
added_exclusion = self.add_defender_exclusion(backend_dir)
try:
os.chdir(backend_dir)
# Create setup.py with the correct version
setup_py = """
from setuptools import setup, find_packages
setup(
name="decky_loader",
version="%s",
packages=find_packages(),
package_data={
'decky_loader': [
'locales/*',
'static/*',
'.loader.version'
],
},
install_requires=[
'aiohttp>=3.8.1',
'certifi>=2022.6.15',
'packaging>=21.3',
'psutil>=5.9.1',
'requests>=2.28.1',
],
)
""" % py_version
with open("setup.py", "w") as f:
f.write(setup_py)
# Install the package in development mode
subprocess.run([sys.executable, "-m", "pip", "install", "-e", "."], check=True)
# Common PyInstaller arguments
pyinstaller_args = [
sys.executable,
"-m",
"PyInstaller",
"--clean",
"--noconfirm",
"pyinstaller.spec"
]
# First build console version
print("Building PluginLoader.exe (console version)...")
os.environ.pop('DECKY_NOCONSOLE', None) # Ensure env var is not set
subprocess.run(pyinstaller_args, check=True)
# Then build no-console version
print("Building PluginLoader_noconsole.exe...")
os.environ['DECKY_NOCONSOLE'] = '1'
subprocess.run(pyinstaller_args, check=True)
# Clean up environment
os.environ.pop('DECKY_NOCONSOLE', None)
# Copy the built executables to dist
os.makedirs(os.path.join(self.root_dir, "dist"), exist_ok=True)
if os.path.exists(os.path.join("dist", "PluginLoader.exe")):
shutil.copy2(
os.path.join("dist", "PluginLoader.exe"),
os.path.join(self.root_dir, "dist", "PluginLoader.exe")
)
else:
raise Exception("PluginLoader.exe not found after build")
if os.path.exists(os.path.join("dist", "PluginLoader_noconsole.exe")):
shutil.copy2(
os.path.join("dist", "PluginLoader_noconsole.exe"),
os.path.join(self.root_dir, "dist", "PluginLoader_noconsole.exe")
)
else:
raise Exception("PluginLoader_noconsole.exe not found after build")
print("Successfully built executables")
except subprocess.CalledProcessError as e:
raise Exception(f"Failed to build executables: {str(e)}")
finally:
if added_exclusion:
self.remove_defender_exclusion(backend_dir)
os.chdir(original_dir)
def install_files(self):
"""Install files to homebrew directory"""
print("\nInstalling files to homebrew directory...")
# Create homebrew directory if it doesn't exist
homebrew_dir = os.path.join(os.path.expanduser("~"), "homebrew")
services_dir = os.path.join(homebrew_dir, "services")
os.makedirs(services_dir, exist_ok=True)
try:
# Copy PluginLoader.exe and PluginLoader_noconsole.exe
for exe_name in ["PluginLoader.exe", "PluginLoader_noconsole.exe"]:
exe_source = os.path.join(self.root_dir, "dist", exe_name)
exe_dest = os.path.join(services_dir, exe_name)
if not os.path.exists(exe_source):
raise Exception(f"{exe_name} not found at {exe_source}")
shutil.copy2(exe_source, exe_dest)
# Create .loader.version file
version_file = os.path.join(services_dir, ".loader.version")
with open(version_file, "w") as f:
f.write(self.release)
print("Successfully installed files")
except Exception as e:
raise Exception(f"Failed to copy files to homebrew: {str(e)}")
def install_nodejs(self):
"""Install Node.js v18.18.0 with npm"""
print("Installing Node.js v18.18.0...")
try:
# First check if Node.js v18.18.0 is already installed in common locations
nodejs_paths = [
r"C:\Program Files\nodejs\node.exe",
r"C:\Program Files (x86)\nodejs\node.exe",
os.path.expandvars(r"%APPDATA%\Local\Programs\nodejs\node.exe")
]
# Try to use existing Node.js 18.18.0 first
for node_path in nodejs_paths:
if os.path.exists(node_path):
try:
version = subprocess.run([node_path, "--version"], capture_output=True, text=True).stdout.strip()
if version.startswith("v18.18.0"):
print(f"Found Node.js {version} at {node_path}")
node_dir = os.path.dirname(node_path)
if node_dir not in os.environ["PATH"]:
os.environ["PATH"] = node_dir + os.pathsep + os.environ["PATH"]
return True
except:
continue
# If we get here, we need to install Node.js 18.18.0
print("Installing Node.js v18.18.0...")
# Create temp directory for downloads
temp_dir = self.root_dir / "temp"
temp_dir.mkdir(exist_ok=True)
# Download Node.js installer
node_installer = temp_dir / "node-v18.18.0-x64.msi"
if not node_installer.exists():
print("Downloading Node.js installer...")
try:
import urllib.request
urllib.request.urlretrieve(
"https://nodejs.org/dist/v18.18.0/node-v18.18.0-x64.msi",
node_installer
)
except Exception as e:
print(f"Error downloading Node.js installer: {str(e)}")
raise
# Install Node.js silently
print("Installing Node.js (this may take a few minutes)...")
try:
# First try to uninstall any existing Node.js using PowerShell
uninstall_cmd = 'Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Node.js*" } | ForEach-Object { $_.Uninstall() }'
subprocess.run(["powershell", "-Command", uninstall_cmd], capture_output=True, timeout=60)
# Wait a bit for uninstallation to complete
time.sleep(5)
# Now install Node.js 18.18.0
subprocess.run(
["msiexec", "/i", str(node_installer), "/qn", "ADDLOCAL=ALL"],
check=True,
timeout=300 # 5 minute timeout
)
print("Waiting for Node.js installation to complete...")
time.sleep(10)
# Add to PATH
nodejs_path = r"C:\Program Files\nodejs"
npm_path = os.path.join(os.environ["APPDATA"], "npm")
# Update PATH for current process
if nodejs_path not in os.environ["PATH"]:
os.environ["PATH"] = nodejs_path + os.pathsep + os.environ["PATH"]
if npm_path not in os.environ["PATH"]:
os.environ["PATH"] = npm_path + os.pathsep + os.environ["PATH"]
# Verify installation
node_version = subprocess.run(["node", "--version"], capture_output=True, text=True, check=True).stdout.strip()
if not node_version.startswith("v18.18.0"):
raise Exception(f"Wrong Node.js version installed: {node_version}")
npm_version = subprocess.run(["npm", "--version"], capture_output=True, text=True, check=True).stdout.strip()
print(f"Successfully installed Node.js {node_version} with npm {npm_version}")
# Clean up
self.safe_remove_directory(temp_dir)
return True
except subprocess.TimeoutExpired:
print("Installation timed out. Please try installing Node.js v18.18.0 manually.")
raise
except Exception as e:
print(f"Installation failed: {str(e)}")
raise
except Exception as e:
print(f"Error installing Node.js: {str(e)}")
raise
def setup_steam_config(self):
"""Configure Steam for Decky Loader"""
print("Configuring Steam...")
try:
# Add -dev argument to Steam shortcut
import winreg
steam_path = None
# Try to find Steam installation path from registry
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam") as key:
steam_path = winreg.QueryValueEx(key, "InstallPath")[0]
except:
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Valve\Steam") as key:
steam_path = winreg.QueryValueEx(key, "InstallPath")[0]
except:
print("Steam installation not found in registry")
if steam_path:
steam_exe = Path(steam_path) / "steam.exe"
if steam_exe.exists():
# Create .cef-enable-remote-debugging file
debug_file = Path(steam_path) / ".cef-enable-remote-debugging"
debug_file.touch()
print("Created .cef-enable-remote-debugging file")
# Create/modify Steam shortcut
desktop = Path.home() / "Desktop"
shortcut_path = desktop / "Steam.lnk"
import pythoncom
from win32com.client import Dispatch
shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(str(shortcut_path))
shortcut.Targetpath = str(steam_exe)
shortcut.Arguments = "-dev"
shortcut.save()
print("Created Steam shortcut with -dev argument")
except Exception as e:
print(f"Error configuring Steam: {str(e)}")
raise
def setup_autostart(self):
"""Setup PluginLoader to run at startup"""
print("Setting up autostart...")
try:
# Get the path to the no-console executable
services_dir = os.path.join(os.path.expanduser("~"), "homebrew", "services")
plugin_loader = os.path.join(services_dir, "PluginLoader_noconsole.exe")
# Get the Windows Startup folder path
startup_folder = os.path.join(os.environ["APPDATA"], "Microsoft", "Windows", "Start Menu", "Programs", "Startup")
# Create a batch file in the startup folder
startup_bat = os.path.join(startup_folder, "start_decky.bat")
# Write the batch file with proper path escaping
with open(startup_bat, "w") as f:
f.write(f'@echo off\n"{plugin_loader}"')
print(f"Created startup script at: {startup_bat}")
return True
except Exception as e:
print(f"Error setting up autostart: {str(e)}")
return False
def check_python_version(self):
"""Check if correct Python version is being used"""
print("Checking Python version...")
if sys.version_info.major != 3 or sys.version_info.minor != 11:
raise Exception("This script requires Python 3.11. Please run using decky_builder.bat")
def check_dependencies(self):
"""Check and install required dependencies"""
print("Checking dependencies...")
try:
# Check Node.js and npm first
try:
# Use shell=True to find node in PATH
node_version = subprocess.run("node --version", shell=True, check=True, capture_output=True, text=True).stdout.strip()
npm_version = subprocess.run("npm --version", shell=True, check=True, capture_output=True, text=True).stdout.strip()
# Check if version meets requirements
if not node_version.startswith("v18."):
print(f"Node.js {node_version} found, but v18.18.0 is required")
self.install_nodejs()
else:
print(f"Node.js {node_version} with npm {npm_version} is installed")
except Exception as e:
print(f"Node.js/npm not found or error: {str(e)}")
self.install_nodejs()
# Install pnpm globally if not present
try:
pnpm_version = subprocess.run("pnpm --version", shell=True, check=True, capture_output=True, text=True).stdout.strip()
print(f"pnpm version {pnpm_version} is installed")
except:
print("Installing pnpm globally...")
subprocess.run("npm i -g pnpm", shell=True, check=True)
pnpm_version = subprocess.run("pnpm --version", shell=True, check=True, capture_output=True, text=True).stdout.strip()
print(f"Installed pnpm version {pnpm_version}")
# Check git
try:
git_version = subprocess.run("git --version", shell=True, check=True, capture_output=True, text=True).stdout.strip()
print(f"{git_version} is installed")
except:
raise Exception("git is not installed. Please install git from https://git-scm.com/downloads")
print("All dependencies are satisfied")
except Exception as e:
print(f"Error checking dependencies: {str(e)}")
raise
def get_release_versions(self):
"""Get list of available release versions"""
print("Fetching available versions...")
try:
response = requests.get(
"https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases"
)
response.raise_for_status()
releases = response.json()
# Split releases into stable and pre-release
stable_releases = []
pre_releases = []
for release in releases:
version = release['tag_name']
if release['prerelease']:
pre_releases.append(version)
else:
stable_releases.append(version)
# Sort versions and take only the latest 3 of each
stable_releases.sort(reverse=True)
pre_releases.sort(reverse=True)
stable_releases = stable_releases[:3]
pre_releases = pre_releases[:3]
# Combine and sort all versions
all_versions = stable_releases + pre_releases
all_versions.sort(reverse=True)
return all_versions
except requests.RequestException as e:
raise Exception(f"Failed to fetch release versions: {str(e)}")
def prompt_for_version(self):
"""Prompt the user to select a version to install."""
versions = self.get_release_versions()
print("\nAvailable versions:")
print("Stable versions:")
stable_count = 0
for i, version in enumerate(versions):
if '-pre' not in version:
print(f"{i+1}. {version}")
stable_count += 1
print("\nPre-release versions:")
for i, version in enumerate(versions):
if '-pre' in version:
print(f"{i+1}. {version}")
while True:
try:
choice = input("\nSelect a version (1-{}): ".format(len(versions)))
index = int(choice) - 1
if 0 <= index < len(versions):
return versions[index]
print("Invalid selection, please try again.")
except ValueError:
print("Invalid input, please enter a number.")
def terminate_processes(self):
"""Terminate running instances of executables that may interfere with the build."""
for proc in psutil.process_iter(['pid', 'name', 'exe']):
if proc.info['name'] in ['PluginLoader.exe', 'PluginLoader_noconsole.exe']:
try:
proc.terminate()
proc.wait()
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
def run(self):
"""Run the build and installation process."""
# Terminate interfering processes
self.terminate_processes()
try:
print("Starting Decky Loader build process...")
self.check_python_version()
self.check_dependencies()
self.setup_directories()
self.clone_repository()
self.setup_homebrew()
self.build_frontend()
self.prepare_backend()
self.install_requirements()
self.build_executables()
self.install_files()
self.setup_steam_config()
self.setup_autostart()
print("\nBuild process completed successfully!")
print("\nNext steps:")
print("1. Close Steam if it's running")
print("2. Launch Steam using the new shortcut on your desktop")
print("3. Enter Big Picture Mode")
print("4. Hold the STEAM button and press A to access the Decky menu")
except Exception as e:
print(f"Error during build process: {str(e)}")
raise
finally:
self.cleanup()
def main():
parser = argparse.ArgumentParser(description='Build and Install Decky Loader for Windows')
parser.add_argument('--release', required=False, default=None,
help='Release version/branch to build (if not specified, will prompt for version)')
args = parser.parse_args()
try:
builder = DeckyBuilder(args.release)
builder.run()
print(f"\nDecky Loader has been installed to: {builder.user_homebrew_dir}")
except Exception as e:
print(f"Error during build process: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()