Update appimage build
This commit is contained in:
@@ -10,15 +10,21 @@ jobs:
|
|||||||
needs: buildAssets
|
needs: buildAssets
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.11"
|
PYTHON_VERSION: "3.13"
|
||||||
LINUX_TAG: "manylinux_2_28_x86_64"
|
LINUX_TAG: "manylinux_2_28"
|
||||||
|
LINUX_ARCH: "x86_64"
|
||||||
steps:
|
steps:
|
||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.13"
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|
||||||
|
- name: Install Packages (apt)
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libxcb-cursor0
|
||||||
|
|
||||||
- name: Install Packages (pip)
|
- name: Install Packages (pip)
|
||||||
run: pip install python-appimage setuptools
|
run: pip install python-appimage setuptools
|
||||||
|
|
||||||
@@ -35,7 +41,7 @@ jobs:
|
|||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
|
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
|
||||||
python pkgutils.py build-appimage --linux-tag $LINUX_TAG --python-version $PYTHON_VERSION
|
python pkgutils.py build-appimage $LINUX_TAG $LINUX_ARCH $PYTHON_VERSION
|
||||||
|
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
+6
-18
@@ -272,24 +272,12 @@ if __name__ == "__main__":
|
|||||||
cmdBuildUbuntu.set_defaults(func=utils.build_debian.launchpad)
|
cmdBuildUbuntu.set_defaults(func=utils.build_debian.launchpad)
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppImage
|
||||||
cmdBuildAppImage = parsers.add_parser(
|
# See https://github.com/pypa/manylinux
|
||||||
"build-appimage", help=(
|
# See https://python-appimage.readthedocs.io/en/latest/#available-python-appimages
|
||||||
"Build an AppImage. "
|
cmdBuildAppImage = parsers.add_parser("build-appimage", help="Build an AppImage.")
|
||||||
"Argument --linux-tag defaults manylinux_2_28_x86_64, and --python-version to 3.13."
|
cmdBuildAppImage.add_argument("linux", help="Manylinux version, e.g. manylinux_2_28.")
|
||||||
)
|
cmdBuildAppImage.add_argument("arch", help="Architecture, e.g. x86_64.")
|
||||||
)
|
cmdBuildAppImage.add_argument("python", help="Python version, e.g. 3.13.")
|
||||||
cmdBuildAppImage.add_argument(
|
|
||||||
"--linux-tag",
|
|
||||||
default="manylinux_2_28_x86_64",
|
|
||||||
help=(
|
|
||||||
"Linux compatibility tag (e.g. manylinux_2_28_x86_64) "
|
|
||||||
"see https://python-appimage.readthedocs.io/en/latest/#available-python-appimages "
|
|
||||||
"and https://github.com/pypa/manylinux for a list of valid tags."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
cmdBuildAppImage.add_argument(
|
|
||||||
"--python-version", default="3.13", help="Python version (e.g. 3.13)"
|
|
||||||
)
|
|
||||||
cmdBuildAppImage.set_defaults(func=utils.build_appimage.appImage)
|
cmdBuildAppImage.set_defaults(func=utils.build_appimage.appImage)
|
||||||
|
|
||||||
# Build Windows Inno Setup Installer
|
# Build Windows Inno Setup Installer
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
+41
-71
@@ -22,13 +22,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from utils.common import (
|
from utils.common import (
|
||||||
ROOT_DIR, SETUP_DIR, appdataXml, copyPackageFiles, copySourceCode,
|
ROOT_DIR, SETUP_DIR, appdataXml, copyPackageFiles, copySourceCode,
|
||||||
extractVersion, makeCheckSum, toUpload, writeFile
|
extractVersion, freshFolder, makeCheckSum, systemCall, toUpload, writeFile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,115 +51,83 @@ def appImage(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("Build AppImage")
|
print("Build AppImage")
|
||||||
print("==============")
|
print("="*120)
|
||||||
print("")
|
|
||||||
|
|
||||||
linuxTag = args.linux_tag
|
mLinux = args.linux
|
||||||
pythonVer = args.python_version
|
mArch = args.arch
|
||||||
|
pyVer = args.python
|
||||||
|
|
||||||
# Version Info
|
# Version Info
|
||||||
# ============
|
|
||||||
|
|
||||||
pkgVers, _, relDate = extractVersion()
|
pkgVers, _, relDate = extractVersion()
|
||||||
relDate = datetime.datetime.strptime(relDate, "%Y-%m-%d")
|
relDate = datetime.datetime.strptime(relDate, "%Y-%m-%d")
|
||||||
print("")
|
|
||||||
|
|
||||||
# Set Up Folder
|
# Set Up Folder
|
||||||
# =============
|
|
||||||
|
|
||||||
bldDir = ROOT_DIR / "dist_appimage"
|
bldDir = ROOT_DIR / "dist_appimage"
|
||||||
bldPkg = f"novelwriter_{pkgVers}"
|
bldPkg = f"novelwriter-{pkgVers}-{mArch}"
|
||||||
|
bldImg = f"{bldPkg}.AppImage"
|
||||||
outDir = bldDir / bldPkg
|
outDir = bldDir / bldPkg
|
||||||
imgDir = bldDir / "appimage"
|
imgDir = bldDir / "appimage"
|
||||||
|
appDir = bldDir / f"novelWriter-{mArch}"
|
||||||
# Set Up Folders
|
|
||||||
# ==============
|
|
||||||
|
|
||||||
bldDir.mkdir(exist_ok=True)
|
bldDir.mkdir(exist_ok=True)
|
||||||
|
freshFolder(outDir)
|
||||||
if outDir.exists():
|
freshFolder(imgDir)
|
||||||
print("Removing old build files ...")
|
freshFolder(appDir)
|
||||||
print("")
|
|
||||||
shutil.rmtree(outDir)
|
|
||||||
|
|
||||||
outDir.mkdir()
|
|
||||||
|
|
||||||
if imgDir.exists():
|
|
||||||
print("Removing old build metadata files ...")
|
|
||||||
print("")
|
|
||||||
shutil.rmtree(imgDir)
|
|
||||||
|
|
||||||
imgDir.mkdir()
|
|
||||||
|
|
||||||
# Remove old AppImages
|
# Remove old AppImages
|
||||||
if images := bldDir.glob("*.AppImage"):
|
if images := bldDir.glob("*.AppImage"):
|
||||||
print("Removing old AppImages")
|
print("Removing old AppImages")
|
||||||
print("")
|
|
||||||
for image in images:
|
for image in images:
|
||||||
image.unlink()
|
image.unlink()
|
||||||
|
|
||||||
# Copy novelWriter Source
|
# Copy novelWriter Source
|
||||||
# =======================
|
|
||||||
|
|
||||||
print("Copying novelWriter source ...")
|
print("Copying novelWriter source ...")
|
||||||
print("")
|
|
||||||
|
|
||||||
copySourceCode(outDir)
|
copySourceCode(outDir)
|
||||||
|
|
||||||
print("")
|
|
||||||
print("Copying or generating additional files ...")
|
print("Copying or generating additional files ...")
|
||||||
print("")
|
|
||||||
|
|
||||||
copyPackageFiles(outDir)
|
copyPackageFiles(outDir)
|
||||||
|
|
||||||
# Write Metadata
|
# Write Metadata
|
||||||
# ==============
|
|
||||||
|
|
||||||
writeFile(imgDir / "novelwriter.appdata.xml", appdataXml())
|
writeFile(imgDir / "novelwriter.appdata.xml", appdataXml())
|
||||||
print("Wrote: novelwriter.appdata.xml")
|
writeFile(imgDir / "requirements.txt", str(outDir))
|
||||||
|
|
||||||
writeFile(imgDir / "entrypoint.sh", (
|
writeFile(imgDir / "entrypoint.sh", (
|
||||||
'#! /bin/bash \n'
|
f"export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{APPDIR}}/usr/lib/{mArch}-linux-gnu/\n"
|
||||||
'{{ python-executable }} -sE ${APPDIR}/opt/python{{ python-version }}/bin/novelwriter "$@"'
|
'{{ python-executable }} -sE ${APPDIR}/opt/python{{ python-version }}/bin/novelwriter "$@"'
|
||||||
))
|
))
|
||||||
print("Wrote: entrypoint.sh")
|
|
||||||
|
|
||||||
writeFile(imgDir / "requirements.txt", str(outDir))
|
|
||||||
print("Wrote: requirements.txt")
|
|
||||||
|
|
||||||
shutil.copyfile(SETUP_DIR / "data" / "novelwriter.desktop", imgDir / "novelwriter.desktop")
|
shutil.copyfile(SETUP_DIR / "data" / "novelwriter.desktop", imgDir / "novelwriter.desktop")
|
||||||
print("Copied: novelwriter.desktop")
|
print("Copied: novelwriter.desktop")
|
||||||
|
|
||||||
shutil.copyfile(SETUP_DIR / "icons" / "novelwriter.svg", imgDir / "novelwriter.svg")
|
shutil.copyfile(SETUP_DIR / "icons" / "novelwriter.png", imgDir / "novelwriter.png")
|
||||||
print("Copied: novelwriter.svg")
|
|
||||||
|
|
||||||
shutil.copyfile(
|
|
||||||
SETUP_DIR / "data" / "hicolor" / "256x256" / "apps" / "novelwriter.png",
|
|
||||||
imgDir / "novelwriter.png"
|
|
||||||
)
|
|
||||||
print("Copied: novelwriter.png")
|
print("Copied: novelwriter.png")
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppDir
|
||||||
# ==============
|
systemCall([
|
||||||
|
sys.executable, "-m", "python_appimage", "build", "app", "--no-packaging",
|
||||||
|
"-l", f"{mLinux}_{mArch}", "-p", pyVer, "appimage"
|
||||||
|
], cwd=bldDir)
|
||||||
|
|
||||||
try:
|
# Copy Libraries
|
||||||
subprocess.call([
|
libPath = Path(f"/usr/lib/{mArch}-linux-gnu")
|
||||||
sys.executable, "-m", "python_appimage", "build", "app",
|
appLibs = appDir / "usr" / "lib" / f"{mArch}-linux-gnu"
|
||||||
"-l", linuxTag, "-p", pythonVer, "appimage"
|
appLibs.mkdir(exist_ok=True)
|
||||||
], cwd=bldDir)
|
shutil.copyfile(libPath / "libxcb-cursor.so.0", appLibs / "libxcb-cursor.so.0")
|
||||||
except Exception as exc:
|
|
||||||
print("AppImage build: FAILED")
|
|
||||||
print("")
|
|
||||||
print(str(exc))
|
|
||||||
print("")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
bldFile = list(bldDir.glob("*.AppImage"))[0]
|
# Build Image
|
||||||
outFile = bldDir / f"novelWriter-{pkgVers}.AppImage"
|
env = os.environ.copy()
|
||||||
bldFile.rename(outFile)
|
env["ARCH"] = mArch
|
||||||
shaFile = makeCheckSum(outFile.name, cwd=bldDir)
|
systemCall([
|
||||||
|
"appimagetool", "--no-appstream", "--updateinformation",
|
||||||
|
f"gh-releases-zsync|vkbo|novelwriter|latest|novelwriter-*-{mArch}.AppImage.zsync",
|
||||||
|
str(appDir), bldImg
|
||||||
|
], cwd=bldDir, env=env)
|
||||||
|
|
||||||
toUpload(outFile)
|
updFile = bldDir / f"{bldImg}.zsync"
|
||||||
|
bldFile = bldDir / bldImg
|
||||||
|
shaFile = makeCheckSum(bldFile.name, cwd=bldDir)
|
||||||
|
|
||||||
|
toUpload(bldFile)
|
||||||
|
toUpload(updFile)
|
||||||
toUpload(shaFile)
|
toUpload(shaFile)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+29
-8
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -77,16 +78,16 @@ def copySourceCode(dst: Path) -> None:
|
|||||||
for item in src.glob("**/*"):
|
for item in src.glob("**/*"):
|
||||||
relSrc = item.relative_to(ROOT_DIR)
|
relSrc = item.relative_to(ROOT_DIR)
|
||||||
if item.suffix in (".pyc", ".pyo"):
|
if item.suffix in (".pyc", ".pyo"):
|
||||||
print(f"Ignore: {relSrc}")
|
print("Ignored:", relSrc)
|
||||||
continue
|
continue
|
||||||
if item.parent.is_dir() and item.parent.name != "__pycache__":
|
if item.parent.is_dir() and item.parent.name != "__pycache__":
|
||||||
dstDir = dst / relSrc.parent
|
dstDir = dst / relSrc.parent
|
||||||
if not dstDir.exists():
|
if not dstDir.exists():
|
||||||
dstDir.mkdir(parents=True)
|
dstDir.mkdir(parents=True)
|
||||||
print(f"Folder: {dstDir}")
|
print("Created:", dstDir.relative_to(ROOT_DIR))
|
||||||
if item.is_file():
|
if item.is_file():
|
||||||
shutil.copyfile(item, dst / relSrc)
|
shutil.copyfile(item, dst / relSrc)
|
||||||
print(f"Copied: {dst / relSrc}")
|
print("Copied:", relSrc)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -95,26 +96,23 @@ def copyPackageFiles(dst: Path, setupPy: bool = False) -> None:
|
|||||||
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
|
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
|
||||||
for copyFile in copyFiles:
|
for copyFile in copyFiles:
|
||||||
shutil.copyfile(copyFile, dst / copyFile)
|
shutil.copyfile(copyFile, dst / copyFile)
|
||||||
print(f"Copied: {copyFile}")
|
print("Copied:", copyFile)
|
||||||
|
|
||||||
writeFile(dst / "MANIFEST.in", (
|
writeFile(dst / "MANIFEST.in", (
|
||||||
"include LICENSE.md\n"
|
"include LICENSE.md\n"
|
||||||
"include CREDITS.md\n"
|
"include CREDITS.md\n"
|
||||||
"recursive-include novelwriter/assets *\n"
|
"recursive-include novelwriter/assets *\n"
|
||||||
))
|
))
|
||||||
print("Wrote: MANIFEST.in")
|
|
||||||
|
|
||||||
if setupPy:
|
if setupPy:
|
||||||
writeFile(dst / "setup.py", (
|
writeFile(dst / "setup.py", (
|
||||||
"import setuptools\n"
|
"import setuptools\n"
|
||||||
"setuptools.setup()\n"
|
"setuptools.setup()\n"
|
||||||
))
|
))
|
||||||
print("Wrote: setup.py")
|
|
||||||
|
|
||||||
text = readFile(ROOT_DIR / "pyproject.toml")
|
text = readFile(ROOT_DIR / "pyproject.toml")
|
||||||
text = text.replace("setup/description_pypi.md", "data/description_short.txt")
|
text = text.replace("setup/description_pypi.md", "data/description_short.txt")
|
||||||
writeFile(dst / "pyproject.toml", text)
|
writeFile(dst / "pyproject.toml", text)
|
||||||
print("Wrote: pyproject.toml")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -188,4 +186,27 @@ def readFile(file: Path) -> str:
|
|||||||
|
|
||||||
def writeFile(file: Path, text: str) -> int:
|
def writeFile(file: Path, text: str) -> int:
|
||||||
"""Write string to file."""
|
"""Write string to file."""
|
||||||
return file.write_text(text, encoding="utf-8")
|
result = file.write_text(text, encoding="utf-8")
|
||||||
|
print("Wrote:", file.relative_to(ROOT_DIR))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def freshFolder(path: Path) -> None:
|
||||||
|
"""Make sure a folder exists and is empty."""
|
||||||
|
if path.exists():
|
||||||
|
print("Removing:", str(path))
|
||||||
|
shutil.rmtree(path)
|
||||||
|
path.mkdir()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def systemCall(cmd: list, cwd: Path | str | None = None, env: dict | None = None) -> None:
|
||||||
|
"""Make a system call using subprocess."""
|
||||||
|
if isinstance(cwd, Path):
|
||||||
|
cwd = str(cwd)
|
||||||
|
try:
|
||||||
|
subprocess.call([str(c) for c in cmd], cwd=cwd, env=env)
|
||||||
|
except Exception as exc:
|
||||||
|
print("ERROR:", str(exc))
|
||||||
|
sys.exit(1)
|
||||||
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user