Appimage Distribution (#1092)
This commit is contained in:
@@ -197,6 +197,7 @@ def cleanBuildDirs():
|
|||||||
removeFolder("dist")
|
removeFolder("dist")
|
||||||
removeFolder("dist_deb")
|
removeFolder("dist_deb")
|
||||||
removeFolder("dist_minimal")
|
removeFolder("dist_minimal")
|
||||||
|
removeFolder("dist_appimage")
|
||||||
removeFolder("novelWriter.egg-info")
|
removeFolder("novelWriter.egg-info")
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
@@ -835,10 +836,231 @@ def makeForLaunchpad(doSign=False, isFirst=False, isSnapshot=False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Make AppImage (build-appimage)
|
||||||
|
##
|
||||||
|
|
||||||
|
def makeAppImage(sysArgs):
|
||||||
|
"""Build an Appimage
|
||||||
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import argparse
|
||||||
|
import platform
|
||||||
|
|
||||||
|
try:
|
||||||
|
import python_appimage # noqa F401
|
||||||
|
except ImportError:
|
||||||
|
print(
|
||||||
|
"ERROR: Package 'python-appimage' is missing on this system.\n"
|
||||||
|
" Please run 'pip install --user python-appimage' to install it.\n"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("Build AppImage")
|
||||||
|
print("==============")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="build_appimage",
|
||||||
|
description="Build an AppImage",
|
||||||
|
epilog="see https://appimage.org/ for more details",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--linux-tag",
|
||||||
|
nargs="?",
|
||||||
|
default=f"manylinux2010_{platform.machine()}",
|
||||||
|
help=(
|
||||||
|
"linux compatibility tag (e.g. manylinux1_x86_64) \n"
|
||||||
|
"see https://python-appimage.readthedocs.io/en/latest/#available-python-appimages \n"
|
||||||
|
"and https://github.com/pypa/manylinux for a list of valid tags"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--python-version", nargs="?", default="3.10", help="python version (e.g. 3.10)"
|
||||||
|
)
|
||||||
|
|
||||||
|
args, unparsedArgs = parser.parse_known_args(sysArgs)
|
||||||
|
|
||||||
|
linuxTag = args.linux_tag
|
||||||
|
pythonVer = args.python_version
|
||||||
|
|
||||||
|
# Version Info
|
||||||
|
# ============
|
||||||
|
|
||||||
|
numVers, _, relDate = extractVersion()
|
||||||
|
pkgVers = compactVersion(numVers)
|
||||||
|
relDate = datetime.datetime.strptime(relDate, "%Y-%m-%d")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Set Up Folder
|
||||||
|
# =============
|
||||||
|
|
||||||
|
bldDir = "dist_appimage"
|
||||||
|
bldPkg = f"novelwriter_{pkgVers}"
|
||||||
|
outDir = f"{bldDir}/{bldPkg}"
|
||||||
|
imageDir = f"{bldDir}/appimage"
|
||||||
|
|
||||||
|
# Set Up Folders
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
if not os.path.isdir(bldDir):
|
||||||
|
os.mkdir(bldDir)
|
||||||
|
|
||||||
|
if os.path.isdir(outDir):
|
||||||
|
print("Removing old build files ...")
|
||||||
|
print("")
|
||||||
|
shutil.rmtree(outDir)
|
||||||
|
|
||||||
|
os.mkdir(outDir)
|
||||||
|
|
||||||
|
if os.path.isdir(imageDir):
|
||||||
|
print("Removing old build metadata files ...")
|
||||||
|
print("")
|
||||||
|
shutil.rmtree(imageDir)
|
||||||
|
|
||||||
|
os.mkdir(imageDir)
|
||||||
|
|
||||||
|
# Remove old Appimages
|
||||||
|
outFiles = glob.glob(f"{bldDir}/*.AppImage")
|
||||||
|
|
||||||
|
if outFiles:
|
||||||
|
print("Removing old AppImages")
|
||||||
|
print("")
|
||||||
|
for image in outFiles:
|
||||||
|
try:
|
||||||
|
os.remove(image)
|
||||||
|
except OSError:
|
||||||
|
print("Error while deleting file : ", image)
|
||||||
|
|
||||||
|
# Build Additional Assets
|
||||||
|
# =======================
|
||||||
|
|
||||||
|
buildQtI18n()
|
||||||
|
buildSampleZip()
|
||||||
|
buildPdfManual()
|
||||||
|
|
||||||
|
# Copy novelWriter Source
|
||||||
|
# =======================
|
||||||
|
|
||||||
|
print("Copying novelWriter source ...")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
for nPath, _, nFiles in os.walk("novelwriter"):
|
||||||
|
if nPath.endswith("__pycache__"):
|
||||||
|
print("Skipped: %s" % nPath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
pPath = f"{outDir}/{nPath}"
|
||||||
|
if not os.path.isdir(pPath):
|
||||||
|
os.mkdir(pPath)
|
||||||
|
|
||||||
|
fCount = 0
|
||||||
|
for fFile in nFiles:
|
||||||
|
nFile = f"{nPath}/{fFile}"
|
||||||
|
pFile = f"{pPath}/{fFile}"
|
||||||
|
|
||||||
|
if fFile.endswith(".pyc"):
|
||||||
|
print("Skipped: %s" % nFile)
|
||||||
|
continue
|
||||||
|
|
||||||
|
shutil.copyfile(nFile, pFile)
|
||||||
|
fCount += 1
|
||||||
|
|
||||||
|
print("Copied: %s/* [Files: %d]" % (nPath, fCount))
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("Copying or generating additional files ...")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Copy/Write Root Files
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
copyFiles = ["LICENSE.md", "CREDITS.md", "CHANGELOG.md", "pyproject.toml"]
|
||||||
|
for copyFile in copyFiles:
|
||||||
|
shutil.copyfile(copyFile, f"{outDir}/{copyFile}")
|
||||||
|
print("Copied: %s" % copyFile)
|
||||||
|
|
||||||
|
writeFile(f"{outDir}/MANIFEST.in", (
|
||||||
|
"include LICENSE.md\n"
|
||||||
|
"include CREDITS.md\n"
|
||||||
|
"include CHANGELOG.md\n"
|
||||||
|
"include data/*\n"
|
||||||
|
"recursive-include novelwriter/assets *\n"
|
||||||
|
))
|
||||||
|
print("Wrote: MANIFEST.in")
|
||||||
|
|
||||||
|
writeFile(f"{outDir}/setup.py", (
|
||||||
|
"import setuptools\n"
|
||||||
|
"setuptools.setup()\n"
|
||||||
|
))
|
||||||
|
print("Wrote: setup.py")
|
||||||
|
|
||||||
|
setupCfg = readFile("setup.cfg").replace(
|
||||||
|
"file: setup/description_pypi.md", "file: data/description_short.txt"
|
||||||
|
)
|
||||||
|
writeFile(f"{outDir}/setup.cfg", setupCfg)
|
||||||
|
print("Wrote: setup.cfg")
|
||||||
|
|
||||||
|
# Write Metadata
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
appDescription = readFile("setup/description_short.txt")
|
||||||
|
appdataXML = readFile("setup/novelwriter.appdata.xml").format(description=appDescription)
|
||||||
|
writeFile(f"{imageDir}/novelwriter.appdata.xml", appdataXML)
|
||||||
|
print("Wrote: novelwriter.appdata.xml")
|
||||||
|
|
||||||
|
writeFile(f"{imageDir}/entrypoint.sh", (
|
||||||
|
'#! /bin/bash \n'
|
||||||
|
'{{ python-executable }} -sE ${APPDIR}/opt/python{{ python-version }}/bin/novelwriter "$@"'
|
||||||
|
))
|
||||||
|
print("Wrote: entrypoint.sh")
|
||||||
|
|
||||||
|
writeFile(f"{imageDir}/requirements.txt", os.path.abspath(outDir))
|
||||||
|
print("Wrote: requirements.txt")
|
||||||
|
|
||||||
|
shutil.copyfile("setup/data/novelwriter.desktop", f"{imageDir}/novelwriter.desktop")
|
||||||
|
print("Copied: setup/data/novelwriter.desktop")
|
||||||
|
|
||||||
|
shutil.copyfile("setup/icons/novelwriter.svg", f"{imageDir}/novelwriter.svg")
|
||||||
|
print("Copied: setup/icons/novelwriter.svg")
|
||||||
|
|
||||||
|
shutil.copyfile("setup/data/hicolor/256x256/apps/novelwriter.png",
|
||||||
|
f"{imageDir}/novelwriter.png")
|
||||||
|
print("Copied: setup/data/hicolor/256x256/apps/novelwriter.png")
|
||||||
|
|
||||||
|
# Build Appimage
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.call([
|
||||||
|
sys.executable, "-m", "python_appimage", "build", "app",
|
||||||
|
"-l", linuxTag, "-p", pythonVer, "appimage"
|
||||||
|
], cwd=bldDir)
|
||||||
|
except Exception as exc:
|
||||||
|
print("AppImage build: FAILED")
|
||||||
|
print("")
|
||||||
|
print(str(exc))
|
||||||
|
print("")
|
||||||
|
print("Dependencies:")
|
||||||
|
print(" * pip install python-appimage")
|
||||||
|
print("")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
outFile = glob.glob(f"{bldDir}/*.AppImage")[0]
|
||||||
|
shaFile = makeCheckSum(os.path.basename(outFile), cwd=bldDir)
|
||||||
|
|
||||||
|
toUpload(outFile)
|
||||||
|
toUpload(shaFile)
|
||||||
|
|
||||||
|
return unparsedArgs
|
||||||
|
|
||||||
##
|
##
|
||||||
# Make Windows Setup EXE (build-win-exe)
|
# Make Windows Setup EXE (build-win-exe)
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
def makeWindowsEmbedded(sysArgs):
|
def makeWindowsEmbedded(sysArgs):
|
||||||
"""Set up a package with embedded Python and dependencies for
|
"""Set up a package with embedded Python and dependencies for
|
||||||
Windows installation.
|
Windows installation.
|
||||||
@@ -1581,6 +1803,8 @@ if __name__ == "__main__":
|
|||||||
" Add --snapshot to make a snapshot package.",
|
" Add --snapshot to make a snapshot package.",
|
||||||
" build-win-exe Build a setup.exe file with Python embedded for Windows.",
|
" build-win-exe Build a setup.exe file with Python embedded for Windows.",
|
||||||
" The package must be built from a minimal windows zip file.",
|
" The package must be built from a minimal windows zip file.",
|
||||||
|
" build-appimage Build an AppImage. Argument --linux-tag defaults to",
|
||||||
|
" manylinux1_x86_64 / i386, and --python-version to 3.10.",
|
||||||
"",
|
"",
|
||||||
"System Install:",
|
"System Install:",
|
||||||
"",
|
"",
|
||||||
@@ -1679,6 +1903,14 @@ if __name__ == "__main__":
|
|||||||
makeWindowsEmbedded(sys.argv)
|
makeWindowsEmbedded(sys.argv)
|
||||||
sys.exit(0) # Don't continue execution
|
sys.exit(0) # Don't continue execution
|
||||||
|
|
||||||
|
if "build-appimage" in sys.argv:
|
||||||
|
sys.argv.remove("build-appimage")
|
||||||
|
if hostOS == OS_LINUX:
|
||||||
|
sys.argv = makeAppImage(sys.argv) # Build appimage and prune its args
|
||||||
|
else:
|
||||||
|
print("ERROR: Command 'build-appimage' can only be used on Linux")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# General Installers
|
# General Installers
|
||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Encoding=UTF-8
|
|
||||||
Name=novelWriter
|
Name=novelWriter
|
||||||
Comment=A markdown-like text editor for planning and writing novels
|
Comment=A markdown-like text editor for planning and writing novels
|
||||||
Exec=novelwriter %f
|
Exec=novelwriter %f
|
||||||
Icon=novelwriter
|
Icon=novelwriter
|
||||||
Categories=Qt;Office;WordProcessor;
|
Categories=Qt;Office;WordProcessor;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
MimeType=application/x-novelwriter-project
|
MimeType=application/x-novelwriter-project;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>novelwriter</id>
|
||||||
|
<metadata_license>GPL-3.0</metadata_license>
|
||||||
|
<project_license>GPL-3.0</project_license>
|
||||||
|
<name>novelWriter</name>
|
||||||
|
<summary>A markdown-like text editor for planning and writing novels</summary>
|
||||||
|
<description>
|
||||||
|
<p>{description}</p>
|
||||||
|
</description>
|
||||||
|
<launchable type="desktop-id">novelwriter.desktop</launchable>
|
||||||
|
<url type="homepage">https://novelwriter.io/</url>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://novelwriter.io/images/screenshot-multi.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<provides>
|
||||||
|
<id>novelwriter.desktop</id>
|
||||||
|
</provides>
|
||||||
|
</component>
|
||||||
Reference in New Issue
Block a user