Compare commits

...

5 Commits

Author SHA1 Message Date
Adrien Ferrand
c52be1eeb5 Focus on installer tests 2021-01-06 01:14:45 +01:00
Adrien Ferrand
39e786405d Upgrade pynsist, nsis and pywin32, remove old workarounds 2021-01-06 01:13:03 +01:00
Adrien Ferrand
1b89ea773c Merge branch 'master' into windows-python38 2021-01-05 18:49:04 +01:00
Adrien Ferrand
590ad226cb Fix name 2020-11-19 23:34:07 +01:00
Adrien Ferrand
9dfe0cd547 Enable windows tests on Python 3.8 and package it on Python 3.8 also. 2020-11-19 00:26:34 +01:00
7 changed files with 195 additions and 201 deletions

View File

@@ -1,62 +1,62 @@
jobs: jobs:
- job: docker_build # - job: docker_build
pool: # pool:
vmImage: ubuntu-18.04 # vmImage: ubuntu-18.04
strategy: # strategy:
matrix: # matrix:
amd64: # amd64:
DOCKER_ARCH: amd64 # DOCKER_ARCH: amd64
# Do not run the heavy non-amd64 builds for test branches # # Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}: # ${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
arm32v6: # arm32v6:
DOCKER_ARCH: arm32v6 # DOCKER_ARCH: arm32v6
arm64v8: # arm64v8:
DOCKER_ARCH: arm64v8 # DOCKER_ARCH: arm64v8
steps: # steps:
- bash: set -e && tools/docker/build.sh $(dockerTag) $DOCKER_ARCH # - bash: set -e && tools/docker/build.sh $(dockerTag) $DOCKER_ARCH
displayName: Build the Docker images # displayName: Build the Docker images
# We don't filter for the Docker Hub organization to continue to allow # # We don't filter for the Docker Hub organization to continue to allow
# easy testing of these scripts on forks. # # easy testing of these scripts on forks.
- bash: | # - bash: |
set -e # set -e
DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}') # DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}')
docker save --output images.tar $DOCKER_IMAGES # docker save --output images.tar $DOCKER_IMAGES
displayName: Save the Docker images # displayName: Save the Docker images
# If the name of the tar file or artifact changes, the deploy stage will # # If the name of the tar file or artifact changes, the deploy stage will
# also need to be updated. # # also need to be updated.
- bash: set -e && mv images.tar $(Build.ArtifactStagingDirectory) # - bash: set -e && mv images.tar $(Build.ArtifactStagingDirectory)
displayName: Prepare Docker artifact # displayName: Prepare Docker artifact
- task: PublishPipelineArtifact@1 # - task: PublishPipelineArtifact@1
inputs: # inputs:
path: $(Build.ArtifactStagingDirectory) # path: $(Build.ArtifactStagingDirectory)
artifact: docker_$(DOCKER_ARCH) # artifact: docker_$(DOCKER_ARCH)
displayName: Store Docker artifact # displayName: Store Docker artifact
- job: docker_run # - job: docker_run
dependsOn: docker_build # dependsOn: docker_build
pool: # pool:
vmImage: ubuntu-18.04 # vmImage: ubuntu-18.04
steps: # steps:
- task: DownloadPipelineArtifact@2 # - task: DownloadPipelineArtifact@2
inputs: # inputs:
artifact: docker_amd64 # artifact: docker_amd64
path: $(Build.SourcesDirectory) # path: $(Build.SourcesDirectory)
displayName: Retrieve Docker images # displayName: Retrieve Docker images
- bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar # - bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar
displayName: Load Docker images # displayName: Load Docker images
- bash: | # - bash: |
set -ex # set -ex
DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}:{{.Tag}}') # DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}:{{.Tag}}')
for DOCKER_IMAGE in ${DOCKER_IMAGES} # for DOCKER_IMAGE in ${DOCKER_IMAGES}
do docker run --rm "${DOCKER_IMAGE}" plugins --prepare # do docker run --rm "${DOCKER_IMAGE}" plugins --prepare
done # done
displayName: Run integration tests for Docker images # displayName: Run integration tests for Docker images
- job: installer_build - job: installer_build
pool: pool:
vmImage: vs2017-win2016 vmImage: vs2017-win2016
steps: steps:
- task: UsePythonVersion@0 - task: UsePythonVersion@0
inputs: inputs:
versionSpec: 3.7 versionSpec: 3.8
architecture: x86 architecture: x86
addToPath: true addToPath: true
- script: python windows-installer/construct.py - script: python windows-installer/construct.py
@@ -113,105 +113,105 @@ jobs:
set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4
displayName: Run certbot integration tests displayName: Run certbot integration tests
- job: snaps_build # - job: snaps_build
pool: # pool:
vmImage: ubuntu-18.04 # vmImage: ubuntu-18.04
timeoutInMinutes: 0 # timeoutInMinutes: 0
variables: # variables:
# Do not run the heavy non-amd64 builds for test branches # # Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}: # ${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
ARCHS: amd64 arm64 armhf # ARCHS: amd64 arm64 armhf
${{ if startsWith(variables['Build.SourceBranchName'], 'test-') }}: # ${{ if startsWith(variables['Build.SourceBranchName'], 'test-') }}:
ARCHS: amd64 # ARCHS: amd64
steps: # steps:
- script: | # - script: |
set -e # set -e
sudo apt-get update # sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd # sudo apt-get install -y --no-install-recommends snapd
sudo snap install --classic snapcraft # sudo snap install --classic snapcraft
displayName: Install dependencies # displayName: Install dependencies
- task: UsePythonVersion@0 # - task: UsePythonVersion@0
inputs: # inputs:
versionSpec: 3.8 # versionSpec: 3.8
addToPath: true # addToPath: true
- task: DownloadSecureFile@1 # - task: DownloadSecureFile@1
name: credentials # name: credentials
inputs: # inputs:
secureFile: launchpad-credentials # secureFile: launchpad-credentials
- script: | # - script: |
set -e # set -e
git config --global user.email "$(Build.RequestedForEmail)" # git config --global user.email "$(Build.RequestedForEmail)"
git config --global user.name "$(Build.RequestedFor)" # git config --global user.name "$(Build.RequestedFor)"
mkdir -p ~/.local/share/snapcraft/provider/launchpad # mkdir -p ~/.local/share/snapcraft/provider/launchpad
cp $(credentials.secureFilePath) ~/.local/share/snapcraft/provider/launchpad/credentials # cp $(credentials.secureFilePath) ~/.local/share/snapcraft/provider/launchpad/credentials
python3 tools/snap/build_remote.py ALL --archs ${ARCHS} --timeout 19800 # python3 tools/snap/build_remote.py ALL --archs ${ARCHS} --timeout 19800
displayName: Build snaps # displayName: Build snaps
- script: | # - script: |
set -e # set -e
mv *.snap $(Build.ArtifactStagingDirectory) # mv *.snap $(Build.ArtifactStagingDirectory)
mv certbot-dns-*/*.snap $(Build.ArtifactStagingDirectory) # mv certbot-dns-*/*.snap $(Build.ArtifactStagingDirectory)
displayName: Prepare artifacts # displayName: Prepare artifacts
- task: PublishPipelineArtifact@1 # - task: PublishPipelineArtifact@1
inputs: # inputs:
path: $(Build.ArtifactStagingDirectory) # path: $(Build.ArtifactStagingDirectory)
artifact: snaps # artifact: snaps
displayName: Store snaps artifacts # displayName: Store snaps artifacts
- job: snap_run # - job: snap_run
dependsOn: snaps_build # dependsOn: snaps_build
pool: # pool:
vmImage: ubuntu-18.04 # vmImage: ubuntu-18.04
steps: # steps:
- task: UsePythonVersion@0 # - task: UsePythonVersion@0
inputs: # inputs:
versionSpec: 3.8 # versionSpec: 3.8
addToPath: true # addToPath: true
- script: | # - script: |
set -e # set -e
sudo apt-get update # sudo apt-get update
sudo apt-get install -y --no-install-recommends nginx-light snapd # sudo apt-get install -y --no-install-recommends nginx-light snapd
python3 -m venv venv # python3 -m venv venv
venv/bin/python tools/pipstrap.py # venv/bin/python tools/pipstrap.py
venv/bin/python tools/pip_install.py -U tox # venv/bin/python tools/pip_install.py -U tox
displayName: Install dependencies # displayName: Install dependencies
- task: DownloadPipelineArtifact@2 # - task: DownloadPipelineArtifact@2
inputs: # inputs:
artifact: snaps # artifact: snaps
path: $(Build.SourcesDirectory)/snap # path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps # displayName: Retrieve Certbot snaps
- script: | # - script: |
set -e # set -e
sudo snap install --dangerous --classic snap/certbot_*_amd64.snap # sudo snap install --dangerous --classic snap/certbot_*_amd64.snap
displayName: Install Certbot snap # displayName: Install Certbot snap
- script: | # - script: |
set -e # set -e
venv/bin/python -m tox -e integration-external,apacheconftest-external-with-pebble # venv/bin/python -m tox -e integration-external,apacheconftest-external-with-pebble
displayName: Run tox # displayName: Run tox
- job: snap_dns_run # - job: snap_dns_run
dependsOn: snaps_build # dependsOn: snaps_build
pool: # pool:
vmImage: ubuntu-18.04 # vmImage: ubuntu-18.04
steps: # steps:
- script: | # - script: |
set -e # set -e
sudo apt-get update # sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd # sudo apt-get install -y --no-install-recommends snapd
displayName: Install dependencies # displayName: Install dependencies
- task: UsePythonVersion@0 # - task: UsePythonVersion@0
inputs: # inputs:
versionSpec: 3.8 # versionSpec: 3.8
addToPath: true # addToPath: true
- task: DownloadPipelineArtifact@2 # - task: DownloadPipelineArtifact@2
inputs: # inputs:
artifact: snaps # artifact: snaps
path: $(Build.SourcesDirectory)/snap # path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps # displayName: Retrieve Certbot snaps
- script: | # - script: |
set -e # set -e
python3 -m venv venv # python3 -m venv venv
venv/bin/python tools/pipstrap.py # venv/bin/python tools/pipstrap.py
venv/bin/python tools/pip_install.py -e certbot-ci # venv/bin/python tools/pip_install.py -e certbot-ci
displayName: Prepare Certbot-CI # displayName: Prepare Certbot-CI
- script: | # - script: |
set -e # set -e
sudo -E venv/bin/pytest certbot-ci/snap_integration_tests/dns_tests --allow-persistent-changes --snap-folder $(Build.SourcesDirectory)/snap --snap-arch amd64 # sudo -E venv/bin/pytest certbot-ci/snap_integration_tests/dns_tests --allow-persistent-changes --snap-folder $(Build.SourcesDirectory)/snap --snap-arch amd64
displayName: Test DNS plugins snaps # displayName: Test DNS plugins snaps

View File

@@ -16,13 +16,13 @@ jobs:
IMAGE_NAME: vs2017-win2016 IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.6 PYTHON_VERSION: 3.6
TOXENV: py36 TOXENV: py36
windows-py37-cover: windows-py38-cover:
IMAGE_NAME: vs2017-win2016 IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7 PYTHON_VERSION: 3.8
TOXENV: py37-cover TOXENV: py38-cover
windows-integration-certbot: windows-integration-certbot:
IMAGE_NAME: vs2017-win2016 IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7 PYTHON_VERSION: 3.8
TOXENV: integration-certbot TOXENV: integration-certbot
linux-oldest-tests-1: linux-oldest-tests-1:
IMAGE_NAME: ubuntu-18.04 IMAGE_NAME: ubuntu-18.04

View File

@@ -1,6 +1,6 @@
stages: stages:
- stage: TestAndPackage - stage: TestAndPackage
jobs: jobs:
- template: ../jobs/standard-tests-jobs.yml # - template: ../jobs/standard-tests-jobs.yml
- template: ../jobs/extended-tests-jobs.yml # - template: ../jobs/extended-tests-jobs.yml
- template: ../jobs/packaging-jobs.yml - template: ../jobs/packaging-jobs.yml

View File

@@ -59,7 +59,7 @@ install_requires = [
# However environment markers are supported only with setuptools >= 36.2. # However environment markers are supported only with setuptools >= 36.2.
# So this dependency is not added for old Linux distributions with old setuptools, # So this dependency is not added for old Linux distributions with old setuptools,
# in order to allow these systems to build certbot from sources. # in order to allow these systems to build certbot from sources.
pywin32_req = 'pywin32>=227' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py pywin32_req = 'pywin32>=300' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2')) setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
if setuptools_known_environment_markers: if setuptools_known_environment_markers:
install_requires.append(pywin32_req + " ; sys_platform == 'win32'") install_requires.append(pywin32_req + " ; sys_platform == 'win32'")

View File

@@ -91,7 +91,7 @@ pylint==2.4.3
# If pynsist version is upgraded, our NSIS template windows-installer/template.nsi # If pynsist version is upgraded, our NSIS template windows-installer/template.nsi
# must be upgraded if necessary using the new built-in one from pynsist. # must be upgraded if necessary using the new built-in one from pynsist.
pynacl==1.3.0 pynacl==1.3.0
pynsist==2.4 pynsist==2.6
pytest==3.2.5 pytest==3.2.5
pytest-cov==2.5.1 pytest-cov==2.5.1
pytest-forked==0.2 pytest-forked==0.2
@@ -101,7 +101,7 @@ pytest-rerunfailures==4.2
python-dateutil==2.8.1 python-dateutil==2.8.1
python-digitalocean==1.11 python-digitalocean==1.11
python-dotenv==0.14.0 python-dotenv==0.14.0
pywin32==227 pywin32==300
PyYAML==5.3.1 PyYAML==5.3.1
repoze.sphinx.autointerface==0.8 repoze.sphinx.autointerface==0.8
requests-file==1.4.2 requests-file==1.4.2

View File

@@ -9,10 +9,10 @@ import sys
import tempfile import tempfile
import time import time
PYTHON_VERSION = (3, 7, 4) PYTHON_VERSION = (3, 8, 6)
PYTHON_BITNESS = 32 PYTHON_BITNESS = 32
PYWIN32_VERSION = 227 # do not forget to edit pywin32 dependency accordingly in setup.py PYWIN32_VERSION = 300 # do not forget to edit pywin32 dependency accordingly in setup.py
NSIS_VERSION = '3.04' NSIS_VERSION = '3.06.1'
def main(): def main():
@@ -48,6 +48,7 @@ def _compile_wheels(repo_path, build_path, venv_python):
with _prepare_constraints(repo_path) as constraints_file_path: with _prepare_constraints(repo_path) as constraints_file_path:
env = os.environ.copy() env = os.environ.copy()
env['PIP_CONSTRAINT'] = constraints_file_path env['PIP_CONSTRAINT'] = constraints_file_path
subprocess.check_call([venv_python, '-m', 'pip', '--version'])
command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path] command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path]
command.extend(wheels_project) command.extend(wheels_project)
subprocess.check_call(command, env=env) subprocess.check_call(command, env=env)
@@ -98,32 +99,6 @@ def _copy_assets(build_path, repo_path):
def _generate_pynsist_config(repo_path, build_path): def _generate_pynsist_config(repo_path, build_path):
print('Generate pynsist configuration') print('Generate pynsist configuration')
pywin32_paths_file = os.path.join(build_path, 'pywin32_paths.py')
# Pywin32 uses non-standard folders to hold its packages. We need to instruct pynsist bootstrap
# explicitly to add them into sys.path. This is done with a custom "pywin32_paths.py" that is
# referred in the pynsist configuration as an "extra_preamble".
# Reference example: https://github.com/takluyver/pynsist/tree/master/examples/pywebview
with open(pywin32_paths_file, 'w') as file_h:
file_h.write('''\
pkgdir = os.path.join(os.path.dirname(installdir), 'pkgs')
sys.path.extend([
os.path.join(pkgdir, 'win32'),
os.path.join(pkgdir, 'win32', 'lib'),
])
# Preload pywintypes and pythoncom
pwt = os.path.join(pkgdir, 'pywin32_system32', 'pywintypes{0}{1}.dll')
pcom = os.path.join(pkgdir, 'pywin32_system32', 'pythoncom{0}{1}.dll')
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import imp
imp.load_dynamic('pywintypes', pwt)
imp.load_dynamic('pythoncom', pcom)
'''.format(PYTHON_VERSION[0], PYTHON_VERSION[1]))
installer_cfg_path = os.path.join(build_path, 'installer.cfg') installer_cfg_path = os.path.join(build_path, 'installer.cfg')
certbot_pkg_path = os.path.join(repo_path, 'certbot') certbot_pkg_path = os.path.join(repo_path, 'certbot')
@@ -158,7 +133,6 @@ files=run.bat
[Command certbot] [Command certbot]
entry_point=certbot.main:main entry_point=certbot.main:main
extra_preamble=pywin32_paths.py
'''.format(certbot_version=certbot_version, '''.format(certbot_version=certbot_version,
installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32',
python_bitness=PYTHON_BITNESS, python_bitness=PYTHON_BITNESS,

View File

@@ -1,4 +1,4 @@
; This NSIS template is based on the built-in one in pynsist 2.3. ; This NSIS template is based on the built-in one in pynsist 2.6.
; Added lines are enclosed within "CERTBOT CUSTOM BEGIN/END" comments. ; Added lines are enclosed within "CERTBOT CUSTOM BEGIN/END" comments.
; If pynsist is upgraded, this template must be updated if necessary using the new built-in one. ; If pynsist is upgraded, this template must be updated if necessary using the new built-in one.
; Original file can be found here: https://github.com/takluyver/pynsist/blob/2.4/nsist/pyapp.nsi ; Original file can be found here: https://github.com/takluyver/pynsist/blob/2.4/nsist/pyapp.nsi
@@ -14,9 +14,14 @@
; Marker file to tell the uninstaller that it's a user installation ; Marker file to tell the uninstaller that it's a user installation
!define USER_INSTALL_MARKER _user_install_marker !define USER_INSTALL_MARKER _user_install_marker
SetCompressor lzma SetCompressor lzma
!if "${NSIS_PACKEDVERSION}" >= 0x03000000
Unicode true
ManifestDPIAware true
!endif
; CERTBOT CUSTOM BEGIN ; CERTBOT CUSTOM BEGIN
; Administrator privileges are required to insert a new task in Windows Scheduler. ; Administrator privileges are required to insert a new task in Windows Scheduler.
; Also comment out some options to disable ability to choose AllUsers/CurrentUser install mode. ; Also comment out some options to disable ability to choose AllUsers/CurrentUser install mode.
@@ -35,9 +40,10 @@ SetCompressor lzma
!define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files !define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files
[% endif %] [% endif %]
!include MultiUser.nsh !include MultiUser.nsh
!include FileFunc.nsh
[% block modernui %] [% block modernui %]
; Modern UI installer stuff ; Modern UI installer stuff
!include "MUI2.nsh" !include "MUI2.nsh"
!define MUI_ABORTWARNING !define MUI_ABORTWARNING
!define MUI_ICON "[[icon]]" !define MUI_ICON "[[icon]]"
@@ -67,6 +73,8 @@ Name "${PRODUCT_NAME} (beta) ${PRODUCT_VERSION}"
OutFile "${INSTALLER_NAME}" OutFile "${INSTALLER_NAME}"
ShowInstDetails show ShowInstDetails show
Var cmdLineInstallDir
Section -SETTINGS Section -SETTINGS
SetOutPath "$INSTDIR" SetOutPath "$INSTDIR"
SetOverwrite ifnewer SetOverwrite ifnewer
@@ -96,14 +104,14 @@ Section "!${PRODUCT_NAME}" sec_app
File "[[ file ]]" File "[[ file ]]"
[% endfor %] [% endfor %]
[% endfor %] [% endfor %]
; Install directories ; Install directories
[% for dir, destination in ib.install_dirs %] [% for dir, destination in ib.install_dirs %]
SetOutPath "[[ pjoin(destination, dir) ]]" SetOutPath "[[ pjoin(destination, dir) ]]"
File /r "[[dir]]\*.*" File /r "[[dir]]\*.*"
[% endfor %] [% endfor %]
[% endblock install_files %] [% endblock install_files %]
[% block install_shortcuts %] [% block install_shortcuts %]
; Install shortcuts ; Install shortcuts
; The output path becomes the working directory for shortcuts ; The output path becomes the working directory for shortcuts
@@ -127,7 +135,6 @@ Section "!${PRODUCT_NAME}" sec_app
[% block install_commands %] [% block install_commands %]
[% if has_commands %] [% if has_commands %]
DetailPrint "Setting up command-line launchers..." DetailPrint "Setting up command-line launchers..."
nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" [[ python ]] "$INSTDIR\bin"'
StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem
; Add to PATH for current user ; Add to PATH for current user
@@ -139,7 +146,7 @@ Section "!${PRODUCT_NAME}" sec_app
AddedSysPath: AddedSysPath:
[% endif %] [% endif %]
[% endblock install_commands %] [% endblock install_commands %]
; Byte-compile Python files. ; Byte-compile Python files.
DetailPrint "Byte-compiling Python modules..." DetailPrint "Byte-compiling Python modules..."
nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"' nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"'
@@ -238,12 +245,25 @@ Function .onMouseOverSection
[% block mouseover_messages %] [% block mouseover_messages %]
StrCmp $0 ${sec_app} "" +2 StrCmp $0 ${sec_app} "" +2
SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}" SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}"
[% endblock mouseover_messages %] [% endblock mouseover_messages %]
FunctionEnd FunctionEnd
Function .onInit Function .onInit
; 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
ClearErrors
${GetOptions} '$0' "/INSTDIR=" $1
IfErrors +2 ; Error means flag not found
StrCpy $cmdLineInstallDir $1
ClearErrors
!insertmacro MULTIUSER_INIT !insertmacro MULTIUSER_INIT
; If cmd line included /INSTDIR, override the install dir set by MultiUser
StrCmp $cmdLineInstallDir "" +2
StrCpy $INSTDIR $cmdLineInstallDir
FunctionEnd FunctionEnd
Function un.onInit Function un.onInit