Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appimage Distribution #1092

Merged
merged 7 commits into from Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
232 changes: 232 additions & 0 deletions setup.py
Expand Up @@ -197,6 +197,7 @@ def removeFolder(rmDir):
removeFolder("dist")
removeFolder("dist_deb")
removeFolder("dist_minimal")
removeFolder("dist_appimage")
removeFolder("novelWriter.egg-info")

print("")
Expand Down Expand Up @@ -835,10 +836,231 @@ def makeForLaunchpad(doSign=False, isFirst=False, isSnapshot=False):
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)
##


def makeWindowsEmbedded(sysArgs):
"""Set up a package with embedded Python and dependencies for
Windows installation.
Expand Down Expand Up @@ -1581,6 +1803,8 @@ def winUninstall():
" Add --snapshot to make a snapshot package.",
" build-win-exe Build a setup.exe file with Python embedded for Windows.",
" 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:",
"",
Expand Down Expand Up @@ -1679,6 +1903,14 @@ def winUninstall():
makeWindowsEmbedded(sys.argv)
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
# ==================

Expand Down
3 changes: 1 addition & 2 deletions setup/data/novelwriter.desktop
@@ -1,10 +1,9 @@
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=novelWriter
Comment=A markdown-like text editor for planning and writing novels
Exec=novelwriter %f
Icon=novelwriter
Categories=Qt;Office;WordProcessor;
Terminal=false
MimeType=application/x-novelwriter-project
MimeType=application/x-novelwriter-project;
21 changes: 21 additions & 0 deletions setup/novelwriter.appdata.xml
@@ -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>