diff --git a/make_windows.py b/make_windows.py new file mode 100644 index 00000000..c64b79d0 --- /dev/null +++ b/make_windows.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import nw +import os +import sys +import getopt +import subprocess + +# Defaults +buildWindowed = True +runPip = False +oneFile = False +makeSetup = False +innoSetup = None + +# Parse Options +shortOpt = "hd" +longOpt = [ + "help", + "debug", + "pip", + "onefile", + "setup", + "inno=", +] +helpMsg = ( + "\n" + "novelWriter Install Script\n" + "\n" + "Usage:\n" + " -h, --help Print this message.\n" + " --pip Install dependecies first.\n" + " --onefile Create a single executable file.\n" + " --setup Make Inno Setup file.\n" + " --inno= Path to the Inoo Setup exec.\n" + " -d, --debug Build novelWriter for debugging. To debug novelwriter after build,\n" + " run it from command line with the debug options. Please check the\n" + " novelWriter --help output for details.\n" +) + +try: + inOpts, inArgs = getopt.getopt(sys.argv[1:], shortOpt, longOpt) +except getopt.GetoptError: + print(helpMsg) + sys.exit(1) + +for inOpt, inArg in inOpts: + if inOpt in ("-h", "--help"): + print(helpMsg) + sys.exit(0) + elif inOpt in ("-d", "--debug"): + buildWindowed = False + elif inOpt == "--pip": + runPip = True + elif inOpt == "--onefile": + oneFile = True + elif inOpt == "--setup": + makeSetup = True + elif inOpt == "--inno": + innoSetup = inArg + +# Run pip +if runPip: + print("") + print("###########################") + print(" Installing Dependencies") + print("###########################") + print("") + try: + subprocess.call([ + sys.executable, "-m", + "pip", "install", "--user", "--upgrade", "pip" + ]) + subprocess.call([ + sys.executable, "-m", + "pip", "install", "--user", "--upgrade", "pyinstaller" + ]) + subprocess.call([ + sys.executable, "-m", + "pip", "install", "--user", "--upgrade", "-r", "requirements.txt" + ]) + except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) + +# Run pyinstaller +print("") +print("#######################") +print(" Running PyInstaller") +print("#######################") +print("") +instOpt = [ + "--name=novelWriter", + "--clean", + "--add-data=%s;%s" % (os.path.join("nw", "assets"), "assets"), + "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"), + "--exclude-module=PyQt5.QtQml", + "--exclude-module=PyQt5.QtBluetooth", + "--exclude-module=PyQt5.QtDBus", + "--exclude-module=PyQt5.QtMultimedia", + "--exclude-module=PyQt5.QtMultimediaWidgets", + "--exclude-module=PyQt5.QtNetwork", + "--exclude-module=PyQt5.QtNetworkAuth", + "--exclude-module=PyQt5.QtNfc", + "--exclude-module=PyQt5.QtQuick", + "--exclude-module=PyQt5.QtQuickWidgets", + "--exclude-module=PyQt5.QtRemoteObjects", + "--exclude-module=PyQt5.QtSensors", + "--exclude-module=PyQt5.QtSerialPort", + "--exclude-module=PyQt5.QtSql", +] +if buildWindowed: + instOpt.append("--windowed") +if oneFile and not makeSetup: + instOpt.append("--onefile") +else: + instOpt.append("--onedir") + +instOpt.append("novelWriter.py") + +import PyInstaller.__main__ # noqa: E402 +PyInstaller.__main__.run(instOpt) + +if not oneFile: + delIfExists = [ + "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll", + "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll", + "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll" + ] + distDir = os.path.join(os.getcwd(), "dist", "novelWriter") + for delFile in delIfExists: + delPath = os.path.join(distDir, delFile) + if os.path.isfile(delPath): + print("Deleting file: %s" % delPath) + os.unlink(delPath) + +print("") +print("Build Finished") +print("") +print("If everything went well, the novelWriter executable should be in the folder named 'dist'") +print("") + +if makeSetup: + print("") + print("######################") + print(" Running Inno Setup") + print("######################") + print("") + if innoSetup is None: + innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe" + if not os.path.isfile(innoSetup): + print("ERROR: Cannot fine Inno Setup's ISCC.exe file.") + print(" Looked in: %s" % innoSetup) + print(" Please provide a path with the --inno= option.") + sys.exit(1) + + # Read the iss template + issData = "" + with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile: + issData = inFile.read() + + issData = issData.replace(r"%%version%%", nw.__version__) + issData = issData.replace(r"%%dir%%", os.getcwd()) + + with open("setup.iss", mode="w+") as outFile: + outFile.write(issData) + + try: + subprocess.call( + [innoSetup, "setup.iss"] + ) + except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) diff --git a/setup.cfg b/setup.cfg index f9b19c12..ad1c6ddb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ description = A markdown-like document editor for writing novels url = https://novelwriter.io long_description = file: README.md long_description_content_type = text/markdown -license_files = LICENSE.md +license_file = LICENSE.md license = GNU General Public License v3 classifiers = Programming Language :: Python :: 3 :: Only diff --git a/setup/win_setup.iss b/setup/win_setup.iss new file mode 100644 index 00000000..6491b317 --- /dev/null +++ b/setup/win_setup.iss @@ -0,0 +1,51 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define nwAppDir "%%dir%%\dist" +#define nwAppName "novelWriter" +#define nwAppVersion "%%version%%" +#define nwAppPublisher "novelWriter" +#define nwAppURL "http://novelWriter.io" +#define nwAppExeName "novelWriter.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{459A75D0-951F-4932-9809-6002EC8E733E} +AppName={#nwAppName} +AppVersion={#nwAppVersion} +AppVerName={#nwAppName} {#nwAppVersion} +AppPublisher={#nwAppPublisher} +AppPublisherURL={#nwAppURL} +AppSupportURL={#nwAppURL} +AppUpdatesURL={#nwAppURL} +DefaultDirName={autopf}\{#nwAppName} +DisableProgramGroupPage=yes +; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check. +UsedUserAreasWarning=no +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +OutputDir={#nwAppDir} +OutputBaseFilename=setup-novelwriter-{#nwAppVersion} +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode + +[Files] +Source: "{#nwAppDir}\novelWriter\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}" +Name: "{autodesktop}\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: desktopicon +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#nwAppName}"; Filename: "{app}\{#nwAppExeName}"; Tasks: quicklaunchicon + +[Run] +Filename: "{app}\{#nwAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(nwAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent