Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/install-script.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Install script

on:
workflow_dispatch:

permissions:
contents: read

jobs:
install-ps1:
name: install.ps1 (${{ matrix.os }}, ${{ matrix.shell }})
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [windows-latest, windows-11-arm]
shell: [powershell, pwsh]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Parse script
run: |
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile("$PWD\scripts\install.ps1", [ref]$null, [ref]$errors) | Out-Null
if ($errors) { $errors | ForEach-Object { Write-Error $_.Message }; exit 1 }
$nonAscii = Select-String -Path scripts\install.ps1 -Pattern '[^\x00-\x7F]'
if ($nonAscii) { $nonAscii | ForEach-Object { Write-Error "Non-ASCII at line $($_.LineNumber)" }; exit 1 }

# Same path as `irm https://mqlang.org/install.ps1 | iex`, with the script read from the checkout.
- name: Install latest via iex
env:
MQ_HOME: ${{ runner.temp }}\mq-latest
run: |
Get-Content -Raw scripts\install.ps1 | Invoke-Expression
& "$env:MQ_HOME\mq.exe" --version
if ($LASTEXITCODE -ne 0) { exit 1 }
# Installed in the current session without a restart.
mq --version
if ($LASTEXITCODE -ne 0) { exit 1 }

- name: Check user PATH is persisted once
env:
MQ_HOME: ${{ runner.temp }}\mq-latest
run: |
# Install a second time: the PATH entry must not be duplicated.
Get-Content -Raw scripts\install.ps1 | Invoke-Expression
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User') -split ';'
$count = @($userPath | Where-Object { $_.TrimEnd('\') -ieq $env:MQ_HOME.TrimEnd('\') }).Count
if ($count -ne 1) { Write-Error "Expected 1 PATH entry for $env:MQ_HOME, found $count"; exit 1 }

- name: Install pinned version with mq-dbg
env:
MQ_HOME: ${{ runner.temp }}\mq-pinned
MQ_VERSION: v0.9.3
MQ_WITH_DEBUG: "1"
MQ_NO_MODIFY_PATH: "1"
run: |
Get-Content -Raw scripts\install.ps1 | Invoke-Expression
foreach ($bin in 'mq', 'mq-dbg') {
$exe = "$env:MQ_HOME\$bin.exe"
if (-not (Test-Path $exe)) { Write-Error "$exe was not installed"; exit 1 }
& $exe --version
if ($LASTEXITCODE -ne 0) { exit 1 }
}
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User') -split ';'
if ($userPath -contains $env:MQ_HOME) { Write-Error 'MQ_NO_MODIFY_PATH was ignored'; exit 1 }

- name: Unknown version fails without exiting the shell
env:
MQ_HOME: ${{ runner.temp }}\mq-missing
MQ_VERSION: v0.0.0
run: |
$failed = $false
try { Get-Content -Raw scripts\install.ps1 | Invoke-Expression } catch { $failed = $true }
if (-not $failed) { Write-Error 'Installing a nonexistent version should fail'; exit 1 }
if (Test-Path "$env:MQ_HOME\mq.exe") { Write-Error 'mq.exe must not be installed on failure'; exit 1 }
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
- name: Copy install script
run: |
cp ./scripts/install.sh ./docs
cp ./scripts/install.ps1 ./docs
cp ./scripts/install_lsp.sh ./docs
cp ./scripts/install_crawler.sh ./docs
- name: Deploy
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ curl -sSL https://mqlang.org/install.sh | bash

Downloads the latest mq binary for your platform, installs it to `~/.local/bin/`, and updates your shell profile to add mq to your PATH.

On Windows (PowerShell):

```powershell
powershell -c "irm https://mqlang.org/install.ps1 | iex"
```

### Package Managers

| Method | Command |
Expand Down
2 changes: 2 additions & 0 deletions docs/_headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/install.ps1
Content-Type: text/plain; charset=utf-8
6 changes: 6 additions & 0 deletions docs/books/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ The quickest way to install `mq` is via the install script:
curl -sSL https://mqlang.org/install.sh | bash
```

On Windows, use PowerShell:

```powershell
powershell -c "irm https://mqlang.org/install.ps1 | iex"
```

On macOS and Linux, you can also use Homebrew:

```bash
Expand Down
25 changes: 25 additions & 0 deletions docs/books/src/start/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ The installer will:
- Install it to `~/.local/bin/`
- Update your shell profile to add mq to your PATH

### Windows (PowerShell)

```powershell
powershell -c "irm https://mqlang.org/install.ps1 | iex"
```

The installer will:
- Download the latest mq binary for your architecture (x86_64 or arm64)
- Verify its checksum
- Install it to `%USERPROFILE%\.local\bin\`
- Add that directory to your user PATH (restart your terminal afterwards)

Options are set through environment variables, because `irm | iex` cannot take arguments:

| Variable | Description |
| ------------------- | ----------------------------------------------------- |
| `MQ_VERSION` | Release tag to install, e.g. `v0.9.3` (default: latest) |
| `MQ_HOME` | Installation directory |
| `MQ_WITH_DEBUG` | Set to `1` to also install the debugger `mq-dbg` |
| `MQ_NO_MODIFY_PATH` | Set to `1` to leave your PATH untouched |

```powershell
$env:MQ_WITH_DEBUG = "1"; powershell -c "irm https://mqlang.org/install.ps1 | iex"
```

## Cargo

```sh
Expand Down
141 changes: 141 additions & 0 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# mq installation script for Windows
#
# powershell -c "irm https://mqlang.org/install.ps1 | iex"
#
# Options are passed as environment variables because `irm | iex` cannot take arguments:
# MQ_VERSION Release tag to install (e.g. v0.9.3). Defaults to the latest release.
# MQ_HOME Installation directory. Defaults to $HOME\.local\bin.
# MQ_WITH_DEBUG Set to 1 to also install mq-dbg.
# MQ_NO_MODIFY_PATH Set to 1 to leave the user PATH untouched.
#
# Keep this file ASCII only: Windows PowerShell 5.1 decodes `irm` output without a charset as Latin-1.

function Install-Mq {
$ErrorActionPreference = 'Stop'
# Invoke-WebRequest is very slow on Windows PowerShell 5.1 while the progress bar is drawn.
$ProgressPreference = 'SilentlyContinue'

$repo = 'harehare/mq'
$Version = $env:MQ_VERSION
$WithDebug = $env:MQ_WITH_DEBUG -eq '1'
$NoModifyPath = $env:MQ_NO_MODIFY_PATH -eq '1'
$InstallDir = if ($env:MQ_HOME) { $env:MQ_HOME } else { Join-Path $HOME '.local\bin' }

function Write-Log([string]$Message) { Write-Host "[mq] $Message" -ForegroundColor Green }
function Write-Warn([string]$Message) { Write-Host "[mq] warning: $Message" -ForegroundColor Yellow }

# Windows PowerShell 5.1 does not enable TLS 1.2 by default.
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch {
Write-Warn "Could not enable TLS 1.2: $($_.Exception.Message)"
}

function Get-Target {
$arch = $null
try {
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
} catch {
# Older .NET Framework: PROCESSOR_ARCHITEW6432 is set when a 32-bit process runs on 64-bit Windows.
$arch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
}
switch -Regex ($arch) {
'^(X64|AMD64)$' { return 'x86_64-pc-windows-msvc' }
'^(Arm64|ARM64)$' { return 'aarch64-pc-windows-msvc' }
default { throw "Unsupported architecture: $arch" }
}
}

function Get-ReleaseUrl([string]$FileName) {
if ($Version) {
return "https://github.com/$repo/releases/download/$Version/$FileName"
}
return "https://github.com/$repo/releases/latest/download/$FileName"
}

# checksums.txt lines look like "<sha256> <artifact dir>/<file name>".
function Get-ExpectedChecksum([string]$Checksums, [string]$FileName) {
foreach ($line in ($Checksums -split "`n")) {
$parts = $line.Trim() -split '\s+', 2
if ($parts.Count -ne 2) { continue }
$name = $parts[1].TrimStart('*')
if ($name -eq $FileName -or $name.EndsWith("/$FileName")) { return $parts[0] }
}
return $null
}

function Install-Binary([string]$Name, [string]$Target, [string]$Checksums, [string]$TempDir) {
$fileName = "$Name-$Target.exe"
$url = Get-ReleaseUrl $fileName
$tempFile = Join-Path $TempDir $fileName

Write-Log "Downloading $url"
Invoke-WebRequest -Uri $url -OutFile $tempFile -UseBasicParsing

$expected = Get-ExpectedChecksum $Checksums $fileName
if (-not $expected) { throw "No checksum found for $fileName" }
$actual = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash
if ($actual -ne $expected) {
throw "Checksum verification failed for $fileName`nExpected: $expected`nGot: $actual"
}
Write-Log "Checksum verified for $fileName"

$dest = Join-Path $InstallDir "$Name.exe"
try {
Move-Item -Path $tempFile -Destination $dest -Force
} catch {
throw "Failed to write $dest. If $Name is running, close it and try again. $($_.Exception.Message)"
}
Write-Log "$Name installed to $dest"
}

function Add-ToUserPath([string]$Dir) {
# Read and write the registry value directly so an existing REG_EXPAND_SZ Path keeps its %VARS%.
$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true)
try {
$current = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$entries = @($current -split ';' | Where-Object { $_ })
if ($entries | Where-Object { $_.TrimEnd('\') -ieq $Dir.TrimEnd('\') }) {
Write-Log "$Dir is already in your PATH"
} else {
$key.SetValue('Path', (($entries + $Dir) -join ';'), [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Log "Added $Dir to your user PATH"
}
} finally {
$key.Close()
}
if (($env:Path -split ';') -notcontains $Dir) { $env:Path = "$env:Path;$Dir" }
}

$target = Get-Target
Write-Log "Detected system: windows/$target"

$tempDir = Join-Path ([IO.Path]::GetTempPath()) ("mq-install-" + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
try {
Write-Log 'Downloading checksums'
$checksums = (Invoke-WebRequest -Uri (Get-ReleaseUrl 'checksums.txt') -UseBasicParsing).Content
if ($checksums -is [byte[]]) { $checksums = [Text.Encoding]::UTF8.GetString($checksums) }

New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
Install-Binary 'mq' $target $checksums $tempDir
if ($WithDebug) { Install-Binary 'mq-dbg' $target $checksums $tempDir }
} finally {
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}

if (-not $NoModifyPath) { Add-ToUserPath $InstallDir }

$installed = & (Join-Path $InstallDir 'mq.exe') --version
if ($LASTEXITCODE -ne 0) { throw 'mq was installed but failed to run' }

Write-Host ''
Write-Log "$installed installed successfully"
Write-Host ' Restart your terminal, then run:'
Write-Host ' mq --version'
Write-Host " echo '# Hello World' | mq '.h'"
Write-Host ' Documentation: https://mqlang.org/'
}

# `exit` would close the user's shell under `irm | iex`, so failures are reported with throw.
Install-Mq
Loading