Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ jobs:
&& files+=(--file "tests/extra-requirements-${{ runner.os }}.txt")
conda install ${files[@]} -y
echo "NSIS_USING_LOG_BUILD=1" >> $GITHUB_ENV
echo "NSIS_SCRIPTS_RAISE_ERRORS=1" >> $GITHUB_ENV
pip install -e . --no-deps --no-build-isolation
- name: Set up conda executable
run: |
Expand Down
122 changes: 2 additions & 120 deletions constructor/nsis/_nsis.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,8 @@
# be tested in an installation.

import os
import re
import sys
from os.path import exists, isfile, join

try:
import winreg
except ImportError:
import _winreg as winreg
from os.path import exists, join

ROOT_PREFIX = sys.prefix

Expand Down Expand Up @@ -57,96 +51,6 @@ def err(x):
OutputDebugString('_nsis.py: Error: ' + x)


class NSISReg:
def __init__(self, reg_path):
self.reg_path = reg_path
if exists(join(ROOT_PREFIX, '.nonadmin')):
self.main_key = winreg.HKEY_CURRENT_USER
else:
self.main_key = winreg.HKEY_LOCAL_MACHINE

def set(self, name, value):
try:
winreg.CreateKey(self.main_key, self.reg_path)
registry_key = winreg.OpenKey(self.main_key, self.reg_path, 0,
winreg.KEY_WRITE)
winreg.SetValueEx(registry_key, name, 0, winreg.REG_SZ, value)
winreg.CloseKey(registry_key)
return True
except WindowsError:
return False

def get(self, name):
try:
registry_key = winreg.OpenKey(self.main_key, self.reg_path, 0,
winreg.KEY_READ)
value, regtype = winreg.QueryValueEx(registry_key, name)
winreg.CloseKey(registry_key)
return value
except WindowsError:
return None


def mk_dirs():
envs_dir = join(ROOT_PREFIX, 'envs')
if not exists(envs_dir):
os.mkdir(envs_dir)


def run_post_install():
"""
call the post install script, if the file exists
"""
path = join(ROOT_PREFIX, 'pkgs', 'post_install.bat')
if not isfile(path):
return
env = os.environ.copy()
env.setdefault('PREFIX', str(ROOT_PREFIX))
cmd_exe = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe')
if not os.path.isfile(cmd_exe):
cmd_exe = os.path.join(os.environ['windir'], 'System32', 'cmd.exe')
if not os.path.isfile(cmd_exe):
err("Error: running %s failed. cmd.exe could not be found. "
"Looked in SystemRoot and windir env vars.\n" % path)
if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"):
sys.exit(1)
args = [cmd_exe, '/d', '/c', path]
import subprocess
try:
subprocess.check_call(args, env=env)
except subprocess.CalledProcessError:
err("Error: running %s failed\n" % path)
if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"):
sys.exit(1)


def run_pre_uninstall():
"""
call the pre uninstall script, if the file exists
"""
path = join(ROOT_PREFIX, 'pre_uninstall.bat')
if not isfile(path):
return
env = os.environ.copy()
env.setdefault('PREFIX', str(ROOT_PREFIX))
cmd_exe = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe')
if not os.path.isfile(cmd_exe):
cmd_exe = os.path.join(os.environ['windir'], 'System32', 'cmd.exe')
if not os.path.isfile(cmd_exe):
err("Error: running %s failed. cmd.exe could not be found. "
"Looked in SystemRoot and windir env vars.\n" % path)
if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"):
sys.exit(1)
args = [cmd_exe, '/d', '/c', path]
import subprocess
try:
subprocess.check_call(args, env=env)
except subprocess.CalledProcessError:
err("Error: running %s failed\n" % path)
if os.environ.get("NSIS_SCRIPTS_RAISE_ERRORS"):
sys.exit(1)


allusers = (not exists(join(ROOT_PREFIX, '.nonadmin')))
# out('allusers is %s\n' % allusers)

Expand Down Expand Up @@ -217,29 +121,9 @@ def add_condabin_to_path():
broadcast_environment_settings_change()


def rm_regkeys():
cmdproc_reg_entry = NSISReg(r'Software\Microsoft\Command Processor')
cmdproc_autorun_val = cmdproc_reg_entry.get('AutoRun')
conda_hook_regex_pat = r'((\s+&\s+)?(if +exist)?(\s*?\"[^\"]*?conda[-_]hook\.bat\"))'
if join(ROOT_PREFIX, 'condabin') in (cmdproc_autorun_val or ''):
cmdproc_autorun_newval = re.sub(conda_hook_regex_pat, '',
cmdproc_autorun_val)
try:
cmdproc_reg_entry.set('AutoRun', cmdproc_autorun_newval)
except Exception:
# Hey, at least we made an attempt to cleanup
pass


def main():
cmd = sys.argv[1].strip()
if cmd == 'post_install':
run_post_install()
elif cmd == 'rmreg':
rm_regkeys()
elif cmd == 'mkdirs':
mk_dirs()
elif cmd == 'addpath':
if cmd == 'addpath':
# These checks are probably overkill, but could be useful
# if I forget to update something that uses this code.
if len(sys.argv) > 2:
Expand All @@ -257,8 +141,6 @@ def main():
add_condabin_to_path()
elif cmd == 'rmpath':
remove_from_path()
elif cmd == 'pre_uninstall':
run_pre_uninstall()
else:
sys.exit("ERROR: did not expect %r" % cmd)

Expand Down
88 changes: 62 additions & 26 deletions constructor/nsis/main.nsi.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ var /global InstMode # 0 = Just Me, 1 = All Users.
!define JUST_ME 0
!define ALL_USERS 1

var /global CMD_EXE

!macro FindCmdExe
# Find cmd.exe
ReadEnvStr $R0 SystemRoot
ReadEnvStr $R1 windir
${If} ${FileExists} "$R0"
StrCpy $CMD_EXE "$R0\System32\cmd.exe"
${ElseIf} ${FileExists} "$R1"
StrCpy $CMD_EXE "$R1\System32\cmd.exe"
${Else}
# Cross our fingers CMD is in PATH
StrCpy $CMD_EXE "cmd.exe"
${EndIf}
!macroend

# Include this one after our defines
!include "OptionsDialog.nsh"

Expand Down Expand Up @@ -1226,6 +1242,8 @@ Section "Install"
call OnDirectoryLeave
${EndIf}

!insertmacro FindCmdExe

SetOutPath "$INSTDIR\Lib"
File "{{ NSIS_DIR }}\_nsis.py"
File "{{ NSIS_DIR }}\_system_path.py"
Expand Down Expand Up @@ -1352,17 +1370,7 @@ Section "Install"

IfFileExists "$INSTDIR\pkgs\pre_install.bat" 0 NoPreInstall
${Print} "Running pre_install scripts..."
ReadEnvStr $5 SystemRoot
ReadEnvStr $6 windir
# This 'FileExists' also returns True for directories
${If} ${FileExists} "$5"
push '"$5\System32\cmd.exe" /D /C "$INSTDIR\pkgs\pre_install.bat"'
${ElseIf} ${FileExists} "$6"
push '"$6\System32\cmd.exe" /D /C "$INSTDIR\pkgs\pre_install.bat"'
${Else}
# Cross our fingers CMD is in PATH
push 'cmd.exe /D /C "$INSTDIR\pkgs\pre_install.bat"'
${EndIf}
push '"$CMD_EXE" /D /C "$INSTDIR\pkgs\pre_install.bat"'
push "Failed to run pre_install"
push 'WithLog'
call AbortRetryNSExecWait
Expand Down Expand Up @@ -1414,19 +1422,20 @@ Section "Install"
AddSize {{ SIZE }}

{%- if has_conda %}
${Print} "Initializing conda directories..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" mkdirs'
push 'Failed to initialize conda directories'
push 'WithLog'
call AbortRetryNSExecWait
StrCpy $R0 "$INSTDIR\envs"
${IfNot} ${FileExists} "$R0"
CreateDirectory "$R0"
${EndIf}
{%- endif %}

${If} $Ana_PostInstall_State = ${BST_CHECKED}
${Print} "Running post install..."
push '"$INSTDIR\pythonw.exe" -E -s "$INSTDIR\Lib\_nsis.py" post_install'
push 'Failed to run post install script'
push 'WithLog'
call AbortRetryNSExecWait
${If} ${FileExists} "$INSTDIR\pkgs\post_install.bat"
${If} $Ana_PostInstall_State = ${BST_CHECKED}
${Print} "Running post install..."
push '"$CMD_EXE" /D /C "$INSTDIR\pkgs\post_install.bat"'
push "Failed to run post_install"
push 'WithLog'
call AbortRetryNSExecWait
${EndIf}
${EndIf}

${If} $Ana_ClearPkgCache_State = ${BST_CHECKED}
Expand Down Expand Up @@ -1543,6 +1552,8 @@ Section "Uninstall"
!insertmacro un.ParseCommandLineArgs
${EndIf}

!insertmacro FindCmdExe

System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_ROOT_PREFIX", "$INSTDIR")".r0'

# ensure that MSVC runtime DLLs are on PATH during uninstallation
Expand Down Expand Up @@ -1598,9 +1609,14 @@ Section "Uninstall"
${EndIf}

{%- if uninstall_with_conda_exe %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
${If} ${FileExists} "$INSTDIR\pkgs\pre_uninstall.bat"
${Print} "Running pre_uninstall scripts..."
push '"$CMD_EXE" /D /C "$INSTDIR\pkgs\pre_uninstall.bat"'
push "Failed to run pre_uninstall"
push 'WithLog'
call un.AbortRetryNSExecWait
${EndIf}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this no longer needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was actually never needed. The uninstallation via conda-standalone already performs the same function during the installation process. I included that line back then by mistake.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks!


# Parse arguments
StrCpy $R0 ""
Expand Down Expand Up @@ -1648,9 +1664,29 @@ Section "Uninstall"
call un.AbortRetryNSExecWait
SetDetailsPrint both
{%- endfor %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
${If} ${FileExists} "$INSTDIR\pkgs\pre_uninstall.bat"
${Print} "Running pre_uninstall scripts..."
push '"$CMD_EXE" /D /C "$INSTDIR\pkgs\pre_uninstall.bat"'
push "Failed to run pre_uninstall"
push 'WithLog'
call un.AbortRetryNSExecWait
${EndIf}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"
{%- if has_conda %}
${If} ${FileExists} "$INSTDIR\.nonadmin"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional:
In the Function un.onInit we also have this logic:

    ${If} ${FileExists} "$INSTDIR\.nonadmin"
        SetShellVarContext Current
    ${Else}
        SetShellVarContext All
    ${EndIf}

If you prefer, you can create a new variable (maybe un.IsUserInstall?) and assign that variable within that function, to reduce the number of if .nonadmin exists.

I have no strong opinion though, I also like the simplicity with simply using $R0 as in your commit.

StrCpy $R0 "user"
${Else}
StrCpy $R0 "system"
${EndIf}
# When running conda.bat directly, there is a non-fatal error
# that DOSKEY (called by conda_hook.bat) is not a valid command.
# While the operation still succeeds, this error is confusing.
# Calling via cmd.exe fixes that.
push '"$CMD_EXE" /D /C "$INSTDIR\condabin\conda.bat" init cmd.exe --reverse --$R0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
push '"$CMD_EXE" /D /C "$INSTDIR\condabin\conda.bat" init cmd.exe --reverse --$R0'
push '"$CMD_EXE" /D /C "$INSTDIR\condabin\conda.bat" init --reverse --$R0'

Should we revert for both cmd and powershell (which is achieved by passing no positional arguments)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The powershell function does not perform path validation, so it will also revert profiles that point to a different installation. The cmd.exe function, on the other hand, only reverts the autorun entry that belongs to the installation.

push 'Failed to clean AutoRun'
push 'WithLog'
call un.AbortRetryNSExecWait
{%- endif %}

${Print} "Removing files and folders..."
nsExec::Exec 'cmd.exe /D /C RMDIR /Q /S "$INSTDIR"'
Expand Down
19 changes: 19 additions & 0 deletions news/1069-port-nsispy-cmds-nsis
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* Port script execution, AutoRun manipulation, and directory creation functions from `_nsis.py` to NSIS. (#1069)

### Bug fixes

* <news item>

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
4 changes: 0 additions & 4 deletions scripts/run_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,6 @@ def run_examples(keep_artifacts=None, conda_exe=None, debug=False):
if os.path.exists(os.path.join(fpath, "construct.yaml")):
example_paths.append(fpath)

# NSIS won't error out when running scripts unless
# we set this custom environment variable
os.environ["NSIS_SCRIPTS_RAISE_ERRORS"] = "1"

parent_output = tempfile.mkdtemp()
tested_files = set()
which_errored = {}
Expand Down
Loading