Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(path): Isolate Scoop apps' PATH #5840

Merged
merged 20 commits into from Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
- **bucket:** Make official buckets higher priority ([#5398](https://github.com/ScoopInstaller/Scoop/issues/5398))
- **core:** Add `-Quiet` switch for `Invoke-ExternalCommand` ([#5346](https://github.com/ScoopInstaller/Scoop/issues/5346))
- **core:** Allow global install of PowerShell modules ([#5611](https://github.com/ScoopInstaller/Scoop/issues/5611))
- **path:** Isolate Scoop apps' PATH ([#5840](https://github.com/ScoopInstaller/Scoop/issues/5840))

### Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions bin/uninstall.ps1
Expand Up @@ -100,5 +100,6 @@ if ($purge) {
}

Remove-Path -Path (shimdir $global) -Global:$global
Remove-Path -Path '%SCOOP_PATH%' -Global:$global

success 'Scoop has been uninstalled.'
52 changes: 52 additions & 0 deletions lib/core.ps1
Expand Up @@ -142,11 +142,63 @@ function set_config {
$scoopConfig.PSObject.Properties.Remove($name)
}

# Initialize config's change
Complete-ConfigChange -Name $name -Value $value

# Save config with UTF8NoBOM encoding
ConvertTo-Json $scoopConfig | Out-UTF8File -FilePath $configFile
return $scoopConfig
}

function Complete-ConfigChange {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0)]
[string]
$Name,
[Parameter(Mandatory, Position = 1)]
[AllowEmptyString()]
[string]
$Value
)

if ($Name -eq 'use_isolated_path') {
. "$PSScriptRoot\..\lib\system.ps1"

if ($Value -eq $true) {
info 'Switching to isolated path... This may take a while, please wait.'
$movedPath = Remove-Path -Path "$scoopdir\apps\*" -Quiet -PassThru
niheaven marked this conversation as resolved.
Show resolved Hide resolved
if ($movedPath) {
Add-Path -Path $movedPath -TargetEnvVar 'SCOOP_PATH' -Quiet
Add-Path -Path '%SCOOP_PATH%' -Quiet
}
if (is_admin) {
$movedPath = Remove-Path -Path "$globaldir\apps\*" -Global -Quiet -PassThru
if ($movedPath) {
Add-Path -Path $movedPath -TargetEnvVar 'SCOOP_PATH' -Global -Quiet
Add-Path -Path '%SCOOP_PATH%' -Global -Quiet
}
}
} else {
info 'Switching to shared path... This may take a while, please wait.'
$movedPath = Get-EnvVar -Name 'SCOOP_PATH'
if ($movedPath) {
Add-Path -Path $movedPath -Quiet
Remove-Path -Path '%SCOOP_PATH%' -Quiet
Set-EnvVar -Name 'SCOOP_PATH' -Quiet
}
if (is_admin) {
$movedPath = Get-EnvVar -Name 'SCOOP_PATH' -Global
if ($movedPath) {
Add-Path -Path $movedPath -Global -Quiet
Remove-Path -Path '%SCOOP_PATH%' -Global -Quiet
Set-EnvVar -Name 'SCOOP_PATH' -Global -Quiet
}
}
}
}
}

function setup_proxy() {
# note: '@' and ':' in password must be escaped, e.g. 'p@ssword' -> p\@ssword'
$proxy = get_config PROXY
Expand Down
19 changes: 7 additions & 12 deletions lib/install.ps1
Expand Up @@ -906,19 +906,13 @@ function env_add_path($manifest, $dir, $global, $arch) {
$env_add_path = arch_specific 'env_add_path' $manifest $arch
$dir = $dir.TrimEnd('\')
if ($env_add_path) {
# GH-3785: Add path in ascending order.
[Array]::Reverse($env_add_path)
$env_add_path | Where-Object { $_ } | ForEach-Object {
if ($_ -eq '.') {
$path_dir = $dir
} else {
$path_dir = Join-Path $dir $_
}
if (!(is_in_dir $dir $path_dir)) {
abort "Error in manifest: env_add_path '$_' is outside the app directory."
niheaven marked this conversation as resolved.
Show resolved Hide resolved
}
Add-Path -Path $path_dir -Global:$global -Force
if (get_config USE_ISOLATED_PATH) {
niheaven marked this conversation as resolved.
Show resolved Hide resolved
Add-Path -Path '%SCOOP_PATH%' -Global:$global
$target_path = 'SCOOP_PATH'
} else {
$target_path = 'PATH'
}
niheaven marked this conversation as resolved.
Show resolved Hide resolved
Add-Path -Path ($env_add_path.Where({ $_ -and !$_.Contains(':') }).ForEach({ Join-Path $dir $_ })) -TargetEnvVar $target_path -Global:$global -Force
niheaven marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -933,6 +927,7 @@ function env_rm_path($manifest, $dir, $global, $arch) {
$path_dir = Join-Path $dir $_
}
Remove-Path -Path $path_dir -Global:$global
Remove-Path -Path $path_dir -TargetEnvVar 'SCOOP_PATH' -Global:$global
}
}
}
Expand Down
81 changes: 55 additions & 26 deletions lib/system.ps1
Expand Up @@ -73,63 +73,92 @@ function Set-EnvVar {
Publish-EnvVar
}

function Test-PathLikeEnvVar {
function Split-PathLikeEnvVar {
param(
[string]$Name,
[string[]]$Pattern,
[string]$Path
)

if ($null -eq $Path -and $Path -eq '') {
return $false, $null
return $null, $null
} else {
$strippedPath = $Path.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries).Where({ $_ -ne $Name }) -join ';'
return ($strippedPath -ne $Path), $strippedPath
$splitPattern = $Pattern.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
$splitPath = $Path.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
$inPath = @()
foreach ($p in $splitPattern) {
$inPath += $splitPath.Where({ $_ -like $p })
$splitPath = $splitPath.Where({ $_ -notlike $p })
}
return ($inPath -join ';'), ($splitPath -join ';')
}
}

function Add-Path {
param(
[string]$Path,
[string[]]$Path,
[string]$TargetEnvVar = 'PATH',
[switch]$Global,
[switch]$Force
[switch]$Force,
[switch]$Quiet
)

if (!$Path.Contains('%')) {
$Path = Get-AbsolutePath $Path
$Path = $Path | ForEach-Object {
if (!$_.Contains('%')) {
Get-AbsolutePath $_
} else {
$_
}
}
# future sessions
$inPath, $strippedPath = Test-PathLikeEnvVar $Path (Get-EnvVar -Name 'PATH' -Global:$Global)
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
if (!$inPath -or $Force) {
Write-Output "Adding $(friendly_path $Path) to $(if ($Global) {'global'} else {'your'}) path."
Set-EnvVar -Name 'PATH' -Value (@($Path, $strippedPath) -join ';') -Global:$Global
if (!$Quiet) {
$Path | ForEach-Object {
Write-Host "Adding $(friendly_path $_) to $(if ($Global) {'global'} else {'your'}) path."
}
}
Set-EnvVar -Name $TargetEnvVar -Value ((@($Path) + $strippedPath) -join ';') -Global:$Global
}
# current session
$inPath, $strippedPath = Test-PathLikeEnvVar $Path $env:PATH
$inPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
if (!$inPath -or $Force) {
$env:PATH = @($Path, $strippedPath) -join ';'
$env:PATH = (@($Path) + $strippedPath) -join ';'
}
}

function Remove-Path {
param(
[string]$Path,
[switch]$Global
[string[]]$Path,
[string]$TargetEnvVar = 'PATH',
[switch]$Global,
[switch]$Quiet,
[switch]$PassThru
)

if (!$Path.Contains('%')) {
$Path = Get-AbsolutePath $Path
$Path = $Path | ForEach-Object {
if (!$_.Contains('%')) {
Get-AbsolutePath $_
} else {
$_
}
}
# future sessions
$inPath, $strippedPath = Test-PathLikeEnvVar $Path (Get-EnvVar -Name 'PATH' -Global:$Global)
$inPath, $strippedPath = Split-PathLikeEnvVar $Path (Get-EnvVar -Name $TargetEnvVar -Global:$Global)
if ($inPath) {
Write-Output "Removing $(friendly_path $Path) from $(if ($Global) {'global'} else {'your'}) path."
Set-EnvVar -Name 'PATH' -Value $strippedPath -Global:$Global
if (!$Quiet) {
$Path | ForEach-Object {
Write-Host "Removing $(friendly_path $_) from $(if ($Global) {'global'} else {'your'}) path."
}
}
Set-EnvVar -Name $TargetEnvVar -Value $strippedPath -Global:$Global
}
# current session
$inPath, $strippedPath = Test-PathLikeEnvVar $Path $env:PATH
if ($inPath) {
$inSessionPath, $strippedPath = Split-PathLikeEnvVar $Path $env:PATH
if ($inSessionPath) {
$env:PATH = $strippedPath
}
if ($PassThru) {
return $inPath
}
}

## Deprecated functions
Expand All @@ -145,8 +174,8 @@ function env($name, $global, $val) {
}

function strip_path($orig_path, $dir) {
Show-DeprecatedWarning $MyInvocation 'Test-PathLikeEnvVar'
Test-PathLikeEnvVar -Name $dir -Path $orig_path
Show-DeprecatedWarning $MyInvocation 'Split-PathLikeEnvVar'
Split-PathLikeEnvVar -Name $dir -Path $orig_path
}

function add_first_in_path($dir, $global) {
Expand Down
4 changes: 4 additions & 0 deletions libexec/scoop-config.ps1
Expand Up @@ -115,6 +115,10 @@
# Nightly version is formatted as 'nightly-yyyyMMdd' and will be updated after one day if this is set to $true.
# Otherwise, nightly version will not be updated unless `--force` is used.
#
# use_isolated_path: $true|$false
# When set to $true, Scoop will use `SCOOP_PATH` environment variable to store apps' `PATH`s.
# This is useful when you want to isolate Scoop from the system `PATH`.
#
# ARIA2 configuration
# -------------------
#
Expand Down
3 changes: 3 additions & 0 deletions libexec/scoop-reset.ps1
Expand Up @@ -80,6 +80,9 @@ $apps | ForEach-Object {
$dir = link_current $dir
create_shims $manifest $dir $global $architecture
create_startmenu_shortcuts $manifest $dir $global $architecture
# unset all potential old env before re-adding
env_rm_path $manifest $dir $global $architecture
env_rm $manifest $global $architecture
env_add_path $manifest $dir $global $architecture
env_set $manifest $dir $global $architecture
# unlink all potential old link before re-persisting
Expand Down