docs: 添加 CLAUDE.md 行为准则文档并重构 README 新手使用说明
This commit is contained in:
67
CLAUDE.md
Normal file
67
CLAUDE.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
||||||
|
|
||||||
|
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||||
|
|
||||||
|
**Important:** Use Chinese for information responses and thinking; use English for searching and querying.
|
||||||
|
|
||||||
|
## 1. Think Before Coding
|
||||||
|
|
||||||
|
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||||
|
|
||||||
|
Before implementing:
|
||||||
|
- State your assumptions explicitly. If uncertain, ask.
|
||||||
|
- If multiple interpretations exist, present them - don't pick silently.
|
||||||
|
- If a simpler approach exists, say so. Push back when warranted.
|
||||||
|
- If something is unclear, stop. Name what's confusing. Ask.
|
||||||
|
|
||||||
|
## 2. Simplicity First
|
||||||
|
|
||||||
|
**Minimum code that solves the problem. Nothing speculative.**
|
||||||
|
|
||||||
|
- No features beyond what was asked.
|
||||||
|
- No abstractions for single-use code.
|
||||||
|
- No "flexibility" or "configurability" that wasn't requested.
|
||||||
|
- No error handling for impossible scenarios.
|
||||||
|
- If you write 200 lines and it could be 50, rewrite it.
|
||||||
|
|
||||||
|
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||||
|
|
||||||
|
## 3. Surgical Changes
|
||||||
|
|
||||||
|
**Touch only what you must. Clean up only your own mess.**
|
||||||
|
|
||||||
|
When editing existing code:
|
||||||
|
- Don't "improve" adjacent code, comments, or formatting.
|
||||||
|
- Don't refactor things that aren't broken.
|
||||||
|
- Match existing style, even if you'd do it differently.
|
||||||
|
- If you notice unrelated dead code, mention it - don't delete it.
|
||||||
|
|
||||||
|
When your changes create orphans:
|
||||||
|
- Remove imports/variables/functions that YOUR changes made unused.
|
||||||
|
- Don't remove pre-existing dead code unless asked.
|
||||||
|
|
||||||
|
The test: Every changed line should trace directly to the user's request.
|
||||||
|
|
||||||
|
## 4. Goal-Driven Execution
|
||||||
|
|
||||||
|
**Define success criteria. Loop until verified.**
|
||||||
|
|
||||||
|
Transform tasks into verifiable goals:
|
||||||
|
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||||
|
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||||
|
- "Refactor X" → "Ensure tests pass before and after"
|
||||||
|
|
||||||
|
For multi-step tasks, state a brief plan:
|
||||||
|
```
|
||||||
|
1. [Step] → verify: [check]
|
||||||
|
2. [Step] → verify: [check]
|
||||||
|
3. [Step] → verify: [check]
|
||||||
|
```
|
||||||
|
|
||||||
|
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||||
130
README.md
130
README.md
@@ -9,22 +9,115 @@
|
|||||||
- 支持自定义输出宽度和目录
|
- 支持自定义输出宽度和目录
|
||||||
- 内置图片优化功能,使用图片优化引擎进行无损压缩
|
- 内置图片优化功能,使用图片优化引擎进行无损压缩
|
||||||
|
|
||||||
## 安装
|
## 新手使用说明
|
||||||
|
|
||||||
使用uv进行基本安装和工具设置。必要工具,推荐使用scoop进行安装。
|
如果你是第一次使用命令行工具,请按以下步骤操作。
|
||||||
|
|
||||||
|
### 准备工作
|
||||||
|
|
||||||
|
- **PowerPoint**:需要安装 Microsoft PowerPoint(Office 2016 或更新版本)
|
||||||
|
- **网络连接**:安装过程中需要下载工具
|
||||||
|
|
||||||
|
### 第一步:安装 uv
|
||||||
|
|
||||||
|
`uv` 是一个 Python 包管理器,用来安装 pptopic 及其依赖。
|
||||||
|
|
||||||
|
打开 PowerShell(在开始菜单搜索 "PowerShell"),进入本项目的目录:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd pptopic
|
||||||
|
```
|
||||||
|
|
||||||
|
运行项目自带的安装脚本:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\uv-installer.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
该脚本会自动下载最新版本的 uv 并添加到系统 PATH。如果你在中国大陆,脚本已内置 GitHub 加速镜像,无需额外配置。
|
||||||
|
|
||||||
|
安装完成后,**关闭并重新打开 PowerShell**,验证安装:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv --version
|
||||||
|
```
|
||||||
|
|
||||||
|
> 如果你已安装 [scoop](https://scoop.sh/),也可以直接 `scoop install uv`。
|
||||||
|
> 如果想安装特定版本的 uv,可以在运行脚本前设置环境变量:`$env:UV_INSTALLER_VERSION = "0.11.20"`
|
||||||
|
|
||||||
|
### 第二步:安装 Python
|
||||||
|
|
||||||
|
使用 uv 安装 Python 3.13 或更新版本:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv python install 3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三步:安装 pptopic
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
uv pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第四步(可选):安装 pngquant 图片优化工具
|
||||||
|
|
||||||
|
虽然是可选安装,但我超级建议你安装,因为ppt导出后,图片通常较大,不进行图片无损压缩,会导致文件大小过大。
|
||||||
|
|
||||||
|
如果你需要对导出的图片进行压缩优化,运行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\install-pngquant.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
该脚本会:
|
||||||
|
1. 如果你已安装 scoop → 通过 scoop 安装 pngquant
|
||||||
|
2. 如果没有 scoop → 自动下载并安装到 `%APPDATA%\pngquant`,并添加到 PATH
|
||||||
|
|
||||||
|
安装完成后,**重新打开 PowerShell**,验证安装:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pngquant --version
|
||||||
|
```
|
||||||
|
|
||||||
|
> 自定义安装目录:`.\install-pngquant.ps1 -InstallDir "D:\Tools\pngquant"`
|
||||||
|
> 强制重新安装:`.\install-pngquant.ps1 -Force`
|
||||||
|
|
||||||
|
### 第五步:开始使用
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 导出 PPTX 为长图
|
||||||
|
pptopic export presentation.pptx
|
||||||
|
|
||||||
|
# 优化图片
|
||||||
|
pptopic optimize image.png
|
||||||
|
```
|
||||||
|
|
||||||
|
至此安装完成。如果任何步骤遇到问题,请参考下方详细说明。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
### 安装 uv
|
### 安装 uv
|
||||||
|
|
||||||
|
使用项目自带的安装脚本(推荐):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\uv-installer.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
该脚本自动检测 uv 最新版本,支持国内 GitHub 加速镜像下载(`mirror.ghproxy.com`、`ghproxy.net`),并自动将 uv 添加到系统 PATH。
|
||||||
|
|
||||||
|
也可以通过 scoop 安装:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 使用 scoop
|
|
||||||
scoop install uv
|
scoop install uv
|
||||||
```
|
```
|
||||||
|
|
||||||
### 安装 pptopic
|
### 安装 pptopic
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd pptopic
|
cd pptopic
|
||||||
$ uv pip install -e .
|
uv sync
|
||||||
|
uv pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
@@ -67,21 +160,30 @@ pptopic optimize image.png --max-height 20000 --engine pngquant
|
|||||||
## 图片优化
|
## 图片优化
|
||||||
|
|
||||||
为了获得最佳的图片压缩效果,推荐使用 [pngquant](https://pngquant.org/) 进行图片压缩。
|
为了获得最佳的图片压缩效果,推荐使用 [pngquant](https://pngquant.org/) 进行图片压缩。
|
||||||
当然,使用自己熟悉的图片优化压缩引擎是一样的。
|
|
||||||
|
|
||||||
### 自动安装脚本
|
### 自动安装脚本
|
||||||
|
|
||||||
使用提供的 PowerShell 脚本自动安装 pngquant:
|
使用提供的 PowerShell 脚本安装 pngquant:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\install-pngquant.ps1
|
.\install-pngquant.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
该脚本会自动:
|
该脚本支持两种安装方式:
|
||||||
1. 检查 pngquant 是否已安装
|
|
||||||
2. 如果未安装,检查 scoop 是否已安装
|
1. **已安装 scoop** → 通过 `scoop install pngquant` 安装
|
||||||
3. 如果 scoop 未安装,自动安装 scoop
|
2. **未安装 scoop** → 从 pngquant 官网下载并安装到 `%APPDATA%\pngquant`,自动添加到 PATH
|
||||||
4. 使用 scoop 安装 pngquant
|
|
||||||
|
```powershell
|
||||||
|
# 自定义安装目录
|
||||||
|
.\install-pngquant.ps1 -InstallDir "D:\Tools\pngquant"
|
||||||
|
|
||||||
|
# 强制重新安装
|
||||||
|
.\install-pngquant.ps1 -Force
|
||||||
|
|
||||||
|
# 不修改 PATH
|
||||||
|
.\install-pngquant.ps1 -NoModifyPath
|
||||||
|
```
|
||||||
|
|
||||||
### 手动安装
|
### 手动安装
|
||||||
|
|
||||||
@@ -89,6 +191,8 @@ pptopic optimize image.png --max-height 20000 --engine pngquant
|
|||||||
scoop install pngquant
|
scoop install pngquant
|
||||||
```
|
```
|
||||||
|
|
||||||
|
或从 [pngquant.org](https://pngquant.org/) 下载 Windows 版本手动配置。
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
param(
|
param(
|
||||||
[switch]$Force
|
[switch]$Force,
|
||||||
|
[string]$InstallDir = "$env:APPDATA\pngquant",
|
||||||
|
[switch]$NoModifyPath
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
$DownloadUrl = "https://pngquant.org/pngquant-windows.zip"
|
||||||
|
|
||||||
function Test-PngquantInstalled {
|
function Test-PngquantInstalled {
|
||||||
try {
|
try {
|
||||||
@@ -21,51 +24,102 @@ function Test-ScoopInstalled {
|
|||||||
Write-Host "scoop 已安装: $($result.Source)" -ForegroundColor Green
|
Write-Host "scoop 已安装: $($result.Source)" -ForegroundColor Green
|
||||||
return $true
|
return $true
|
||||||
} catch {
|
} catch {
|
||||||
Write-Host "scoop 未安装" -ForegroundColor Yellow
|
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Install-Scoop {
|
function Install-ViaScoop {
|
||||||
Write-Host "正在安装 scoop..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
$scoopInstallScript = "irm get.scoop.sh | iex"
|
|
||||||
|
|
||||||
try {
|
|
||||||
Invoke-Expression $scoopInstallScript
|
|
||||||
Write-Host "scoop 安装成功" -ForegroundColor Green
|
|
||||||
|
|
||||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","User") + ";" + [System.Environment]::GetEnvironmentVariable("Path","Machine")
|
|
||||||
|
|
||||||
return $true
|
|
||||||
} catch {
|
|
||||||
Write-Host "scoop 安装失败: $_" -ForegroundColor Red
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Install-Pngquant {
|
|
||||||
Write-Host "正在使用 scoop 安装 pngquant..." -ForegroundColor Cyan
|
Write-Host "正在使用 scoop 安装 pngquant..." -ForegroundColor Cyan
|
||||||
|
|
||||||
try {
|
try {
|
||||||
scoop install pngquant
|
scoop install pngquant
|
||||||
Write-Host "pngquant 安装成功" -ForegroundColor Green
|
Write-Host "pngquant 安装成功" -ForegroundColor Green
|
||||||
return $true
|
return $true
|
||||||
} catch {
|
} catch {
|
||||||
Write-Host "pngquant 安装失败: $_" -ForegroundColor Red
|
Write-Host "scoop 安装 pngquant 失败: $_" -ForegroundColor Red
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Install-Manual {
|
||||||
|
Write-Host "正在从 $DownloadUrl 下载 pngquant..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$zipPath = "$env:TEMP\pngquant-windows.zip"
|
||||||
|
$extractTemp = "$env:TEMP\pngquant_extract"
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Download
|
||||||
|
$wc = New-Object System.Net.WebClient
|
||||||
|
$wc.DownloadFile($DownloadUrl, $zipPath)
|
||||||
|
|
||||||
|
# Clean up previous temp extract
|
||||||
|
if (Test-Path $extractTemp) {
|
||||||
|
Remove-Item $extractTemp -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract
|
||||||
|
Expand-Archive -Path $zipPath -DestinationPath $extractTemp
|
||||||
|
|
||||||
|
# Create install dir
|
||||||
|
if (-not (Test-Path $InstallDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy exe files to install dir
|
||||||
|
$exeFiles = Get-ChildItem -Path $extractTemp -Filter "*.exe" -Recurse
|
||||||
|
foreach ($exe in $exeFiles) {
|
||||||
|
Copy-Item -Path $exe.FullName -Destination $InstallDir -Force
|
||||||
|
Write-Host " 已安装: $($exe.Name)" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $extractTemp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Add to PATH
|
||||||
|
if (-not $NoModifyPath) {
|
||||||
|
Add-ToPath $InstallDir
|
||||||
|
}
|
||||||
|
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
Write-Host "手动安装失败: $_" -ForegroundColor Red
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Add-ToPath($LiteralPath) {
|
||||||
|
$RegistryPath = 'registry::HKEY_CURRENT_USER\Environment'
|
||||||
|
$CurrentDirs = (Get-Item -LiteralPath $RegistryPath).GetValue(
|
||||||
|
'Path', '', 'DoNotExpandEnvironmentNames'
|
||||||
|
) -split ';' -ne ''
|
||||||
|
|
||||||
|
if ($LiteralPath -in $CurrentDirs) {
|
||||||
|
Write-Host "安装目录已在 PATH 中: $LiteralPath" -ForegroundColor Green
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$NewPath = (,$LiteralPath + $CurrentDirs) -join ';'
|
||||||
|
Set-ItemProperty -Type ExpandString -LiteralPath $RegistryPath Path $NewPath
|
||||||
|
|
||||||
|
# 通知系统环境变量已更新
|
||||||
|
$DummyName = 'pngquant-install-' + [guid]::NewGuid().ToString()
|
||||||
|
[Environment]::SetEnvironmentVariable($DummyName, 'dummy', 'User')
|
||||||
|
[Environment]::SetEnvironmentVariable($DummyName, [NullString]::value, 'User')
|
||||||
|
|
||||||
|
Write-Host "已将 $LiteralPath 添加到用户 PATH" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
Write-Host "========================================" -ForegroundColor Cyan
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
Write-Host " pngquant 安装脚本" -ForegroundColor Cyan
|
Write-Host " pngquant 安装脚本" -ForegroundColor Cyan
|
||||||
Write-Host "========================================" -ForegroundColor Cyan
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
if (Test-PngquantInstalled) {
|
if (Test-PngquantInstalled) {
|
||||||
Write-Host ""
|
|
||||||
Write-Host "pngquant 已经安装,无需重复安装。" -ForegroundColor Green
|
|
||||||
if (-not $Force) {
|
if (-not $Force) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "pngquant 已经安装,无需重复安装。" -ForegroundColor Green
|
||||||
|
Write-Host "如需强制重新安装,请使用 -Force 参数。" -ForegroundColor Yellow
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
Write-Host "使用 -Force 参数强制重新安装..." -ForegroundColor Yellow
|
Write-Host "使用 -Force 参数强制重新安装..." -ForegroundColor Yellow
|
||||||
@@ -73,42 +127,40 @@ if (Test-PngquantInstalled) {
|
|||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
if (-not (Test-ScoopInstalled)) {
|
if (Test-ScoopInstalled) {
|
||||||
Write-Host ""
|
Write-Host "检测到 scoop,使用 scoop 安装..." -ForegroundColor Cyan
|
||||||
Write-Host "scoop 未安装,正在安装 scoop..." -ForegroundColor Yellow
|
if (Install-ViaScoop) {
|
||||||
|
|
||||||
if (-not (Install-Scoop)) {
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "scoop 安装失败,请手动安装后重试。" -ForegroundColor Red
|
pngquant --version
|
||||||
Write-Host "手动安装命令: irm get.scoop.sh | iex" -ForegroundColor Yellow
|
exit 0
|
||||||
exit 1
|
|
||||||
}
|
}
|
||||||
|
Write-Host "scoop 安装失败,回退到手动安装方式..." -ForegroundColor Yellow
|
||||||
Write-Host ""
|
} else {
|
||||||
Write-Host "请重新运行此脚本以继续安装 pngquant。" -ForegroundColor Yellow
|
Write-Host "未检测到 scoop,使用手动安装方式..." -ForegroundColor Cyan
|
||||||
Write-Host "或者手动运行: scoop install pngquant" -ForegroundColor Yellow
|
|
||||||
exit 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Write-Host "安装目录: $InstallDir" -ForegroundColor Cyan
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
if (Install-Pngquant) {
|
if (Install-Manual) {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
Write-Host " pngquant 安装完成!" -ForegroundColor Green
|
Write-Host " pngquant 安装完成!" -ForegroundColor Green
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host "安装路径: $InstallDir" -ForegroundColor Cyan
|
||||||
$pngquantPath = Get-Command pngquant -ErrorAction SilentlyContinue
|
|
||||||
if ($pngquantPath) {
|
try {
|
||||||
Write-Host "安装路径: $($pngquantPath.Source)" -ForegroundColor Cyan
|
$env:Path = "$InstallDir;$env:Path"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "验证安装:" -ForegroundColor Cyan
|
||||||
|
pngquant --version
|
||||||
|
} catch {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "安装文件已就绪,请重启终端后运行 pngquant --version 验证。" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "验证安装:" -ForegroundColor Cyan
|
|
||||||
pngquant --version
|
|
||||||
} else {
|
} else {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "pngquant 安装失败。" -ForegroundColor Red
|
Write-Host "pngquant 安装失败。" -ForegroundColor Red
|
||||||
Write-Host "请尝试手动安装: scoop install pngquant" -ForegroundColor Yellow
|
Write-Host "请尝试手动下载: $DownloadUrl" -ForegroundColor Yellow
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
683
uv-installer.ps1
Normal file
683
uv-installer.ps1
Normal file
@@ -0,0 +1,683 @@
|
|||||||
|
# Licensed under the MIT license
|
||||||
|
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
|
||||||
|
# option. This file may not be copied, modified, or distributed
|
||||||
|
# except according to those terms.
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
|
||||||
|
The installer for uv (auto-detects latest release)
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
|
||||||
|
This script auto-detects the latest uv release from GitHub and fetches an appropriate archive from
|
||||||
|
various download sources, then unpacks the binaries and installs them to the first of the following locations
|
||||||
|
|
||||||
|
$env:XDG_BIN_HOME
|
||||||
|
$env:XDG_DATA_HOME/../bin
|
||||||
|
$HOME/.local/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
|
||||||
|
)
|
||||||
|
function Get-LatestVersion {
|
||||||
|
if ($env:UV_INSTALLER_VERSION) {
|
||||||
|
return $env:UV_INSTALLER_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
$fallback_version = "0.11.20"
|
||||||
|
|
||||||
|
try {
|
||||||
|
$http = New-Object System.Net.Http.HttpClient
|
||||||
|
$http.Timeout = [TimeSpan]::FromSeconds(5)
|
||||||
|
$http.DefaultRequestHeaders.Add("User-Agent", "PowerShell/uv-installer")
|
||||||
|
$http.DefaultRequestHeaders.Add("Accept", "application/vnd.github+json")
|
||||||
|
$response = $http.GetStringAsync("https://api.github.com/repos/astral-sh/uv/releases/latest").Result
|
||||||
|
$json = $response | ConvertFrom-Json
|
||||||
|
$version = $json.tag_name
|
||||||
|
if ($version) {
|
||||||
|
Write-Information "Latest uv version: $version"
|
||||||
|
return $version
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Information "Could not reach GitHub API, using fallback version $fallback_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback_version
|
||||||
|
}
|
||||||
|
|
||||||
|
$app_name = 'uv'
|
||||||
|
$app_version = Get-LatestVersion
|
||||||
|
if ($env:UV_DOWNLOAD_URL) {
|
||||||
|
$ArtifactDownloadUrls = @($env:UV_DOWNLOAD_URL)
|
||||||
|
} elseif ($env:INSTALLER_DOWNLOAD_URL) {
|
||||||
|
$ArtifactDownloadUrls = @($env:INSTALLER_DOWNLOAD_URL)
|
||||||
|
} elseif ($env:UV_INSTALLER_GHE_BASE_URL) {
|
||||||
|
$installer_base_url = $env:UV_INSTALLER_GHE_BASE_URL
|
||||||
|
$ArtifactDownloadUrls = @("$installer_base_url/astral-sh/uv/releases/download/$app_version")
|
||||||
|
} elseif ($env:UV_INSTALLER_GITHUB_BASE_URL) {
|
||||||
|
$installer_base_url = $env:UV_INSTALLER_GITHUB_BASE_URL
|
||||||
|
$ArtifactDownloadUrls = @("$installer_base_url/astral-sh/uv/releases/download/$app_version")
|
||||||
|
} else {
|
||||||
|
$ArtifactDownloadUrls = @(
|
||||||
|
"https://releases.astral.sh/github/uv/releases/download/$app_version",
|
||||||
|
"https://mirror.ghproxy.com/https://github.com/astral-sh/uv/releases/download/$app_version",
|
||||||
|
"https://ghproxy.net/https://github.com/astral-sh/uv/releases/download/$app_version",
|
||||||
|
"https://github.com/astral-sh/uv/releases/download/$app_version"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth_token = $env:UV_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":"uv","name":"uv","owner":"astral-sh","release_type":"github"},"version":"$app_version"}
|
||||||
|
"@
|
||||||
|
if ($env:XDG_CONFIG_HOME) {
|
||||||
|
$receipt_home = "${env:XDG_CONFIG_HOME}\uv"
|
||||||
|
} else {
|
||||||
|
$receipt_home = "${env:LOCALAPPDATA}\uv"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env:UV_DISABLE_UPDATE) {
|
||||||
|
$install_updater = $false
|
||||||
|
} else {
|
||||||
|
$install_updater = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($NoModifyPath) {
|
||||||
|
Write-Information "-NoModifyPath has been deprecated; please set UV_NO_MODIFY_PATH=1 in the environment"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env:UV_NO_MODIFY_PATH) {
|
||||||
|
$NoModifyPath = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$unmanaged_install = $env:UV_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-gnu" = @{
|
||||||
|
"artifact_name" = "uv-aarch64-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.exe")
|
||||||
|
"libs" = @()
|
||||||
|
"staticlibs" = @()
|
||||||
|
"zip_ext" = ".zip"
|
||||||
|
"aliases" = @{
|
||||||
|
}
|
||||||
|
"aliases_json" = '{}'
|
||||||
|
}
|
||||||
|
"aarch64-pc-windows-msvc" = @{
|
||||||
|
"artifact_name" = "uv-aarch64-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.exe")
|
||||||
|
"libs" = @()
|
||||||
|
"staticlibs" = @()
|
||||||
|
"zip_ext" = ".zip"
|
||||||
|
"aliases" = @{
|
||||||
|
}
|
||||||
|
"aliases_json" = '{}'
|
||||||
|
}
|
||||||
|
"i686-pc-windows-gnu" = @{
|
||||||
|
"artifact_name" = "uv-i686-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.exe")
|
||||||
|
"libs" = @()
|
||||||
|
"staticlibs" = @()
|
||||||
|
"zip_ext" = ".zip"
|
||||||
|
"aliases" = @{
|
||||||
|
}
|
||||||
|
"aliases_json" = '{}'
|
||||||
|
}
|
||||||
|
"i686-pc-windows-msvc" = @{
|
||||||
|
"artifact_name" = "uv-i686-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.exe")
|
||||||
|
"libs" = @()
|
||||||
|
"staticlibs" = @()
|
||||||
|
"zip_ext" = ".zip"
|
||||||
|
"aliases" = @{
|
||||||
|
}
|
||||||
|
"aliases_json" = '{}'
|
||||||
|
}
|
||||||
|
"x86_64-pc-windows-gnu" = @{
|
||||||
|
"artifact_name" = "uv-x86_64-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.exe")
|
||||||
|
"libs" = @()
|
||||||
|
"staticlibs" = @()
|
||||||
|
"zip_ext" = ".zip"
|
||||||
|
"aliases" = @{
|
||||||
|
}
|
||||||
|
"aliases_json" = '{}'
|
||||||
|
}
|
||||||
|
"x86_64-pc-windows-msvc" = @{
|
||||||
|
"artifact_name" = "uv-x86_64-pc-windows-msvc.zip"
|
||||||
|
"bins" = @("uv.exe", "uvx.exe", "uvw.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\uv-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:UV_INSTALL_DIR)) {
|
||||||
|
$force_install_dir = $env:UV_INSTALL_DIR
|
||||||
|
$install_layout = "flat"
|
||||||
|
} elseif (($env:CARGO_DIST_FORCE_INSTALL_DIR)) {
|
||||||
|
$force_install_dir = $env:CARGO_DIST_FORCE_INSTALL_DIR
|
||||||
|
$install_layout = "flat"
|
||||||
|
} 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) {
|
||||||
|
# Install to $env:XDG_BIN_HOME
|
||||||
|
$dest_dir = if (($base_dir = $env:XDG_BIN_HOME)) {
|
||||||
|
Join-Path $base_dir ""
|
||||||
|
}
|
||||||
|
$dest_dir_lib = $dest_dir
|
||||||
|
$receipt_dest_dir = $dest_dir
|
||||||
|
$install_layout = "flat"
|
||||||
|
}
|
||||||
|
if (-Not $dest_dir) {
|
||||||
|
# Install to $env:XDG_DATA_HOME/../bin
|
||||||
|
$dest_dir = if (($base_dir = $env:XDG_DATA_HOME)) {
|
||||||
|
Join-Path $base_dir "../bin"
|
||||||
|
}
|
||||||
|
$dest_dir_lib = $dest_dir
|
||||||
|
$receipt_dest_dir = $dest_dir
|
||||||
|
$install_layout = "flat"
|
||||||
|
}
|
||||||
|
if (-Not $dest_dir) {
|
||||||
|
# Install to $HOME/.local/bin
|
||||||
|
$dest_dir = if (($base_dir = $HOME)) {
|
||||||
|
Join-Path $base_dir ".local/bin"
|
||||||
|
}
|
||||||
|
$dest_dir_lib = $dest_dir
|
||||||
|
$receipt_dest_dir = $dest_dir
|
||||||
|
$install_layout = "flat"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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/uv-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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user