# Licensed under the MIT license # , at your # option. This file may not be copied, modified, or distributed # except according to those terms. <# .SYNOPSIS The installer for git_rnd_name 0.2.0 .DESCRIPTION This script detects what platform you're on and fetches an appropriate archive from https://github.com/W-Mai/git_rnd_name/releases/download/v0.2.0 then unpacks the binaries and installs them to $env:CARGO_HOME/bin (or $HOME/.cargo/bin) It will then add that dir to PATH by editing your Environment.Path registry key .PARAMETER NoModifyPath Don't add the install directory to PATH .PARAMETER Help Print help #> param ( [Parameter(HelpMessage = "Don't add the install directory to PATH")] [switch]$NoModifyPath, [Parameter(HelpMessage = "Print Help")] [switch]$Help ) $app_name = 'git_rnd_name' $app_version = '0.2.0' if ($env:GIT_RND_NAME_DOWNLOAD_URL) { $ArtifactDownloadUrls = @($env:GIT_RND_NAME_DOWNLOAD_URL) } elseif ($env:INSTALLER_DOWNLOAD_URL) { $ArtifactDownloadUrls = @($env:INSTALLER_DOWNLOAD_URL) } elseif ($env:GIT_RND_NAME_INSTALLER_GHE_BASE_URL) { $installer_base_url = $env:GIT_RND_NAME_INSTALLER_GHE_BASE_URL $ArtifactDownloadUrls = @("$installer_base_url/W-Mai/git_rnd_name/releases/download/v0.2.0") } elseif ($env:GIT_RND_NAME_INSTALLER_GITHUB_BASE_URL) { $installer_base_url = $env:GIT_RND_NAME_INSTALLER_GITHUB_BASE_URL $ArtifactDownloadUrls = @("$installer_base_url/W-Mai/git_rnd_name/releases/download/v0.2.0") } else { $ArtifactDownloadUrls = @("https://github.com/W-Mai/git_rnd_name/releases/download/v0.2.0") } $auth_token = $env:GIT_RND_NAME_GITHUB_TOKEN $receipt = @" {"binaries":["CARGO_DIST_BINS"],"binary_aliases":{},"cdylibs":["CARGO_DIST_DYLIBS"],"cstaticlibs":["CARGO_DIST_STATICLIBS"],"install_layout":"unspecified","install_prefix":"AXO_INSTALL_PREFIX","modify_path":true,"provider":{"source":"cargo-dist","version":"0.31.0"},"source":{"app_name":"git_rnd_name","name":"git_rnd_name","owner":"W-Mai","release_type":"github"},"version":"0.2.0"} "@ if ($env:XDG_CONFIG_HOME) { $receipt_home = "${env:XDG_CONFIG_HOME}\git_rnd_name" } else { $receipt_home = "${env:LOCALAPPDATA}\git_rnd_name" } if ($env:GIT_RND_NAME_DISABLE_UPDATE) { $install_updater = $false } else { $install_updater = $true } if ($NoModifyPath) { Write-Information "-NoModifyPath has been deprecated; please set GIT_RND_NAME_NO_MODIFY_PATH=1 in the environment" } if ($env:GIT_RND_NAME_NO_MODIFY_PATH) { $NoModifyPath = $true } $unmanaged_install = $env:GIT_RND_NAME_UNMANAGED_INSTALL if ($unmanaged_install) { $NoModifyPath = $true $install_updater = $false } function Install-Binary($install_args) { if ($Help) { Get-Help $PSCommandPath -Detailed Exit } Initialize-Environment # Platform info injected by dist $platforms = @{ "aarch64-pc-windows-msvc" = @{ "artifact_name" = "git_rnd_name-x86_64-pc-windows-msvc.zip" "bins" = @("grn.exe") "libs" = @() "staticlibs" = @() "zip_ext" = ".zip" "aliases" = @{ } "aliases_json" = '{}' } "x86_64-pc-windows-gnu" = @{ "artifact_name" = "git_rnd_name-x86_64-pc-windows-msvc.zip" "bins" = @("grn.exe") "libs" = @() "staticlibs" = @() "zip_ext" = ".zip" "aliases" = @{ } "aliases_json" = '{}' } "x86_64-pc-windows-msvc" = @{ "artifact_name" = "git_rnd_name-x86_64-pc-windows-msvc.zip" "bins" = @("grn.exe") "libs" = @() "staticlibs" = @() "zip_ext" = ".zip" "aliases" = @{ } "aliases_json" = '{}' } } $arch = Get-TargetTriple $platforms if (-not $platforms.ContainsKey($arch)) { $platforms_json = ConvertTo-Json $platforms throw "ERROR: could not find binaries for this platform. Last platform tried: $arch platform info: $platforms_json" } Write-Information "downloading $app_name $app_version ($arch)" $download_result = $false $first_url = $true foreach ($url in $ArtifactDownloadUrls) { if (-not $first_url) { Write-Information "trying alternative download URL" } $first_url = $false try { $fetched = Download -download_url "$url" -platforms $platforms -arch $arch $download_result = $true break } catch { Write-Information "failed to download from $url" # keep going, maybe we have backup download URLs } } if (-not $download_result) { throw "failed to download binaries" } # FIXME: add a flag that lets the user not do this step try { Invoke-Installer -artifacts $fetched -platforms $platforms "$install_args" } catch { throw @" We encountered an error trying to perform the installation; please review the error messages below. $_ "@ } } function Get-TargetTriple($platforms) { $double = Get-Arch if ($platforms.Contains("$double-msvc")) { return "$double-msvc" } else { return "$double-gnu" } } function Get-Arch() { try { # NOTE: this might return X64 on ARM64 Windows, which is OK since emulation is available. # It works correctly starting in PowerShell Core 7.3 and Windows PowerShell in Win 11 22H2. # Ideally this would just be # [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture # but that gets a type from the wrong assembly on Windows PowerShell (i.e. not Core) $a = [System.Reflection.Assembly]::LoadWithPartialName("System.Runtime.InteropServices.RuntimeInformation") $t = $a.GetType("System.Runtime.InteropServices.RuntimeInformation") $p = $t.GetProperty("OSArchitecture") # Possible OSArchitecture Values: https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.architecture # Rust supported platforms: https://doc.rust-lang.org/stable/rustc/platform-support.html switch ($p.GetValue($null).ToString()) { "X86" { return "i686-pc-windows" } "X64" { return "x86_64-pc-windows" } "Arm" { return "thumbv7a-pc-windows" } "Arm64" { return "aarch64-pc-windows" } } } catch { # The above was added in .NET 4.7.1, so Windows PowerShell in versions of Windows # prior to Windows 10 v1709 may not have this API. Write-Verbose "Get-TargetTriple: Exception when trying to determine OS architecture." Write-Verbose $_ } # This is available in .NET 4.0. We already checked for PS 5, which requires .NET 4.5. Write-Verbose("Get-TargetTriple: falling back to Is64BitOperatingSystem.") if ([System.Environment]::Is64BitOperatingSystem) { return "x86_64-pc-windows" } else { return "i686-pc-windows" } } function WebProxyFromUrl { param([string]$ProxyUrl) if ([string]::IsNullOrWhiteSpace($ProxyUrl)) { return $null } try { # Parse the proxy URL $uri = [System.Uri]$ProxyUrl # Create WebProxy instance $webProxy = New-Object System.Net.WebProxy($uri) # Set credentials if provided in URL if (-not [string]::IsNullOrEmpty($uri.UserInfo)) { $userInfo = $uri.UserInfo.Split(':') $username = [System.Uri]::UnescapeDataString($userInfo[0]) $password = if ($null -eq $userInfo[1]) { "" } else { [System.Uri]::UnescapeDataString($userInfo[1]) } $webProxy.Credentials = New-Object System.Net.NetworkCredential($username, $password) } return $webProxy } catch { Write-Verbose("Failed to parse proxy URL '$ProxyUrl': $($_.Exception.Message)") return $null } } function WebProxyFromEnvironment { $httpsProxy = [System.Environment]::GetEnvironmentVariable("HTTPS_PROXY") $allProxy = [System.Environment]::GetEnvironmentVariable("ALL_PROXY") $proxyUrl = if (-not [string]::IsNullOrWhiteSpace($httpsProxy)) { $httpsProxy } else { $allProxy } $webProxy = WebProxyFromUrl -ProxyUrl $proxyUrl return $webProxy } function Download($download_url, $platforms, $arch) { # Lookup what we expect this platform to look like $info = $platforms[$arch] $zip_ext = $info["zip_ext"] $bin_names = $info["bins"] $lib_names = $info["libs"] $staticlib_names = $info["staticlibs"] $artifact_name = $info["artifact_name"] # Make a new temp dir to unpack things to $tmp = New-Temp-Dir $dir_path = "$tmp\$app_name$zip_ext" # Download and unpack! $url = "$download_url/$artifact_name" Write-Verbose " from $url" Write-Verbose " to $dir_path" $wc = New-Object Net.Webclient $proxy = WebProxyFromEnvironment if ($null -ne $proxy) { $wc.Proxy = $proxy } if ($auth_token) { $wc.Headers["Authorization"] = "Bearer $auth_token" } $wc.downloadFile($url, $dir_path) Write-Verbose "Unpacking to $tmp" # Select the tool to unpack the files with. # # As of windows 10(?), powershell comes with tar preinstalled, but in practice # it only seems to support .tar.gz, and not xz/zstd. Still, we should try to # forward all tars to it in case the user has a machine that can handle it! switch -Wildcard ($zip_ext) { ".zip" { Expand-Archive -Path $dir_path -DestinationPath "$tmp"; Break } ".tar.*" { tar xf $dir_path --strip-components 1 -C "$tmp"; Break } Default { throw "ERROR: unknown archive format $zip_ext" } } # Let the next step know what to copy $bin_paths = @() foreach ($bin_name in $bin_names) { Write-Verbose " Unpacked $bin_name" $bin_paths += "$tmp\$bin_name" } $lib_paths = @() foreach ($lib_name in $lib_names) { Write-Verbose " Unpacked $lib_name" $lib_paths += "$tmp\$lib_name" } $staticlib_paths = @() foreach ($lib_name in $staticlib_names) { Write-Verbose " Unpacked $lib_name" $staticlib_paths += "$tmp\$lib_name" } if (($null -ne $info["updater"]) -and $install_updater) { $updater_id = $info["updater"]["artifact_name"] $updater_url = "$download_url/$updater_id" $out_name = "$tmp\git_rnd_name-update.exe" $wc.downloadFile($updater_url, $out_name) $bin_paths += $out_name } return @{ "bin_paths" = $bin_paths "lib_paths" = $lib_paths "staticlib_paths" = $staticlib_paths } } function Invoke-Installer($artifacts, $platforms) { # Replaces the placeholder binary entry with the actual list of binaries $arch = Get-TargetTriple $platforms if (-not $platforms.ContainsKey($arch)) { $platforms_json = ConvertTo-Json $platforms throw "ERROR: could not find binaries for this platform. Last platform tried: $arch platform info: $platforms_json" } $info = $platforms[$arch] # Forces the install to occur at this path, not the default $force_install_dir = $null $install_layout = "unspecified" # Check the newer app-specific variable before falling back # to the older generic one if (($env:GIT_RND_NAME_INSTALL_DIR)) { $force_install_dir = $env:GIT_RND_NAME_INSTALL_DIR $install_layout = "cargo-home" } elseif (($env:CARGO_DIST_FORCE_INSTALL_DIR)) { $force_install_dir = $env:CARGO_DIST_FORCE_INSTALL_DIR $install_layout = "cargo-home" } elseif ($unmanaged_install) { $force_install_dir = $unmanaged_install $install_layout = "flat" } # Check if the install layout should be changed from `flat` to `cargo-home` # for backwards compatible updates of applications that switched layouts. if (($force_install_dir) -and ($install_layout -eq "flat")) { # If the install directory is targeting the Cargo home directory, then # we assume this application was previously installed that layout # Note the installer passes the path with `\\` separators, but here they are # `\` so we normalize for comparison. We don't use `Resolve-Path` because they # may not exist. $cargo_home = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { Join-Path $(if ($HOME) { $HOME } else { "." }) ".cargo" } if ($force_install_dir.Replace('\\', '\') -eq $cargo_home) { $install_layout = "cargo-home" } } # The actual path we're going to install to $dest_dir = $null $dest_dir_lib = $null # The install prefix we write to the receipt. # For organized install methods like CargoHome, which have # subdirectories, this is the root without `/bin`. For other # methods, this is the same as `_install_dir`. $receipt_dest_dir = $null # Before actually consulting the configured install strategy, see # if we're overriding it. if (($force_install_dir)) { switch ($install_layout) { "hierarchical" { $dest_dir = Join-Path $force_install_dir "bin" $dest_dir_lib = Join-Path $force_install_dir "lib" } "cargo-home" { $dest_dir = Join-Path $force_install_dir "bin" $dest_dir_lib = $dest_dir } "flat" { $dest_dir = $force_install_dir $dest_dir_lib = $dest_dir } Default { throw "Error: unrecognized installation layout: $install_layout" } } $receipt_dest_dir = $force_install_dir } if (-Not $dest_dir) { # first try $env:CARGO_HOME, then fallback to $HOME # (for whatever reason $HOME is not a normal env var and doesn't need the $env: prefix) $root = if (($base_dir = $env:CARGO_HOME)) { $base_dir } elseif (($base_dir = $HOME)) { Join-Path $base_dir ".cargo" } else { throw "ERROR: could not find your HOME dir or CARGO_HOME to install binaries to" } $dest_dir = Join-Path $root "bin" $dest_dir_lib = $dest_dir $receipt_dest_dir = $root $install_layout = "cargo-home" } # Looks like all of the above assignments failed if (-Not $dest_dir) { throw "ERROR: could not find a valid path to install to; please check the installation instructions" } # The replace call here ensures proper escaping is inlined into the receipt $receipt = $receipt.Replace('AXO_INSTALL_PREFIX', $receipt_dest_dir.replace("\", "\\")) $receipt = $receipt.Replace('"install_layout":"unspecified"', -join('"install_layout":"', $install_layout, '"')) $dest_dir = New-Item -Force -ItemType Directory -Path $dest_dir $dest_dir_lib = New-Item -Force -ItemType Directory -Path $dest_dir_lib Write-Information "installing to $dest_dir" # Just copy the binaries from the temp location to the install dir foreach ($bin_path in $artifacts["bin_paths"]) { $installed_file = Split-Path -Path "$bin_path" -Leaf Copy-Item "$bin_path" -Destination "$dest_dir" -ErrorAction Stop Remove-Item "$bin_path" -Recurse -Force -ErrorAction Stop Write-Information " $installed_file" if (($dests = $info["aliases"][$installed_file])) { $source = Join-Path "$dest_dir" "$installed_file" foreach ($dest_name in $dests) { $dest = Join-Path $dest_dir $dest_name $null = New-Item -ItemType HardLink -Target "$source" -Path "$dest" -Force -ErrorAction Stop } } } foreach ($lib_path in $artifacts["lib_paths"]) { $installed_file = Split-Path -Path "$lib_path" -Leaf Copy-Item "$lib_path" -Destination "$dest_dir_lib" -ErrorAction Stop Remove-Item "$lib_path" -Recurse -Force -ErrorAction Stop Write-Information " $installed_file" } foreach ($lib_path in $artifacts["staticlib_paths"]) { $installed_file = Split-Path -Path "$lib_path" -Leaf Copy-Item "$lib_path" -Destination "$dest_dir_lib" -ErrorAction Stop Remove-Item "$lib_path" -Recurse -Force -ErrorAction Stop Write-Information " $installed_file" } $formatted_bins = ($info["bins"] | ForEach-Object { '"' + $_ + '"' }) -join "," $receipt = $receipt.Replace('"CARGO_DIST_BINS"', $formatted_bins) $formatted_libs = ($info["libs"] | ForEach-Object { '"' + $_ + '"' }) -join "," $receipt = $receipt.Replace('"CARGO_DIST_DYLIBS"', $formatted_libs) $formatted_staticlibs = ($info["staticlibs"] | ForEach-Object { '"' + $_ + '"' }) -join "," $receipt = $receipt.Replace('"CARGO_DIST_STATICLIBS"', $formatted_staticlibs) # Also replace the aliases with the arch-specific one $receipt = $receipt.Replace('"binary_aliases":{}', -join('"binary_aliases":', $info['aliases_json'])) if ($NoModifyPath) { $receipt = $receipt.Replace('"modify_path":true', '"modify_path":false') } # Write the install receipt if ($install_updater) { $null = New-Item -Path $receipt_home -ItemType "directory" -ErrorAction SilentlyContinue # Trying to get Powershell 5.1 (not 6+, which is fake and lies) to write utf8 is a crime # because "Out-File -Encoding utf8" actually still means utf8BOM, so we need to pull out # .NET's APIs which actually do what you tell them (also apparently utf8NoBOM is the # default in newer .NETs but I'd rather not rely on that at this point). $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [IO.File]::WriteAllLines("$receipt_home/git_rnd_name-receipt.json", "$receipt", $Utf8NoBomEncoding) } # Respect the environment, but CLI takes precedence if ($null -eq $NoModifyPath) { $NoModifyPath = $env:INSTALLER_NO_MODIFY_PATH } Write-Information "everything's installed!" if (-not $NoModifyPath) { Add-Ci-Path $dest_dir if (Add-Path $dest_dir) { Write-Information "" Write-Information "To add $dest_dir to your PATH, either restart your shell or run:" Write-Information "" Write-Information " set Path=$dest_dir;%Path% (cmd)" Write-Information " `$env:Path = `"$dest_dir;`$env:Path`" (powershell)" } } } # Attempt to do CI-specific rituals to get the install-dir on PATH faster function Add-Ci-Path($OrigPathToAdd) { # If GITHUB_PATH is present, then write install_dir to the file it refs. # After each GitHub Action, the contents will be added to PATH. # So if you put a curl | sh for this script in its own "run" step, # the next step will have this dir on PATH. # # Note that GITHUB_PATH will not resolve any variables, so we in fact # want to write the install dir and not an expression that evals to it if (($gh_path = $env:GITHUB_PATH)) { Write-Output "$OrigPathToAdd" | Out-File -FilePath "$gh_path" -Encoding utf8 -Append } } # Try to permanently add the given path to the user-level # PATH via the registry # # Returns true if the registry was modified, otherwise returns false # (indicating it was already on PATH) # # This is a lightly modified version of this solution: # https://stackoverflow.com/questions/69236623/adding-path-permanently-to-windows-using-powershell-doesnt-appear-to-work/69239861#69239861 function Add-Path($LiteralPath) { Write-Verbose "Adding $LiteralPath to your user-level PATH" $RegistryPath = 'registry::HKEY_CURRENT_USER\Environment' # Note the use of the .GetValue() method to ensure that the *unexpanded* value is returned. # If 'Path' is not an existing item in the registry, '' is returned. $CurrentDirectories = (Get-Item -LiteralPath $RegistryPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne '' if ($LiteralPath -in $CurrentDirectories) { Write-Verbose "Install directory $LiteralPath already on PATH, all done!" return $false } Write-Verbose "Actually mutating 'Path' Property" # Add the new path to the front of the PATH. # The ',' turns $LiteralPath into an array, which the array of # $CurrentDirectories is then added to. $NewPath = (,$LiteralPath + $CurrentDirectories) -join ';' # Update the registry. Will create the property if it did not already exist. # Note the use of ExpandString to create a registry property with a REG_EXPAND_SZ data type. Set-ItemProperty -Type ExpandString -LiteralPath $RegistryPath Path $NewPath # Broadcast WM_SETTINGCHANGE to get the Windows shell to reload the # updated environment, via a dummy [Environment]::SetEnvironmentVariable() operation. $DummyName = 'cargo-dist-' + [guid]::NewGuid().ToString() [Environment]::SetEnvironmentVariable($DummyName, 'cargo-dist-dummy', 'User') [Environment]::SetEnvironmentVariable($DummyName, [NullString]::value, 'User') Write-Verbose "Successfully added $LiteralPath to your user-level PATH" return $true } function Initialize-Environment() { If (($PSVersionTable.PSVersion.Major) -lt 5) { throw @" Error: PowerShell 5 or later is required to install $app_name. Upgrade PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell "@ } # show notification to change execution policy: $allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'Bypass') If ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) { throw @" Error: PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ", ")] to run $app_name. For example, to set the execution policy to 'RemoteSigned' please run: Set-ExecutionPolicy RemoteSigned -scope CurrentUser "@ } # GitHub requires TLS 1.2 If ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') { throw @" Error: Installing $app_name requires at least .NET Framework 4.5 Please download and install it first: https://www.microsoft.com/net/download "@ } } function New-Temp-Dir() { [CmdletBinding(SupportsShouldProcess)] param() $parent = [System.IO.Path]::GetTempPath() [string] $name = [System.Guid]::NewGuid() New-Item -ItemType Directory -Path (Join-Path $parent $name) } # PSScriptAnalyzer doesn't like how we use our params as globals, this calms it $Null = $ArtifactDownloadUrls, $NoModifyPath, $Help # Make Write-Information statements be visible $InformationPreference = "Continue" # The default interactive handler try { Install-Binary "$Args" } catch { Write-Information $_ exit 1 }