Complete the zipapp packaging for Windows

This commit is contained in:
Veronica K. B. Olsen
2021-01-23 14:25:12 +01:00
parent e3972c6205
commit 5f86ab27bb
3 changed files with 55 additions and 37 deletions
+10 -4
View File
@@ -269,10 +269,16 @@ class Config:
logger.verbose("Config path: %s" % self.confPath) logger.verbose("Config path: %s" % self.confPath)
logger.verbose("Data path: %s" % self.dataPath) logger.verbose("Data path: %s" % self.dataPath)
self.confFile = self.appHandle+".conf" self.confFile = self.appHandle+".conf"
self.lastPath = os.path.expanduser("~") self.lastPath = os.path.expanduser("~")
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))) self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
self.appRoot = os.path.join(self.appPath, os.path.pardir) self.appRoot = os.path.abspath(os.path.join(self.appPath, os.path.pardir))
if self.appRoot.endswith(".pyz"):
self.appRoot = os.path.abspath(os.path.join(self.appRoot, os.path.pardir))
self.appPath = self.appRoot
# Assets
self.assetPath = os.path.join(self.appPath, "assets") self.assetPath = os.path.join(self.appPath, "assets")
self.themeRoot = os.path.join(self.assetPath, "themes") self.themeRoot = os.path.join(self.assetPath, "themes")
self.dictPath = os.path.join(self.assetPath, "dict") self.dictPath = os.path.join(self.assetPath, "dict")
+43 -31
View File
@@ -216,12 +216,13 @@ def buildSampleZip():
# Make Simple Package (winpack) # Make Simple Package (winpack)
## ##
def makeWindowsPackage(): def makeSimplePackage(embedPython):
"""Run zipapp to freeze the packages. This assumes zipapp and pip """Run zipapp to freeze the packages. This assumes zipapp and pip
are already installed. are already installed.
""" """
import urllib.request import urllib.request
import zipfile import zipfile
import zipapp
# Set Up Folder # Set Up Folder
# ============= # =============
@@ -230,7 +231,10 @@ def makeWindowsPackage():
os.mkdir("dist") os.mkdir("dist")
outDir = os.path.join("dist", "novelWriter") outDir = os.path.join("dist", "novelWriter")
zipDir = os.path.join("dist", "zipapp_temp")
libDir = os.path.join(outDir, "lib") libDir = os.path.join(outDir, "lib")
if os.path.isdir(zipDir):
shutil.rmtree(zipDir)
if os.path.isdir(outDir): if os.path.isdir(outDir):
shutil.rmtree(outDir) shutil.rmtree(outDir)
@@ -240,23 +244,24 @@ def makeWindowsPackage():
# Download Python Embeddable # Download Python Embeddable
# ========================== # ==========================
print("") if embedPython:
print("# Downloading Python Embeddable") print("")
print("# =============================") print("# Downloading Python Embeddable")
print("") print("# =============================")
print("")
pyUrl = "https://www.python.org/ftp/python/3.8.7/python-3.8.7-embed-amd64.zip" pyUrl = "https://www.python.org/ftp/python/3.8.7/python-3.8.7-embed-amd64.zip"
pyZip = os.path.join(outDir, "python_embed.zip") pyZip = os.path.join(outDir, "python_embed.zip")
print("URL: %s" % pyUrl) print("URL: %s" % pyUrl)
urllib.request.urlretrieve(pyUrl, pyZip) urllib.request.urlretrieve(pyUrl, pyZip)
print("Extracting ...") print("Extracting ...")
with zipfile.ZipFile(pyZip, "r") as inFile: with zipfile.ZipFile(pyZip, "r") as inFile:
inFile.extractall(outDir) inFile.extractall(outDir)
os.unlink(pyZip) os.unlink(pyZip)
print("") print("")
# Make sample.zip # Make sample.zip
# =============== # ===============
@@ -281,7 +286,7 @@ def makeWindowsPackage():
cpIgnore = shutil.ignore_patterns("__pycache__") cpIgnore = shutil.ignore_patterns("__pycache__")
print("Copying: nw") print("Copying: nw")
shutil.copytree("nw", os.path.join(outDir, "nw"), ignore=cpIgnore) shutil.copytree("nw", os.path.join(zipDir, "nw"), ignore=cpIgnore)
for copyFile in copyList: for copyFile in copyList:
print("Copying: %s" % copyFile) print("Copying: %s" % copyFile)
shutil.copy2(copyFile, os.path.join(outDir, copyFile)) shutil.copy2(copyFile, os.path.join(outDir, copyFile))
@@ -289,8 +294,11 @@ def makeWindowsPackage():
print("Copying: %s" % iconFile) print("Copying: %s" % iconFile)
shutil.copy2(os.path.join("setup", "icons", iconFile), os.path.join(outDir, iconFile)) shutil.copy2(os.path.join("setup", "icons", iconFile), os.path.join(outDir, iconFile))
print("Writing: novelWriter.pyw") nwDir = os.path.join(outDir, "nw")
with open(os.path.join(outDir, "novelWriter.pyw"), mode="w") as outFile: os.rename(os.path.join(zipDir, "nw", "assets"), os.path.join(outDir, "assets"))
print("Writing: __main__.py")
with open(os.path.join(zipDir, "__main__.py"), mode="w") as outFile:
outFile.write( outFile.write(
"#!\"pythonw.exe\"\n" "#!\"pythonw.exe\"\n"
"\n" "\n"
@@ -298,7 +306,9 @@ def makeWindowsPackage():
"import sys\n" "import sys\n"
"\n" "\n"
"sys.path.insert(\n" "sys.path.insert(\n"
" 0, os.path.abspath(os.path.join(os.path.dirname(__file__), \"lib\"))\n" " 0, os.path.abspath(\n"
" os.path.join(os.path.dirname(__file__), os.path.pardir, \"lib\")\n"
" )\n"
")\n\n" ")\n\n"
"if __name__ == \"__main__\":\n" "if __name__ == \"__main__\":\n"
" import nw\n" " import nw\n"
@@ -306,6 +316,9 @@ def makeWindowsPackage():
) )
print("") print("")
pyzFile = os.path.join(outDir, "novelWriter.pyz")
zipapp.create_archive(zipDir, target=pyzFile, interpreter="python3")
# Install Dependencies # Install Dependencies
# ==================== # ====================
@@ -722,9 +735,9 @@ if __name__ == "__main__":
"\n" "\n"
"Python Packaging:\n" "Python Packaging:\n"
"\n" "\n"
" winpack Creates a pyz package in a folder with all dependencies using the\n" " pack-pyz Creates a pyz package in a folder with all dependencies using the\n"
" zipapp tool. This option is intended for Windows deployment.\n" " zipapp tool. This option is intended for Windows deployment.\n"
" freeze Freeze the package and produces a folder with all dependencies using\n" " pack-exe Freeze the package and produces a folder with all dependencies using\n"
" the pyinstaller tool. This option is not designed for a specific OS.\n" " the pyinstaller tool. This option is not designed for a specific OS.\n"
" onefile Build a standalone executable with all dependencies bundled using the\n" " onefile Build a standalone executable with all dependencies bundled using the\n"
" pyinstaller tool. Implies 'freeze', cannot be used with 'setup-exe'.\n" " pyinstaller tool. Implies 'freeze', cannot be used with 'setup-exe'.\n"
@@ -751,7 +764,8 @@ if __name__ == "__main__":
makeSetupExe = False makeSetupExe = False
makeSetupPyz = False makeSetupPyz = False
doFreeze = False doFreeze = False
winPack = False simplePack = False
embedPython = False
# General # General
# ======= # =======
@@ -783,16 +797,14 @@ if __name__ == "__main__":
# Python Packaging # Python Packaging
# ================ # ================
if "winpack" in sys.argv: if "pack-pyz" in sys.argv:
sys.argv.remove("winpack") sys.argv.remove("pack-pyz")
simplePack = True
if hostOS == OS_WIN: if hostOS == OS_WIN:
winPack = True embedPython = True
else:
print("Error: Command 'winpack' is Windows only.")
sys.exit(1)
if "freeze" in sys.argv: if "pack-exe" in sys.argv:
sys.argv.remove("freeze") sys.argv.remove("pack-exe")
doFreeze = True doFreeze = True
if "onefile" in sys.argv: if "onefile" in sys.argv:
@@ -843,8 +855,8 @@ if __name__ == "__main__":
# For functions that are controlled by multiple flags, or need to be # For functions that are controlled by multiple flags, or need to be
# run in a specific order. # run in a specific order.
if winPack: if simplePack:
makeWindowsPackage() makeSimplePackage(embedPython)
if doFreeze: if doFreeze:
freezePackage(buildWindowed, oneFile, makeSetupExe, hostOS) freezePackage(buildWindowed, oneFile, makeSetupExe, hostOS)
+2 -2
View File
@@ -6,7 +6,7 @@
#define nwAppVersion "%%version%%" #define nwAppVersion "%%version%%"
#define nwAppPublisher "novelWriter" #define nwAppPublisher "novelWriter"
#define nwAppURL "http://novelWriter.io" #define nwAppURL "http://novelWriter.io"
#define nwAppExeName "novelWriter.pyw" #define nwAppExeName "novelWriter.pyz"
[Setup] [Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
@@ -54,6 +54,6 @@ Filename: "{app}\pythonw.exe"; Parameters: "{#nwAppExeName}"; Description: "{cm
[Registry] [Registry]
Root: HKA; Subkey: "Software\Classes\.nwx\OpenWithProgids"; ValueType: string; ValueName: "novelWriterProject.nwx"; ValueData: ""; Flags: uninsdeletevalue Root: HKA; Subkey: "Software\Classes\.nwx\OpenWithProgids"; ValueType: string; ValueName: "novelWriterProject.nwx"; ValueData: ""; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx"; ValueType: string; ValueName: ""; ValueData: "novelWriter Project File"; Flags: uninsdeletekey Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx"; ValueType: string; ValueName: ""; ValueData: "novelWriter Project File"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\nw\assets\icons\x-novelwriter-project.ico" Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\assets\icons\x-novelwriter-project.ico"
Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pythonw.exe"" ""{app}\{#nwAppExeName}"" ""%1""" Root: HKA; Subkey: "Software\Classes\novelWriterProject.nwx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\pythonw.exe"" ""{app}\{#nwAppExeName}"" ""%1"""
Root: HKA; Subkey: "Software\Classes\Applications\{#nwAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: "" Root: HKA; Subkey: "Software\Classes\Applications\{#nwAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".nwx"; ValueData: ""