95 lines
4.8 KiB
Python
95 lines
4.8 KiB
Python
"""Build the self-contained Unheard City for the current platform (Windows or Linux).
|
|
|
|
python tools/build_app.py [--art FOLDER] -> dist/UnheardCity/ and dist/UnheardCity-<ver>-<os>.zip|.tar.gz
|
|
|
|
One command does everything: artwork (app/assets, made from FOLDER - default ~/Downloads/Arkham City - when
|
|
missing or older than tools/make_assets.py), PyInstaller bundle, THIRD_PARTY_LICENSES.txt, docs, archive.
|
|
|
|
Requirements: the dev environment (`pip install -e .[app,dev]`). All native libraries come from the pinned
|
|
PyPI wheels (Qt, libsndfile with libvorbis/FLAC/opus/mpg123, soxr); no external programs are bundled or
|
|
needed (see docs/decisions.md). Build on each target OS (PyInstaller does not cross-compile).
|
|
"""
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import sys
|
|
import tarfile
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, ROOT)
|
|
sys.path.insert(0, os.path.join(ROOT, "tools"))
|
|
NAME = "UnheardCity"
|
|
|
|
EXCLUDE = [
|
|
"tkinter", "matplotlib", "scipy", "pandas", "IPython", "pytest", "soundcard", "ackit.verify",
|
|
"PySide6.QtWebEngineCore", "PySide6.QtWebEngineWidgets", "PySide6.QtWebEngineQuick", "PySide6.QtWebChannel",
|
|
"PySide6.QtWebSockets", "PySide6.QtQml", "PySide6.QtQuick", "PySide6.QtQuickWidgets", "PySide6.QtQuick3D",
|
|
"PySide6.Qt3DCore", "PySide6.Qt3DRender", "PySide6.QtCharts", "PySide6.QtDataVisualization", "PySide6.QtGraphs",
|
|
"PySide6.QtPdf", "PySide6.QtPdfWidgets", "PySide6.QtSql", "PySide6.QtBluetooth", "PySide6.QtNfc",
|
|
"PySide6.QtPositioning", "PySide6.QtLocation", "PySide6.QtSensors", "PySide6.QtSerialPort", "PySide6.QtDesigner",
|
|
"PySide6.QtHelp", "PySide6.QtNetworkAuth", "PySide6.QtRemoteObjects", "PySide6.QtScxml", "PySide6.QtSpatialAudio",
|
|
"PySide6.QtTextToSpeech", "PySide6.QtHttpServer", "PySide6.QtVirtualKeyboard", "PySide6.QtTest",
|
|
]
|
|
|
|
|
|
def main():
|
|
from ackit import __version__
|
|
os.chdir(ROOT)
|
|
assets = os.path.join(ROOT, "app", "assets")
|
|
art = sys.argv[sys.argv.index("--art") + 1] if "--art" in sys.argv else None
|
|
ico = os.path.join(assets, "app_icon.ico")
|
|
stale = not os.path.exists(ico) or os.path.getmtime(ico) < os.path.getmtime(os.path.join(ROOT, "tools", "make_assets.py"))
|
|
if art or stale:
|
|
import make_assets
|
|
src = art or make_assets.DEFAULT_SRC
|
|
if os.path.exists(os.path.join(src, "bac_logo_full.png")):
|
|
make_assets.main(src)
|
|
else:
|
|
print("NOTE: no artwork in %s - building with the plain icon and no wallpaper" % src)
|
|
sep = os.pathsep
|
|
args = [os.path.join("tools", "editor_launcher.py"), "--name", NAME, "--noconfirm", "--clean", "--onedir",
|
|
"--windowed", "--distpath", "dist", "--workpath", os.path.join("build", "pyinstaller"),
|
|
"--specpath", os.path.join("build", "pyinstaller"),
|
|
"--add-data", os.path.join(ROOT, "ackit", "data") + sep + "ackit/data",
|
|
"--add-data", os.path.join(ROOT, "data", "catalog") + sep + "data/catalog",
|
|
"--paths", ROOT]
|
|
if os.path.isdir(assets):
|
|
args += ["--add-data", assets + sep + "app/assets"]
|
|
if sys.platform == "win32" and os.path.exists(os.path.join(assets, "app_icon.ico")):
|
|
args += ["--icon", os.path.join(assets, "app_icon.ico")]
|
|
for m in EXCLUDE:
|
|
args += ["--exclude-module", m]
|
|
import PyInstaller.__main__
|
|
PyInstaller.__main__.run(args)
|
|
|
|
out = os.path.join("dist", NAME)
|
|
import collect_licenses
|
|
collect_licenses.main(os.path.join(out, "THIRD_PARTY_LICENSES.txt"))
|
|
shutil.copy("README.md", os.path.join(out, "README.md"))
|
|
docs = os.path.join(out, "docs")
|
|
os.makedirs(docs, exist_ok=True)
|
|
for f in ("user-guide.md",):
|
|
if os.path.exists(os.path.join("docs", f)):
|
|
shutil.copy(os.path.join("docs", f), docs)
|
|
if os.path.isdir(os.path.join("docs", "img")):
|
|
shutil.copytree(os.path.join("docs", "img"), os.path.join(docs, "img"), dirs_exist_ok=True)
|
|
if sys.platform == "win32": # per-user installer: Install.cmd -> install.ps1 (+ uninstall.ps1)
|
|
for f in ("Install.cmd", "install.ps1", "uninstall.ps1"):
|
|
text = open(os.path.join("tools", "installer", f), encoding="utf-8").read().replace("@VERSION@", __version__)
|
|
with open(os.path.join(out, f), "w", encoding="ascii", newline="\r\n") as fh:
|
|
fh.write(text)
|
|
|
|
plat = {"win32": "windows", "linux": "linux"}.get(sys.platform, sys.platform)
|
|
base = os.path.join("dist", "%s-%s-%s-%s" % (NAME, __version__, plat, platform.machine().lower()))
|
|
if sys.platform == "win32":
|
|
shutil.make_archive(base, "zip", "dist", NAME)
|
|
print("built", base + ".zip")
|
|
else:
|
|
with tarfile.open(base + ".tar.gz", "w:gz") as t:
|
|
t.add(out, arcname=NAME)
|
|
print("built", base + ".tar.gz")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|