1#
2# A NSIS installer script for scOrange Windows application
3#
4
5# Required definitions need to be passed to the makensis call
6#  - BASEDIR base location of all required files, ... (see below)
7#  - PYARCH python architecture identifier (win32 or amd64)
8#  - PY{MAJOR,MINOR,MICRO} Python version of the included installer
9#  - APPNAME Application (short) name
10#  - VER{MAJOR,MINOR,MICRO} Application version
11#  - PYINSTALLER basename of the python installer
12#  - INSTALL_REGISTRY_KEY reg subkey name to use for storing install infomation
13#       (details will be stored under Software/${INSTALL_REGISTRY_KEY})
14
15
16# Required data layout at compile time
17# (by default BASEDIR is the directory where this .nsi script is located;
18# use -NOCD parameter when invoking makensis to preserve invoker cwd)
19
20# ${BASEDIR}/
21#   wheelhouse/
22#   requirements.txt
23
24
25## InstallationTypes:
26##    - Normal
27##    - Private
28
29
30# Python installation type: Normal | Private  (Env | Private)
31# - Normal:
32#   If a compatible Python installation is found it is used, otherwise an
33#   Python installer is executed (this installer must register the
34#   appropriate keys in the Windows registry).
35# - Private:
36#   A private copy of the Python interpreter is installed for use by the
37#   application
38
39#!include InstallConfig.nsh
40
41!ifndef PYINSTALL_TYPE
42    !error "PYINSTALL_TYPE must be defined"
43!endif
44
45!ifndef APPNAME
46    !error "APPNAME must be defined"
47!endif
48
49!ifndef APPLICATIONNAME
50    !define APPLICATIONNAME ${APPNAME}
51!endif
52
53!ifdef VERMAJOR & VERMINOR
54    !ifdef VERMICRO
55        !define APPVERSION ${VERMAJOR}.${VERMINOR}.${VERMICRO}
56    !else
57        !define APPVERSION ${VERMAJOR}.${VERMINOR}
58    !endif
59!else
60    !error "VERMAJOR and VERMINOR are no defiend"
61!endif
62
63!define PYTHON_VERSION ${PYMAJOR}.${PYMINOR}.${PYMICRO}
64!define PYTAG ${PYMAJOR}${PYMINOR}
65
66!if ${PYARCH} == win32
67    !define BITS 32
68!else if ${PYARCH} == win_amd64
69    !define BITS 64
70!else
71    !error "Invalid PYARCH ${PYARCH}"
72!endif
73
74
75!ifndef OUTFILENAME
76    !define OUTFILENAME \
77        ${APPNAME}-${APPVERSION}-python-${PYMAJOR}.${PYMINOR}-${PYARCH}-setup.exe
78!endif
79
80
81OutFile ${OUTFILENAME}
82Name ${APPLICATIONNAME}-${VERSION}
83
84!ifndef DEFAULT_INSTALL_FOLDER
85    !define DEFAULT_INSTALL_FOLDER ${APPNAME}
86!endif
87
88# Wheel packages which are already compressed.
89SetCompress "off"
90
91!ifdef INSTALLERICON
92    Icon "${INSTALLERICON}"
93    UninstallIcon "${INSTALLERICON}"
94
95    !define MUI_ICON ${INSTALLERICON}
96    !define MUI_UNICON ${INSTALLERICON}
97!endif
98
99!ifndef BASEDIR
100    !define BASEDIR .
101!endif
102
103# Application launcher shortcut name (in start menu or on the desktop)
104!ifndef LAUNCHER_SHORTCUT_NAME
105    !define LAUNCHER_SHORTCUT_NAME "${APPNAME}"
106!endif
107
108!ifndef INSTALL_REGISTRY_KEY
109    !error 'INSTALL_REGISTRY_KEY must be defined'
110!endif
111
112# Registry key/values where the installation layout is saved
113!define INSTALL_SETTINGS_KEY Software\${INSTALL_REGISTRY_KEY}
114!define INSTALL_SETTINGS_INSTDIR InstallDir
115!define INSTALL_SETTINGS_INSTMODE InstallMode
116
117# Support for both current or all users installation layout
118# (see MultiUser/Readme.html)
119!define MULTIUSER_EXECUTIONLEVEL Highest
120# By default select current user mode
121!define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER
122
123!if ${BITS} == 64
124    # Use correct program files folder.
125    # NSIS >= 3.02 (currently still unreleased)
126    !define MULTIUSER_USE_PROGRAMFILES64
127!endif
128
129# Enable the Current/All Users selection page.
130!define MULTIUSER_MUI
131
132# Enable /AllUsers or /CurrentUser command line switch
133# (see MultiUser/Readme.html)
134!define MULTIUSER_INSTALLMODE_COMMANDLINE
135
136# the default folder name where the application will be installed
137!define MULTIUSER_INSTALLMODE_INSTDIR ${DEFAULT_INSTALL_FOLDER}
138
139!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY ${INSTALL_SETTINGS_KEY}
140!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME ${INSTALL_SETTINGS_INSTDIR}
141
142!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY ${INSTALL_SETTINGS_KEY}
143!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME ${INSTALL_SETTINGS_INSTMODE}
144
145# A function which will restore the install dir passed on the command
146# line (/D=DIR) if running in silent mode (MultiUser.nsh will not respect
147# the default $InstDir).
148!define MULTIUSER_INSTALLMODE_FUNCTION RestoreSilentInstallDir
149
150!include Sections.nsh
151!include MultiUser.nsh
152!include MUI2.nsh
153!include LogicLib.nsh
154!include FileFunc.nsh
155
156# Installer Pages Definitions
157# ---------------------------
158
159!insertmacro MUI_PAGE_WELCOME
160
161!ifdef LICENSE_FILE
162    !insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}"
163!endif
164
165!define MUI_STARTMENUPAGE_DEFAULTFOLDER ${APPNAME}
166# Stay on InstFiles page to allow log view inspection
167!define MUI_FINISHPAGE_NOAUTOCLOSE
168
169Var StartMenuFolder
170
171# All/Current user install selection selection page:
172!insertmacro MULTIUSER_PAGE_INSTALLMODE
173
174# Components Selection Page:
175# - put the component description box at the bottom of the list (more
176#   compact GUI)
177!define MUI_COMPONENTSPAGE_SMALLDESC
178!insertmacro MUI_PAGE_COMPONENTS
179
180# Install Directory selection page:
181# Custom function to call on leaving the Directory Page (validation)
182!define MUI_PAGE_CUSTOMFUNCTION_LEAVE "DirectoryLeave"
183!insertmacro MUI_PAGE_DIRECTORY
184
185# Start Menu Directory selection page:
186# - custom start menu page pre callback function to skip the page if
187#   Start Menu section is unselected
188!define MUI_PAGE_CUSTOMFUNCTION_PRE "StartMenuPre"
189# - no check box to enable/disable start menu creation
190#   (is controled by the Components Page)
191!define MUI_STARTMENUPAGE_NODISABLE
192!insertmacro MUI_PAGE_STARTMENU StartMenuPageID $StartMenuFolder
193
194# Install Files page:
195!insertmacro MUI_PAGE_INSTFILES
196
197# Finish Page:
198# - run the application from the finish page
199!define MUI_FINISHPAGE_RUN
200!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchApplication"
201!define MUI_FINISHPAGE_RUN_TEXT "Start ${APPLICATIONNAME}"
202# - add link at the bottom
203!define MUI_FINISHPAGE_LINK "singlecell.biolab.si"
204!define MUI_FINISHPAGE_LINK_LOCATION "http://singlecell.biolab.si"
205
206!insertmacro MUI_PAGE_FINISH
207
208# Uninstaller Page Definitions
209# ----------------------------
210!insertmacro MUI_UNPAGE_CONFIRM
211!insertmacro MUI_UNPAGE_INSTFILES
212!insertmacro MUI_UNPAGE_FINISH
213
214!insertmacro MUI_LANGUAGE English
215
216# Temporary folder where temp data will be extracted
217# ($PLUGINSDIR is the (supposedly) recommend place for this; deleted on exit)
218!define TEMPDIR "$PLUGINSDIR\${APPNAME}-installer-data"
219
220!include "PythonHelpers.nsh"
221
222#
223# ${ExtractTemp} Resource TargetLocation
224#
225#   Extract a Resource (available at compile time) to TargetLocation
226#   (available at install time)
227#
228!macro _EXTRACT_TEMP_MACRO RESOURCE LOCATION
229    SetOutPath ${LOCATION}
230    File ${RESOURCE}
231!macroend
232!define ExtractTemp "!insertmacro _EXTRACT_TEMP_MACRO"
233
234#
235# ${ExtractTempRec} Resource TargetLocation
236#
237#   Extract a Resource (available at compile time) recursively to
238#   TargetLocation (available at install time)
239#
240!macro _EXTRACT_TEMP_MACRO_REC RESOURCE LOCATION
241    SetOutPath ${LOCATION}
242    File /r ${RESOURCE}
243!macroend
244!define ExtractTempRec "!insertmacro _EXTRACT_TEMP_MACRO_REC"
245
246
247# Key prefix where "Add/Remove Programs" entries are registerd
248!define WINDOWS_UNINSTALL_REGKEY \
249    "Software\Microsoft\Windows\CurrentVersion\Uninstall"
250
251# Full key path for the application uninstall entry in Add/Remove Programs
252!define APPLICATION_UNINSTALL_REGKEY "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}"
253
254# Uninstaller base name
255!ifndef UNINSTALL_EXEFILE
256    !define UNINSTALL_EXEFILE "${APPLICATIONNAME}-Uninstall.exe"
257!endif
258
259# Preserved $InstDir as present in .onInit (i.e from /D=... command line
260# switch)
261Var SilentInstallDir
262
263# The (env) base Python prefix
264Var BasePythonPrefix
265
266# The created env Python installation layout
267Var PythonPrefix
268Var PythonExecPrefix
269Var PythonScriptsPrefix
270
271# Python installaion mode (1 if installed for all users; 0 if installed for
272# current user only
273Var PythonInstallMode
274
275Var LogFile
276!macro __LOG_INIT
277    !ifdef __UNINSTALL__
278        FileOpen $LogFile "$TEMP\${UNINSTALL_EXEFILE}-uninstall-log.txt" "w"
279    !else
280        FileOpen $LogFile "$TEMP\$EXEFILE-install-log.txt" "w"
281    !endif
282    FileWrite $LogFile "------------------------$\r$\n"
283    !ifdef __UNINSTALL__
284        FileWrite $LogFile "${APPLICATIONNAME} ${VERSION} Uninstall Log$\r$\n"
285    !else
286        FileWrite $LogFile "${APPLICATIONNAME} ${VERSION} Install Log$\r$\n"
287    !endif
288    FileWrite $LogFile "------------------------$\r$\n"
289!macroend
290
291!define LogInit "!insertmacro __LOG_INIT"
292
293# ${LogWrite}  STR
294# ----------------
295# Write a STR to $LogFile. The $LogFile file must already be open
296# (by ${LogInit})
297!macro __LOG_WRITE TEXT
298    FileWrite $LogFile '${TEXT}$\r$\n'
299!macroend
300!define LogWrite "!insertmacro __LOG_WRITE"
301
302
303# ${ExecToLog} COMMAND
304# --------------------
305# Log the COMMAND using ${LogWrite} and execute it using nsExec::ExecToLog
306!macro __EXEC_TO_LOG COMMAND
307    ${LogWrite} 'nsExec::ExecToLog: ${COMMAND}'
308    SetDetailsPrint listonly
309    DetailPrint 'Executing: ${COMMAND}'
310    SetDetailsPrint both
311    nsExec::ExecToLog '${COMMAND}'
312!macroend
313!define ExecToLog "!insertmacro __EXEC_TO_LOG"
314
315
316# Validate the selected installation directory. Show a message and Abort
317# if it is not suitable for installation.
318Function DirectoryLeave
319    ${LogWrite} "DirectoryLeave"
320    ${LogWrite} "InstDir: $InstDir"
321    Push $R0
322    Push $R1
323    Push $R2
324    Push $R3
325    ${DirState} "$InstDir" $R0
326    ${LogWrite} "InstDir state: $R0"
327    # Check if this looks like a location of an existing install
328    ${If} $R0 == 1
329        # Directory exists and is not empty
330        ${If} ${FileExists} "$InstDir\${UNINSTALL_EXEFILE}"
331!if ${PYINSTALL_TYPE} == Private
332        ${AndIf} ${FileExists} "$InstDir\Python${PYTAG}-${BITS}\*.*"
333!else if ${PYINSTALL_TYPE} == Normal
334        ${AndIf} ${FileExists} "$InstDir\pyvenv.cfg"
335        ${AndIfNot} ${FileExists} "$InstDir\python.exe"
336            # Check that the existing env is binary compatible
337            ${GetPythonVersion} "$InstDir\Scripts\python.exe" $R1 $R2
338            ${GetPythonArch} "$InstDir\Scripts\python.exe" $R1 $R3
339            ${If} $R1 != 0
340            ${OrIf} "$R2/$R3" != "${PYMAJOR}.${PYMINOR}/${PYARCH}"
341                ${LogWrite} 'Python version/architecture mismatch \
342                             ($R2/$R3 != ${PYMAJOR}.${PYMINOR}/${PYARCH})'
343                MessageBox MB_OK '\
344                    "$InstDir" contains a pre-existing environment \
345                    that is not binary compatible with this installer.$\r$\n\
346                    Please choose another destination folder.'
347                Abort '$R1 $R2 != "${PYMAJOR}.${PYMINOR} ${PYARCH}"'
348            ${EndIf}
349            ${LogWrite} 'Found existing python in $InstDir: $R2 - $R3'
350!endif
351            MessageBox MB_YESNO|MB_ICONQUESTION \
352                '"$InstDir" looks like an existing ${APPLICATIONNAME} \
353                 installation. Do you want to overwrite it?' \
354                /SD IDYES IDYES continue_ IDNO abort_
355            abort_:
356                ${LogWrite} "Overwrite of an existing installation cancelled \
357                             by user (InstDir: $InstDir)"
358                Abort "Aborting"
359            continue_:
360                ${LogWrite} "Reusing an existing installation directory: \
361                            $InstDir"
362        ${Else}
363            ${LogWrite} "$InstDir is not empty, aborting"
364            MessageBox MB_OK '"$InstDir" exists and is not empty.$\r$\n \
365                              Please choose annother destination folder.'
366            Abort '"$InstDir" exists an is not empty'
367        ${EndIf}
368    ${EndIf}
369    Pop $R2
370    Pop $R1
371    Pop $R0
372FunctionEnd
373
374
375Function RestoreSilentInstallDir
376    ${If} $SilentInstallDir != ""
377    ${AndIf} ${Silent}
378        StrCpy $InstDir $SilentInstallDir
379        ${LogWrite} "Restored InstDir to: $SilentInstallDir"
380    ${EndIf}
381FunctionEnd
382
383
384# Section Python
385# --------------
386# Install Official Python distribution
387
388!if ${PYINSTALL_TYPE} == Private
389# Install/layout a Python installation inside the
390# $InstDir\Python${PYTAG}-${BITS} folder.
391Section "-Python ${PYTHON_VERSION} (${BITS} bit)" SectionPython
392    ${If} $InstDir == ""
393        Abort "Invalid installation prefix"
394    ${EndIf}
395    ${LogWrite} "Installing a private python installation"
396    ${ExtractTemp} "${BASEDIR}\${PYINSTALLER}" "${TEMPDIR}"
397    DetailPrint 'Installing a private Python ${PYTHON_VERSION} (${BITS} bit) \
398                 in "$InstDir\Python${PYTAG}-${BITS}"'
399
400    ${PyInstallNoRegister} "${TEMPDIR}\${PYINSTALLER}" \
401                           "$InstDir\Python${PYTAG}-${BITS}" $0
402    # msvcredist?
403    ${If} $0 != 0
404        Abort "Python installation failed (error value: $0)"
405    ${EndIf}
406    ${IfNot} ${FileExists} "$InstDir\Python${PYTAG}-${BITS}\python.exe"
407        ${LogWrite} "Failed to install Python in $InstDir$\r$\n\
408                     Python executable not found in: \
409                     $InstDir\Python${PYTAG}-${BITS}"
410        Abort "Failed to install Python in $InstDir"
411    ${EndIf}
412    ${GetPythonVersion} "$InstDir\Python${PYTAG}-${BITS}\python.exe" $0 $1
413    ${If} $0 != 0
414        Abort "Python installation failed (simple command returned an error: $0)"
415    ${EndIf}
416    StrCpy $BasePythonPrefix "$InstDir\Python${PYTAG}-${BITS}"
417SectionEnd
418
419Function un.Python
420    # Uninstall a private copy of python
421    ${If} $InstDir != ""
422    ${AndIf} ${FileExists} "$InstDir\Python${PYTAG}-${BITS}\*.*"
423        # Delete entire dir
424        RMDir /R /REBOOTOK "$InstDir\Python${PYTAG}-${BITS}"
425    ${EndIf}
426FunctionEnd
427
428!else if ${PYINSTALL_TYPE} == Normal
429# Run the embeded Python installer to install the official python distribution,
430# if one is not already installed. After the installation completes the
431# location of the installation is queried from the windows registry.
432Section "Python ${PYTHON_VERSION} (${BITS} bit)" SectionPython
433    SectionIn RO
434    ${If} $BasePythonPrefix != ""
435        # The section should heve been unselected (disabled) in .onInit
436        Abort "Installer logic error"
437    ${EndIf}
438
439    ${LogWrite} "Installing Python ${PYTHON_VERSION} (${BITS} bit)"
440    ${ExtractTemp} "${BASEDIR}\${PYINSTALLER}" "${TEMPDIR}"
441    DetailPrint "Executing external installer for Python \
442                ${PYTHON_VERSION} (${BITS} bit)"
443    # TODO: Ask for confirmation again?
444    ${PyInstall} "${TEMPDIR}\${PYINSTALLER}" $MultiUser.InstallMode $0
445    ${If} $0 != 0
446        Abort "Python installation failed (error value: $0)"
447    ${EndIf}
448    ${GetPythonInstall} \
449        "${PYMAJOR}.${PYMINOR}" $BasePythonPrefix $PythonInstallMode
450    ${If} $BasePythonPrefix == ""
451        Abort "Python installation failed (cannot determine Python \
452               installation prefix)."
453    ${EndIf}
454SectionEnd
455
456Function un.Python
457    # Nothing to do
458FunctionEnd
459!endif
460
461Section "-Python env setup" SectionEnvSetup
462    ${If} $BasePythonPrefix == ""
463        Abort "No base python configured. Cannot proceed."
464    ${EndIf}
465    ${LogWrite} "Setup effective python prefix"
466!if ${PYINSTALL_TYPE} == Private
467    ${LogWrite} "Using Python installed in $BasePythonPrefix"
468    StrCpy $PythonPrefix "$BasePythonPrefix"
469    StrCpy $PythonExecPrefix "$PythonPrefix"
470    StrCpy $PythonScriptsPrefix "$PythonPrefix\Scripts"
471!else if ${PYINSTALL_TYPE} == Normal
472    ${IfNot} ${FileExists} "$InstDir\pyvenv.cfg"
473        DetailPrint "Creating virtual env in $InstDir \
474                     (using base $BasePythonPrefix)"
475        ${LogWrite} "Creating virtaul env in $InstDir \
476                     (using base $BasePythonPrefix)"
477        ${ExecToLog} '"$BasePythonPrefix\python" -m venv "$InstDir"'
478    ${EndIf}
479    StrCpy $PythonPrefix "$InstDir"
480    StrCpy $PythonExecPrefix "$PythonPrefix\Scripts"
481    StrCpy $PythonScriptsPrefix "$PythonPrefix\Scripts"
482!endif
483    ${LogWrite} "Target environment layout:"
484    ${LogWrite} "   PythonPrefix: $PythonPrefix"
485    ${LogWrite} "   PythonExecPrefix: $PythonExecPrefix"
486    ${LogWrite} "   PythonScriptsPrefix: $PythonScriptsPrefix"
487SectionEnd
488
489Function un.Environment
490!if ${PYINSTALL_TYPE} == Normal
491    ${If} $InstDir != ""
492    ${AndIf} ${FileExists} "$InstDir\pyvenv.cfg"
493    ${AndIf} ${FileExists} "$InstDir\${UNINSTALL_EXEFILE}"
494        ${LogWrite} "Removing $InstDir"
495        # TODO: Need a more specific marker
496        RMDir /R /REBOOTOK "$InstDir"
497    ${EndIf}
498!endif
499FunctionEnd
500
501
502# Instal into the python environment in $PythonPrefix all the required packages
503Section "Install required pacakges" InstallPackages
504    SectionIn RO
505    ${If} $PythonExecPrefix == ""
506        Abort "No python executable configured. Cannot proceed."
507    ${EndIf}
508    ${ExtractTemp} "${BASEDIR}\requirements.txt" "${TEMPDIR}"
509    ${ExtractTempRec} "${BASEDIR}\wheelhouse\*.*" "${TEMPDIR}\wheelhouse"
510
511    # Install into PythonPrefix
512    ${LogWrite} "Installing packages into $PythonPrefix"
513    DetailPrint "Installing required packages"
514    ${ExecToLog} '"$PythonExecPrefix\python" -m ensurepip'
515    # First update pip to at least the bundled version (>=9)
516    ${ExecToLog} '\
517        "$PythonExecPrefix\python" -m pip install --upgrade \
518             --isolated --no-cache-dir --no-index \
519             --find-links "${TEMPDIR}\wheelhouse" \
520             pip>=9 \
521            '
522    Pop $0
523    ${If} $0 != 0
524        ${LogWrite} "pip install exited with: $0)"
525        Abort "Failed to install required packages (exit status $0)"
526    ${EndIf}
527
528    ${ExecToLog} '\
529        "$PythonExecPrefix\python" -m pip install \
530             --isolated --no-cache-dir --no-index \
531             --find-links "${TEMPDIR}\wheelhouse" \
532             -r "${TEMPDIR}\requirements.txt" \
533        '
534    Pop $0
535    ${If} $0 != 0
536        ${LogWrite} "pip install exited with: $0)"
537        Abort "Failed to install required packages (exit status $0)"
538    ${EndIf}
539SectionEnd
540
541
542Function un.InstallPackages
543FunctionEnd
544
545
546Section -Icons
547    # Layout icons if necessary (are not present)
548    ${IfNot} ${FileExists} $PythonPrefix\share\scorange\icons\*.ico"
549        ${ExtractTempRec} "${BASEDIR}\icons\*.ico" "${TEMPDIR}\icons"
550        CreateDirectory "$PythonPrefix\share\scorange\icons"
551        CopyFiles /SILENT "${TEMPDIR}\icons\*.ico" \
552                          "$PythonPrefix\share\scorange\icons"
553    ${EndIf}
554SectionEnd
555
556
557# Create utility shortcut launchers in the $InstDir
558Section -Launchers
559    SetOutPath "$InstDir"
560    DetailPrint "Creating launcher shortcuts"
561    # Startup shortcut
562    CreateShortCut \
563        "$InstDir\${LAUNCHER_SHORTCUT_NAME}.lnk" \
564        "$PythonExecPrefix\pythonw.exe" "-m orangecontrib.single_cell" \
565        "$PythonPrefix\share\scorange\icons\scOrange.ico" 0
566    # Utility shortcut to launch the application with max log level attached
567    # to the console that remains visible after exit
568    CreateShortCut \
569        "$InstDir\${LAUNCHER_SHORTCUT_NAME} Debug.lnk" \
570        "%COMSPEC%" '/K "$PythonExecPrefix\python.exe" -m orangecontrib.single_cell -l4' \
571        "$PythonPrefix\share\scorange\icons\scOrange.ico" 0
572!if ${PYINSTALL_TYPE} == Normal
573    # A utility shortcut for activating the environment
574    CreateShortCut \
575        "$InstDir\${APPNAME} Command Prompt.lnk" \
576        "%COMSPEC%" '/K "$PythonScriptsPrefix\activate.bat"'
577!endif
578
579SectionEnd
580
581
582Function un.Launchers
583    Delete "$InstDir\${LAUNCHER_SHORTCUT_NAME}.lnk"
584    Delete "$InstDir\${LAUNCHER_SHORTCUT_NAME} Debug.lnk"
585!if ${PYINSTALL_TYPE} == Normal
586    Delete "$InstDir\${APPNAME} Command Prompt.lnk"
587!endif
588FunctionEnd
589
590
591SectionGroup "Shortcuts" SectionShortcuts
592Section "Start Menu Shortcuts" SectionStartMenu
593    !insertmacro MUI_STARTMENU_WRITE_BEGIN StartMenuPageID
594    DetailPrint "Creating Start Menu Shortcuts"
595    ${If} $StartMenuFolder != ""
596        ${LogWrite} "Creating shortcuts in $SMPROGRAMS\$StartMenuFolder"
597        # Output path is used as the default CWD for the created shortcuts
598        SetDetailsPrint none
599        SetOutPath "%HOMEDRIVE%\%HOMEPATH%"
600        SetDetailsPrint both
601        CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
602        CreateShortCut \
603            "$SMPROGRAMS\$StartMenuFolder\${LAUNCHER_SHORTCUT_NAME}.lnk" \
604            "$PythonExecPrefix\pythonw.exe" "-m orangecontrib.single_cell" \
605            "$PythonPrefix\share\scorange\icons\scOrange.ico" 0
606!if ${PYINSTALL_TYPE} == Normal
607        # A utility shortcut for activating the environment
608        CreateShortCut \
609            "$SMPROGRAMS\$StartMenuFolder\${APPNAME} Command Prompt.lnk" \
610            "%COMSPEC%" '/K "$PythonScriptsPrefix\activate.bat"'
611!endif
612    ${EndIf}
613    !insertmacro MUI_STARTMENU_WRITE_END
614SectionEnd
615
616# StartMenuPage 'pre' callback. Skip Start Menu directory selection if
617# the SectionStarMenu is not selected on the Components page
618Function StartMenuPre
619    ${IfNot} ${SectionIsSelected} ${SectionStartMenu}
620        ${LogWrite} "Skiping start menu page because it is not selected"
621        Abort
622    ${EndIf}
623FunctionEnd
624
625Section "Desktop Shortcuts" SectionDesktop
626    DetailPrint "Installing Desktop shortcurt"
627    CreateShortCut \
628        "$DESKTOP\${LAUNCHER_SHORTCUT_NAME}.lnk" \
629        "$PythonExecPrefix\pythonw.exe" "-m orangecontrib.single_cell" \
630        "$PythonPrefix\share\scorange\icons\scOrange.ico" 0
631SectionEnd
632
633SectionGroupEnd
634
635Function un.Shortcuts
636    !insertmacro MUI_STARTMENU_GETFOLDER StartMenuPageID $0
637    ${If} $0 != ""
638    ${AndIf} ${FileExists} "$SMPROGRAMS\$0"
639        ${LogWrite} "Removing Start Menu Shortcuts (from $SMPROGRAMS\$0)"
640        DetailPrint "Removing Start Menu shortcuts"
641        Delete "$SMPROGRAMS\$0\${LAUNCHER_SHORTCUT_NAME}.lnk"
642        RMDir "$SMPROGRAMS\$0"
643    ${EndIf}
644    ${LogWrite} "Removing Desktop shortcurt"
645    DetailPrint "Removing Desktop shortcurt"
646    Delete "$DESKTOP\${LAUNCHER_SHORTCUT_NAME}.lnk"
647FunctionEnd
648
649
650# Should this section be made selectable by the user. Would allow multiple
651# installations.
652Section -Register SectionRegister
653    DetailPrint "Writing to registry"
654    ${LogWrite} 'Register installation layout (${INSTALL_SETTINGS_KEY})'
655    ${LogWrite} '    BasePythonPrefix "$BasePythonPrefix"'
656    WriteRegStr SHELL_CONTEXT \
657                ${INSTALL_SETTINGS_KEY} BasePythonPrefix "$BasePythonPrefix"
658    ${LogWrite} '    PythonPrefix "$PythonPrefix"'
659    WriteRegStr SHELL_CONTEXT \
660                ${INSTALL_SETTINGS_KEY} PythonPrefix "$PythonPrefix"
661    ${LogWrite} '    ${INSTALL_SETTINGS_INSTDIR} "$InstDir"'
662    WriteRegStr SHELL_CONTEXT \
663                ${INSTALL_SETTINGS_KEY} ${INSTALL_SETTINGS_INSTDIR} \
664                "$InstDir"
665
666    WriteRegStr SHELL_CONTEXT \
667                ${INSTALL_SETTINGS_KEY} ${INSTALL_SETTINGS_INSTMODE} \
668                $MultiUser.InstallMode
669    ${LogWrite} '    InstallType "${PYINSTALL_TYPE}"'
670    WriteRegStr SHELL_CONTEXT \
671                ${INSTALL_SETTINGS_KEY} InstallType ${PYINSTALL_TYPE}
672
673    ${LogWrite} "Register .ows filetype"
674    WriteRegStr SHELL_CONTEXT \
675        "Software\Classes\.ows" "" "scOrange"
676    WriteRegStr SHELL_CONTEXT \
677        "Software\Classes\scOrange" "" "Orange Workflow"
678    WriteRegStr SHELL_CONTEXT \
679        "Software\Classes\scOrange\DefaultIcon" "" \
680        "$PythonPrefix\share\scorange\icons\OrangeOWS.ico"
681    WriteRegStr SHELL_CONTEXT \
682        "Software\Classes\scOrange\Shell\Open\Command\" "" \
683        '"$PythonExecPrefix\pythonw.exe" -m orangecontrib.single_cell "%1"'
684
685    WriteUninstaller "$InstDir\${UNINSTALL_EXEFILE}"
686
687    # Register uninstaller in Add/Remove Programs
688
689    ${LogWrite} "Register uninstaller (${WINDOWS_UNINSTALL_REGKEY}\${APPNAME})"
690
691    WriteRegStr SHELL_CONTEXT \
692                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
693                DisplayName "${APPNAME} ${APPVERSION} (${BITS} bit)"
694    WriteRegStr SHELL_CONTEXT \
695                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
696                DisplayVersion "${APPVERSION}"
697    WriteRegStr SHELL_CONTEXT \
698                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
699                DisplayIcon "$InstDir\${UNINSTALL_EXEFILE}"
700    WriteRegStr SHELL_CONTEXT \
701                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
702                "UninstallString" \
703                '"$InstDir\${UNINSTALL_EXEFILE}" /$MultiUser.InstallMode'
704    WriteRegStr SHELL_CONTEXT \
705                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
706                "QuietUninstallString" \
707                '"$InstDir\${UNINSTALL_EXEFILE}" /$MultiUser.InstallMode /S'
708    WriteRegStr SHELL_CONTEXT \
709                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
710                InstallLocation "$InstDir"
711    WriteRegStr SHELL_CONTEXT \
712                "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
713                URLInfoAbout http://singlecell.biolab.si
714
715    WriteRegDWORD SHELL_CONTEXT \
716                  "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
717                   NoModify 1
718    WriteRegDWORD SHELL_CONTEXT \
719                  "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" \
720                  NoRepair 1
721SectionEnd
722
723
724Var un.InstallDir
725
726Function un.Register
727    DetailPrint "Unregister"
728    ReadRegStr $un.InstallDir SHCTX ${INSTALL_SETTINGS_KEY} InstallDir
729    ${If} $un.InstallDir != ""
730    ${AndIf} $un.InstallDir == $InstDir
731        ${LogWrite} "Deleting reg key: ${INSTALL_SETTINGS_KEY}"
732        DeleteRegKey SHCTX "${INSTALL_SETTINGS_KEY}"
733        ${LogWrite} "Deleting reg key: Software\Classes\scOrange"
734        DeleteRegKey SHCTX Software\Classes\scOrange
735    ${Else}
736        ${LogWrite} "InstallDir from ${INSTALL_SETTINGS_KEY} does not match \
737                    InstDir ($un.InstallDir != $InstDir). Leaving it."
738    ${EndIf}
739
740    ReadRegStr $un.InstallDir SHCTX \
741               "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}" InstallLocation
742    ${If} $un.InstallDir != ""
743    ${AndIf} $un.InstallDir == $InstDir
744        ${LogWrite} "Deleting reg key: ${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}"
745        DeleteRegKey SHCTX "${WINDOWS_UNINSTALL_REGKEY}\${APPNAME}"
746    ${Else}
747        ${LogWrite} "InstallLocation from \
748                     ${WINDOWS_UNINSTALL_REGKEY}\${APPNAME} does not match \
749                     InstDir ($0 != $InstDir). Leaving it."
750    ${EndIf}
751FunctionEnd
752
753Function LaunchApplication
754    ExecShell "open" "$PythonExecPrefix\pythonw.exe" "-m orangecontrib.single_cell"
755FunctionEnd
756
757
758!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
759
760!insertmacro MUI_DESCRIPTION_TEXT ${SectionPython} \
761    "Install Python ${PYTHON_VERSION} (${BITS} bit)"
762!insertmacro MUI_DESCRIPTION_TEXT ${InstallPackages} \
763    "Install required packages into the destination environment"
764!insertmacro MUI_DESCRIPTION_TEXT ${SectionStartMenu} \
765    "Install Start Menu shortcuts"
766!insertmacro MUI_DESCRIPTION_TEXT ${SectionDesktop} \
767    "Install Desktop shortcuts"
768
769!insertmacro MUI_FUNCTION_DESCRIPTION_END
770
771
772Section Uninstall
773    ReadRegStr $BasePythonPrefix SHCTX ${INSTALL_SETTINGS_KEY} BasePythonPrefix
774    ReadRegStr $PythonPrefix SHCTX ${INSTALL_SETTINGS_KEY} PythonPrefix
775    ReadRegStr $un.InstallDir SHCTX ${INSTALL_SETTINGS_KEY} InstallDir
776
777    ${LogWrite} "InstDir: $InstDir"
778    ${LogWrite} "Layout from registry:"
779    ${LogWrite} "    InstallDir: $un.InstallDir"
780    ${LogWrite} "    PythonPrefix: $PythonPrefix"
781    ${LogWrite} "    BasePythonPrefix: $BasePythonPrefix"
782
783    Call un.Register
784    Call un.Shortcuts
785    Call un.Launchers
786    Call un.InstallPackages
787    Call un.Environment
788    Call un.Python
789
790    ${If} ${FileExists} "$InstDir\${UNINSTALL_EXEFILE}"
791        Delete "$InstDir\${UNINSTALL_EXEFILE}"
792    ${EndIf}
793SectionEnd
794
795
796Function .onInit
797    ${LogInit}
798    InitPluginsDir
799    # $InstDir is not empty if specified by the /D command line switch.
800    # Store it because MultiUser.nsh will override it with its own either
801    # in MULTIUSER_INIT or MULTIUSER_PAGE_INSTALLMODE.
802    ${If} $InstDir != ""
803    ${AndIf} ${Silent}
804        StrCpy $SilentInstallDir $InstDir
805        ${LogWrite} "SilentInstallDir: $SilentInstallDir"
806    ${EndIf}
807    ${LogWrite} "Setting ${BITS} bit registry view."
808    SetRegView ${BITS}
809
810    # Initialize MultiUser.nsh
811    !insertmacro MULTIUSER_INIT
812
813!if ${PYINSTALL_TYPE} == Normal
814    ${GetPythonInstall} ${PYMAJOR}.${PYMINOR} $BasePythonPrefix $PythonInstallMode
815    ${LogWrite} "Python Prefix: $BasePythonPrefix"
816    ${LogWrite} "Python Install Type: $PythonInstallMode"
817    ${If} $BasePythonPrefix != ""
818        ${GetPythonVersion} "$BasePythonPrefix\python.exe" $0 $1
819        ${LogWrite} "Python Version: $1"
820        ${GetPythonArch} "$BasePythonPrefix\python.exe" $0 $1
821        ${LogWrite} "Python platform: $1"
822
823        # Found an appropriate python installation and can reuse it
824        # Change the SectionPython to Unselected
825        SectionGetText ${SectionPython} $0
826        SectionSetText ${SectionPython} "$0 - Already installed"
827        !insertmacro UnselectSection ${SectionPython}
828        !insertmacro SetSectionFlag ${SectionPython} ${SF_RO}
829    ${Else}
830        !insertmacro SelectSection ${SectionPython}
831        !insertmacro SetSectionFlag ${SectionPython} ${SF_RO}
832    ${EndIf}
833!endif
834FunctionEnd
835
836
837Function un.onInit
838    ${LogInit}
839    ${LogWrite} "Setting ${BITS} bit registry view."
840    SetRegView ${BITS}
841    !insertmacro MULTIUSER_UNINIT
842FunctionEnd
843