|
| 1 | +# !/usr/bin/env python3.10 |
| 2 | + |
| 3 | +import os |
| 4 | +import platform |
| 5 | +import subprocess |
| 6 | +import shutil |
| 7 | +# import sys |
| 8 | +# import venv |
| 9 | +import argparse |
| 10 | + |
| 11 | + |
| 12 | +def parse_arguments(): |
| 13 | + parser = argparse.ArgumentParser(description="Build script for AgentPilot") |
| 14 | + parser.add_argument('--skip-venv', action='store_true', help='Skip creating a new virtual environment') |
| 15 | + parser.add_argument('--version', type=str, default='0.3.1', help='Version number for the build') |
| 16 | + return parser.parse_args() |
| 17 | + |
| 18 | + |
| 19 | +# def get_pyenv_path(): |
| 20 | +# return os.path.join(os.environ["PYENV_ROOT"], "versions", 'apbuildvenv') |
| 21 | + |
| 22 | + |
| 23 | +def run_command(command, shell=False, env=None): |
| 24 | + if isinstance(command, str): |
| 25 | + command = command.split() |
| 26 | + print(f"Running command: {' '.join(command)}") |
| 27 | + if env: |
| 28 | + print(f"Environment: {env}") |
| 29 | + |
| 30 | + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell, env=env) |
| 31 | + output, error = process.communicate() |
| 32 | + if process.returncode != 0: |
| 33 | + print(f"Error executing command: {' '.join(command)}") |
| 34 | + print(f"Output: {output.decode()}") |
| 35 | + print(f"Error: {error.decode()}") |
| 36 | + exit(1) |
| 37 | + return output.decode() |
| 38 | + |
| 39 | + |
| 40 | +class Builder: |
| 41 | + def __init__(self): |
| 42 | + self.args = parse_arguments() |
| 43 | + self.platform = platform.system() |
| 44 | + self.version = self.args.version |
| 45 | + self.venv_path = os.path.join(os.environ["PYENV_ROOT"], "versions", 'apbuildvenv') |
| 46 | + self.pip_path = self.get_pip_path() |
| 47 | + self.pyinstaller_path = self.get_pyinstaller_path() |
| 48 | + os.chdir(os.path.dirname(os.path.abspath(__file__))) |
| 49 | + |
| 50 | + def get_pip_path(self): |
| 51 | + if self.platform == "Windows": |
| 52 | + return os.path.join(self.venv_path, "Scripts", "pip") |
| 53 | + else: |
| 54 | + return os.path.join(self.venv_path, "bin", "pip") |
| 55 | + |
| 56 | + def get_pyinstaller_path(self): |
| 57 | + if self.platform == "Windows": |
| 58 | + return os.path.join(self.venv_path, "Scripts", "pyinstaller.exe") |
| 59 | + else: |
| 60 | + return os.path.join(self.venv_path, "bin", "pyinstaller") |
| 61 | + |
| 62 | + def create_executable(self): |
| 63 | + self.setup_environment() |
| 64 | + self.install_requirements() |
| 65 | + self.build_project() |
| 66 | + self.copy_assets() |
| 67 | + |
| 68 | + if self.platform == "Windows": |
| 69 | + self.rename_executable() |
| 70 | + elif self.platform == "Linux": |
| 71 | + self.make_appimage() |
| 72 | + |
| 73 | + self.move_all_to_folder() |
| 74 | + self.compress_app() |
| 75 | + |
| 76 | + def setup_environment(self): |
| 77 | + if self.args.skip_venv: |
| 78 | + return |
| 79 | + |
| 80 | + output = run_command("pyenv virtualenvs --bare") |
| 81 | + venvs = output.splitlines() |
| 82 | + env_exists = 'apbuildvenv' in venvs |
| 83 | + if env_exists: |
| 84 | + run_command("pyenv virtualenv-delete -f apbuildvenv") |
| 85 | + |
| 86 | + print("Creating new pyenv virtual environment..") |
| 87 | + run_command("pyenv virtualenv 3.10.11 apbuildvenv") |
| 88 | + |
| 89 | + def install_requirements(self): |
| 90 | + run_command([self.pip_path, "install", "-r", "requirements.txt"]) |
| 91 | + |
| 92 | + def build_project(self): |
| 93 | + print("Installing PyInstaller..") |
| 94 | + run_command([self.pip_path, "install", "pyinstaller"]) |
| 95 | + |
| 96 | + print("Building executable..") |
| 97 | + run_command([self.pyinstaller_path, "build.spec"]) |
| 98 | + |
| 99 | + def copy_assets(self): |
| 100 | + shutil.copy("data.db", "dist/data.db") |
| 101 | + shutil.copytree("docs/avatars", "dist/avatars", dirs_exist_ok=True) |
| 102 | + |
| 103 | + def rename_executable(self): |
| 104 | + old_filename = "__main__" |
| 105 | + new_filename = f"AgentPilot_{self.version}" |
| 106 | + if self.platform == "Windows": |
| 107 | + os.rename(f"dist/{old_filename}.exe", f"dist/{new_filename}.exe") |
| 108 | + else: |
| 109 | + os.rename(f"dist/{old_filename}", f"dist/{new_filename}") |
| 110 | + |
| 111 | + def move_all_to_folder(self): |
| 112 | + folder_name = f"AgentPilot_{self.version}_{self.platform}_Portable" |
| 113 | + |
| 114 | + if os.path.exists(f'dist/{folder_name}'): |
| 115 | + shutil.rmtree(f'dist/{folder_name}') |
| 116 | + os.mkdir(f'dist/{folder_name}') |
| 117 | + |
| 118 | + # move all files to folder |
| 119 | + ignore_exts = ['zip', 'tar.gz'] |
| 120 | + for file in os.listdir("dist"): |
| 121 | + if file != folder_name and not any(file.endswith(e) for e in ignore_exts): |
| 122 | + shutil.move(f"dist/{file}", f"dist/{folder_name}/{file}") |
| 123 | + |
| 124 | + def make_appimage(self): |
| 125 | + # Create AppDir folder |
| 126 | + if os.path.exists("AppDir"): |
| 127 | + shutil.rmtree("AppDir") |
| 128 | + os.mkdir("AppDir") |
| 129 | + |
| 130 | + # make AppDir/usr/bin |
| 131 | + os.makedirs("AppDir/usr/bin") |
| 132 | + |
| 133 | + # create agentpilot.desktop |
| 134 | + with open("AppDir/agentpilot.desktop", "w") as f: |
| 135 | + f.write("""[Desktop Entry] |
| 136 | +Type=Application |
| 137 | +Name=AgentPilot |
| 138 | +Comment=Build and chat with agents |
| 139 | +Exec=usr/bin/main |
| 140 | +Icon=icon |
| 141 | +Terminal=false |
| 142 | +Categories=Utility;""") |
| 143 | + |
| 144 | + # create AppRun link |
| 145 | + with open("AppDir/AppRun", "w") as f: |
| 146 | + f.write('''#!/bin/sh |
| 147 | +HERE=$(dirname "$(readlink -f "${0}")") |
| 148 | +export PATH="${HERE}/usr/bin:$PATH" |
| 149 | +exec main "$@"''') |
| 150 | + os.chmod("AppDir/AppRun", 0o755) |
| 151 | + |
| 152 | + # copy icon |
| 153 | + shutil.copy("src/utils/resources/icon.png", "AppDir/icon.png") |
| 154 | + shutil.copy("src/utils/resources/icon.png", "AppDir/.DirIcon") |
| 155 | + |
| 156 | + # copy executable |
| 157 | + shutil.copy(f"dist/__main__", "AppDir/usr/bin/main") |
| 158 | + |
| 159 | + # check if appimagetool file exists |
| 160 | + if not os.path.exists("appimagetool.AppImage"): |
| 161 | + print("AppImageTool not found. Downloading..") |
| 162 | + run_command("wget -c https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage") |
| 163 | + os.rename("appimagetool-x86_64.AppImage", "appimagetool.AppImage") |
| 164 | + |
| 165 | + # make appimage with appimagetool |
| 166 | + run_command("chmod +x appimagetool.AppImage") |
| 167 | + run_command("./appimagetool.AppImage AppDir") |
| 168 | + |
| 169 | + # rename appimage and move to the folder |
| 170 | + os.rename("AgentPilot-x86_64.AppImage", f"dist/AgentPilot_{self.version}.AppImage") |
| 171 | + |
| 172 | + # remove the original executable |
| 173 | + os.remove(f"dist/__main__") |
| 174 | + |
| 175 | + def compress_app(self): |
| 176 | + source_folder = f"dist/AgentPilot_{self.version}_{self.platform}_Portable" |
| 177 | + output_filename = f"dist/AgentPilot_{self.version}_{self.platform}_Portable" |
| 178 | + |
| 179 | + base_name = os.path.basename(source_folder) |
| 180 | + base_dir = os.path.dirname(source_folder) |
| 181 | + |
| 182 | + ext = "zip" if self.platform == "Windows" else "tar.gz" |
| 183 | + if os.path.exists(f"{output_filename}.{ext}"): |
| 184 | + os.remove(f"{output_filename}.{ext}") |
| 185 | + |
| 186 | + if self.platform == "Windows": |
| 187 | + shutil.make_archive( |
| 188 | + base_name=output_filename, |
| 189 | + format="zip", |
| 190 | + root_dir=base_dir, |
| 191 | + base_dir=base_name |
| 192 | + ) |
| 193 | + else: |
| 194 | + shutil.make_archive( |
| 195 | + base_name=output_filename, |
| 196 | + format="gztar", |
| 197 | + root_dir=base_dir, |
| 198 | + base_dir=base_name |
| 199 | + ) |
| 200 | + |
| 201 | + |
| 202 | +if __name__ == "__main__": |
| 203 | + builder = Builder() |
| 204 | + builder.create_executable() |
0 commit comments