From 403066a83f07d573823811adae3f925f3472260b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 16 Oct 2020 18:53:29 +0200 Subject: [PATCH 01/14] Moe settings from setup.py to setup.cfg, and add pyproject.toml --- pyproject.toml | 3 +++ setup.cfg | 45 ++++++++++++++++++++++++++++++++++++++++++++- setup.py | 48 +----------------------------------------------- 3 files changed, 48 insertions(+), 48 deletions(-) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..9787c3bd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index ff0117c5..f9b19c12 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,49 @@ [metadata] -license_files = LICENSE.md +name = novelWriter version = attr: nw.__version__ +author = Veronica Berglyd Olsen +author_email = code@vkbo.net +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 = GNU General Public License v3 +classifiers = + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + Development Status :: 4 - Beta + Operating System :: OS Independent + Intended Audience :: End Users/Desktop + Natural Language :: English + Topic :: Text Editors +python_requires = >=3.6 +install_requires = + pyqt5>=5.2.1 + lxml>=4.2.0 + pyenchant>=3.0.0 +project_urls = + Bug Tracker = https://github.com/vkbo/novelWriter/issues + Documentation = https://github.com/vkbo/novelWriter/issues + Source Code = https://github.com/vkbo/novelWriter + +[options] +include_package_data = True +packages = find: + +[options.packages.find] +exclude = docs, tests, sample + +[options.entry_points] +console_script = + novelWriter-cli = nw:main +gui_scripts = + novelWriter = nw:main [bdist_wheel] universal = 0 diff --git a/setup.py b/setup.py index 6b10a84e..ed17b904 100755 --- a/setup.py +++ b/setup.py @@ -113,50 +113,4 @@ if len(sys.argv) == 1: # Build the Package ## -# Read content from files -with open("README.md", "r") as inFile: - longDescription = inFile.read() - -setuptools.setup( - name = "novelWriter", - # version = __version__, # Set in setup.cfg - author = "Veronica Berglyd Olsen", - author_email = "code@vkbo.net", - description = "A markdown-like document editor for writing novels", - long_description = longDescription, - long_description_content_type = "text/markdown", - license = "GNU General Public License v3", - url = "https://novelwriter.io", - entry_points = { - "console_scripts" : ["novelWriter-cli=nw:main"], - "gui_scripts" : ["novelWriter=nw:main"], - }, - packages = setuptools.find_packages(exclude=["docs", "tests", "sample"]), - include_package_data = True, - package_data = {"": ["*.conf"]}, - project_urls = { - "Bug Tracker": "https://github.com/vkbo/novelWriter/issues", - "Documentation": "https://github.com/vkbo/novelWriter/issues", - "Source Code": "https://github.com/vkbo/novelWriter", - }, - classifiers = [ - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: Implementation :: CPython", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Development Status :: 4 - Beta", - "Operating System :: OS Independent", - "Intended Audience :: End Users/Desktop", - "Natural Language :: English", - "Topic :: Text Editors", - ], - python_requires = ">=3.6", - install_requires = [ - "pyqt5>=5.2.1", - "lxml>=4.2.0", - "pyenchant>=3.0.0", - ], -) +setuptools.setup() From 70086f5ca65dde4bafe39ff1d8d4bfe8710ddd5d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 00:58:48 +0200 Subject: [PATCH 02/14] Added python and iss script for making setup.exe releases on Windows --- make_windows.py | 177 ++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 2 +- setup/win_setup.iss | 51 +++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 make_windows.py create mode 100644 setup/win_setup.iss 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 From 115cef3ffb18c5fe601ddbdcdd1505207037bf20 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 01:05:49 +0200 Subject: [PATCH 03/14] Added more comments to windows script --- make_windows.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/make_windows.py b/make_windows.py index c64b79d0..27e8c112 100644 --- a/make_windows.py +++ b/make_windows.py @@ -1,5 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +This script will either build: + * A single file executable named dist/novelWriter.exe. This is a quite + slow option, and the file is fairly big. Option --onefile + * A single directory named dist/novelWriter with a novelWriter.exe, and + all dependecies included. This is the default. + * The latter can be combined with a build stage of a setup.exe file + named setup-novelwriter-.exe. Option --setup. + +In addition, providing the --pip flag will cause the script to try to +install all dependencies needed for runing the build, and for running +novelWriter itself. +""" import nw import os @@ -26,14 +39,14 @@ longOpt = [ ] helpMsg = ( "\n" - "novelWriter Install Script\n" + "novelWriter Install Script for Windows\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" + " --inno= Path to the Inno 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" @@ -111,8 +124,10 @@ instOpt = [ "--exclude-module=PyQt5.QtSerialPort", "--exclude-module=PyQt5.QtSql", ] + if buildWindowed: instOpt.append("--windowed") + if oneFile and not makeSetup: instOpt.append("--onefile") else: @@ -124,6 +139,7 @@ import PyInstaller.__main__ # noqa: E402 PyInstaller.__main__.run(instOpt) if not oneFile: + # These dll files are not nee3ded, and take up a fair bit of space. delIfExists = [ "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll", "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll", @@ -168,9 +184,7 @@ if makeSetup: outFile.write(issData) try: - subprocess.call( - [innoSetup, "setup.iss"] - ) + subprocess.call([innoSetup, "setup.iss"]) except Exception as e: print("Failed with error:") print(str(e)) From 0665b9b0321910752e30eba3a781543178e31b38 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 01:30:13 +0200 Subject: [PATCH 04/14] Added temp iss file to gitignore, and fiex a bug in windows make --- .gitignore | 1 + make_windows.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 57dc50a9..830a409a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /deploy/ *.spec *.egg-info +setup.iss # Documentation /docs/build/ diff --git a/make_windows.py b/make_windows.py index 27e8c112..cc75f519 100644 --- a/make_windows.py +++ b/make_windows.py @@ -14,7 +14,6 @@ install all dependencies needed for runing the build, and for running novelWriter itself. """ -import nw import os import sys import getopt @@ -177,6 +176,7 @@ if makeSetup: with open(os.path.join("setup", "win_setup.iss"), mode="r") as inFile: issData = inFile.read() + import nw # noqa: E402 issData = issData.replace(r"%%version%%", nw.__version__) issData = issData.replace(r"%%dir%%", os.getcwd()) From ede9d034c797b393867ea86a29464469aeaf42c1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 01:43:47 +0200 Subject: [PATCH 05/14] Make sample.zip before pyinstaller runs, and make sure the make_windows script is windows only --- make_windows.py | 395 +++++++++++++++++++++++++----------------------- 1 file changed, 204 insertions(+), 191 deletions(-) mode change 100644 => 100755 make_windows.py diff --git a/make_windows.py b/make_windows.py old mode 100644 new mode 100755 index cc75f519..d5b938c0 --- a/make_windows.py +++ b/make_windows.py @@ -1,191 +1,204 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -This script will either build: - * A single file executable named dist/novelWriter.exe. This is a quite - slow option, and the file is fairly big. Option --onefile - * A single directory named dist/novelWriter with a novelWriter.exe, and - all dependecies included. This is the default. - * The latter can be combined with a build stage of a setup.exe file - named setup-novelwriter-.exe. Option --setup. - -In addition, providing the --pip flag will cause the script to try to -install all dependencies needed for runing the build, and for running -novelWriter itself. -""" - -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 for Windows\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 Inno 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: - # These dll files are not nee3ded, and take up a fair bit of space. - 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() - - import nw # noqa: E402 - 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) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This script will either build: + * A single file executable named dist/novelWriter.exe. This is a quite + slow option, and the file is fairly big. Option --onefile + * A single directory named dist/novelWriter with a novelWriter.exe, and + all dependecies included. This is the default. + * The latter can be combined with a build stage of a setup.exe file + named setup-novelwriter-.exe. Option --setup. + +In addition, providing the --pip flag will cause the script to try to +install all dependencies needed for runing the build, and for running +novelWriter itself. +""" + +import os +import sys +import getopt +import subprocess + +if not sys.platform.startswith("win32"): + print("ERROR: This script is intended for Windows only.") + sys.exit(1) + +# 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 for Windows\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 Inno 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") + +# Make sample.zip first +print("Building sample.zip") +try: + subprocess.call([sys.executable, "setup.py" "sample"]) +except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) + +import PyInstaller.__main__ # noqa: E402 +PyInstaller.__main__.run(instOpt) + +if not oneFile: + # These dll files are not nee3ded, and take up a fair bit of space. + 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() + + import nw # noqa: E402 + 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) From a5201d79cbbba375b824d3d11d00c97db63bb2c3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 01:46:50 +0200 Subject: [PATCH 06/14] Fix typo --- make_windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make_windows.py b/make_windows.py index d5b938c0..674fb023 100755 --- a/make_windows.py +++ b/make_windows.py @@ -141,7 +141,7 @@ instOpt.append("novelWriter.py") # Make sample.zip first print("Building sample.zip") try: - subprocess.call([sys.executable, "setup.py" "sample"]) + subprocess.call([sys.executable, "setup.py", "sample"]) except Exception as e: print("Failed with error:") print(str(e)) From 3b63dd49dc4acf374c210f29b73e50b9cf09b272 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 13:03:32 +0200 Subject: [PATCH 07/14] Cleanup of the setup script --- docs/source/conf.py | 1 - setup.py | 93 +++++++++++++++++++++++++++++---------------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b72b8db3..a76fce4c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,7 +12,6 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os # import sys # sys.path.insert(0, os.path.abspath(".")) import os diff --git a/setup.py b/setup.py index ed17b904..403a71f2 100755 --- a/setup.py +++ b/setup.py @@ -4,27 +4,22 @@ import sys import subprocess import setuptools -## -# Build the Package -## +# =========================================================================== # +# Qt Assistant Documentation Builder +# =========================================================================== # -buildDocs = False -buildSample = False +def buildQtDocs(): + """This function will build the documentation as a Qt help file. The + file is then copied into the nw/assets/help directory and can be + included in builds. -if "qthelp" in sys.argv: - buildDocs = True - sys.argv.remove("qthelp") - -if "sample" in sys.argv: - buildSample = True - sys.argv.remove("sample") - -## -# Qt Assistant Documentation -## - -if buildDocs: + Depends on packages: + * pip install sphinx + * pip install sphinx-rtd-theme + * pip install sphinxcontrib-qthelp + It also requires the qhelpgenerator to be available on the system. + """ buildDir = os.path.join("docs", "build", "qthelp") helpDir = os.path.join("nw", "assets", "help") @@ -41,14 +36,14 @@ if buildDocs: try: subprocess.call(["make", "-C", "docs", "qthelp"]) except Exception as e: - print("Failed with error:") + print("QtHelp Build Error:") print(str(e)) buildFail = True try: subprocess.call(["qhelpgenerator", os.path.join(buildDir, inFile)]) except Exception as e: - print("Failed with error:") + print("QtHelp Build Error:") print(str(e)) buildFail = True @@ -56,7 +51,7 @@ if buildDocs: try: os.mkdir(helpDir) except Exception as e: - print("Failed with error:") + print("QtHelp Build Error:") print(str(e)) buildFail = True @@ -68,7 +63,7 @@ if buildDocs: os.rename(os.path.join(buildDir, outFile), os.path.join(helpDir, outFile)) os.rename(os.path.join(buildDir, datFile), os.path.join(helpDir, datFile)) except Exception as e: - print("Failed with error:") + print("QtHelp Build Error:") print(str(e)) buildFail = True @@ -80,11 +75,20 @@ if buildDocs: print("Documentation build: OK") print("") -## -# Sample Project ZIP file -## + return -if buildSample: +# =========================================================================== # +# Sample Project ZIP File Builder +# =========================================================================== # + +def buildSampleZip(): + """Bundle the sample project into a single zip file to be saved into + the nw/assets folder for further bundling into builds. + """ + print("") + print("Building Sample ZIP File") + print("========================") + print("") srcSample = "sample" dstSample = os.path.join("nw", "assets", "sample.zip") @@ -96,8 +100,10 @@ if buildSample: from zipfile import ZipFile with ZipFile(dstSample, "w") as zipObj: + print("Compressing: nwProject.nwx") zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx") for docFile in os.listdir(os.path.join(srcSample, "content")): + print("Compressing: content/%s" % docFile) srcDoc = os.path.join(srcSample, "content", docFile) zipObj.write(srcDoc, "content/"+docFile) @@ -105,12 +111,33 @@ if buildSample: print("Error: Could not find sample project source directory.") sys.exit(1) -if len(sys.argv) == 1: - # Nothing more to do - sys.exit(0) + print("") + print("Built file: %s" % dstSample) + print("") -## -# Build the Package -## + return -setuptools.setup() +# =========================================================================== # +# Process Jobs +# =========================================================================== # + +if __name__ == "__main__": + + # Process non-standard jobs + + if "qthelp" in sys.argv: + sys.argv.remove("qthelp") + buildQtDocs() + + if "sample" in sys.argv: + sys.argv.remove("sample") + buildSampleZip() + + if len(sys.argv) == 1: + # Nothing more to do + sys.exit(0) + + # Run the standard setup + setuptools.setup() + +# END Main From f0adc59d4cfcbe686f1c845ad2b2ad2463be5813 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 13:56:27 +0200 Subject: [PATCH 08/14] Rewritten make_windows.py script --- make_windows.py | 378 +++++++++++++++++++++++++++++++----------------- 1 file changed, 247 insertions(+), 131 deletions(-) diff --git a/make_windows.py b/make_windows.py index 674fb023..766846b3 100755 --- a/make_windows.py +++ b/make_windows.py @@ -16,72 +16,22 @@ novelWriter itself. import os import sys -import getopt +import shutil import subprocess -if not sys.platform.startswith("win32"): - print("ERROR: This script is intended for Windows only.") - sys.exit(1) +OS_NONE = 0 +OS_LINUX = 1 +OS_WIN = 2 +OS_DARWIN = 3 -# Defaults -buildWindowed = True -runPip = False -oneFile = False -makeSetup = False -innoSetup = None +# =============================================================================================== # +# Package Installer +# =============================================================================================== # -# Parse Options -shortOpt = "hd" -longOpt = [ - "help", - "debug", - "pip", - "onefile", - "setup", - "inno=", -] -helpMsg = ( - "\n" - "novelWriter Install Script for Windows\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 Inno 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: +def installPackages(): print("") - print("###########################") - print(" Installing Dependencies") - print("###########################") + print("Installing Dependencies") + print("#######################") print("") try: subprocess.call([ @@ -101,86 +51,131 @@ if runPip: 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", -] + return -if buildWindowed: - instOpt.append("--windowed") +# =============================================================================================== # +# Run PyInstaller on Package +# =============================================================================================== # -if oneFile and not makeSetup: - instOpt.append("--onefile") -else: - instOpt.append("--onedir") +def freezePackage(buildWindowed, oneFile, makeSetup, hostOS): + """Run PyInstaller to freeze the packages. This assumes all + dependencies are already in place. + """ + import PyInstaller.__main__ # noqa: E402 -instOpt.append("novelWriter.py") + print("") + print("Running PyInstaller") + print("###################") + print("") -# Make sample.zip first -print("Building sample.zip") -try: - subprocess.call([sys.executable, "setup.py", "sample"]) -except Exception as e: - print("Failed with error:") - print(str(e)) - sys.exit(1) + if hostOS == OS_WIN: + dotDot = ";" + else: + dotDot = ":" -import PyInstaller.__main__ # noqa: E402 -PyInstaller.__main__.run(instOpt) - -if not oneFile: - # These dll files are not nee3ded, and take up a fair bit of space. - delIfExists = [ - "Qt5DBus.dll", "Qt5Network.dll", "Qt5Qml.dll", "Qt5QmlModels.dll", "Qt5Quick.dll", - "Qt5Quick3D.dll", "Qt5Quick3DAssetImport.dll", "Qt5Quick3DRender.dll", - "Qt5Quick3DRuntimeRender.dll", "Qt5Quick3DUtils.dll", "Qt5Sql.dll" + instOpt = [ + "--name=novelWriter", + "--clean", + "--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "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", ] - 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 buildWindowed: + instOpt.append("--windowed") + + if oneFile and not makeSetup: + instOpt.append("--onefile") + else: + instOpt.append("--onedir") + + instOpt.append("novelWriter.py") + + # Make sample.zip first + try: + subprocess.call([sys.executable, "setup.py", "sample"]) + except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) + + PyInstaller.__main__.run(instOpt) + + if not oneFile: + # These files are not needed, and take up a fair bit of space. + delFiles = [] + if hostOS == OS_WIN: + delFiles = [ + "Qt5DBus.dll", + "Qt5Network.dll", + "Qt5Qml.dll", + "Qt5QmlModels.dll", + "Qt5Quick.dll", + "Qt5Quick3D.dll", + "Qt5Quick3DAssetImport.dll", + "Qt5Quick3DRender.dll", + "Qt5Quick3DRuntimeRender.dll", + "Qt5Quick3DUtils.dll", + "Qt5Sql.dll" + ] + elif hostOS == OS_LINUX: + delFiles = [ + "libQt5DBus.so.5", + "libQt5Network.so.5", + "libQt5Qml.so.5", + "libQt5QmlModels.so.5", + "libQt5Quick.so.5", + "libQt5Quick3D.so.5", + "libQt5Quick3DAssetImport.so.5", + "libQt5Quick3DRender.so.5", + "libQt5Quick3DRuntimeRender.so.5", + "libQt5Quick3DUtils.so.5", + "libQt5Sql.so.5" + ] + distDir = os.path.join(os.getcwd(), "dist", "novelWriter") + for delFile in delFiles: + delPath = os.path.join(distDir, delFile) + if os.path.isfile(delPath): + print("Deleting file: %s" % delPath) + os.unlink(delPath) -if makeSetup: print("") - print("######################") - print(" Running Inno Setup") - print("######################") + print("Build Finished") print("") - if innoSetup is None: - innoSetup = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe" - if not os.path.isfile(innoSetup): + print("The novelWriter executable should be in the folder named 'dist'") + print("") + + return + +# =============================================================================================== # +# Inno Setup Builder +# =============================================================================================== # + +def innoSetup(innoExec): + """Run the Inno Setup tool to build a setup.exe file for Windows. + """ + print("") + print("Running Inno Setup") + print("##################") + print("") + if innoExec is None: + innoExec = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe" + if not os.path.isfile(innoExec): print("ERROR: Cannot fine Inno Setup's ISCC.exe file.") - print(" Looked in: %s" % innoSetup) + print(" Looked in: %s" % innoExec) print(" Please provide a path with the --inno= option.") sys.exit(1) @@ -197,8 +192,129 @@ if makeSetup: outFile.write(issData) try: - subprocess.call([innoSetup, "setup.iss"]) + subprocess.call([innoExec, "setup.iss"]) except Exception as e: print("Failed with error:") print(str(e)) sys.exit(1) + + return + +# =============================================================================================== # +# Clean Build and Dist Folders +# =============================================================================================== # + +def cleanInstall(): + """Recursively delete the 'build' and 'dist' folders. + """ + print("") + print("Cleaning up build environment ...") + + buildDir = os.path.join(os.getcwd(), "build") + if os.path.isdir(buildDir): + try: + shutil.rmtree(buildDir) + print("Deleted folder 'build'") + except Exception as e: + print("Error: Cannot delete 'build' folder.") + print(str(e)) + sys.exit(1) + else: + print("Folder 'build' not found") + + distDir = os.path.join(os.getcwd(), "dist") + if os.path.isdir(distDir): + try: + shutil.rmtree(distDir) + print("Deleted folder 'dist'") + except Exception as e: + print("Error: Cannot delete 'dist' folder.") + print(str(e)) + sys.exit(1) + else: + print("Folder 'dist' not found") + + print("") + + return + +# =============================================================================================== # +# Process Build Steps +# =============================================================================================== # + +if __name__ == "__main__": + """Parse command line options and run the commands. + """ + # Detect OS + if sys.platform.startswith("linux"): + hostOS = OS_LINUX + elif sys.platform.startswith("darwin"): + hostOS = OS_DARWIN + elif sys.platform.startswith("win32"): + hostOS = OS_WIN + elif sys.platform.startswith("cygwin"): + hostOS = OS_WIN + else: + hostOS = OS_NONE + + # Flags and Variables + buildWindowed = True + oneFile = False + makeSetup = False + innoExec = None + + if "help" in sys.argv: + print( + "\n" + "novelWriter Make Tool\n" + "=====================\n" + "This tool provides build commands for distibuting novelWriter as\n" + "a package. The available options are as follows:\n" + "\n" + "pip Run pip to install all package dependencies for\n" + " novelWriter and this build tool.\n" + "onefile Build a standalone executable with all dependencies\n" + " bundled. This does not produce a setup.exe on Windows.\n" + "setup Build a setup.exe installer for Windows. This option\n" + " automaticall disables the 'onefile' option.\n" + "clean This will attempt to delete the 'build' and 'dist'\n" + " folders in the current folder.\n" + ) + sys.exit(0) + + if not os.path.isfile(os.path.join(os.getcwd(), "novelWriter.py")): + print("Error: This script must be run in the root folder of novelWriter.") + sys.exit(1) + + if not os.path.isdir(os.path.join(os.getcwd(), "nw")): + print("Error: This script must be run in the root folder of novelWriter.") + sys.exit(1) + + if "clean" in sys.argv: + sys.argv.remove("clean") + cleanInstall() + sys.exit(0) + + if "pip" in sys.argv: + sys.argv.remove("pip") + installPackages() + + if "onefile" in sys.argv: + sys.argv.remove("onefile") + oneFile = True + + if "setup" in sys.argv: + sys.argv.remove("setup") + if hostOS == OS_WIN: + oneFile = False + makeSetup = True + else: + print("Error: Argument 'setup' for Inno Setup is Windows only.") + sys.exit(1) + + freezePackage(buildWindowed, oneFile, makeSetup, hostOS) + + if makeSetup: + innoSetup(innoExec) + +# END Main From a20296cded774a572150f37ea9ab4bd013d7a5e7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 13:57:19 +0200 Subject: [PATCH 09/14] Rnamed make_windows.py to make.py, and deleted install.py --- install.py | 84 -------------------------------------- make_windows.py => make.py | 0 2 files changed, 84 deletions(-) delete mode 100755 install.py rename make_windows.py => make.py (100%) diff --git a/install.py b/install.py deleted file mode 100755 index 11754a26..00000000 --- a/install.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import sys -import getopt -import subprocess - -# Defaults -buildWindowed = True - -# Parse Options -shortOpt = "hd" -longOpt = [ - "help", - "debug", -] -helpMsg = ( - "\n" - "novelWriter Install Script\n" - "\n" - "Usage:\n" - " -h, --help Print this message.\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(2) - -for inOpt, inArg in inOpts: - if inOpt in ("-h", "--help"): - print(helpMsg) - sys.exit(0) - elif inOpt in ("-d", "--debug"): - buildWindowed = False - -# Run pip -packList = ["pyinstaller"] -with open("requirements.txt", mode="r") as reqFile: - for reqPack in reqFile: - if len(reqPack.strip()) > 0: - packList.append(reqPack) - -for packName in packList: - print("Installing package dependency: %s" % packName) - try: - subprocess.call([sys.executable, "-m", "pip", "install", packName]) - except Exception as e: - print("Failed with error:") - print(str(e)) - -# Run pyinstaller -if sys.platform.startswith("win32"): - dotDot = ";" -else: - dotDot = ":" - -instOpt = [ - "--name=novelWriter", - "--clean", - "--onefile", - "--add-data=%s%s%s" % (os.path.join("nw", "assets"), dotDot, "assets"), - "--icon=%s" % os.path.join("nw", "assets", "icons", "novelwriter.ico"), -] -if buildWindowed: - instOpt.append("--windowed") - -instOpt.append("novelWriter.py") - -import PyInstaller.__main__ # noqa: E402 -PyInstaller.__main__.run(instOpt) - -print("") -print("##################") -print(" Build Finished") -print("##################") -print("") -print("If everything went well, the novelWriter executable should be in the folder named 'dist'") -print("") diff --git a/make_windows.py b/make.py similarity index 100% rename from make_windows.py rename to make.py From db2eb98c274e36a545e962f35c199cd1aa69df1f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 14:09:32 +0200 Subject: [PATCH 10/14] Minor changes to the windows inno setup --- make.py | 20 ++++++-------------- setup/win_setup.iss | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/make.py b/make.py index 766846b3..6162a313 100755 --- a/make.py +++ b/make.py @@ -164,20 +164,13 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS): # Inno Setup Builder # =============================================================================================== # -def innoSetup(innoExec): +def innoSetup(): """Run the Inno Setup tool to build a setup.exe file for Windows. """ print("") print("Running Inno Setup") print("##################") print("") - if innoExec is None: - innoExec = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe" - if not os.path.isfile(innoExec): - print("ERROR: Cannot fine Inno Setup's ISCC.exe file.") - print(" Looked in: %s" % innoExec) - print(" Please provide a path with the --inno= option.") - sys.exit(1) # Read the iss template issData = "" @@ -192,9 +185,9 @@ def innoSetup(innoExec): outFile.write(issData) try: - subprocess.call([innoExec, "setup.iss"]) + subprocess.call(["iscc", "setup.iss"]) except Exception as e: - print("Failed with error:") + print("Inno Setup failed with error:") print(str(e)) sys.exit(1) @@ -261,7 +254,6 @@ if __name__ == "__main__": buildWindowed = True oneFile = False makeSetup = False - innoExec = None if "help" in sys.argv: print( @@ -271,10 +263,10 @@ if __name__ == "__main__": "This tool provides build commands for distibuting novelWriter as\n" "a package. The available options are as follows:\n" "\n" - "pip Run pip to install all package dependencies for\n" - " novelWriter and this build tool.\n" "onefile Build a standalone executable with all dependencies\n" " bundled. This does not produce a setup.exe on Windows.\n" + "pip Run pip to install all package dependencies for\n" + " novelWriter and this build tool.\n" "setup Build a setup.exe installer for Windows. This option\n" " automaticall disables the 'onefile' option.\n" "clean This will attempt to delete the 'build' and 'dist'\n" @@ -315,6 +307,6 @@ if __name__ == "__main__": freezePackage(buildWindowed, oneFile, makeSetup, hostOS) if makeSetup: - innoSetup(innoExec) + innoSetup() # END Main diff --git a/setup/win_setup.iss b/setup/win_setup.iss index 6491b317..83f016b6 100644 --- a/setup/win_setup.iss +++ b/setup/win_setup.iss @@ -27,7 +27,7 @@ UsedUserAreasWarning=no ;PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog OutputDir={#nwAppDir} -OutputBaseFilename=setup-novelwriter-{#nwAppVersion} +OutputBaseFilename=novelwriter_{#nwAppVersion}_win10_full_setup Compression=lzma SolidCompression=yes WizardStyle=modern From 614572ca80e70a6602979c646465b91b64b7a2ea Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 15:05:58 +0200 Subject: [PATCH 11/14] Minor modifications to make.py, and made Inno Setyp 64 bit --- make.py | 47 ++++++++++++++++++++++++++++----------------- setup/win_setup.iss | 3 ++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/make.py b/make.py index 6162a313..7a0923b9 100755 --- a/make.py +++ b/make.py @@ -254,24 +254,29 @@ if __name__ == "__main__": buildWindowed = True oneFile = False makeSetup = False + doFreeze = False - if "help" in sys.argv: - print( - "\n" - "novelWriter Make Tool\n" - "=====================\n" - "This tool provides build commands for distibuting novelWriter as\n" - "a package. The available options are as follows:\n" - "\n" - "onefile Build a standalone executable with all dependencies\n" - " bundled. This does not produce a setup.exe on Windows.\n" - "pip Run pip to install all package dependencies for\n" - " novelWriter and this build tool.\n" - "setup Build a setup.exe installer for Windows. This option\n" - " automaticall disables the 'onefile' option.\n" - "clean This will attempt to delete the 'build' and 'dist'\n" - " folders in the current folder.\n" - ) + helpMsg = ( + "\n" + "novelWriter Make Tool\n" + "=====================\n" + "This tool provides build commands for distibuting novelWriter as a\n" + "package. The available options are as follows:\n" + "\n" + "freeze Freeze the package and produces a folder of all\n" + " dependecies using pyinstaller.\n" + "onefile Build a standalone executable with all dependencies\n" + " bundled. Implies 'freeze', cannot be used with 'setup'.\n" + "pip Run pip to install all package dependencies for\n" + " novelWriter and this build tool.\n" + "setup Build a setup.exe installer for Windows. This option\n" + " automaticall disables the 'onefile' option.\n" + "clean This will attempt to delete the 'build' and 'dist'\n" + " folders in the current folder.\n" + ) + + if "help" in sys.argv or len(sys.argv) <= 1: + print(helpMsg) sys.exit(0) if not os.path.isfile(os.path.join(os.getcwd(), "novelWriter.py")): @@ -291,8 +296,13 @@ if __name__ == "__main__": sys.argv.remove("pip") installPackages() + if "freeze" in sys.argv: + sys.argv.remove("freeze") + doFreeze = True + if "onefile" in sys.argv: sys.argv.remove("onefile") + doFreeze = True oneFile = True if "setup" in sys.argv: @@ -304,7 +314,8 @@ if __name__ == "__main__": print("Error: Argument 'setup' for Inno Setup is Windows only.") sys.exit(1) - freezePackage(buildWindowed, oneFile, makeSetup, hostOS) + if doFreeze: + freezePackage(buildWindowed, oneFile, makeSetup, hostOS) if makeSetup: innoSetup() diff --git a/setup/win_setup.iss b/setup/win_setup.iss index 83f016b6..8b6be609 100644 --- a/setup/win_setup.iss +++ b/setup/win_setup.iss @@ -27,10 +27,11 @@ UsedUserAreasWarning=no ;PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog OutputDir={#nwAppDir} -OutputBaseFilename=novelwriter_{#nwAppVersion}_win10_full_setup +OutputBaseFilename=novelwriter_{#nwAppVersion}_win_amd64_setup Compression=lzma SolidCompression=yes WizardStyle=modern +ArchitecturesInstallIn64BitMode=x64 [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" From 3378b387cfdc5d7f2feec98ae881c86abdb2f004 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 15:52:40 +0200 Subject: [PATCH 12/14] Reduce size of freeze build and added more documentation --- README.md | 2 ++ make.py | 50 +++++++++++++++++++++++++++++--------------------- setup/BUILD.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 setup/BUILD.md diff --git a/README.md b/README.md index 3b7835ed..be86e0f4 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ for debugging. You can also provide a path to a folder containing a novelWriter project as the last parameter. +For more install options, see [Build and Install novelWriter](setup/BUILD.md). + ### Launcher and Icons diff --git a/make.py b/make.py index 7a0923b9..15ed18ae 100755 --- a/make.py +++ b/make.py @@ -28,28 +28,29 @@ OS_DARWIN = 3 # Package Installer # =============================================================================================== # -def installPackages(): +def installPackages(hostOS): + """Install package dependencies both for this script and for running + novelWriter itself. + """ 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) + + installQueue = ["pip", "pyinstaller", "-r requirements.txt"] + if hostOS == OS_DARWIN: + installQueue.append("pyobjc") + + pyCmd = [sys.executable, "-m"] + pipCmd = ["pip", "install", "--user", "--upgrade"] + for stepCmd in installQueue: + pkgCmd = stepCmd.split(" ") + try: + subprocess.call(pyCmd + pipCmd + pkgCmd) + except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) return @@ -73,6 +74,7 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS): else: dotDot = ":" + sys.modules["FixTk"] = None instOpt = [ "--name=novelWriter", "--clean", @@ -92,6 +94,12 @@ def freezePackage(buildWindowed, oneFile, makeSetup, hostOS): "--exclude-module=PyQt5.QtSensors", "--exclude-module=PyQt5.QtSerialPort", "--exclude-module=PyQt5.QtSql", + "--exclude-module=FixTk", + "--exclude-module=tcl", + "--exclude-module=tk", + "--exclude-module=_tkinter", + "--exclude-module=tkinter", + "--exclude-module=Tkinter", ] if buildWindowed: @@ -263,8 +271,9 @@ if __name__ == "__main__": "This tool provides build commands for distibuting novelWriter as a\n" "package. The available options are as follows:\n" "\n" + "help Print the help message.\n" "freeze Freeze the package and produces a folder of all\n" - " dependecies using pyinstaller.\n" + " dependencies using pyinstaller.\n" "onefile Build a standalone executable with all dependencies\n" " bundled. Implies 'freeze', cannot be used with 'setup'.\n" "pip Run pip to install all package dependencies for\n" @@ -290,11 +299,10 @@ if __name__ == "__main__": if "clean" in sys.argv: sys.argv.remove("clean") cleanInstall() - sys.exit(0) if "pip" in sys.argv: sys.argv.remove("pip") - installPackages() + installPackages(hostOS) if "freeze" in sys.argv: sys.argv.remove("freeze") diff --git a/setup/BUILD.md b/setup/BUILD.md new file mode 100644 index 00000000..d97e702a --- /dev/null +++ b/setup/BUILD.md @@ -0,0 +1,44 @@ +# Build and Install novelWriter + +The root folder of the repository contains two scripts for setup and install: + +## Script `setup.py` + +The `setup.py` is a standard Python setup script with a couple of additional options: + +* `qthelp`: Will attempt to build a single file QtAssistand documentation file. + This requires the Qt tools to be installed on the local system, as well as the sphinx build tools + for the documentation. +* `sample`: Will create a `sample.zip` file in the `nw/assets` folder. + This is the file the New Project Wizard uses to generate an example project. + If novelWriter is run from source, this file is not needed. + +To install novelWriter as a local Python package, run: +```bash +sudo python setup.py install +``` + +## Script `make.py` + +The `make.py` script provides a number of convenient options for building packages if novelWriter. + +Usage: +```bash +python make.py [command] +``` + +It currently accept the following commands: + +* `help`: Print the help message. +* `freeze`: Freeze the package and produces a folder of all dependencies using pyinstaller. +* `onefile`: Build a standalone executable with all dependencies bundled. + Implies `freeze`, cannot be used with `setup`. +* `pip`: Run pip to install all package dependencies for novelWriter and this build tool. +* `setup`: Build a setup.exe installer for Windows. + This option automaticall disables the `onefile` option. +* `clean`: This will attempt to delete the `build` and `dist` folders in the current folder. + +For instance, to create a Windows installer, run: +```bash +python make.py freeze setup +``` From 139293b21cfb340c9a70cca279e80021fc35765f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 16:58:47 +0200 Subject: [PATCH 13/14] Added a launcher option to setup.py, and updated documentation --- README.md | 47 +++++---- setup.py | 181 +++++++++++++++++++++++++++++++++-- setup/BUILD.md | 7 +- setup/installDebianUbuntu.sh | 42 -------- 4 files changed, 209 insertions(+), 68 deletions(-) delete mode 100755 setup/installDebianUbuntu.sh diff --git a/README.md b/README.md index be86e0f4..f8d19eb6 100644 --- a/README.md +++ b/README.md @@ -65,30 +65,42 @@ You can update novelWriter to the latest version by running: pip install --upgrade novelwriter ``` -The application can then be started with one of the commands, depending on your Python configuration: -```bash -./novelWriter.py -python novelWriter.py -python3 novelWriter.py -``` - It also takes a few parameters for debugging and such, which can be listed with the switch `--help`. The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output for debugging. You can also provide a path to a folder containing a novelWriter project as the last parameter. +## Installing from Source (Linux) + +You can then install novelWriter from the source directly with: +```bash +python3 setup.py sample +sudo python3 setup.py install +sudo python3 setup.py launcher +``` + +The last line will install the application icons and set up a launcher for novelWriter. +The method uses hardcoded paths, so it may or may not work for your Linux distro. + +It may prompt you to choose which executable to configure. +You can also use this to configure it to run from source. + +## Running from Source (Linux) + +If you want to run directly from the source, the application can be started with: +```bash +./novelWriter.py +``` + +You can also create a launcher for the source with: +```bash +sudo python3 setup.py launcher +``` + For more install options, see [Build and Install novelWriter](setup/BUILD.md). -### Launcher and Icons - -In the root setup folder there are icons and scripts and a template for setting up a launcher on -Gnome desktops. You may need to modify those scripts slightly, but as they are, they work on Debian -and Ubuntu. For other operating systems, please consult your operating system documentation for how -to make those. Feel free to submit more if you are able to make them. - - ## Package Dependencies It is recommended that novelWriter runs with Qt 5.10 or later, and requires Python 3.6 or later. @@ -99,7 +111,7 @@ Minimum version of Qt is 5.2. Generally, dependencies can be installed via `pip` with: ```bash -pip3 install -r requirements.txt +pip install -r requirements.txt ``` You can also install the packages from the distro's own package manager. @@ -144,6 +156,9 @@ It should look something like this: C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py ``` +You can also run the `make.py` script to generate an installer. +See [Build and Install novelWriter](setup/BUILD.md) for more details. + ### Package Versions diff --git a/setup.py b/setup.py index 403a71f2..4683e3f2 100755 --- a/setup.py +++ b/setup.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import os import sys +import shutil import subprocess import setuptools -# =========================================================================== # +# =============================================================================================== # # Qt Assistant Documentation Builder -# =========================================================================== # +# =============================================================================================== # def buildQtDocs(): """This function will build the documentation as a Qt help file. The @@ -77,9 +78,9 @@ def buildQtDocs(): return -# =========================================================================== # +# =============================================================================================== # # Sample Project ZIP File Builder -# =========================================================================== # +# =============================================================================================== # def buildSampleZip(): """Bundle the sample project into a single zip file to be saved into @@ -117,13 +118,173 @@ def buildSampleZip(): return -# =========================================================================== # +# =============================================================================================== # +# Create Launcher +# =============================================================================================== # + +def makeLauncherLinux(): + """Will attempt to install icons and make a launcher. + """ + print("") + print("Creating Launcher") + print("=================") + print("") + + exOpts = [] + + testExec = shutil.which("novelWriter") + if testExec is not None: + exOpts.append(testExec) + + testExec = shutil.which("novelwriter") + if testExec is not None: + exOpts.append(testExec) + + testExec = os.path.join(os.getcwd(), "novelWriter.py") + if os.path.isfile(testExec): + exOpts.append(testExec) + + useExec = "" + nOpts = len(exOpts) + if nOpts == 0: + print("Error: No executables for novelWriter found.") + sys.exit(1) + elif nOpts == 1: + useExec = exOpts[0] + else: + print("Found multiple novelWriter executables:") + print("") + for iExec, anExec in enumerate(exOpts): + print(" [%d] %s" % (iExec, anExec)) + print("") + intVal = int(input("Please select which novelWriter executable to use: ")) + print("") + + if intVal >= 0 and intVal < nOpts: + useExec = exOpts[intVal] + else: + print("Error: Invalid selection.") + sys.exit(1) + + print("Using executable: %s " % useExec) + + # Read the Template + desktopData = "" + with open(os.path.join("setup", "novelwriter.desktop"), mode="r") as inFile: + desktopData = inFile.read() + + desktopData = desktopData.replace(r"%%exec%%", useExec) + + desktopFile = "/usr/share/applications/novelwriter.desktop" + try: + with open(desktopFile, mode="w+") as outFile: + outFile.write(desktopData) + print("Wrote file: %s" % desktopFile) + except Exception as e: + print("Error: Could not write novelwriter.desktop file.") + print(str(e)) + sys.exit(1) + + print("") + + # Copy Icons + + iconDirs = [ + "/usr/share/icons/hicolor/24x24/apps", + "/usr/share/icons/hicolor/48x48/apps", + "/usr/share/icons/hicolor/96x96/apps", + "/usr/share/icons/hicolor/256x256/apps", + "/usr/share/icons/hicolor/512x512/apps", + "/usr/share/icons/hicolor/scalable/apps", + "/usr/share/icons/hicolor/scalable/mimetypes", + ] + for iconDir in iconDirs: + if not os.path.isdir: + try: + os.mkdir(iconDir) + print("Created folder: %s" % iconDir) + except Exception as e: + print("Error: Could not make folder: %s" % iconDir) + print(str(e)) + + copyList = [( + "setup/icons/24x24/novelwriter.png", + "/usr/share/icons/hicolor/24x24/apps/novelwriter.png" + ), ( + "setup/icons/48x48/novelwriter.png", + "/usr/share/icons/hicolor/48x48/apps/novelwriter.png" + ), ( + "setup/icons/96x96/novelwriter.png", + "/usr/share/icons/hicolor/96x96/apps/novelwriter.png" + ), ( + "setup/icons/256x256/novelwriter.png", + "/usr/share/icons/hicolor/256x256/apps/novelwriter.png" + ), ( + "setup/icons/512x512/novelwriter.png", + "/usr/share/icons/hicolor/512x512/apps/novelwriter.png" + ), ( + "setup/icons/novelwriter.svg", + "/usr/share/icons/hicolor/scalable/apps/novelwriter.svg" + ), ( + "setup/icons/x-novelwriter-project.svg", + "/usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg" + ), ( + "setup/mime/x-novelwriter-project.xml", + "/usr/share/mime/packages/x-novelwriter-project.xml" + )] + for srcFile, dstFile in copyList: + try: + shutil.copyfile(srcFile, dstFile) + print("Copied file to: %s" % dstFile) + except Exception as e: + print("Error: Could not copy file: %s" % srcFile) + print(str(e)) + + print("") + + # Update System + try: + subprocess.call(["update-mime-database", "/usr/share/mime/"]) + print("Updated mime database.") + except Exception as e: + print("Error: Filed to update mime database.") + print(str(e)) + + try: + subprocess.call(["update-icon-caches", "/usr/share/icons/*"]) + print("Updated icon cache.") + except Exception as e: + print("Error: Filed to update icon cache.") + print(str(e)) + + print("") + print("Done!") + print("") + + return + +# =============================================================================================== # # Process Jobs -# =========================================================================== # +# =============================================================================================== # if __name__ == "__main__": - # Process non-standard jobs + helpMsg = ( + "\n" + "novelWriter Setup Tool\n" + "======================\n" + "This tool provides some additional setup commands for novelWriter.\n" + "\n" + "help Print the help message.\n" + "gthelp Build the help documentation for use with the QtAssistant.\n" + "sample Build the sample project as a zip file.\n" + "launcher Install launcher icons for freedesktop systems.\n" + ) + + if "help" in sys.argv: + sys.argv.remove("help") + print(helpMsg) + sys.exit(0) if "qthelp" in sys.argv: sys.argv.remove("qthelp") @@ -133,7 +294,11 @@ if __name__ == "__main__": sys.argv.remove("sample") buildSampleZip() - if len(sys.argv) == 1: + if "launcher" in sys.argv: + sys.argv.remove("launcher") + makeLauncherLinux() + + if len(sys.argv) <= 1: # Nothing more to do sys.exit(0) diff --git a/setup/BUILD.md b/setup/BUILD.md index d97e702a..81479cf7 100644 --- a/setup/BUILD.md +++ b/setup/BUILD.md @@ -2,6 +2,7 @@ The root folder of the repository contains two scripts for setup and install: + ## Script `setup.py` The `setup.py` is a standard Python setup script with a couple of additional options: @@ -10,8 +11,10 @@ The `setup.py` is a standard Python setup script with a couple of additional opt This requires the Qt tools to be installed on the local system, as well as the sphinx build tools for the documentation. * `sample`: Will create a `sample.zip` file in the `nw/assets` folder. - This is the file the New Project Wizard uses to generate an example project. - If novelWriter is run from source, this file is not needed. + This is the file the New Project Wizard uses to generate an example project. + If novelWriter is run from source, this file is not needed. +* `launcher`: Will try to copy the novelWriter icons and create a novelWriter.desktop file to launch + the application. This should work on standard Linux desktops. To install novelWriter as a local Python package, run: ```bash diff --git a/setup/installDebianUbuntu.sh b/setup/installDebianUbuntu.sh deleted file mode 100755 index c47f4d26..00000000 --- a/setup/installDebianUbuntu.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -cd .. - -EXEC=$(pwd)/novelWriter.py -EXEC=$(echo $EXEC | sed 's_/_\\/_g') - -sed "s/%%exec%%/$EXEC/g" setup/novelwriter.desktop > /usr/share/applications/novelwriter.desktop - -if [ ! -d /usr/share/icons/hicolor/24x24/apps ]; then - mkdir -pv /usr/share/icons/hicolor/24x24/apps -fi -if [ ! -d /usr/share/icons/hicolor/48x48/apps ]; then - mkdir -pv /usr/share/icons/hicolor/48x48/apps -fi -if [ ! -d /usr/share/icons/hicolor/96x96/apps ]; then - mkdir -pv /usr/share/icons/hicolor/96x96/apps -fi -if [ ! -d /usr/share/icons/hicolor/256x256/apps ]; then - mkdir -pv /usr/share/icons/hicolor/256x256/apps -fi -if [ ! -d /usr/share/icons/hicolor/512x512/apps ]; then - mkdir -pv /usr/share/icons/hicolor/512x512/apps -fi -if [ ! -d /usr/share/icons/hicolor/scalable/apps ]; then - mkdir -pv /usr/share/icons/hicolor/scalable/apps -fi -if [ ! -d /usr/share/icons/hicolor/scalable/mimetypes ]; then - mkdir -pv /usr/share/icons/hicolor/scalable/mimetypes -fi - -cp -v setup/icons/24x24/novelwriter.png /usr/share/icons/hicolor/24x24/apps/ -cp -v setup/icons/48x48/novelwriter.png /usr/share/icons/hicolor/48x48/apps/ -cp -v setup/icons/96x96/novelwriter.png /usr/share/icons/hicolor/96x96/apps/ -cp -v setup/icons/256x256/novelwriter.png /usr/share/icons/hicolor/256x256/apps/ -cp -v setup/icons/512x512/novelwriter.png /usr/share/icons/hicolor/512x512/apps/ -cp -v setup/icons/novelwriter.svg /usr/share/icons/hicolor/scalable/apps/ -cp -v setup/icons/x-novelwriter-project.svg /usr/share/icons/hicolor/scalable/mimetypes/application-x-novelwriter-project.svg -cp -v setup/mime/x-novelwriter-project.xml /usr/share/mime/packages/ - -update-mime-database /usr/share/mime/ -update-icon-caches /usr/share/icons/* From 2ffca7db48c8f115dac9de07d96c691528d7c0b1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 17 Oct 2020 17:26:04 +0200 Subject: [PATCH 14/14] Updated and cleaned up the readme --- README.md | 84 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f8d19eb6..ce5cfdf0 100644 --- a/README.md +++ b/README.md @@ -52,28 +52,37 @@ in principle work fine on other operating systems as well as long as dependencie tests are run on the latest versions of Ubuntu Linux, Windows Server and macOS. -## Installing and Running +# Installing and Running -You can runt novelWriter either from a downloaded copy of the source code, or by running: +novelWriter is available on [pypi.org](https://pypi.org/project/novelWriter/), and can be installed with: ```bash pip install novelwriter ``` -**Note:** On some systems you must use `pip3` instead for the Python 3 version. -You can update novelWriter to the latest version by running: +To upgrade an existing installation, use: ```bash pip install --upgrade novelwriter ``` -It also takes a few parameters for debugging and such, which can be listed with the switch `--help`. -The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output -for debugging. +Dependencies are installed automatically, but can generally be installed with: +```bash +pip install -r requirements.txt +``` -You can also provide a path to a folder containing a novelWriter project as the last parameter. +Below are some brief instructions on how to get started on different operating systems. -## Installing from Source (Linux) -You can then install novelWriter from the source directly with: +## Linux + +Either download the source, or install with pip. + +If you run from source, install the dependencies via pip, or directly from the OS repo. +There are very few dependencies, and they should be available in the standard repo. +The Python packages needed are `pyqt5`, `lxml` and `pyenchant`. + +### Installing from Source + +You can also install novelWriter from source with: ```bash python3 setup.py sample sudo python3 setup.py install @@ -82,18 +91,18 @@ sudo python3 setup.py launcher The last line will install the application icons and set up a launcher for novelWriter. The method uses hardcoded paths, so it may or may not work for your Linux distro. +If you have any issues, please submit a ticket so the script can be tuned. -It may prompt you to choose which executable to configure. -You can also use this to configure it to run from source. +The script may prompt you to choose which executable to configure if it finds more than one. -## Running from Source (Linux) +### Running from Source If you want to run directly from the source, the application can be started with: ```bash ./novelWriter.py ``` -You can also create a launcher for the source with: +You can also create a launcher for running directly from source with: ```bash sudo python3 setup.py launcher ``` @@ -101,28 +110,7 @@ sudo python3 setup.py launcher For more install options, see [Build and Install novelWriter](setup/BUILD.md). -## Package Dependencies - -It is recommended that novelWriter runs with Qt 5.10 or later, and requires Python 3.6 or later. -Minimum version of Qt is 5.2. - - -### Linux - -Generally, dependencies can be installed via `pip` with: -```bash -pip install -r requirements.txt -``` - -You can also install the packages from the distro's own package manager. -For the apt package manager on Debian/Ubuntu systems, the following Python3 packages are needed: - -* `python3-pyqt5` for the GUI -* `python3-lxml` for writing project files -* `python3-enchant` for better spell checking (optional) - - -### macOS +## macOS These instructions assume you're using brew, and have Python and pip set up. If not, see the [brew docs](https://docs.brew.sh/Homebrew-and-Python) for help. @@ -140,11 +128,16 @@ It comes with a lot of default dictionaries. brew install enchant ``` - ### Windows -On Windows, the `pip install` command is generally sufficient to install everything you need. -That should also install the Qt libraries and the spell check dictionary dependencies. +On Windows, you may first need to install Python. +See the [python.org](https://www.python.org/) website for download packages. +It is recommended that you install the latest version of Python 3.8. + +To install dependencies, run: +```bash +pip install --user -r requirements.txt +``` **Note:** On Windows, make sure Python3 is in your PATH if you want to launch novelWriter from command line. You can also right click the `novelWriter.py` file, create a shortcut, then right @@ -156,11 +149,11 @@ It should look something like this: C:\...\AppData\Local\Programs\Python\Python38\python.exe novelWriter.py ``` -You can also run the `make.py` script to generate an installer. +You can also run the `make.py` script to generate a single executable, or an installer. See [Build and Install novelWriter](setup/BUILD.md) for more details. -### Package Versions +## Package Versions Exporting to Markdown requires PyQt/Qt 5.14. There are no known minimum for `lxml`, but the code was originally written with 4.2. The optional spell check library must be at least 3.0.0 to work @@ -172,8 +165,15 @@ checker, but more can be added to the `nw/assets/dict` folder. See the [README]( file in that folder for how to generate more dictionaries. Note that the difflib-based option is both slow and limited. +## Debugging -## Key Features +If you need to debug novelWriter, you must run it from command line. +It takes a few parameters, which can be listed with the switch `--help`. +The `--info`, `--debug` or `--verbose` flags are particularly useful for increasing logging output +for debugging. + + +# Key Features Some features of novelWriter are listed below. Consult the documentation for more information.