diff --git a/.gitignore b/.gitignore index 6df5c0a..70dc25b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc __pycache__ +.idea/ build/ MANIFEST dist/ diff --git a/examples/run_auto/guessnumber.py b/examples/run_auto/guessnumber.py new file mode 100644 index 0000000..86f66b2 --- /dev/null +++ b/examples/run_auto/guessnumber.py @@ -0,0 +1,28 @@ +"""A fun number guessing game!""" + +import random + +def main(): + number = random.randint(1, 100) + guesses = 0 + + print("I'm thinking of a number, between 1 and 100. Can you guess what it is?") + while True: + guesses += 1 + guess = input('= ') + try: + guess = int(guess) + except ValueError: + print("Base 10 integers only, please.") + continue + + if guess > number: + print("Too high!") + elif guess < number: + print("Too low!") + else: + print("That's right, {}. You got it in {} guesses.".format(number, guesses)) + break + + print() + input("Press enter to quit.") diff --git a/examples/run_auto/installer.cfg b/examples/run_auto/installer.cfg new file mode 100644 index 0000000..f1eb172 --- /dev/null +++ b/examples/run_auto/installer.cfg @@ -0,0 +1,21 @@ +[Application] +name=Guess the Number +version=1.0 +entry_point=guessnumber:main +# We need to set this to get a console: +console=true +run_after_install=true +run_on_windows_start=true + +[Python] +version=3.6.3 +bitness=64 +format=bundled + +[Include] +packages=guessnumber + +# This optional section adds a command which can be run from the Windows +# command prompt. +[Command guessnumber] +entry_point=guessnumber:main diff --git a/nsist/__init__.py b/nsist/__init__.py index fe0b214..992cacb 100644 --- a/nsist/__init__.py +++ b/nsist/__init__.py @@ -104,12 +104,13 @@ class InstallerBuilder(object): :param str nsi_template: Path to a template NSI file to use """ def __init__(self, appname, version, shortcuts, *, publisher=None, - icon=DEFAULT_ICON, packages=None, extra_files=None, - py_version=DEFAULT_PY_VERSION, py_bitness=DEFAULT_BITNESS, - py_format='bundled', inc_msvcrt=True, build_dir=DEFAULT_BUILD_DIR, - installer_name=None, nsi_template=None, - exclude=None, pypi_wheel_reqs=None, extra_wheel_sources=None, - local_wheels=None, commands=None, license_file=None): + icon=DEFAULT_ICON, packages=None, extra_files=None, + py_version=DEFAULT_PY_VERSION, py_bitness=DEFAULT_BITNESS, + py_format='bundled', inc_msvcrt=True, build_dir=DEFAULT_BUILD_DIR, + installer_name=None, nsi_template=None, + exclude=None, pypi_wheel_reqs=None, extra_wheel_sources=None, + local_wheels=None, commands=None, license_file=None, + run_after_install=False, run_on_windows_start=False): self.appname = appname self.version = version self.publisher = publisher @@ -123,6 +124,8 @@ def __init__(self, appname, version, shortcuts, *, publisher=None, self.local_wheels = local_wheels or [] self.commands = commands or {} self.license_file = license_file + self.run_after_install = run_after_install + self.run_on_windows_start = run_on_windows_start # Python options self.py_version = py_version @@ -314,7 +317,7 @@ def prepare_shortcuts(self): else: shutil.copy2(sc['script'], self.build_dir) - target = '$INSTDIR\\Python\\python{}.exe' + target = r'$INSTDIR\Python\python{}.exe' sc['target'] = target.format('' if sc['console'] else 'w') sc['parameters'] = '"%s"' % ntpath.join('$INSTDIR', sc['script']) files.add(os.path.basename(sc['script'])) @@ -460,6 +463,16 @@ def run_nsis(self): print("http://nsis.sourceforge.net/Download") return 1 + nsis_dir = os.path.dirname(makensis) + nsis_inc_nsh = os.path.abspath(os.path.join(nsis_dir, "include", "nsProcess.nsh")) + nsis_plg_dll = os.path.abspath(os.path.join(nsis_dir, "Plugins", "x86-ansi", "nsProcess.dll")) + nsis_plg_dllw = os.path.abspath(os.path.join(nsis_dir, "Plugins", "x86-unicode", "nsProcess.dll")) + + if not os.path.exists(nsis_inc_nsh) or not os.path.exists(nsis_plg_dll) or not os.path.exists(nsis_plg_dllw): + print("NsProcess plugin was not found. Install NsProcess plugin and try again.") + print("https://nsis.sourceforge.io/NsProcess_plugin") + return 2 + logger.info('\n~~~ Running makensis ~~~') return call([makensis, self.nsi_file]) diff --git a/nsist/configreader.py b/nsist/configreader.py index af14c52..6f5180b 100644 --- a/nsist/configreader.py +++ b/nsist/configreader.py @@ -64,6 +64,8 @@ def _check_invalid_keys(self, section_name, section): ('console', False), ('extra_preamble', False), ('license_file', False), + ('run_after_install', False), + ('run_on_windows_start', False), ]), 'Build': SectionValidator([ ('directory', False), @@ -239,4 +241,6 @@ def get_installer_builder_args(config): args['nsi_template'] = config.get('Build', 'nsi_template', fallback=None) args['exclude'] = config.get('Include', 'exclude', fallback='').strip().splitlines() args['local_wheels'] = config.get('Include', 'local_wheels', fallback='').strip().splitlines() + args['run_after_install'] = appcfg.get('run_after_install', None) + args['run_on_windows_start'] = appcfg.get('run_on_windows_start', None) return args diff --git a/nsist/pyapp.nsi b/nsist/pyapp.nsi index 279a8c5..ee67e93 100644 --- a/nsist/pyapp.nsi +++ b/nsist/pyapp.nsi @@ -35,6 +35,32 @@ SetCompressor lzma !define MUI_ICON "[[icon]]" !define MUI_UNICON "[[icon]]" +[% if ib.run_after_install is not none %] +; Run an application shortcut after an install +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_TEXT "Start a shortcut" +!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink" +[% endif %] + +!include "nsProcess.nsh" +!define APP_EXE "python.exe" +!define AppName "${PRODUCT_NAME}" + +!macro KillProcess + ${nsProcess::FindProcess} "${APP_EXE}" $R0 + + ${If} $R0 == 0 + DetailPrint "${AppName} is running. Closing it down" + ${nsProcess::KillProcess} "${APP_EXE}" $R0 + DetailPrint "Waiting for ${AppName} to close" + Sleep 2000 + ${Else} + DetailPrint "${APP_EXE} was not found to be running" + ${EndIf} + + ${nsProcess::Unload} +!macroend + ; UI pages [% block ui_pages %] !insertmacro MUI_PAGE_WELCOME @@ -151,6 +177,14 @@ Section "!${PRODUCT_NAME}" sec_app WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ "NoRepair" 1 + [% if ib.run_on_windows_start is not none %] + ; Running a .exe file on Windows Start + [% for scname in ib.shortcuts %] + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Run" \ + "${PRODUCT_NAME}" "$SMPROGRAMS\[[scname]].lnk" + [% endfor %] + [% endif %] + ; Check if we need to reboot IfRebootFlag 0 noreboot MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \ @@ -160,6 +194,8 @@ Section "!${PRODUCT_NAME}" sec_app SectionEnd Section "Uninstall" + !insertmacro KillProcess + SetRegView [[ib.py_bitness]] SetShellVarContext all IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3 @@ -200,6 +236,9 @@ Section "Uninstall" [% endblock uninstall_shortcuts %] RMDir $INSTDIR DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" + [% if ib.run_on_windows_start is not none %] + DeleteRegKey /ifempty HKLM "Software\Microsoft\Windows\CurrentVersion\Run" + [% endif %] SectionEnd [% endblock sections %] @@ -219,6 +258,24 @@ Function .onMouseOverSection FunctionEnd Function .onInit +; https://nsis.sourceforge.io/Auto-uninstall_old_before_installing_new + ReadRegStr $R0 SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "UninstallString" + StrCmp $R0 "" done + + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \ + "${PRODUCT_NAME} is already installed. $\n$\nClick 'OK' to remove the \ + previous version or 'Cancel' to cancel this upgrade." \ + IDOK uninst + Abort + +; Run the uninstaller +uninst: + ClearErrors + ExecWait $R0 + +done: ; Multiuser.nsh breaks /D command line parameter. Parse /INSTDIR instead. ; Cribbing from https://nsis-dev.github.io/NSIS-Forums/html/t-299280.html ${GetParameters} $0 @@ -247,3 +304,11 @@ Function correct_prog_files StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}" FunctionEnd [% endif %] + +[% if ib.run_after_install is not none %] +Function LaunchLink + [% for scname in ib.shortcuts %] + ExecShell "" "$SMPROGRAMS\[[scname]].lnk" + [% endfor %] +FunctionEnd +[% endif %]