aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 14:23:18 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 14:23:18 +0000
commit2fc1af63b20ab67683b74e6bb8f2a3edf5ef2a3d (patch)
treefe261e313a653fad59f89a3031557eb96374ac35
parent1c64adaad7ad89386ae9cb44c8e3050672a59d6e (diff)
Install manuals in an independent directory and register manpath
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
-rw-r--r--AGENTS/ARCH.md20
-rw-r--r--doc/install.md58
-rw-r--r--install.ps131
-rwxr-xr-xinstall.sh115
4 files changed, 193 insertions, 31 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 30c4a3b..e551c8f 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -93,9 +93,17 @@ LLVM 18 executable has only libc, libm and the system loader as dynamic dependen
and runs in a minimal chroot without LLVM/lli. Windows EMBEDDED is untested.
On Linux, `make -C build install` builds the manual and calls the POSIX
-`install.sh` to copy `cpsc` and `doc/cpsc.1` into `CMAKE_INSTALL_PREFIX/bin` and
-`CMAKE_INSTALL_PREFIX/share/man/man1` (default prefix `/usr/local`). The script
-also supports `--prefix`, `--source-dir` and `DESTDIR` staging. On macOS it removes
+`install.sh` to copy `cpsc` into `CMAKE_INSTALL_PREFIX/bin` and all manuals into
+`CMAKE_INSTALL_PREFIX/share/cpsc/man/man1` and `man7` (default prefix `/usr/local`).
+Linux man-db registration appends a deduplicated MANDATORY_MANPATH entry to
+`/etc/manpath.config` or `/etc/man_db.conf` as root, or `~/.manpath` as a user.
+macOS root registration uses `/usr/local/etc/man.d/cpsc.conf`; user installation
+adds a MANPATH block to zsh/bash login configuration and prints optional sudo
+commands for global registration. Existing configuration is preserved. Explicit
+MANPATH settings may need updating; Linux registration rejects whitespace in the
+prefix, and Unix prefixes cannot contain colons/newlines.
+The script also supports `--prefix`, `--source-dir` and `DESTDIR` staging;
+staging never changes host manpath or shell configuration. On macOS it removes
`com.apple.quarantine` from the installed executable if present; attribute inspection
or removal failures abort installation with a diagnostic. Ordinary builds
do not require Pandoc; `doc`, installation and release packaging do. The modular
@@ -108,8 +116,8 @@ as `build/cpsc-VERSION-SYSTEM-ARCH.tar.xz`, retaining the configured execution m
timestamp (`yyyy-mm-dd-hh-mm-ss`) followed by its short ID. Git is only required
for packaging; recipients can install the extracted archive using just the script.
-The Windows `install.ps1` installs `cpsc.exe` and `doc/cpsc.html` for the current
-user under `%LOCALAPPDATA%\Programs\cpsc` (or `-Prefix`). It appends that directory
+The Windows `install.ps1` installs `cpsc.exe`, `doc/cpsc.html` and
+`doc/language/*.html` for the current user under `%LOCALAPPDATA%\Programs\cpsc` (or `-Prefix`). It appends that directory
to the user PATH without copying the combined process PATH, and creates a
per-user Start Menu help shortcut. `-SourceDir` selects the artifact directory;
`DESTDIR` staging is unsupported on Windows. The script is compatible with
@@ -127,7 +135,7 @@ Windows `mingw32-make -C build install` builds the HTML manual and invokes
directory above; explicit prefixes are preserved. Unix still invokes `install.sh`.
The release target explicitly passes the configured Windows flag to
`cmake/Release.cmake`, since a standalone CMake script otherwise describes the
-host platform. Windows packages `cpsc.exe`, `doc/cpsc.html`, `install.ps1` and
+host platform. Windows packages `cpsc.exe`, `doc/cpsc.html`, `doc/language/*.html`, `install.ps1` and
`LICENSE` as `build/cpsc-VERSION-Windows-ARCH.zip` using CMake's default Deflate
compression. Unix retains `.tar.xz`, the POSIX installer and the man pages.
Linux smoke tests can configure an isolated build with `-DWIN32=TRUE` to exercise
diff --git a/doc/install.md b/doc/install.md
index ef4c3f6..9fe660a 100644
--- a/doc/install.md
+++ b/doc/install.md
@@ -9,12 +9,15 @@ After configuring and building, install the compiler and manual with:
```bash
sudo make -C build install
cpsc test.cps -r
-man cpsc
+man 1 cpsc
+man 7 cpsc-language-overview
+man 7 cpsc-keyword-if
+man 7 cpsc-function-num_to_str
```
-This calls `install.sh` and installs `cpsc` to `/usr/local/bin` and its manual to
-`/usr/local/share/man/man1`. Installation generates the manual automatically and
-requires Pandoc. Ordinary builds still do not require Pandoc.
+This calls `install.sh` and installs `cpsc` to `/usr/local/bin` and its manuals to
+`/usr/local/share/cpsc/man/man1` and `/usr/local/share/cpsc/man/man7`.
+Installation generates all manuals automatically and requires Pandoc. Ordinary builds still do not require Pandoc.
For installation without sudo, set a writable prefix in the existing build:
@@ -28,6 +31,38 @@ You can also run `./install.sh --source-dir build --prefix "$HOME/.local"`
directly after building `cpsc` and running `make -C build doc`.
`DESTDIR` is supported for staging, for example `DESTDIR=/tmp/cpsc-stage make -C build install`.
+### Manual search path registration
+
+On Linux with man-db, root installation appends a cpsc entry to
+`/etc/manpath.config` (or `/etc/man_db.conf`); installation as an ordinary user
+uses `~/.manpath`. Existing configuration is preserved, and repeated installation
+does not add the same entry again. Linux registration requires a prefix without
+whitespace. Unix prefixes cannot contain colons or newlines.
+
+On macOS, root installation registers the directory in
+`/usr/local/etc/man.d/cpsc.conf`. Without sudo, the installer appends a cpsc
+MANPATH block to `~/.zprofile` for zsh or `~/.bash_profile` for bash, preserving
+existing and default search paths. Open a new login terminal afterward. Other
+shells receive a manual setup hint. For a user installation, global registration
+is optional; the installer recommends these manual commands (adjust the path):
+
+```bash
+sudo mkdir -p /usr/local/etc/man.d
+printf '%s\n' "MANPATH $HOME/.local/share/cpsc/man" | sudo tee -a /usr/local/etc/man.d/cpsc.conf
+```
+
+An explicitly set MANPATH overrides man-db configuration. If you already set it,
+add the cpsc directory and retain a colon to include default system paths:
+
+```bash
+export MANPATH="$HOME/.local/share/cpsc/man:${MANPATH:-}"
+```
+
+DESTDIR staging copies files only and never changes the host's system/user
+manpath or shell configuration. Register the final directory when deploying.
+The installer leaves old shared-directory manuals in place; when upgrading,
+remove the previous `PREFIX/share/man/man1/cpsc.1` yourself if it is obsolete.
+
On macOS, `install.sh` also removes `com.apple.quarantine` from the installed
executable when present. An executable without that attribute installs normally;
failure to inspect or remove the attribute is reported as an installation error.
@@ -50,9 +85,10 @@ Use `EMBEDDED` when recipients should not need LLVM or `lli` at run time.
Extract the archive with `tar -xJf`, enter the extracted directory, then run
`sudo ./install.sh`, or `./install.sh --prefix "$HOME/.local"` for a local installation.
The package installer does not require CMake, Git or Pandoc.
-For a custom prefix, add its `bin` directory to PATH; `man` normally discovers the
-adjacent `share/man` directory from PATH. If your man configuration overrides this,
-use `man -M "$HOME/.local/share/man" cpsc` for the local prefix.
+For a custom prefix, add its `bin` directory to PATH. The installer registers
+`PREFIX/share/cpsc/man` in manpath configuration; it does not rely on automatic
+discovery of an adjacent shared man directory. For explicit lookup, use
+`man -M "$HOME/.local/share/cpsc/man" 7 cpsc-keyword-if`.
## Windows installation
@@ -69,15 +105,17 @@ prefix is preserved. To change it, run
`cmake -S . -B build -DCMAKE_INSTALL_PREFIX="D:/My Programs/cpsc"` before installing.
You can also call `install.ps1` directly. It installs for the current user without
-administrator rights. Its source directory must contain `cpsc.exe` and `doc/cpsc.html`:
+administrator rights. Its source directory must contain `cpsc.exe`,
+`doc/cpsc.html` and `doc/language/*.html`:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -SourceDir .\build
```
The default destination is `%LOCALAPPDATA%\Programs\cpsc`: `cpsc.exe` goes in
-that directory and the HTML manual goes in its `doc` subdirectory. The installer
-adds the directory to the user PATH and creates Start Menu > cpsc > cpsc Help,
+that directory, the command manual goes in `doc`, and syntax manuals go in
+`doc/language`. The command manual links to the language overview and its pages.
+The installer adds the directory to the user PATH and creates Start Menu > cpsc > cpsc Help,
which opens the manual in the default browser. Open a new terminal afterward;
existing parent terminals retain their old PATH. Reinstalling does not duplicate
the PATH entry. Use `-Prefix 'D:\My Programs\cpsc'` for a custom destination,
diff --git a/install.ps1 b/install.ps1
index b26923a..7d8ccce 100644
--- a/install.ps1
+++ b/install.ps1
@@ -3,7 +3,7 @@
.SYNOPSIS
Installs cpsc and its HTML manual for the current user.
.DESCRIPTION
-Copies cpsc.exe and doc\cpsc.html, creates a Start Menu help shortcut, and
+Copies cpsc.exe, doc\cpsc.html and doc\language\*.html, creates a help shortcut, and
adds the installation directory to the user PATH. No administrator is needed
for the default location: %LOCALAPPDATA%\Programs\cpsc.
.EXAMPLE
@@ -26,7 +26,7 @@ $ErrorActionPreference = 'Stop'
if ($Help) {
Write-Output 'Usage: install.ps1 [-Prefix DIR] [-SourceDir DIR] [-Help]'
Write-Output 'Default destination: %LOCALAPPDATA%\Programs\cpsc'
- Write-Output 'Source must contain cpsc.exe and doc\cpsc.html; default: script directory.'
+ Write-Output 'Source must contain cpsc.exe, doc\cpsc.html and doc\language\*.html; default: script directory.'
return
}
@@ -71,16 +71,29 @@ try {
}
}
+ $sourceLanguageDir = Join-Path $SourceDir 'doc\language'
+ if (-not (Test-Path -LiteralPath $sourceLanguageDir -PathType Container)) {
+ throw "Missing language manuals: $sourceLanguageDir; build the doc target first."
+ }
+ $languageManuals = @(Get-ChildItem -LiteralPath $sourceLanguageDir -Filter 'cpsc-*.html' -File)
+ if ($languageManuals.Count -eq 0) {
+ throw "Missing language manuals: $sourceLanguageDir; build the doc target first."
+ }
+
$docDir = Join-Path $Prefix 'doc'
+ $languageDir = Join-Path $docDir 'language'
$targetExe = Join-Path $Prefix 'cpsc.exe'
$targetManual = Join-Path $docDir 'cpsc.html'
- foreach ($directory in @($Prefix, $docDir)) {
+ foreach ($directory in @($Prefix, $docDir, $languageDir)) {
if ((Test-Path -LiteralPath $directory) -and
-not (Test-Path -LiteralPath $directory -PathType Container)) {
throw "Installation directory is occupied by a file: $directory"
}
}
- foreach ($file in @($targetExe, $targetManual)) {
+ $targetFiles = @($targetExe, $targetManual) + @($languageManuals | ForEach-Object {
+ Join-Path $languageDir $_.Name
+ })
+ foreach ($file in $targetFiles) {
if (Test-Path -LiteralPath $file -PathType Container) {
throw "Installation file is occupied by a directory: $file"
}
@@ -94,13 +107,19 @@ try {
$shell = New-Object -ComObject WScript.Shell
# Directory.CreateDirectory treats paths literally, including spaces and brackets.
- [IO.Directory]::CreateDirectory($docDir) | Out-Null
+ [IO.Directory]::CreateDirectory($languageDir) | Out-Null
if ($sourceExe -ine $targetExe) {
Copy-Item -LiteralPath $sourceExe -Destination $targetExe -Force
}
if ($sourceManual -ine $targetManual) {
Copy-Item -LiteralPath $sourceManual -Destination $targetManual -Force
}
+ foreach ($manual in $languageManuals) {
+ $destination = Join-Path $languageDir $manual.Name
+ if ($manual.FullName -ine $destination) {
+ Copy-Item -LiteralPath $manual.FullName -Destination $destination -Force
+ }
+ }
[IO.Directory]::CreateDirectory($shortcutDir) | Out-Null
$shortcut = $shell.CreateShortcut((Join-Path $shortcutDir 'cpsc Help.lnk'))
$shortcut.TargetPath = $targetManual
@@ -117,7 +136,7 @@ try {
$env:PATH = "$env:PATH;$Prefix"
}
Write-Output "Installed $targetExe"
- Write-Output "Installed $targetManual"
+ Write-Output "Installed $targetManual and $($languageManuals.Count) language manuals in $languageDir"
Write-Output 'Open the manual from Start Menu > cpsc > cpsc Help.'
Write-Output 'Open a new terminal to use cpsc by name (existing parent terminals keep their old PATH).'
}
diff --git a/install.sh b/install.sh
index 43a4a42..fbe98a2 100755
--- a/install.sh
+++ b/install.sh
@@ -5,11 +5,12 @@ usage() {
cat <<'EOF'
Usage: install.sh [--prefix DIR] [--source-dir DIR]
-Install cpsc and its man page to DIR/bin and DIR/share/man/man1.
+Install cpsc to DIR/bin and its man pages to DIR/share/cpsc/man/man1 and man7.
The default prefix is /usr/local. The source directory defaults to the
-directory containing this script and must contain cpsc and doc/cpsc.1.
+directory containing this script and must contain cpsc, doc/cpsc.1 and
+doc/language/*.7. Installation registers the independent manual search path.
Use --source-dir build when running this script from the source checkout.
-DESTDIR may be set to stage an installation without changing the prefix.
+DESTDIR may be set to stage files without changing host manpath configuration.
EOF
}
@@ -45,18 +46,108 @@ source_dir=$(CDPATH= cd -- "$source_dir" && pwd) || die "cannot open source dire
|| die "missing readable executable: $source_dir/cpsc; build cpsc first"
[ -f "$source_dir/doc/cpsc.1" ] && [ -r "$source_dir/doc/cpsc.1" ] \
|| die "missing readable manual: $source_dir/doc/cpsc.1; run make doc first"
+set -- "$source_dir"/doc/language/cpsc-*.7
+[ -f "$1" ] || die "missing language manuals: $source_dir/doc/language; run make doc first"
+for manual do
+ [ -f "$manual" ] && [ -r "$manual" ] || die "missing readable manual: $manual"
+done
command -v install >/dev/null 2>&1 || die "required command not found: install"
+platform=$(uname -s)
+man_root=${prefix%/}/share/cpsc/man
+case "$man_root" in
+ *:*) die "--prefix cannot contain a colon (manpath separator)" ;;
+ *'
+'*) die "--prefix cannot contain a newline" ;;
+esac
+if [ "$platform" = Linux ] && [ -z "$destdir" ]; then
+ case "$man_root" in
+ *[[:space:]]*) die "Linux man-db registration requires a --prefix without whitespace" ;;
+ esac
+fi
+
+# Append only our entry; leave existing system/user configuration intact.
+register_line() {
+ config=$1
+ entry=$2
+ if [ -e "$config" ]; then
+ [ -f "$config" ] && [ -r "$config" ] && [ -w "$config" ] \
+ || die "cannot update manpath configuration: $config"
+ if grep -Fqx -- "$entry" "$config"; then return; fi
+ fi
+ printf '\n# cpsc manual: %s\n%s\n' "$man_root" "$entry" >> "$config" \
+ || die "cannot register manuals in $config"
+ printf 'Registered manuals in %s\n' "$config"
+}
+
+register_manpath() {
+ case "$platform" in
+ Linux)
+ if [ "$(id -u)" -eq 0 ]; then
+ if [ -f /etc/manpath.config ]; then
+ config=/etc/manpath.config
+ elif [ -f /etc/man_db.conf ]; then
+ config=/etc/man_db.conf
+ else
+ die "cannot locate man-db configuration; register $man_root manually"
+ fi
+ else
+ [ -n "${HOME:-}" ] || die "HOME is unset; cannot locate user manpath configuration"
+ config=$HOME/.manpath
+ fi
+ register_line "$config" "MANDATORY_MANPATH $man_root"
+ ;;
+ Darwin)
+ if [ "$(id -u)" -eq 0 ]; then
+ install -d /usr/local/etc/man.d || die "cannot create macOS man configuration directory"
+ register_line /usr/local/etc/man.d/cpsc.conf "MANPATH $man_root"
+ else
+ [ -n "${HOME:-}" ] || die "HOME is unset; cannot locate user shell configuration"
+ case "${SHELL:-}" in
+ */zsh) config=$HOME/.zprofile ;;
+ */bash) config=$HOME/.bash_profile ;;
+ *)
+ printf 'Set MANPATH manually for your shell: %s:\n' "$man_root"
+ config=
+ ;;
+ esac
+ # Quote a literal path inside double quotes in the shell snippet.
+ shell_man_root=$(printf '%s' "$man_root" | sed 's/[\\`"$]/\\&/g')
+ if [ -n "$config" ]; then
+ marker="# cpsc manual: $man_root"
+ if ! grep -Fqx -- "$marker" "$config" 2>/dev/null; then
+ printf '\n%s\n%s\n%s\n%s\n%s\n' "$marker" \
+ 'case ":${MANPATH:-}:" in' \
+ " *\":$shell_man_root:\"*) ;;" \
+ " *) export MANPATH=\"$shell_man_root:\${MANPATH:-}\" ;;" \
+ 'esac' >> "$config" || die "cannot update shell configuration: $config"
+ fi
+ printf 'Registered manuals in %s; open a new login terminal.\n' "$config"
+ fi
+ printf '%s\n' 'For optional global registration, run manually:' \
+ ' sudo mkdir -p /usr/local/etc/man.d' \
+ " printf '%s\\n' \"MANPATH $shell_man_root\" | sudo tee -a /usr/local/etc/man.d/cpsc.conf"
+ fi
+ ;;
+ *) printf 'Register %s in your system or user manpath configuration.\n' "$man_root" ;;
+ esac
+}
+
bin_dir=$destdir${prefix%/}/bin
-man_dir=$destdir${prefix%/}/share/man/man1
-install -d "$bin_dir" "$man_dir" \
+man_dir=$destdir$man_root
+install -d "$bin_dir" "$man_dir/man1" "$man_dir/man7" \
|| die "cannot create installation directories; check permissions or choose a writable --prefix"
install -m 755 "$source_dir/cpsc" "$bin_dir/cpsc" \
|| die "cannot install cpsc to $bin_dir; check permissions"
-install -m 644 "$source_dir/doc/cpsc.1" "$man_dir/cpsc.1" \
+install -m 644 "$source_dir/doc/cpsc.1" "$man_dir/man1/cpsc.1" \
|| die "cannot install the manual to $man_dir; check permissions"
-if [ "$(uname -s)" = Darwin ]; then
+for manual do
+ install -m 644 "$manual" "$man_dir/man7/$(basename -- "$manual")" \
+ || die "cannot install language manual: $manual"
+done
+
+if [ "$platform" = Darwin ]; then
command -v xattr >/dev/null 2>&1 || die "required command not found: xattr"
attributes=$(xattr "$bin_dir/cpsc") \
|| die "cannot read extended attributes of $bin_dir/cpsc"
@@ -66,11 +157,17 @@ if [ "$(uname -s)" = Darwin ]; then
fi
fi
-printf 'Installed %s\nInstalled %s\n' "$bin_dir/cpsc" "$man_dir/cpsc.1"
+printf 'Installed %s\nInstalled manuals in %s\n' "$bin_dir/cpsc" "$man_dir"
if [ -z "$destdir" ]; then
+ register_manpath
case ":${PATH:-}:" in
*":${prefix%/}/bin:"*) ;;
*) printf 'Add %s/bin to PATH to run cpsc by name.\n' "${prefix%/}" ;;
esac
- printf 'Read the manual with: man cpsc\n'
+ printf 'Read manuals with: man 1 cpsc; man 7 cpsc-language-overview\n'
+ if [ -n "${MANPATH:-}" ]; then
+ printf 'Your explicit MANPATH overrides configuration; also include %s (retain a colon for defaults).\n' "$man_root"
+ fi
+else
+ printf 'Staged files only; register %s in manpath when deploying.\n' "$man_root"
fi