diff --git a/docs/source/setup_windows.rst b/docs/source/setup_windows.rst index a6130854..b59b9670 100644 --- a/docs/source/setup_windows.rst +++ b/docs/source/setup_windows.rst @@ -75,10 +75,13 @@ Step 2: Dependencies and Icons **Alternative A: By Script** -Open the folder where you extracted the novelWriter source, and double-click the file named +Open the folder where you extracted novelWriter, and double-click the file named ``setup_windows.bat``. This should open a command line window and run the setup script to install dependencies, and add desktop and start menu icons. +.. note:: + If you downloaded the full source package, the file may be in the ``setup`` subfolder. + The script will also check that it can find Python on your system and alert you if it cannot run it. If you are sure you have installed it, but the script cannot find it, you probably didn't install it with the "Add Python to PATH" option mentioned in Step 1. @@ -100,7 +103,7 @@ and run the following commands: .. code-block:: console - python setup.py pip + pip install --user pywin32 -r requirements.txt python setup.py win-install The first command will install the dependencies on your system from the `Python Package Index`_, @@ -108,3 +111,39 @@ and the second command will create a desktop icon and a start menu icon. That sh you need. .. _Python Package Index: https://pypi.org/ + + +Uninstalling +============ + +**Alternative A: By Script** + +Open the folder where you keep the novelWriter files, and double-click the file named +``uninstall_windows.bat``. This should open a command line window and run the setup script to +remove the main dependency packages and remove desktop and start menu icons. + +.. note:: + If you downloaded the full source package, the file may be in the ``setup`` subfolder. + +If you plan to also remove Python from your system, you must run the above script first as it needs +Python in order to run. + +.. note:: + Due to limitations of the ``pip`` installer, dependencies of the dependencies will not be + removed, only the ones the setup script directly installed. + +**Alternative B: Manual Uninstallation** + +Like for the install process, the script just runs two commands. You can of course run them +yourself if you wish. They are: + +.. code-block:: console + + python setup.py win-uninstall + pip uninstall -r requirements.txt + +There may be other packages on your system installed by ``pip``. To list all packages, run: + +.. code-block:: console + + pip freeze --user diff --git a/setup.py b/setup.py index 022a1247..194a8e23 100755 --- a/setup.py +++ b/setup.py @@ -35,6 +35,30 @@ OS_LINUX = 1 OS_WIN = 2 OS_DARWIN = 3 +# =============================================================================================== # +# Utilities +# =============================================================================================== # + +def extractVersion(): + """Extract the novelWriter version number without having to import + anything else from the main package. + """ + def getValue(theString): + theBits = theString.partition("=") + return theBits[2].strip().strip('"') + + numVers = "Unknown" + hexVers = "Unknown" + initFile = os.path.join("nw", "__init__.py") + with open(initFile, mode="r") as inFile: + for aLine in inFile: + if aLine.startswith("__version__"): + numVers = getValue((aLine)) + if aLine.startswith("__hexversion__"): + hexVers = getValue((aLine)) + + return numVers, hexVers + # =============================================================================================== # # General # =============================================================================================== # @@ -237,9 +261,11 @@ def buildSampleZip(): def makeMinimalPackage(targetOS): """Pack the core source file in a single zip file. """ - from nw import __version__ from zipfile import ZipFile, ZIP_DEFLATED + # Get the version + numVers, _ = extractVersion() + # Make sample.zip first try: buildSampleZip() @@ -251,21 +277,24 @@ def makeMinimalPackage(targetOS): print("") print("Building Minimal ZIP File") print("=========================") - print("") if not os.path.isdir("dist"): os.mkdir("dist") if targetOS == OS_LINUX: targName = "-linux" + print("Target OS: Linux") elif targetOS == OS_DARWIN: targName = "-darwin" + print("Target OS: Darwin") elif targetOS == OS_WIN: targName = "-win" + print("Target OS: Windows") else: targName = "" + print("") - zipFile = f"novelWriter-{__version__}-minimal{targName}.zip" + zipFile = f"novelWriter-{numVers}-minimal{targName}.zip" outFile = os.path.join("dist", zipFile) if os.path.isfile(outFile): os.unlink(outFile) @@ -295,8 +324,10 @@ def makeMinimalPackage(targetOS): if targetOS == OS_WIN: zipObj.write("novelWriter.py", "novelWriter.pyw") print("Adding File: novelWriter.pyw") - zipObj.write("setup_windows.bat") + zipObj.write(os.path.join("setup", "setup_windows.bat"), "setup_windows.bat") print("Adding File: setup_windows.bat") + zipObj.write(os.path.join("setup", "uninstall_windows.bat"), "uninstall_windows.bat") + print("Adding File: uninstall_windows.bat") else: zipObj.write("novelWriter.py") print("Adding File: novelWriter.py") @@ -704,14 +735,13 @@ def xdgUninstall(): return ## -# WIN Installation (win-install, launcher) +# WIN Installation (win-install) ## def winInstall(): """Will attempt to install icons and make a launcher for Windows. """ import winreg - from nw import __version__, __hexversion__ try: import win32com.client except ImportError: @@ -727,13 +757,14 @@ def winInstall(): print("===============") print("") - nwTesting = not __hexversion__[-2] == "f" + numVers, hexVers = extractVersion() + nwTesting = not hexVers[-2] == "f" wShell = win32com.client.Dispatch("WScript.Shell") if nwTesting: - linkName = "novelWriter Testing %s.lnk" % __version__ + linkName = "novelWriter Testing %s.lnk" % numVers else: - linkName = "novelWriter %s.lnk" % __version__ + linkName = "novelWriter %s.lnk" % numVers desktopDir = wShell.SpecialFolders("Desktop") desktopIcon = os.path.join(desktopDir, linkName) @@ -827,6 +858,92 @@ def winInstall(): return +## +# WIN Uninstallation (win-uninstall) +## + +def winUninstall(): + """Will attempt to uninstall icons previously installed. + """ + import winreg + try: + import win32com.client + except ImportError: + print( + "ERROR: Package 'pywin32' is missing on this system.\n" + " Please run 'pip install --user pywin32' to install it." + ) + sys.exit(1) + + print("") + print("Windows Uninstall") + print("=================") + print("") + + numVers, hexVers = extractVersion() + nwTesting = not hexVers[-2] == "f" + wShell = win32com.client.Dispatch("WScript.Shell") + + if nwTesting: + linkName = "novelWriter Testing %s.lnk" % numVers + else: + linkName = "novelWriter %s.lnk" % numVers + + desktopDir = wShell.SpecialFolders("Desktop") + desktopIcon = os.path.join(desktopDir, linkName) + + startMenuDir = wShell.SpecialFolders("StartMenu") + startMenuProg = os.path.join(startMenuDir, "Programs", "novelWriter") + startMenuIcon = os.path.join(startMenuProg, linkName) + + print("Deleting Links ...") + if os.path.isfile(desktopIcon): + os.unlink(desktopIcon) + print("Deleted: %s" % desktopIcon) + else: + print("Not Found: %s" % desktopIcon) + + if os.path.isfile(startMenuIcon): + os.unlink(startMenuIcon) + print("Deleted: %s" % startMenuIcon) + else: + print("Not Found: %s" % startMenuIcon) + + if os.path.isdir(startMenuProg): + if not os.listdir(startMenuProg): + os.rmdir(startMenuProg) + print("Deleted: %s" % startMenuProg) + else: + print("Not Empty: %s" % startMenuProg) + + print("") + print("Removing registry keys ...") + + theKeys = [ + r"Software\Classes\novelWriterProject.nwx\shell\open\command", + r"Software\Classes\novelWriterProject.nwx\shell\open", + r"Software\Classes\novelWriterProject.nwx\shell", + r"Software\Classes\novelWriterProject.nwx\DefaultIcon", + r"Software\Classes\novelWriterProject.nwx", + r"Software\Classes\.nwx\OpenWithProgids", + r"Software\Classes\.nwx", + r"Software\Classes\Applications\novelWriter.pyw\SupportedTypes", + r"Software\Classes\Applications\novelWriter.pyw", + ] + + for aKey in theKeys: + try: + winreg.DeleteKey(winreg.HKEY_CURRENT_USER, aKey) + print("Deleted: HKEY_CURRENT_USER\\%s" % aKey) + except WindowsError: + print("Not Found: HKEY_CURRENT_USER\\%s" % aKey) + + print("") + print("Done!") + print("") + + return + # =============================================================================================== # # Windows Installers # =============================================================================================== # @@ -848,8 +965,8 @@ def innoSetup(): with open(os.path.join("setup", "win_setup_pyz.iss"), mode="r") as inFile: issData = inFile.read() - import nw # noqa: E402 - issData = issData.replace(r"%%version%%", nw.__version__) + numVers, _ = extractVersion() + issData = issData.replace(r"%%version%%", numVers) issData = issData.replace(r"%%dir%%", os.getcwd()) with open("setup.iss", mode="w+") as outFile: @@ -937,6 +1054,7 @@ if __name__ == "__main__": " with sudo for system-wide install, or as user for single user install.\n" " Running 'xdg-uninstall' will remove the icons.\n" " win-install Install desktop and start menu icons for Windows systems.\n" + " Running 'win-uninstall' will remove the icons.\n" ) # Flags and Variables @@ -952,6 +1070,13 @@ if __name__ == "__main__": print(helpMsg) sys.exit(0) + if "version" in sys.argv: + sys.argv.remove("version") + numVers, hexVers = extractVersion() + print("Semantic Version: %s" % numVers) + print("Hexadecimal Version: %s" % hexVers) + sys.exit(0) + if "pip" in sys.argv: sys.argv.remove("pip") installPackages(hostOS) @@ -1011,6 +1136,14 @@ if __name__ == "__main__": print("ERROR: Command 'win-install' can only be used on Windows") sys.exit(1) + if "win-uninstall" in sys.argv: + sys.argv.remove("win-uninstall") + if hostOS == OS_WIN: + winUninstall() + else: + print("ERROR: Command 'win-uninstall' can only be used on Windows") + sys.exit(1) + # Windows Setup Installer # ======================= diff --git a/setup/README.md b/setup/README.md index a2c3f163..4d819e5c 100644 --- a/setup/README.md +++ b/setup/README.md @@ -49,3 +49,4 @@ or with sudo for system-wide install, or as user for single user install. Running `xdg-uninstall` will remove the icons. `win-install` – Install desktop and start menu icons for Windows systems. +Running `win-uninstall` will remove the icons. diff --git a/setup_windows.bat b/setup/setup_windows.bat similarity index 57% rename from setup_windows.bat rename to setup/setup_windows.bat index 12a8cc3f..f6b6d5ab 100644 --- a/setup_windows.bat +++ b/setup/setup_windows.bat @@ -6,10 +6,21 @@ echo Looking for Python python --version 2>NUL if errorlevel 1 goto errorNoPython echo Python found. OK. + +if exist setup.py ( + echo setup.py found. OK. +) else ( + cd .. + if exist setup.py ( + echo setup.py found. OK. + ) else ( + goto errorNoSetup + ) +) echo. :: Install the PyQt5, lxml and pyenchant dependencies -python setup.py pip +pip install --user pywin32 -r requirements.txt :: Create the desktop and start menu icons python setup.py win-install @@ -24,5 +35,13 @@ echo. echo Please download and install it from https://www.python.org/downloads/ echo Also make sure the "Add Python to PATH" option is selected during installation. echo. +goto:eof + +:errorNoSetup +echo. +echo ERROR^: Could not find the setup.py script. +echo. +echo Make sure you run setup.py from the novelWriter root folder. +echo. pause diff --git a/setup/uninstall_windows.bat b/setup/uninstall_windows.bat new file mode 100644 index 00000000..cc3a4d85 --- /dev/null +++ b/setup/uninstall_windows.bat @@ -0,0 +1,57 @@ +@echo off + +:: Check for Python Installation +echo. +echo Looking for Python +python --version 2>NUL +if errorlevel 1 goto errorNoPython +echo Python found. OK. + +if exist setup.py ( + echo setup.py found. OK. +) else ( + cd .. + if exist setup.py ( + echo setup.py found. OK. + ) else ( + goto errorNoSetup + ) +) +echo. + +echo Ready to remove the novelWriter icons, registry keys, and package dependencies. +set /P c=Are you sure you want to continue [y/n]? +if /I "%c%" EQU "y" goto doUninst +goto afterUninst + +:doUninst + +:: Remove the desktop and start menu icons +python setup.py win-uninstall + +:: Remove the PyQt5, lxml and pyenchant dependencies +pip uninstall --yes -r requirements.txt + +:afterUninst + +echo. +pause +goto:eof + +:errorNoPython +echo. +echo ERROR^: Python is not installed on your system, or cannot be found. +echo. +echo Please download and install it from https://www.python.org/downloads/ +echo Also make sure the "Add Python to PATH" option is selected during installation. +echo. +goto:eof + +:errorNoSetup +echo. +echo ERROR^: Could not find the setup.py script. +echo. +echo Make sure you run setup.py from the novelWriter root folder. +echo. + +pause