From 860aa0dafd4cfc51abaf2115726711275248da49 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 8 Jun 2021 23:46:20 +0200 Subject: [PATCH] Improvements to bootstrap scripts --- bootstrap-haskell | 17 ++-- bootstrap-haskell.ps1 | 214 ++++++++++++++++++++++++++++-------------- 2 files changed, 148 insertions(+), 83 deletions(-) diff --git a/bootstrap-haskell b/bootstrap-haskell index d25a492..412ec8d 100755 --- a/bootstrap-haskell +++ b/bootstrap-haskell @@ -415,17 +415,13 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then case $MY_SHELL in "") break ;; fish) - if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then mkdir -p "${GHCUP_PROFILE_FILE%/*}" - echo "# ghcup-env" >> "${GHCUP_PROFILE_FILE}" - echo "set -q GHCUP_INSTALL_BASE_PREFIX[1]; or set GHCUP_INSTALL_BASE_PREFIX \$HOME" >> "${GHCUP_PROFILE_FILE}" - echo "test -f $GHCUP_DIR/env ; and set -gx PATH \$HOME/.cabal/bin $GHCUP_BIN \$PATH" >> "${GHCUP_PROFILE_FILE}" - fi + sed -i -e '/# ghcup-env$/ s/^#*/#/' "${GHCUP_PROFILE_FILE}" + echo "set -q GHCUP_INSTALL_BASE_PREFIX[1]; or set GHCUP_INSTALL_BASE_PREFIX \$HOME ; test -f $GHCUP_DIR/env ; and set -gx PATH \$HOME/.cabal/bin $GHCUP_BIN \$PATH # ghcup-env" >> "${GHCUP_PROFILE_FILE}" break ;; bash) - if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then - echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" - fi + sed -i -e '/# ghcup-env$/ s/^#*/#/' "${GHCUP_PROFILE_FILE}" + echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" case "${plat}" in "Darwin"|"darwin") if ! grep -q "ghcup-env" "${HOME}/.bash_profile" ; then @@ -436,9 +432,8 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then break ;; zsh) - if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then - echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" - fi + sed -i -e '/# ghcup-env$/ s/^#*/#/' "${GHCUP_PROFILE_FILE}" + echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" break ;; esac warn "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect," diff --git a/bootstrap-haskell.ps1 b/bootstrap-haskell.ps1 index 71a91a4..9045ecd 100644 --- a/bootstrap-haskell.ps1 +++ b/bootstrap-haskell.ps1 @@ -1,3 +1,30 @@ +<# + .SYNOPSIS + Script to bootstrap a Haskell environment + + .DESCRIPTION + This is the windows GHCup installer, installing: + + * ghcup - The Haskell toolchain installer" + * ghc - The Glasgow Haskell Compiler" + * msys2 - Unix-style toolchain needed for dependencies and tools + * cabal - The Cabal build tool for managing Haskell software" + * stack - (optional) A cross-platform program for developing Haskell projects" + * hls - (optional) A language server for developers to integrate with their editor/IDE" +#> +param ( + # Run a non-interactive installation + [bool]$Silent = $false, + # Specify the install root (default: 'C:\') + [string]$InstallDir, + # Instead of installing a new MSys2, use an existing installation + [string]$ExistingMsys2Dir, + # Perform a quick installation, omitting some expensive operations (you may have to install dependencies yourself later) + [bool]$Quick, + # Overwrite (or rather backup) a previous install + [bool]$Overwrite +) + function Print-Msg { param ( [Parameter(Mandatory=$true, HelpMessage='String to output')][string]$msg, [string]$color = "Green" ) Write-Host ('{0}' -f $msg) -ForegroundColor $color @@ -15,51 +42,48 @@ function Create-Shortcut { } function Add-EnvPath { - param( - [Parameter(Mandatory=$true,HelpMessage='The Pathe to add to Users environment')] - [string] $Path, + param( + [Parameter(Mandatory=$true,HelpMessage='The Path to add to Users environment')] + [string] $Path, - [ValidateSet('Machine', 'User', 'Session')] - [string] $Container = 'Session' - ) + [ValidateSet('Machine', 'User', 'Session')] + [string] $Container = 'Session' + ) - function Where-Something - { - param - ( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage='Data to filter')] - $InputObject - ) - process - { - if ($InputObject) - { - $InputObject - } + if ($Container -eq 'Session') { + $envPaths = [Collections.Generic.List[String]]($env:Path -split ([IO.Path]::PathSeparator)) + if ($envPaths -notcontains $Path) { + $envPaths.Add($Path) + $env:PATH = $envPaths -join ([IO.Path]::PathSeparator) + } + } + else { + [Microsoft.Win32.RegistryHive]$hive, $keyPath = switch ($Container) { + 'Machine' { 'LocalMachine', 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment' } + 'User' { 'CurrentUser', 'Environment' } } - } - if ($Container -ne 'Session') { - $containerMapping = @{ - Machine = [EnvironmentVariableTarget]::Machine - User = [EnvironmentVariableTarget]::User - } - $containerType = $containerMapping[$Container] + $hiveKey = $envKey = $null + try { + $hiveKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, 'Default') + $envKey = $hiveKey.OpenSubKey($keyPath, $true) + $rawPath = $envKey.GetValue('PATH', '', 'DoNotExpandEnvironmentNames') - $persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';' - if ($persistedPaths -notcontains $Path) { - $persistedPaths = $persistedPaths + $Path | Where-Something - [Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType) - } - } - - $envPaths = $env:Path -split ';' - if ($envPaths -notcontains $Path) { - $envPaths = $envPaths + $Path | Where-Something - $env:Path = $envPaths -join ';' - } + $envPaths = [Collections.Generic.List[String]]($rawPath -split ([IO.Path]::PathSeparator)) + if ($envPaths -notcontains $Path) { + $envPaths.Add($Path) + $envKey.SetValue('PATH', ($envPaths -join ([IO.Path]::PathSeparator)), 'ExpandString') + } + } + finally { + if ($envKey) { $envKey.Dispose() } + if ($hiveKey) { $hiveKey.Dispose() } + } + } } + + filter Get-FileSize { '{0:N2} {1}' -f $( if ($_ -lt 1kb) { $_, 'Bytes' } @@ -101,32 +125,47 @@ function Test-AbsolutePath { $ErrorActionPreference = 'Stop' -$GhcupBasePrefix = [System.Environment]::GetEnvironmentVariable('GHCUP_INSTALL_BASE_PREFIX', 'user') +$GhcupBasePrefixEnv = [System.Environment]::GetEnvironmentVariable('GHCUP_INSTALL_BASE_PREFIX', 'user') -if ($GhcupBasePrefix) { - $defaultGhcupBasePrefix = $GhcupBasePrefix +if ($GhcupBasePrefixEnv) { + $defaultGhcupBasePrefix = $GhcupBasePrefixEnv } else { $defaultGhcupBasePrefix = 'C:\' } -while ($true) { - Print-Msg -color Magenta -msg ('Where to install to (this should be a short Path, preferably a Drive like ''C:\''){1}Press enter to accept the default [{0}]:' -f $defaultGhcupBasePrefix, "`n") - $basePrefixPrompt = Read-Host - $GhcupBasePrefix = ($defaultGhcupBasePrefix,$basePrefixPrompt)[[bool]$basePrefixPrompt] - if (!($GhcupBasePrefix.EndsWith('\'))) { - $GhcupBasePrefix = ('{0}\' -f $GhcupBasePrefix) - } - - if (!($GhcupBasePrefix)) { - Print-Msg -color Red -msg "No directory specified!" - } elseif (!(Test-Path -LiteralPath ('{0}' -f $GhcupBasePrefix))) { - Print-Msg -color Red -msg "Directory does not exist, need to specify an existing Drive/Directory" - } elseif (!(Split-Path -IsAbsolute -Path "$GhcupBasePrefix")) { - Print-Msg -color Red -msg "Invalid/Non-absolute Path specified" +if ($Silent -and !($InstallDir)) { + $GhcupBasePrefix = $defaultGhcupBasePrefix +} elseif ($InstallDir) { + if (!(Test-Path -LiteralPath ('{0}' -f $InstallDir) -IsValid)) { + Print-Msg -color Red -msg "Not a valid directory!" + Exit 1 + } elseif (!(Split-Path -IsAbsolute -Path "$InstallDir")) { + Print-Msg -color Red -msg "Non-absolute Path specified!" + Exit 1 } else { - Break + $GhcupBasePrefix = $InstallDir + } +} else { + while ($true) { + Print-Msg -color Magenta -msg ('Where to install to (this should be a short Path, preferably a Drive like ''C:\''){1}Press enter to accept the default [{0}]:' -f $defaultGhcupBasePrefix, "`n") + $basePrefixPrompt = Read-Host + $GhcupBasePrefix = ($defaultGhcupBasePrefix,$basePrefixPrompt)[[bool]$basePrefixPrompt] + if (!($GhcupBasePrefix.EndsWith('\'))) { + $GhcupBasePrefix = ('{0}\' -f $GhcupBasePrefix) + } + + if (!($GhcupBasePrefix)) { + Print-Msg -color Red -msg "No directory specified!" + } elseif (!(Test-Path -LiteralPath ('{0}' -f $GhcupBasePrefix))) { + Print-Msg -color Red -msg "Directory does not exist, need to specify an existing Drive/Directory" + } elseif (!(Split-Path -IsAbsolute -Path "$GhcupBasePrefix")) { + Print-Msg -color Red -msg "Invalid/Non-absolute Path specified" + } else { + Break + } } } + Print-Msg -msg ('Setting env variable GHCUP_INSTALL_BASE_PREFIX to ''{0}''' -f $GhcupBasePrefix) $null = [Environment]::SetEnvironmentVariable("GHCUP_INSTALL_BASE_PREFIX", $GhcupBasePrefix, [System.EnvironmentVariableTarget]::User) @@ -140,11 +179,19 @@ $GhcupMsys2 = [System.Environment]::GetEnvironmentVariable('GHCUP_MSYS2', 'user' Print-Msg -msg 'Preparing for GHCup installation...' if (Test-Path -LiteralPath ('{0}' -f $GhcupDir)) { - $decision = $Host.UI.PromptForChoice('Install GHCup' - , 'GHCup is already installed, what do you want to do?' - , @('&Reinstall' - '&Continue' - '&Abort'), 1) + Print-Msg -msg ('GHCup already installed at ''{0}''...' -f $GhcupDir) + if ($Overwrite) { + $decision = 0 + } elseif (!($Silent)) { + $decision = $Host.UI.PromptForChoice('Install GHCup' + , 'GHCup is already installed, what do you want to do?' + , @('&Reinstall' + '&Continue' + '&Abort'), 1) + } else { + $decision = 1 + } + if ($decision -eq 0) { $suffix = [IO.Path]::GetRandomFileName() Print-Msg -msg ('Backing up {0} to {0}-{1} ...' -f $GhcupDir, $suffix) @@ -152,7 +199,7 @@ if (Test-Path -LiteralPath ('{0}' -f $GhcupDir)) { } elseif ($decision -eq 1) { Print-Msg -msg 'Continuing installation...' } elseif ($decision -eq 2) { - Break + Exit 0 } } @@ -163,10 +210,15 @@ $null = New-Item -Path ('{0}' -f $GhcupDir) -Name 'bin' -ItemType 'directory' -E Print-Msg -msg 'First checking for Msys2...' if (!(Test-Path -Path ('{0}' -f $MsysDir))) { - $msys2Decision = $Host.UI.PromptForChoice('Install MSys2' - , 'Do you want GHCup to install a default MSys2 toolchain (recommended)?' - , @('&Yes' - '&No'), 0) + if ($Silent) { + $msys2Decision = 0 + } else { + $msys2Decision = $Host.UI.PromptForChoice('Install MSys2' + , 'Do you want GHCup to install a default MSys2 toolchain (recommended)?' + , @('&Yes' + '&No'), 0) + } + if ($msys2Decision -eq 0) { Print-Msg -msg ('...Msys2 doesn''t exist, installing into {0} ...this may take a while' -f $MsysDir) @@ -195,8 +247,20 @@ if (!(Test-Path -Path ('{0}' -f $MsysDir))) { Print-Msg -msg 'Upgrading full system twice...' & "$Bash" -lc 'pacman --noconfirm -Syuu' - Print-Msg -msg 'Installing GHC Build Dependencies...' - & "$Bash" -lc 'pacman --noconfirm -S --needed git tar curl wget base-devel gettext binutils autoconf make libtool automake python p7zip patch unzip mingw-w64-x86_64-toolchain mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb mingw-w64-x86_64-python2 mingw-w64-x86_64-python3-sphinx' + if ($Quick) { + $ghcBuildDeps = $Quick + } elseif (!($Silent)) { + $ghcBuildDeps = $Host.UI.PromptForChoice('Install Dependencies' + , 'Install various dependencies to be able to build GHC itself and make use of ''ghcup compile'' command? (recommended, however this might take a while)' + , @('&Yes' + '&No'), 0) + } else { + $ghcBuildDeps = 0 + } + if ($ghcBuildDeps -eq 0) { + Print-Msg -msg 'Installing GHC Build Dependencies...' + & "$Bash" -lc 'pacman --noconfirm -S --needed git tar curl wget base-devel gettext binutils autoconf make libtool automake python p7zip patch unzip mingw-w64-x86_64-toolchain mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb mingw-w64-x86_64-python2 mingw-w64-x86_64-python3-sphinx' + } Print-Msg -msg 'Updating SSL root certificate authorities...' & "$Bash" -lc 'pacman --noconfirm -S ca-certificates' @@ -238,16 +302,22 @@ Create-Shortcut -SourceExe ('{0}\msys2_shell.cmd' -f $MsysDir) -ArgumentsToSourc Create-Shortcut -SourceExe 'https://www.msys2.org/docs/package-management' -ArgumentsToSourceExe '' -DestinationPath ('{0}\Desktop\Mingw package management docs.url' -f $HOME) Print-Msg -msg ('Adding {0}\bin to Users Path...' -f $GhcupDir) -Add-EnvPath -Path ('{0}\bin' -f $GhcupDir) -Container 'User' +Add-EnvPath -Path ('{0}\bin' -f ([System.IO.Path]::GetFullPath("$GhcupDir"))) -Container 'User' Print-Msg -msg 'Starting GHCup installer...' $Msys2Shell = ('{0}\msys2_shell.cmd' -f $MsysDir) -if ((Get-Process -ID $PID).ProcessName.StartsWith("bootstrap-haskell")) { - & "$Bash" -lc ('[ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir) +if ($Silent) { + $SilentExport = 'export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 ;' } else { - & "$Msys2Shell" -mingw64 -mintty -c ('[ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; trap ''echo Press any key to exit && read -n 1 && exit'' 2 ; curl --proto =https --tlsv1.2 -sSf -k {0} | bash ; echo ''Press any key to exit'' && read -n 1' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir) + $SilentExport = '' +} + +if ((Get-Process -ID $PID).ProcessName.StartsWith("bootstrap-haskell")) { + & "$Bash" -lc ('{4} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport) +} else { + & "$Msys2Shell" -mingw64 -mintty -c ('{4} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; trap ''echo Press any key to exit && read -n 1 && exit'' 2 ; curl --proto =https --tlsv1.2 -sSf -k {0} | bash ; echo ''Press any key to exit'' && read -n 1' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport) }