incan 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
incan-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Generated by Cargo
|
|
2
|
+
# will have compiled files and executables
|
|
3
|
+
debug
|
|
4
|
+
target
|
|
5
|
+
|
|
6
|
+
# These are backup files generated by rustfmt
|
|
7
|
+
**/*.rs.bk
|
|
8
|
+
|
|
9
|
+
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
10
|
+
*.pdb
|
|
11
|
+
|
|
12
|
+
# Generated by cargo mutants
|
|
13
|
+
# Contains mutation testing data
|
|
14
|
+
**/mutants.out*/
|
|
15
|
+
|
|
16
|
+
# Python (from any scripts/tooling that might be run)
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
*$py.class
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
.env/
|
|
27
|
+
env/
|
|
28
|
+
dist/
|
|
29
|
+
build/
|
|
30
|
+
|
|
31
|
+
# Node.js / VS Code extension (editors/vscode)
|
|
32
|
+
node_modules/
|
|
33
|
+
out/
|
|
34
|
+
*.vsix
|
|
35
|
+
.vscode-test/
|
|
36
|
+
*.tsbuildinfo
|
|
37
|
+
|
|
38
|
+
# npm / npx caches and logs (local tooling)
|
|
39
|
+
.npm-cache/
|
|
40
|
+
npm-debug.log*
|
|
41
|
+
yarn-debug.log*
|
|
42
|
+
yarn-error.log*
|
|
43
|
+
pnpm-debug.log*
|
|
44
|
+
|
|
45
|
+
# Local AI agent instructions
|
|
46
|
+
.claude/
|
|
47
|
+
.agents/state/*
|
|
48
|
+
!.agents/state/.gitkeep
|
|
49
|
+
|
|
50
|
+
# MkDocs build output
|
|
51
|
+
site/
|
|
52
|
+
|
|
53
|
+
# Documentation engines
|
|
54
|
+
.obsidian/
|
|
55
|
+
|
|
56
|
+
# -- Project-specific files --
|
|
57
|
+
examples/advanced/test_output.txt
|
|
58
|
+
|
|
59
|
+
# OrdinalMap benchmark local outputs
|
|
60
|
+
benchmarks/collections/ordinal_map/__pycache__/
|
|
61
|
+
benchmarks/collections/ordinal_map/target/
|
|
62
|
+
|
|
63
|
+
# Generated files we do not want to commit
|
|
64
|
+
workspaces/docs-site/docs/_snippets/rfcs_refs.md
|
|
65
|
+
workspaces/docs-site/docs/_snippets/tables/rfcs_index.md
|
incan-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: incan
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Thin installer package for the Incan toolchain
|
|
5
|
+
Project-URL: Homepage, https://incan.io
|
|
6
|
+
Project-URL: Repository, https://github.com/encero-systems/incan
|
|
7
|
+
Author-email: Danny Meijer <dannys.code.corner@gmail.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Keywords: compiler,incan,toolchain
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Incan Toolchain Python Installer
|
|
21
|
+
|
|
22
|
+
Incan is a statically typed, Pythonic programming language for writing clear high-level application code that compiles to native Rust. Learn more at [incan.io](https://incan.io).
|
|
23
|
+
|
|
24
|
+
This package is a thin installer and command shim for the Incan toolchain. It installs verified toolchain archives from the shared Incan release manifest instead of building the compiler from Python packaging.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pipx install incan
|
|
28
|
+
incan --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The command shims install the toolchain into a package-local cache on first use and default to the release manifest that matches this package version. If `pipx` warns that `~/.local/bin` is not on `PATH`, run `pipx ensurepath` or add that directory to your shell startup before calling `incan`. Set `INCAN_PIP_TOOLCHAIN_HOME`, `INCAN_PIP_BIN_DIR`, or `INCAN_TOOLCHAIN_MANIFEST` when you need a custom cache location or manifest.
|
incan-0.4.0/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Incan Toolchain Python Installer
|
|
2
|
+
|
|
3
|
+
Incan is a statically typed, Pythonic programming language for writing clear high-level application code that compiles to native Rust. Learn more at [incan.io](https://incan.io).
|
|
4
|
+
|
|
5
|
+
This package is a thin installer and command shim for the Incan toolchain. It installs verified toolchain archives from the shared Incan release manifest instead of building the compiler from Python packaging.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pipx install incan
|
|
9
|
+
incan --version
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The command shims install the toolchain into a package-local cache on first use and default to the release manifest that matches this package version. If `pipx` warns that `~/.local/bin` is not on `PATH`, run `pipx ensurepath` or add that directory to your shell startup before calling `incan`. Set `INCAN_PIP_TOOLCHAIN_HOME`, `INCAN_PIP_BIN_DIR`, or `INCAN_TOOLCHAIN_MANIFEST` when you need a custom cache location or manifest.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "incan"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Thin installer package for the Incan toolchain"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Danny Meijer", email = "dannys.code.corner@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["incan", "compiler", "toolchain"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Topic :: Software Development :: Compilers",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://incan.io"
|
|
28
|
+
Repository = "https://github.com/encero-systems/incan"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
incan = "incan_toolchain.cli:incan"
|
|
32
|
+
incan-lsp = "incan_toolchain.cli:incan_lsp"
|
|
33
|
+
install-incan = "incan_toolchain.cli:install"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/incan_toolchain"]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Command shims for the Incan toolchain Python package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _package_root() -> Path:
|
|
13
|
+
return Path(__file__).resolve().parents[2]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _installer_script() -> Path:
|
|
17
|
+
candidates = [
|
|
18
|
+
Path(__file__).resolve().parent / "vendor" / "install-incan.sh",
|
|
19
|
+
_package_root().parent / "install-incan.sh",
|
|
20
|
+
]
|
|
21
|
+
for candidate in candidates:
|
|
22
|
+
if candidate.exists():
|
|
23
|
+
return candidate
|
|
24
|
+
raise RuntimeError("could not find bundled install-incan.sh")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _toolchain_home() -> Path:
|
|
28
|
+
return Path(os.environ.get("INCAN_PIP_TOOLCHAIN_HOME", _package_root() / ".incan" / "home"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _bin_dir() -> Path:
|
|
32
|
+
return Path(os.environ.get("INCAN_PIP_BIN_DIR", _package_root() / ".incan" / "bin"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _package_version() -> str:
|
|
36
|
+
init_text = (Path(__file__).resolve().parent / "__init__.py").read_text(encoding="utf-8")
|
|
37
|
+
match = re.search(r'__version__\s*=\s*"([^"]+)"', init_text)
|
|
38
|
+
if not match:
|
|
39
|
+
raise RuntimeError("could not read incan package version")
|
|
40
|
+
return match.group(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _release_tag() -> str:
|
|
44
|
+
version = _package_version()
|
|
45
|
+
match = re.fullmatch(r"(?P<base>\d+\.\d+\.\d+)(?P<kind>a|b|rc)(?P<number>\d+)", version)
|
|
46
|
+
if match:
|
|
47
|
+
return f"v{match.group('base')}-{match.group('kind')}{match.group('number')}"
|
|
48
|
+
match = re.fullmatch(r"(?P<base>\d+\.\d+\.\d+)\.dev(?P<number>\d+)", version)
|
|
49
|
+
if match:
|
|
50
|
+
return f"v{match.group('base')}-dev.{match.group('number')}"
|
|
51
|
+
return f"v{version}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _package_manifest_url() -> str:
|
|
55
|
+
release = _release_tag()
|
|
56
|
+
return f"https://github.com/encero-systems/incan/releases/download/{release}/manifest.json"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _has_value_option(args: list[str], name: str) -> bool:
|
|
60
|
+
return name in args or any(arg.startswith(f"{name}=") for arg in args)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _installer_args(args: list[str]) -> list[str]:
|
|
64
|
+
next_args = list(args)
|
|
65
|
+
if not _has_value_option(next_args, "--manifest") and not os.environ.get("INCAN_TOOLCHAIN_MANIFEST"):
|
|
66
|
+
next_args.extend(["--manifest", _package_manifest_url()])
|
|
67
|
+
if not _has_value_option(next_args, "--incan-home"):
|
|
68
|
+
next_args.extend(["--incan-home", str(_toolchain_home())])
|
|
69
|
+
if not _has_value_option(next_args, "--bin-dir"):
|
|
70
|
+
next_args.extend(["--bin-dir", str(_bin_dir())])
|
|
71
|
+
return next_args
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _run_installer(args: list[str]) -> int:
|
|
75
|
+
return subprocess.call(["bash", str(_installer_script()), *_installer_args(args)])
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def install() -> None:
|
|
79
|
+
raise SystemExit(_run_installer(sys.argv[1:]))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _command_path(command: str) -> Path:
|
|
83
|
+
return _bin_dir() / command
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _ensure_command(command: str) -> None:
|
|
87
|
+
if _command_path(command).exists():
|
|
88
|
+
return
|
|
89
|
+
status = _run_installer([])
|
|
90
|
+
if status != 0:
|
|
91
|
+
raise SystemExit(status)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _run_command(command: str) -> None:
|
|
95
|
+
_ensure_command(command)
|
|
96
|
+
os.execv(str(_command_path(command)), [command, *sys.argv[1:]])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def incan() -> None:
|
|
100
|
+
_run_command("incan")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def incan_lsp() -> None:
|
|
104
|
+
_run_command("incan-lsp")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "install":
|
|
109
|
+
sys.argv.pop(1)
|
|
110
|
+
install()
|
|
111
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "incan-lsp":
|
|
112
|
+
sys.argv.pop(1)
|
|
113
|
+
incan_lsp()
|
|
114
|
+
incan()
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
default_manifest_url="https://github.com/encero-systems/incan/releases/latest/download/manifest.json"
|
|
5
|
+
manifest_ref="${INCAN_TOOLCHAIN_MANIFEST:-$default_manifest_url}"
|
|
6
|
+
incan_home="${INCAN_HOME:-$HOME/.incan}"
|
|
7
|
+
bin_dir="${INCAN_BIN_DIR:-$HOME/.local/bin}"
|
|
8
|
+
skip_rust_install="${INCAN_SKIP_RUST_INSTALL:-false}"
|
|
9
|
+
rustup_init_ref="${INCAN_RUSTUP_INIT:-https://sh.rustup.rs}"
|
|
10
|
+
target_override=""
|
|
11
|
+
archive_override=""
|
|
12
|
+
dry_run="false"
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'USAGE'
|
|
16
|
+
Install the Incan toolchain from a versioned release manifest.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
install-incan.sh [options]
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--manifest <URL|PATH> Release manifest to use (default: GitHub Releases latest manifest)
|
|
23
|
+
--target <TRIPLE> Host target override for tests or cross-install staging
|
|
24
|
+
--archive <PATH> Use an already-downloaded archive while still verifying the manifest checksum
|
|
25
|
+
--incan-home <PATH> toolchain install root (default: $INCAN_HOME or ~/.incan)
|
|
26
|
+
--bin-dir <PATH> Directory where command symlinks are created (default: $INCAN_BIN_DIR or ~/.local/bin)
|
|
27
|
+
--skip-rust Do not install or update the Rust backend toolchain
|
|
28
|
+
--dry-run Resolve manifest and target without downloading, extracting, or writing files
|
|
29
|
+
-h, --help Show this help
|
|
30
|
+
USAGE
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fail() {
|
|
34
|
+
printf 'install-incan: %s\n' "$*" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
truthy() {
|
|
39
|
+
case "${1:-}" in
|
|
40
|
+
1|true|TRUE|yes|YES|on|ON) return 0 ;;
|
|
41
|
+
*) return 1 ;;
|
|
42
|
+
esac
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
has_command() {
|
|
46
|
+
command -v "$1" >/dev/null 2>&1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
while [ "$#" -gt 0 ]; do
|
|
50
|
+
case "$1" in
|
|
51
|
+
--manifest)
|
|
52
|
+
[ "$#" -ge 2 ] || fail "--manifest requires a value"
|
|
53
|
+
manifest_ref="$2"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--target)
|
|
57
|
+
[ "$#" -ge 2 ] || fail "--target requires a value"
|
|
58
|
+
target_override="$2"
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
--archive)
|
|
62
|
+
[ "$#" -ge 2 ] || fail "--archive requires a value"
|
|
63
|
+
archive_override="$2"
|
|
64
|
+
shift 2
|
|
65
|
+
;;
|
|
66
|
+
--incan-home)
|
|
67
|
+
[ "$#" -ge 2 ] || fail "--incan-home requires a value"
|
|
68
|
+
incan_home="$2"
|
|
69
|
+
shift 2
|
|
70
|
+
;;
|
|
71
|
+
--bin-dir)
|
|
72
|
+
[ "$#" -ge 2 ] || fail "--bin-dir requires a value"
|
|
73
|
+
bin_dir="$2"
|
|
74
|
+
shift 2
|
|
75
|
+
;;
|
|
76
|
+
--skip-rust)
|
|
77
|
+
skip_rust_install="true"
|
|
78
|
+
shift
|
|
79
|
+
;;
|
|
80
|
+
--dry-run)
|
|
81
|
+
dry_run="true"
|
|
82
|
+
shift
|
|
83
|
+
;;
|
|
84
|
+
-h|--help)
|
|
85
|
+
usage
|
|
86
|
+
exit 0
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
fail "unknown option: $1"
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
require_command() {
|
|
95
|
+
has_command "$1" || fail "required command not found: $1"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
detect_target() {
|
|
99
|
+
local os arch
|
|
100
|
+
os="$(uname -s)"
|
|
101
|
+
arch="$(uname -m)"
|
|
102
|
+
case "${os}:${arch}" in
|
|
103
|
+
Darwin:arm64|Darwin:aarch64) printf '%s\n' "aarch64-apple-darwin" ;;
|
|
104
|
+
Darwin:x86_64) printf '%s\n' "x86_64-apple-darwin" ;;
|
|
105
|
+
Linux:x86_64|Linux:amd64) printf '%s\n' "x86_64-unknown-linux-gnu" ;;
|
|
106
|
+
Linux:arm64|Linux:aarch64) fail "Linux arm64 toolchain archives are not shipped in 0.4; use a source build or x86_64 Linux host for now" ;;
|
|
107
|
+
MINGW*|MSYS*|CYGWIN*|Windows_NT:*) fail "native Windows is not supported by the 0.4 toolchain installer; use WSL2 for now" ;;
|
|
108
|
+
*) fail "unsupported host: ${os} ${arch}" ;;
|
|
109
|
+
esac
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
copy_or_download() {
|
|
113
|
+
local ref="$1"
|
|
114
|
+
local out="$2"
|
|
115
|
+
case "$ref" in
|
|
116
|
+
http://*|https://*)
|
|
117
|
+
require_command curl
|
|
118
|
+
curl -fL --progress-bar "$ref" -o "$out"
|
|
119
|
+
;;
|
|
120
|
+
file://*)
|
|
121
|
+
cp "${ref#file://}" "$out"
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
cp "$ref" "$out"
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
json_top_value() {
|
|
130
|
+
local file="$1"
|
|
131
|
+
local field="$2"
|
|
132
|
+
if command -v jq >/dev/null 2>&1; then
|
|
133
|
+
jq -r --arg field "$field" '.[$field] // empty' "$file"
|
|
134
|
+
else
|
|
135
|
+
require_command python3
|
|
136
|
+
python3 - "$file" "$field" <<'PY'
|
|
137
|
+
import json
|
|
138
|
+
import sys
|
|
139
|
+
|
|
140
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
141
|
+
payload = json.load(handle)
|
|
142
|
+
value = payload.get(sys.argv[2], "")
|
|
143
|
+
if isinstance(value, (dict, list)):
|
|
144
|
+
print(json.dumps(value, separators=(",", ":")))
|
|
145
|
+
else:
|
|
146
|
+
print(value)
|
|
147
|
+
PY
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
json_host_value() {
|
|
152
|
+
local file="$1"
|
|
153
|
+
local target="$2"
|
|
154
|
+
local field="$3"
|
|
155
|
+
if command -v jq >/dev/null 2>&1; then
|
|
156
|
+
jq -r --arg target "$target" --arg field "$field" '.hosts[$target][$field] // empty' "$file"
|
|
157
|
+
else
|
|
158
|
+
require_command python3
|
|
159
|
+
python3 - "$file" "$target" "$field" <<'PY'
|
|
160
|
+
import json
|
|
161
|
+
import sys
|
|
162
|
+
|
|
163
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
164
|
+
payload = json.load(handle)
|
|
165
|
+
value = payload.get("hosts", {}).get(sys.argv[2], {}).get(sys.argv[3], "")
|
|
166
|
+
if isinstance(value, (dict, list)):
|
|
167
|
+
print(json.dumps(value, separators=(",", ":")))
|
|
168
|
+
else:
|
|
169
|
+
print(value)
|
|
170
|
+
PY
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
json_command_path() {
|
|
175
|
+
local file="$1"
|
|
176
|
+
local target="$2"
|
|
177
|
+
local command_name="$3"
|
|
178
|
+
if command -v jq >/dev/null 2>&1; then
|
|
179
|
+
jq -r --arg target "$target" --arg command "$command_name" '.hosts[$target].commands[$command] // empty' "$file"
|
|
180
|
+
else
|
|
181
|
+
require_command python3
|
|
182
|
+
python3 - "$file" "$target" "$command_name" <<'PY'
|
|
183
|
+
import json
|
|
184
|
+
import sys
|
|
185
|
+
|
|
186
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
187
|
+
payload = json.load(handle)
|
|
188
|
+
print(payload.get("hosts", {}).get(sys.argv[2], {}).get("commands", {}).get(sys.argv[3], ""))
|
|
189
|
+
PY
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
json_commands() {
|
|
194
|
+
local file="$1"
|
|
195
|
+
if command -v jq >/dev/null 2>&1; then
|
|
196
|
+
jq -r '.commands[]?' "$file"
|
|
197
|
+
else
|
|
198
|
+
require_command python3
|
|
199
|
+
python3 - "$file" <<'PY'
|
|
200
|
+
import json
|
|
201
|
+
import sys
|
|
202
|
+
|
|
203
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
204
|
+
payload = json.load(handle)
|
|
205
|
+
for command_name in payload.get("commands", []):
|
|
206
|
+
print(command_name)
|
|
207
|
+
PY
|
|
208
|
+
fi
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
json_hosts() {
|
|
212
|
+
local file="$1"
|
|
213
|
+
if command -v jq >/dev/null 2>&1; then
|
|
214
|
+
jq -r '.hosts | keys[]?' "$file"
|
|
215
|
+
else
|
|
216
|
+
require_command python3
|
|
217
|
+
python3 - "$file" <<'PY'
|
|
218
|
+
import json
|
|
219
|
+
import sys
|
|
220
|
+
|
|
221
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
222
|
+
payload = json.load(handle)
|
|
223
|
+
for host in payload.get("hosts", {}):
|
|
224
|
+
print(host)
|
|
225
|
+
PY
|
|
226
|
+
fi
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
json_rust_toolchain_value() {
|
|
230
|
+
local file="$1"
|
|
231
|
+
local field="$2"
|
|
232
|
+
if command -v jq >/dev/null 2>&1; then
|
|
233
|
+
jq -r --arg field "$field" '.rust_toolchain[$field] // empty' "$file"
|
|
234
|
+
else
|
|
235
|
+
require_command python3
|
|
236
|
+
python3 - "$file" "$field" <<'PY'
|
|
237
|
+
import json
|
|
238
|
+
import sys
|
|
239
|
+
|
|
240
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
241
|
+
payload = json.load(handle)
|
|
242
|
+
value = payload.get("rust_toolchain", {}).get(sys.argv[2], "")
|
|
243
|
+
if isinstance(value, (dict, list)):
|
|
244
|
+
print(json.dumps(value, separators=(",", ":")))
|
|
245
|
+
else:
|
|
246
|
+
print(value)
|
|
247
|
+
PY
|
|
248
|
+
fi
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
json_rust_targets() {
|
|
252
|
+
local file="$1"
|
|
253
|
+
if command -v jq >/dev/null 2>&1; then
|
|
254
|
+
jq -r '.rust_toolchain.targets[]?' "$file"
|
|
255
|
+
else
|
|
256
|
+
require_command python3
|
|
257
|
+
python3 - "$file" <<'PY'
|
|
258
|
+
import json
|
|
259
|
+
import sys
|
|
260
|
+
|
|
261
|
+
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
262
|
+
payload = json.load(handle)
|
|
263
|
+
for target in payload.get("rust_toolchain", {}).get("targets", []):
|
|
264
|
+
print(target)
|
|
265
|
+
PY
|
|
266
|
+
fi
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
sha256_file() {
|
|
270
|
+
local file="$1"
|
|
271
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
272
|
+
sha256sum "$file" | awk '{print $1}'
|
|
273
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
274
|
+
shasum -a 256 "$file" | awk '{print $1}'
|
|
275
|
+
else
|
|
276
|
+
fail "required command not found: sha256sum or shasum"
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
install_rustup() {
|
|
281
|
+
local channel="$1"
|
|
282
|
+
printf 'Installing Rust backend with rustup (%s)...\n' "$channel"
|
|
283
|
+
case "$rustup_init_ref" in
|
|
284
|
+
http://*|https://*)
|
|
285
|
+
require_command curl
|
|
286
|
+
curl --proto '=https' --tlsv1.2 -sSf "$rustup_init_ref" | sh -s -- -y --profile minimal --default-toolchain "$channel"
|
|
287
|
+
;;
|
|
288
|
+
file://*)
|
|
289
|
+
sh "${rustup_init_ref#file://}" -y --profile minimal --default-toolchain "$channel"
|
|
290
|
+
;;
|
|
291
|
+
*)
|
|
292
|
+
sh "$rustup_init_ref" -y --profile minimal --default-toolchain "$channel"
|
|
293
|
+
;;
|
|
294
|
+
esac
|
|
295
|
+
export PATH="${CARGO_HOME:-$HOME/.cargo}/bin:$PATH"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
ensure_rust_backend() {
|
|
299
|
+
local manifest_file="$1"
|
|
300
|
+
local channel
|
|
301
|
+
channel="$(json_rust_toolchain_value "$manifest_file" "channel")"
|
|
302
|
+
[ -n "$channel" ] || channel="stable"
|
|
303
|
+
|
|
304
|
+
if truthy "$skip_rust_install"; then
|
|
305
|
+
printf 'Rust backend provisioning skipped.\n'
|
|
306
|
+
return 0
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if ! has_command rustup; then
|
|
310
|
+
install_rustup "$channel"
|
|
311
|
+
fi
|
|
312
|
+
has_command rustup || fail "rustup was not available after Rust backend installation"
|
|
313
|
+
|
|
314
|
+
if ! has_command cargo || ! has_command rustc; then
|
|
315
|
+
printf 'Installing Rust toolchain %s...\n' "$channel"
|
|
316
|
+
rustup toolchain install "$channel" --profile minimal
|
|
317
|
+
rustup default "$channel"
|
|
318
|
+
export PATH="${CARGO_HOME:-$HOME/.cargo}/bin:$PATH"
|
|
319
|
+
fi
|
|
320
|
+
has_command cargo || fail "cargo was not available after Rust backend installation"
|
|
321
|
+
has_command rustc || fail "rustc was not available after Rust backend installation"
|
|
322
|
+
|
|
323
|
+
printf 'Rust backend:\n'
|
|
324
|
+
printf ' rustc: %s\n' "$(rustc --version)"
|
|
325
|
+
printf ' cargo: %s\n' "$(cargo --version)"
|
|
326
|
+
while IFS= read -r rust_target; do
|
|
327
|
+
[ -n "$rust_target" ] || continue
|
|
328
|
+
printf ' target: %s\n' "$rust_target"
|
|
329
|
+
rustup target add "$rust_target"
|
|
330
|
+
done <<RUST_TARGETS
|
|
331
|
+
$(json_rust_targets "$manifest_file")
|
|
332
|
+
RUST_TARGETS
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
target="${target_override:-$(detect_target)}"
|
|
336
|
+
tmp_dir="$(mktemp -d)"
|
|
337
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
338
|
+
manifest_file="${tmp_dir}/manifest.json"
|
|
339
|
+
copy_or_download "$manifest_ref" "$manifest_file"
|
|
340
|
+
|
|
341
|
+
schema_version="$(json_top_value "$manifest_file" "schema_version")"
|
|
342
|
+
[ "$schema_version" = "1" ] || fail "unsupported toolchain manifest schema_version: ${schema_version:-missing}"
|
|
343
|
+
|
|
344
|
+
toolchain_version="$(json_top_value "$manifest_file" "toolchain_version")"
|
|
345
|
+
[ -n "$toolchain_version" ] || fail "manifest is missing toolchain_version"
|
|
346
|
+
|
|
347
|
+
archive_url="$(json_host_value "$manifest_file" "$target" "archive_url")"
|
|
348
|
+
archive_sha256="$(json_host_value "$manifest_file" "$target" "archive_sha256")"
|
|
349
|
+
archive_format="$(json_host_value "$manifest_file" "$target" "archive_format")"
|
|
350
|
+
if [ -z "$archive_url" ] || [ -z "$archive_sha256" ]; then
|
|
351
|
+
printf 'Available targets:\n' >&2
|
|
352
|
+
json_hosts "$manifest_file" >&2
|
|
353
|
+
fail "manifest does not contain an archive for target ${target}"
|
|
354
|
+
fi
|
|
355
|
+
[ "${archive_format:-tar.gz}" = "tar.gz" ] || fail "unsupported archive format for ${target}: ${archive_format}"
|
|
356
|
+
|
|
357
|
+
printf 'Incan toolchain %s\n' "$toolchain_version"
|
|
358
|
+
printf ' target: %s\n' "$target"
|
|
359
|
+
printf ' archive: %s\n' "$archive_url"
|
|
360
|
+
printf ' incan home: %s\n' "$incan_home"
|
|
361
|
+
printf ' bin dir: %s\n' "$bin_dir"
|
|
362
|
+
|
|
363
|
+
if [ "$dry_run" = "true" ]; then
|
|
364
|
+
printf 'Dry run only; no files were written.\n'
|
|
365
|
+
exit 0
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
ensure_rust_backend "$manifest_file"
|
|
369
|
+
require_command tar
|
|
370
|
+
archive_file="${archive_override:-${tmp_dir}/toolchain.tar.gz}"
|
|
371
|
+
if [ -n "$archive_override" ]; then
|
|
372
|
+
[ -f "$archive_override" ] || fail "archive override does not exist: $archive_override"
|
|
373
|
+
else
|
|
374
|
+
printf 'Downloading toolchain archive...\n'
|
|
375
|
+
copy_or_download "$archive_url" "$archive_file"
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
printf 'Verifying toolchain archive checksum...\n'
|
|
379
|
+
actual_sha256="$(sha256_file "$archive_file")"
|
|
380
|
+
[ "$actual_sha256" = "$archive_sha256" ] || fail "checksum mismatch for ${archive_file}: expected ${archive_sha256}, got ${actual_sha256}"
|
|
381
|
+
|
|
382
|
+
toolchain_dir="${incan_home}/toolchains/${toolchain_version}"
|
|
383
|
+
if [ -e "$toolchain_dir" ]; then
|
|
384
|
+
fail "toolchain directory already exists: ${toolchain_dir}"
|
|
385
|
+
fi
|
|
386
|
+
extract_dir="${tmp_dir}/toolchain"
|
|
387
|
+
mkdir -p "$extract_dir"
|
|
388
|
+
printf 'Extracting toolchain archive...\n'
|
|
389
|
+
tar -xzf "$archive_file" -C "$extract_dir"
|
|
390
|
+
|
|
391
|
+
while IFS= read -r command_name; do
|
|
392
|
+
[ -n "$command_name" ] || continue
|
|
393
|
+
command_path="$(json_command_path "$manifest_file" "$target" "$command_name")"
|
|
394
|
+
[ -n "$command_path" ] || command_path="bin/${command_name}"
|
|
395
|
+
source_path="${extract_dir}/${command_path}"
|
|
396
|
+
[ -f "$source_path" ] || fail "archive did not contain ${command_name} at ${command_path}"
|
|
397
|
+
done <<COMMANDS
|
|
398
|
+
$(json_commands "$manifest_file")
|
|
399
|
+
COMMANDS
|
|
400
|
+
|
|
401
|
+
[ -f "${extract_dir}/stdlib/testing.incn" ] || fail "archive did not contain stdlib/testing.incn"
|
|
402
|
+
for support_crate in incan_core incan_derive incan_stdlib incan_web_macros; do
|
|
403
|
+
[ -f "${extract_dir}/crates/${support_crate}/Cargo.toml" ] || fail "archive did not contain crates/${support_crate}/Cargo.toml"
|
|
404
|
+
done
|
|
405
|
+
[ -f "${extract_dir}/crates/Cargo.toml" ] || fail "archive did not contain crates/Cargo.toml"
|
|
406
|
+
|
|
407
|
+
mkdir -p "$(dirname "$toolchain_dir")" "$bin_dir"
|
|
408
|
+
mv "$extract_dir" "$toolchain_dir"
|
|
409
|
+
|
|
410
|
+
while IFS= read -r command_name; do
|
|
411
|
+
[ -n "$command_name" ] || continue
|
|
412
|
+
command_path="$(json_command_path "$manifest_file" "$target" "$command_name")"
|
|
413
|
+
[ -n "$command_path" ] || command_path="bin/${command_name}"
|
|
414
|
+
source_path="${toolchain_dir}/${command_path}"
|
|
415
|
+
chmod +x "$source_path"
|
|
416
|
+
link_path="${bin_dir}/${command_name}"
|
|
417
|
+
if [ -e "$link_path" ] && [ ! -L "$link_path" ]; then
|
|
418
|
+
fail "refusing to replace non-symlink command path: ${link_path}"
|
|
419
|
+
fi
|
|
420
|
+
ln -sfn "$source_path" "$link_path"
|
|
421
|
+
printf 'Linked %s -> %s\n' "$link_path" "$source_path"
|
|
422
|
+
done <<COMMANDS
|
|
423
|
+
$(json_commands "$manifest_file")
|
|
424
|
+
COMMANDS
|
|
425
|
+
|
|
426
|
+
current_link="${incan_home}/current"
|
|
427
|
+
if [ -e "$current_link" ] && [ ! -L "$current_link" ]; then
|
|
428
|
+
fail "refusing to replace non-symlink current toolchain path: ${current_link}"
|
|
429
|
+
fi
|
|
430
|
+
ln -sfn "$toolchain_dir" "$current_link"
|
|
431
|
+
|
|
432
|
+
printf 'Installed Incan toolchain %s into %s\n' "$toolchain_version" "$toolchain_dir"
|