@greenpandastudios/aug-cli 0.20.1 → 0.21.0
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.
- package/README.md +1 -1
- package/docs/assets/benchmarks/execution.svg +774 -637
- package/docs/assets/benchmarks/http.svg +278 -249
- package/docs/assets/benchmarks/improvements.svg +66 -66
- package/docs/assets/benchmarks/kernels-mobile.svg +1 -0
- package/docs/assets/benchmarks/kernels.svg +1 -0
- package/docs/assets/benchmarks/memory.svg +153 -153
- package/docs/benchmark-results.json +227089 -90363
- package/docs/compatibility.md +6 -6
- package/docs/contributing-benchmarks.md +36 -6
- package/docs/dev-containers.md +8 -15
- package/docs/dgx-consumers.json +122 -0
- package/docs/dgx-gyms.json +784 -0
- package/docs/dgx-kernels.json +1170 -0
- package/docs/dgx-performance.json +227849 -0
- package/docs/dgx-spark.md +64 -0
- package/docs/diagnostics.md +1 -1
- package/docs/docker.md +16 -17
- package/docs/example-projects.json +32 -0
- package/docs/examples/approved-design/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/approved-design/domain/app.md +4 -4
- package/docs/examples/approved-design/main.md +2 -2
- package/docs/examples/calls-benchmark/index.md +36 -0
- package/docs/examples/calls-benchmark/main.md +78 -0
- package/docs/examples/calls-benchmark/operations.md +60 -0
- package/docs/examples/developer-workflow/calculator.md +5 -5
- package/docs/examples/developer-workflow/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/developer-workflow/logging/console.md +2 -2
- package/docs/examples/developer-workflow/logging/logger.md +2 -2
- package/docs/examples/developer-workflow/main.md +2 -2
- package/docs/examples/errors-benchmark/index.md +36 -0
- package/docs/examples/errors-benchmark/main.md +92 -0
- package/docs/examples/errors-benchmark/operations.md +65 -0
- package/docs/examples/float-benchmark/index.md +35 -0
- package/docs/examples/float-benchmark/main.md +75 -0
- package/docs/examples/generic-di/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/generic-di/main.md +2 -2
- package/docs/examples/generic-di/types.md +3 -3
- package/docs/examples/hello/app/greeter.md +3 -3
- package/docs/examples/hello/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/hello/logging/console.md +2 -2
- package/docs/examples/hello/logging/logger.md +2 -2
- package/docs/examples/hello/main.md +2 -2
- package/docs/examples/index.md +18 -1
- package/docs/examples/interceptors/app.md +5 -5
- package/docs/examples/interceptors/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/interceptors/interceptors.md +2 -2
- package/docs/examples/interceptors/logging.md +3 -3
- package/docs/examples/interceptors/main.md +2 -2
- package/docs/examples/list-benchmark/index.md +35 -0
- package/docs/examples/list-benchmark/main.md +78 -0
- package/docs/examples/map-churn-benchmark/index.md +35 -0
- package/docs/examples/map-churn-benchmark/main.md +106 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/api.md +104 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.md +54 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi-json.md +48 -0
- package/docs/examples/native-blake3/hashing.md +93 -0
- package/docs/examples/native-blake3/index.md +38 -0
- package/docs/examples/native-blake3/main.md +73 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/api.md +406 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.md +51 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.md +63 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi-json.md +161 -0
- package/docs/examples/native-pytorch/index.md +38 -0
- package/docs/examples/native-pytorch/main.md +73 -0
- package/docs/examples/native-pytorch/tensors.md +123 -0
- package/docs/examples/native-sqlite/database.md +116 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/api.md +284 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.md +51 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.md +75 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi-json.md +119 -0
- package/docs/examples/native-sqlite/index.md +38 -0
- package/docs/examples/native-sqlite/main.md +73 -0
- package/docs/examples/native-zlib/compression.md +99 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/api.md +153 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.md +54 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi-json.md +76 -0
- package/docs/examples/native-zlib/index.md +38 -0
- package/docs/examples/native-zlib/main.md +78 -0
- package/docs/examples/new-syntax/console.md +2 -2
- package/docs/examples/new-syntax/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/new-syntax/greeter.md +3 -3
- package/docs/examples/new-syntax/logger.md +2 -2
- package/docs/examples/new-syntax/main.md +2 -2
- package/docs/examples/ownership-transfer/dependencies/august/{0.20.1 → 0.21.0}/io/contracts.md +3 -3
- package/docs/examples/ownership-transfer/main.md +2 -2
- package/docs/examples/ownership-transfer/resource.md +2 -2
- package/docs/examples/packages-math/aug-package-json.md +1 -1
- package/docs/examples/records-benchmark/data.md +55 -0
- package/docs/examples/records-benchmark/index.md +36 -0
- package/docs/examples/records-benchmark/main.md +85 -0
- package/docs/examples/strings-benchmark/index.md +35 -0
- package/docs/examples/strings-benchmark/main.md +76 -0
- package/docs/examples/tasks-benchmark/index.md +36 -0
- package/docs/examples/tasks-benchmark/main.md +89 -0
- package/docs/examples/tasks-benchmark/operations.md +58 -0
- package/docs/getting-started.md +3 -3
- package/docs/grammar.md +2 -2
- package/docs/gym-results.json +784 -0
- package/docs/implementation-map.md +6 -0
- package/docs/index.md +1 -1
- package/docs/kernel-results.json +1170 -0
- package/docs/language-conformance.md +6 -0
- package/docs/language-constructs.md +9 -1
- package/docs/learn/index.md +1 -1
- package/docs/maintaining-docs.md +1 -1
- package/docs/native-implementation.md +276 -0
- package/docs/native-interop-llvm-plan.md +342 -0
- package/docs/native-package-examples.md +684 -0
- package/docs/native-packages.md +179 -0
- package/docs/packages.md +2 -2
- package/docs/performance.md +50 -29
- package/docs/production-readiness.md +11 -1
- package/docs/public/downloads/approved-design.zip +0 -0
- package/docs/public/downloads/calls-benchmark.zip +0 -0
- package/docs/public/downloads/developer-workflow.zip +0 -0
- package/docs/public/downloads/errors-benchmark.zip +0 -0
- package/docs/public/downloads/float-benchmark.zip +0 -0
- package/docs/public/downloads/generic-di.zip +0 -0
- package/docs/public/downloads/hello.zip +0 -0
- package/docs/public/downloads/interceptors.zip +0 -0
- package/docs/public/downloads/json-benchmark.zip +0 -0
- package/docs/public/downloads/list-benchmark.zip +0 -0
- package/docs/public/downloads/map-churn-benchmark.zip +0 -0
- package/docs/public/downloads/native-blake3.zip +0 -0
- package/docs/public/downloads/native-pytorch.zip +0 -0
- package/docs/public/downloads/native-sqlite.zip +0 -0
- package/docs/public/downloads/native-zlib.zip +0 -0
- package/docs/public/downloads/new-syntax.zip +0 -0
- package/docs/public/downloads/oidc-login.zip +0 -0
- package/docs/public/downloads/ownership-transfer.zip +0 -0
- package/docs/public/downloads/packages-app.zip +0 -0
- package/docs/public/downloads/packages-math.zip +0 -0
- package/docs/public/downloads/records-benchmark.zip +0 -0
- package/docs/public/downloads/strings-benchmark.zip +0 -0
- package/docs/public/downloads/tasks-benchmark.zip +0 -0
- package/docs/qualification-results.md +66 -0
- package/docs/reference.md +24 -0
- package/docs/releasing.md +81 -6
- package/docs/research/libtorch-native-qualification.md +115 -0
- package/docs/research/native-interop-llvm.md +197 -0
- package/docs/research/qualification-methods.md +15 -0
- package/docs/roadmap.md +1 -0
- package/docs/safety-gyms.md +41 -0
- package/docs/tooling.md +16 -15
- package/docs/web-library-gaps.md +1 -1
- package/docs/web.md +4 -0
- package/examples/approved-design/.aug-spec/manifest.json +3 -3
- package/examples/approved-design/domain/app.aug.md +4 -4
- package/examples/approved-design/main.aug.md +2 -2
- package/examples/developer-workflow/.aug-spec/manifest.json +3 -3
- package/examples/developer-workflow/calculator.aug.md +5 -5
- package/examples/developer-workflow/logging/console.aug.md +2 -2
- package/examples/developer-workflow/logging/logger.aug.md +2 -2
- package/examples/developer-workflow/main.aug.md +2 -2
- package/examples/generic-di/.aug-spec/manifest.json +3 -3
- package/examples/generic-di/main.aug.md +2 -2
- package/examples/generic-di/types.aug.md +3 -3
- package/examples/hello/.aug-spec/manifest.json +3 -3
- package/examples/hello/app/greeter.aug.md +3 -3
- package/examples/hello/logging/console.aug.md +2 -2
- package/examples/hello/logging/logger.aug.md +2 -2
- package/examples/hello/main.aug.md +2 -2
- package/examples/interceptors/.aug-spec/manifest.json +3 -3
- package/examples/interceptors/app.aug.md +5 -5
- package/examples/interceptors/interceptors.aug.md +2 -2
- package/examples/interceptors/logging.aug.md +3 -3
- package/examples/interceptors/main.aug.md +2 -2
- package/examples/native-blake3/.aug-spec/manifest.json +16 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.1/native.abi.json +29 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug +14 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug.md +34 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug +4 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug.md +8 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi.json +29 -0
- package/examples/native-blake3/AGENTS.md +5 -0
- package/examples/native-blake3/aug.lock.json +253 -0
- package/examples/native-blake3/hashing.aug +11 -0
- package/examples/native-blake3/hashing.aug.md +25 -0
- package/examples/native-blake3/main.aug +8 -0
- package/examples/native-blake3/main.aug.md +13 -0
- package/examples/native-pytorch/.aug-spec/manifest.json +18 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.1/native.abi.json +142 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.3/native.abi.json +142 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug +101 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug.md +160 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug +3 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug.md +8 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug +6 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug.md +13 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi.json +142 -0
- package/examples/native-pytorch/AGENTS.md +5 -0
- package/examples/native-pytorch/aug.lock.json +309 -0
- package/examples/native-pytorch/main.aug +8 -0
- package/examples/native-pytorch/main.aug.md +13 -0
- package/examples/native-pytorch/tensors.aug +23 -0
- package/examples/native-pytorch/tensors.aug.md +35 -0
- package/examples/native-sqlite/.aug-spec/manifest.json +18 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.2/native.abi.json +100 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug +48 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug.md +91 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug +3 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug.md +8 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug +8 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug.md +22 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi.json +100 -0
- package/examples/native-sqlite/AGENTS.md +5 -0
- package/examples/native-sqlite/aug.lock.json +236 -0
- package/examples/native-sqlite/database.aug +15 -0
- package/examples/native-sqlite/database.aug.md +27 -0
- package/examples/native-sqlite/main.aug +8 -0
- package/examples/native-sqlite/main.aug.md +13 -0
- package/examples/native-zlib/.aug-spec/manifest.json +16 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.1/native.abi.json +57 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug +29 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug.md +54 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug +4 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug.md +8 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi.json +57 -0
- package/examples/native-zlib/AGENTS.md +5 -0
- package/examples/native-zlib/aug.lock.json +236 -0
- package/examples/native-zlib/compression.aug +15 -0
- package/examples/native-zlib/compression.aug.md +27 -0
- package/examples/native-zlib/main.aug +10 -0
- package/examples/native-zlib/main.aug.md +13 -0
- package/examples/new-syntax/.aug-spec/manifest.json +3 -3
- package/examples/new-syntax/console.aug.md +2 -2
- package/examples/new-syntax/greeter.aug.md +3 -3
- package/examples/new-syntax/logger.aug.md +2 -2
- package/examples/new-syntax/main.aug.md +2 -2
- package/examples/oidc-login/aug.lock.json +6 -6
- package/examples/ownership-transfer/.aug-spec/manifest.json +3 -3
- package/examples/ownership-transfer/main.aug.md +2 -2
- package/examples/ownership-transfer/resource.aug.md +2 -2
- package/examples/packages/math/aug-package.json +1 -1
- package/native/compiler-packs.json +43 -0
- package/package.json +3 -2
- package/runtime/aug_http.c +48 -21
- package/runtime/aug_http_ir.c +30 -0
- package/runtime/aug_http_ir.h +18 -0
- package/runtime/aug_ir.c +209 -0
- package/runtime/aug_ir.h +89 -0
- package/runtime/aug_json.c +6 -1
- package/runtime/aug_runtime.c +82 -16
- package/runtime/aug_runtime.h +16 -2
- package/runtime/aug_tasks.c +38 -7
- package/runtime/aug_values.c +12 -6
- package/scripts/bootstrap-native.mjs +4 -1
- package/src/ast.js +1 -1
- package/src/checker.js +110 -28
- package/src/cli.js +92 -14
- package/src/codegen.js +31 -6
- package/src/compiler-packs.js +66 -0
- package/src/config.js +2 -2
- package/src/editor.js +15 -6
- package/src/formatter.js +4 -2
- package/src/git-http.js +137 -0
- package/src/git-packages.js +9 -1
- package/src/help.js +10 -2
- package/src/ir-types.js +5 -0
- package/src/ir-verify.js +277 -0
- package/src/ir.js +962 -0
- package/src/llvm-debug.js +101 -0
- package/src/llvm-native.js +152 -0
- package/src/llvm-platform.js +20 -0
- package/src/llvm.js +781 -0
- package/src/native-artifacts.js +207 -0
- package/src/native-bindings.js +189 -0
- package/src/native-contracts.js +295 -0
- package/src/native-declarations.js +68 -0
- package/src/native-facts.js +63 -0
- package/src/package-locking.js +43 -0
- package/src/package-manager.js +23 -8
- package/src/parser.js +20 -2
- package/src/project.js +2 -2
- package/src/runtime-abi.js +23 -0
- package/src/runtime-adapters.js +18 -0
- package/src/schemas.js +24 -17
- package/src/semantic.js +8 -3
- package/src/snippets.js +1 -0
- package/src/spec.js +49 -8
- /package/examples/approved-design/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/approved-design/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/developer-workflow/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/developer-workflow/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/generic-di/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/generic-di/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/hello/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/hello/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/interceptors/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/interceptors/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/new-syntax/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/new-syntax/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
- /package/examples/ownership-transfer/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug +0 -0
- /package/examples/ownership-transfer/.aug-spec/august/{0.20.1 → 0.21.0}/io/contracts.aug.md +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { release } from 'node:os';
|
|
5
|
+
/** The public native ABI never exposes August runtime layouts. */
|
|
6
|
+
export const nativeProfile = 'aug-native-abi-1';
|
|
7
|
+
const digest = /^[0-9a-f]{64}$/;
|
|
8
|
+
const identifier = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9
|
+
const moduleName = /^[A-Za-z][A-Za-z0-9_]*(?:\/[A-Za-z][A-Za-z0-9_]*)*$/;
|
|
10
|
+
const fail = (message) => { throw new Error('NATIVE_ABI: ' + message); };
|
|
11
|
+
const object = (value, label) => {
|
|
12
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
13
|
+
fail(label + ' must be an object');
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
const fields = (value, allowed, label) => {
|
|
17
|
+
for (const key of Object.keys(value))
|
|
18
|
+
if (!allowed.includes(key))
|
|
19
|
+
fail(`${label} contains unsupported field ${key}`);
|
|
20
|
+
};
|
|
21
|
+
const string = (value, label) => {
|
|
22
|
+
if (typeof value !== 'string' || !value || value.includes('\0'))
|
|
23
|
+
fail(label + ' must be a nonempty string');
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
const array = (value, label) => {
|
|
27
|
+
if (!Array.isArray(value) || value.length > 10000)
|
|
28
|
+
fail(label + ' must be an array of at most 10,000 entries');
|
|
29
|
+
return value;
|
|
30
|
+
};
|
|
31
|
+
const strings = (value, label) => array(value, label).map(item => string(item, label));
|
|
32
|
+
const positive = (value, label) => { if (!Number.isSafeInteger(value) || Number(value) <= 0)
|
|
33
|
+
fail(label + ' must be a positive safe integer'); };
|
|
34
|
+
const hash = (value, label) => { if (typeof value !== 'string' || !digest.test(value))
|
|
35
|
+
fail(label + ' must be a lowercase SHA-256 digest'); };
|
|
36
|
+
const symbol = (value, label) => { if (typeof value !== 'string' || !identifier.test(value))
|
|
37
|
+
fail(label + ' must be a C identifier'); };
|
|
38
|
+
const https = (value, label) => {
|
|
39
|
+
let url;
|
|
40
|
+
try {
|
|
41
|
+
url = new URL(string(value, label));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
fail(label + ' must be an HTTPS URL');
|
|
45
|
+
}
|
|
46
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.hash)
|
|
47
|
+
fail(label + ' must be an HTTPS URL without credentials or a fragment');
|
|
48
|
+
};
|
|
49
|
+
/** Artifact paths are portable and cannot name a file outside their archive. */
|
|
50
|
+
export function nativePath(value) {
|
|
51
|
+
const path = string(value, 'relative artifact path');
|
|
52
|
+
if (isAbsolute(path) || path.includes('\\') || path.split('/').some(part => !part || part === '.' || part === '..' || part.startsWith('.')) || /[:\r\n]/.test(path))
|
|
53
|
+
fail('Expected a relative artifact path without traversal: ' + path);
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
56
|
+
/** Source installation validates metadata; it does not run a native build recipe. */
|
|
57
|
+
export function validateNativeManifest(value) {
|
|
58
|
+
const native = object(value, 'native metadata');
|
|
59
|
+
fields(native, ['profile', 'bindings', 'bindingsSha256', 'upstream', 'artifacts', 'sourceBuild'], 'native metadata');
|
|
60
|
+
if (native.profile !== nativeProfile)
|
|
61
|
+
fail('Unsupported native profile ' + native.profile);
|
|
62
|
+
nativePath(native.bindings);
|
|
63
|
+
if (native.bindings !== 'native.abi.json')
|
|
64
|
+
fail('Bindings must be declared in native.abi.json at the package root');
|
|
65
|
+
hash(native.bindingsSha256, 'bindingsSha256');
|
|
66
|
+
const upstream = object(native.upstream, 'upstream');
|
|
67
|
+
fields(upstream, ['repository', 'version', 'sourceRevision'], 'upstream');
|
|
68
|
+
https(upstream.repository, 'upstream.repository');
|
|
69
|
+
string(upstream.version, 'upstream.version');
|
|
70
|
+
if (!/^[0-9a-f]{40,64}$/.test(upstream.sourceRevision ?? ''))
|
|
71
|
+
fail('upstream.sourceRevision must pin a full source revision or archive digest');
|
|
72
|
+
const artifacts = array(native.artifacts, 'artifacts');
|
|
73
|
+
if (!artifacts.length)
|
|
74
|
+
fail('At least one prebuilt artifact is required');
|
|
75
|
+
const ids = new Set(), selections = new Set();
|
|
76
|
+
for (const entry of artifacts) {
|
|
77
|
+
const artifact = object(entry, 'artifact');
|
|
78
|
+
fields(artifact, ['id', 'target', 'url', 'sha256', 'maximumDownloadBytes', 'maximumUnpackedBytes', 'link', 'runtime', 'components', 'fileManifest', 'provenance', 'notices'], 'artifact');
|
|
79
|
+
string(artifact.id, 'artifact.id');
|
|
80
|
+
if (ids.has(artifact.id))
|
|
81
|
+
fail('Duplicate artifact id ' + artifact.id);
|
|
82
|
+
ids.add(artifact.id);
|
|
83
|
+
const target = object(artifact.target, 'artifact.target');
|
|
84
|
+
fields(target, ['triple', 'os', 'arch', 'minimumOS', 'minimumLibc', 'cpuBaseline', 'libc', 'cxxRuntime', 'cxxABI', 'features'], 'artifact.target');
|
|
85
|
+
const expected = nativeTarget(string(target.triple, 'target.triple'));
|
|
86
|
+
if (expected.os !== target.os || expected.arch !== target.arch || expected.libc !== target.libc)
|
|
87
|
+
fail('Target triple does not match its OS, architecture, or libc');
|
|
88
|
+
string(target.cpuBaseline, 'target.cpuBaseline');
|
|
89
|
+
if (target.os === 'macos' && (!/^\d+\.\d+(?:\.\d+)?$/.test(target.minimumOS ?? '')))
|
|
90
|
+
fail('macOS artifacts declare minimumOS');
|
|
91
|
+
if (target.os === 'linux' && target.libc === 'glibc' && !/^\d+\.\d+(?:\.\d+)?$/.test(target.minimumLibc ?? ''))
|
|
92
|
+
fail('GNU/Linux artifacts declare minimumLibc');
|
|
93
|
+
if (target.minimumLibc !== undefined && (target.os !== 'linux' || target.libc !== 'glibc'))
|
|
94
|
+
fail('minimumLibc belongs to GNU/Linux artifacts');
|
|
95
|
+
for (const key of ['cxxRuntime', 'cxxABI'])
|
|
96
|
+
if (target[key] !== undefined)
|
|
97
|
+
string(target[key], key);
|
|
98
|
+
if (target.features !== undefined)
|
|
99
|
+
strings(target.features, 'target.features');
|
|
100
|
+
const selection = JSON.stringify([target.triple, target.minimumOS ?? '', target.minimumLibc ?? '', target.features ?? []]);
|
|
101
|
+
if (selections.has(selection))
|
|
102
|
+
fail('Ambiguous artifact selection for ' + target.triple);
|
|
103
|
+
selections.add(selection);
|
|
104
|
+
https(artifact.url, 'artifact.url');
|
|
105
|
+
hash(artifact.sha256, 'artifact.sha256');
|
|
106
|
+
positive(artifact.maximumDownloadBytes, 'maximumDownloadBytes');
|
|
107
|
+
positive(artifact.maximumUnpackedBytes, 'maximumUnpackedBytes');
|
|
108
|
+
const link = object(artifact.link, 'link');
|
|
109
|
+
fields(link, ['kind', 'libraries'], 'link');
|
|
110
|
+
if (!['dynamic', 'static'].includes(link.kind))
|
|
111
|
+
fail('link.kind must be dynamic or static');
|
|
112
|
+
if (!array(link.libraries, 'link.libraries').length)
|
|
113
|
+
fail('An artifact must supply link libraries');
|
|
114
|
+
link.libraries.forEach(nativePath);
|
|
115
|
+
const runtime = object(artifact.runtime, 'runtime');
|
|
116
|
+
fields(runtime, ['files', 'relocation', 'closureManifest'], 'runtime');
|
|
117
|
+
array(runtime.files, 'runtime.files').forEach(nativePath);
|
|
118
|
+
if (runtime.relocation !== 'loader-relative')
|
|
119
|
+
fail('Runtime libraries must use loader-relative relocation');
|
|
120
|
+
if (runtime.closureManifest !== undefined)
|
|
121
|
+
nativePath(runtime.closureManifest);
|
|
122
|
+
for (const component of array(artifact.components, 'components')) {
|
|
123
|
+
const c = object(component, 'component');
|
|
124
|
+
fields(c, ['id', 'version', 'compatibilityKey', 'linkage', 'required'], 'component');
|
|
125
|
+
for (const key of ['id', 'version', 'compatibilityKey'])
|
|
126
|
+
string(c[key], key);
|
|
127
|
+
if (!['static', 'dynamic'].includes(c.linkage) || typeof c.required !== 'boolean')
|
|
128
|
+
fail('Invalid component linkage/required');
|
|
129
|
+
}
|
|
130
|
+
for (const key of ['fileManifest', 'provenance', 'notices'])
|
|
131
|
+
nativePath(artifact[key]);
|
|
132
|
+
}
|
|
133
|
+
if (native.sourceBuild !== undefined) {
|
|
134
|
+
const build = object(native.sourceBuild, 'sourceBuild');
|
|
135
|
+
fields(build, ['recipe', 'inputs', 'tools', 'automatic'], 'sourceBuild');
|
|
136
|
+
nativePath(build.recipe);
|
|
137
|
+
nativePath(build.inputs);
|
|
138
|
+
strings(build.tools, 'sourceBuild.tools');
|
|
139
|
+
if (build.automatic !== false)
|
|
140
|
+
fail('Native source builds must be explicitly requested; automatic must be false');
|
|
141
|
+
}
|
|
142
|
+
return native;
|
|
143
|
+
}
|
|
144
|
+
function view(value, result) {
|
|
145
|
+
const v = object(value, 'native view');
|
|
146
|
+
fields(v, ['kind', 'resource', 'ownership', 'release', 'releaseLength', 'minimum', 'maximum', ...(result ? [] : ['name'])], 'native view');
|
|
147
|
+
if (!['void', 'i64', 'i32', 'f64', 'bool', 'utf8', 'bytes', 'f64-list', 'utf8-list', 'resource'].includes(v.kind))
|
|
148
|
+
fail('Unsupported native kind ' + v.kind);
|
|
149
|
+
if (v.kind === 'void' && !result)
|
|
150
|
+
fail('void is only a result');
|
|
151
|
+
if (v.kind === 'resource') {
|
|
152
|
+
string(v.resource, 'resource identity');
|
|
153
|
+
if (!result && !['read', 'borrow', 'consume'].includes(v.ownership))
|
|
154
|
+
fail('A resource input requires read, borrow, or consume ownership');
|
|
155
|
+
if (result && v.ownership !== undefined)
|
|
156
|
+
fail('Resource results always transfer ownership; do not declare an input loan');
|
|
157
|
+
}
|
|
158
|
+
else if (v.resource !== undefined || v.ownership !== undefined)
|
|
159
|
+
fail('Resource ownership is only valid on resources');
|
|
160
|
+
if (result && ['utf8', 'bytes', 'f64-list', 'utf8-list'].includes(v.kind) && !v.release)
|
|
161
|
+
fail('Allocated native results require a matching release symbol');
|
|
162
|
+
if (result && v.kind === 'utf8-list')
|
|
163
|
+
fail('utf8-list outputs are not in this ABI profile');
|
|
164
|
+
if (v.release !== undefined)
|
|
165
|
+
symbol(v.release, 'result.release');
|
|
166
|
+
if ((v.release !== undefined || v.releaseLength !== undefined) && (!result || !['utf8', 'bytes', 'f64-list'].includes(v.kind)))
|
|
167
|
+
fail('Release metadata belongs only to allocated buffer results');
|
|
168
|
+
if (v.releaseLength !== undefined && typeof v.releaseLength !== 'boolean')
|
|
169
|
+
fail('releaseLength must be boolean');
|
|
170
|
+
if ((v.minimum !== undefined || v.maximum !== undefined) && !['i64', 'i32'].includes(v.kind))
|
|
171
|
+
fail('Integer bounds require i64 or i32');
|
|
172
|
+
for (const key of ['minimum', 'maximum'])
|
|
173
|
+
if (v[key] !== undefined && !Number.isSafeInteger(v[key]))
|
|
174
|
+
fail(key + ' must be a safe integer');
|
|
175
|
+
if (v.minimum !== undefined && v.maximum !== undefined && v.minimum > v.maximum)
|
|
176
|
+
fail('Empty native integer range');
|
|
177
|
+
return v;
|
|
178
|
+
}
|
|
179
|
+
export function validateNativeDescriptor(value) {
|
|
180
|
+
const descriptor = object(value, 'binding descriptor');
|
|
181
|
+
fields(descriptor, ['format', 'profile', 'resources', 'functions'], 'binding descriptor');
|
|
182
|
+
if (descriptor.format !== 1 || descriptor.profile !== nativeProfile)
|
|
183
|
+
fail('Unsupported binding descriptor version/profile');
|
|
184
|
+
const ids = new Set();
|
|
185
|
+
for (const entry of array(descriptor.resources, 'resources')) {
|
|
186
|
+
const r = object(entry, 'resource');
|
|
187
|
+
fields(r, ['module', 'name', 'release'], 'resource');
|
|
188
|
+
if (!moduleName.test(r.module ?? ''))
|
|
189
|
+
fail('Resource module must be a source-relative module path');
|
|
190
|
+
symbol(r.name, 'resource.name');
|
|
191
|
+
symbol(r.release, 'resource.release');
|
|
192
|
+
const id = r.module + '.' + r.name;
|
|
193
|
+
if (ids.has(id))
|
|
194
|
+
fail('Duplicate native resource ' + id);
|
|
195
|
+
ids.add(id);
|
|
196
|
+
}
|
|
197
|
+
const resources = new Set(ids);
|
|
198
|
+
ids.clear();
|
|
199
|
+
for (const entry of array(descriptor.functions, 'functions')) {
|
|
200
|
+
const fn = object(entry, 'function');
|
|
201
|
+
fields(fn, ['module', 'name', 'symbol', 'params', 'result', 'error', 'callingConvention', 'status', 'uses', 'changes', 'thread', 'retainsInputs'], 'function');
|
|
202
|
+
if (!moduleName.test(fn.module ?? ''))
|
|
203
|
+
fail('Function module must be a source-relative module path');
|
|
204
|
+
symbol(fn.name, 'function.name');
|
|
205
|
+
symbol(fn.symbol, 'function.symbol');
|
|
206
|
+
const id = fn.module + '.' + fn.name;
|
|
207
|
+
if (ids.has(id))
|
|
208
|
+
fail('Duplicate native function ' + id);
|
|
209
|
+
ids.add(id);
|
|
210
|
+
const names = new Set();
|
|
211
|
+
for (const p of array(fn.params, 'function.params')) {
|
|
212
|
+
view(p, false);
|
|
213
|
+
if (p.kind === 'resource' && !resources.has(p.resource))
|
|
214
|
+
fail('Unknown resource identity ' + p.resource);
|
|
215
|
+
symbol(p.name, 'parameter.name');
|
|
216
|
+
if (names.has(p.name))
|
|
217
|
+
fail('Duplicate parameter ' + p.name);
|
|
218
|
+
names.add(p.name);
|
|
219
|
+
}
|
|
220
|
+
const result = view(fn.result, true);
|
|
221
|
+
if (result.kind === 'resource' && !resources.has(result.resource))
|
|
222
|
+
fail('Unknown result resource identity ' + result.resource);
|
|
223
|
+
if (fn.callingConvention !== 'C' || !['i32', 'direct'].includes(fn.status) || fn.thread !== 'caller' || fn.retainsInputs !== false)
|
|
224
|
+
fail('Only C calls on the caller thread with call-duration loans are supported');
|
|
225
|
+
if (fn.error !== undefined) {
|
|
226
|
+
string(fn.error, 'function.error');
|
|
227
|
+
const dot = fn.error.lastIndexOf('.');
|
|
228
|
+
if (!moduleName.test(fn.error.slice(0, dot)) || !identifier.test(fn.error.slice(dot + 1)))
|
|
229
|
+
fail('Native checked errors require a module-qualified declaration identity');
|
|
230
|
+
}
|
|
231
|
+
if (fn.status === 'i32' && !fn.error)
|
|
232
|
+
fail('Status-returning native calls require a checked error type');
|
|
233
|
+
if (fn.status === 'direct' && (fn.error || result.kind === 'resource' || ['utf8', 'bytes', 'f64-list', 'utf8-list'].includes(result.kind)))
|
|
234
|
+
fail('Direct native calls support scalar results only');
|
|
235
|
+
strings(fn.uses, 'function.uses');
|
|
236
|
+
strings(fn.changes, 'function.changes');
|
|
237
|
+
}
|
|
238
|
+
return descriptor;
|
|
239
|
+
}
|
|
240
|
+
export function readNativeDescriptor(directory, native) {
|
|
241
|
+
const root = realpathSync(directory), file = realpathSync(resolve(root, native.bindings));
|
|
242
|
+
if (relative(root, file).split(sep).includes('..'))
|
|
243
|
+
fail('Binding descriptor escapes its package');
|
|
244
|
+
const bytes = readFileSync(file);
|
|
245
|
+
if (createHash('sha256').update(bytes).digest('hex') !== native.bindingsSha256)
|
|
246
|
+
throw new Error('NATIVE_INTEGRITY: binding descriptor differs from bindingsSha256 in ' + directory);
|
|
247
|
+
return validateNativeDescriptor(JSON.parse(bytes.toString('utf8')));
|
|
248
|
+
}
|
|
249
|
+
/** A target is a deployment identity, not an assumption that the host can build it. */
|
|
250
|
+
export function nativeTarget(triple, minimumOS) {
|
|
251
|
+
triple ??= process.platform === 'darwin' ? (process.arch === 'arm64' ? 'aarch64' : 'x86_64') + '-apple-darwin' : process.platform === 'linux' ? (process.arch === 'arm64' ? 'aarch64' : 'x86_64') + '-unknown-linux-gnu' : (process.arch === 'arm64' ? 'aarch64' : 'x86_64') + '-pc-windows-msvc';
|
|
252
|
+
const match = /^(aarch64|x86_64)-(apple-darwin|unknown-linux-(gnu|musl)|pc-windows-msvc)$/.exec(triple);
|
|
253
|
+
if (!match)
|
|
254
|
+
throw new Error('NATIVE_TARGET: Unsupported target ' + triple);
|
|
255
|
+
const os = match[2] === 'apple-darwin' ? 'macos' : match[2].startsWith('unknown-linux') ? 'linux' : 'windows';
|
|
256
|
+
const arch = match[1] === 'aarch64' ? 'arm64' : 'x64';
|
|
257
|
+
return { triple, os, arch, cpuBaseline: arch === 'arm64' ? 'armv8-a' : 'x86-64', libc: os === 'macos' ? 'libSystem' : os === 'windows' ? 'ucrt' : match[3] === 'musl' ? 'musl' : 'glibc', ...(os === 'macos' ? { minimumOS: minimumOS ?? '14.0' } : {}) };
|
|
258
|
+
}
|
|
259
|
+
export function nativeHostTarget() {
|
|
260
|
+
// Darwin 23/24 are macOS 14/15. Apple's 2025 numbering change maps Darwin 25 to macOS 26.
|
|
261
|
+
const darwin = Number(release().split('.')[0]), macos = darwin >= 25 ? darwin + 1 : darwin - 9;
|
|
262
|
+
const target = nativeTarget(undefined, process.platform === 'darwin' ? `${macos}.0` : undefined);
|
|
263
|
+
if (process.platform === 'linux') {
|
|
264
|
+
const header = process.report.getReport()?.header;
|
|
265
|
+
if (header?.glibcVersionRuntime)
|
|
266
|
+
target.minimumLibc = header.glibcVersionRuntime;
|
|
267
|
+
else {
|
|
268
|
+
target.libc = 'musl';
|
|
269
|
+
target.triple = target.triple.replace(/-gnu$/, '-musl');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return target;
|
|
273
|
+
}
|
|
274
|
+
const versionAtLeast = (actual, required) => {
|
|
275
|
+
const a = actual.split('.').map(Number), b = required.split('.').map(Number);
|
|
276
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
277
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0))
|
|
278
|
+
return (a[i] ?? 0) > (b[i] ?? 0);
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
};
|
|
282
|
+
export function selectNativeArtifact(artifacts, target) {
|
|
283
|
+
const matching = artifacts.filter(a => a.target.triple === target.triple && a.target.libc === target.libc &&
|
|
284
|
+
a.target.cpuBaseline === target.cpuBaseline && (!a.target.cxxRuntime ||
|
|
285
|
+
a.target.os === 'macos' && a.target.cxxRuntime === 'system-libc++' && a.target.cxxABI === 'apple-libc++' ||
|
|
286
|
+
a.target.os === 'linux' && a.target.cxxRuntime === 'bundled-libstdc++' && a.target.cxxABI === 'itanium-cxx11') &&
|
|
287
|
+
(!a.target.minimumLibc || !!target.minimumLibc && versionAtLeast(target.minimumLibc, a.target.minimumLibc)) &&
|
|
288
|
+
(!a.target.minimumOS || !!target.minimumOS && versionAtLeast(target.minimumOS, a.target.minimumOS)));
|
|
289
|
+
if (matching.length !== 1) {
|
|
290
|
+
const floors = artifacts.filter(a => a.target.triple === target.triple).map(a => a.target.minimumOS ?? a.target.minimumLibc).filter(Boolean);
|
|
291
|
+
const runtime = target.os === 'linux' ? 'glibc' : 'macOS', actual = target.os === 'linux' ? target.minimumLibc : target.minimumOS;
|
|
292
|
+
throw new Error('NATIVE_TARGET: ' + (matching.length ? 'Ambiguous prebuilt artifacts' : floors.length ? `This package requires ${runtime} ${floors.join(' or ')}+ and its declared C++ ABI; requested ${actual ?? 'unknown'}.` : 'No compatible prebuilt artifact for ' + target.triple) + ` Supported artifacts: ${artifacts.map(a => a.id + ' (' + a.target.triple + ')').join(', ')}. No source build was started.`);
|
|
293
|
+
}
|
|
294
|
+
return matching[0];
|
|
295
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { readNativeDescriptor } from "./native-contracts.js";
|
|
3
|
+
/** Resolve descriptors against exact source declarations, never unqualified spellings. */
|
|
4
|
+
export function nativeDeclarations(project) {
|
|
5
|
+
const result = { functions: new Map(), resources: new Map(), providers: new Map(), providerMetadata: new Map(), providerDescriptors: new Map(), errors: new Map(), diagnostics: [] };
|
|
6
|
+
const issue = (span, message) => result.diagnostics.push({ ...span, code: 'NATIVE_ABI', message });
|
|
7
|
+
const sources = [];
|
|
8
|
+
const symbols = new Map();
|
|
9
|
+
if (project.library?.native)
|
|
10
|
+
sources.push({ directory: project.root, sourceRoot: project.sourceRoot, native: project.library.native, identity: project.library.name + '@' + project.library.version });
|
|
11
|
+
for (const scope of project.packages.scopes.values()) {
|
|
12
|
+
// readPackage already verified the descriptor hash; retain that source identity.
|
|
13
|
+
const native = scope.native;
|
|
14
|
+
if (native)
|
|
15
|
+
sources.push({ directory: scope.directory, sourceRoot: scope.sourceRoot, native, identity: scope.name + '@' + scope.version });
|
|
16
|
+
}
|
|
17
|
+
for (const source of sources) {
|
|
18
|
+
result.providerMetadata.set(source.identity, source.native);
|
|
19
|
+
result.providerDescriptors.set(source.identity, resolve(source.directory, source.native.bindings));
|
|
20
|
+
let descriptor;
|
|
21
|
+
try {
|
|
22
|
+
descriptor = readNativeDescriptor(source.directory, source.native);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
issue({ file: resolve(source.directory, source.native.bindings), start: 0, end: 0, line: 1, column: 1 }, error instanceof Error ? error.message : String(error));
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const name of [...descriptor.resources.map(r => r.release), ...descriptor.functions.flatMap(f => [f.symbol, ...(f.result.release ? [f.result.release] : [])])]) {
|
|
29
|
+
const provider = symbols.get(name);
|
|
30
|
+
if (provider && provider !== source.identity)
|
|
31
|
+
issue({ file: resolve(source.directory, source.native.bindings), start: 0, end: 0, line: 1, column: 1 }, `Native symbol ${name} has ambiguous providers ${provider} and ${source.identity}`);
|
|
32
|
+
else
|
|
33
|
+
symbols.set(name, source.identity);
|
|
34
|
+
}
|
|
35
|
+
for (const resource of descriptor.resources) {
|
|
36
|
+
const file = resolve(source.sourceRoot, resource.module + '.aug');
|
|
37
|
+
const def = project.scopes.get(file)?.get(resource.name);
|
|
38
|
+
if (!def || def.file !== file || def.node.kind !== 'resource') {
|
|
39
|
+
issue({ file, start: 0, end: 0, line: 1, column: 1 }, 'Native descriptor has no matching resource declaration ' + resource.module + '.' + resource.name);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
result.resources.set(def.node, resource);
|
|
43
|
+
result.providers.set(def.node, source.identity);
|
|
44
|
+
}
|
|
45
|
+
for (const fn of descriptor.functions) {
|
|
46
|
+
const file = resolve(source.sourceRoot, fn.module + '.aug');
|
|
47
|
+
const def = project.scopes.get(file)?.get(fn.name);
|
|
48
|
+
if (!def || def.file !== file || def.node.kind !== 'function' || !def.node.externC || def.node.valueAbi) {
|
|
49
|
+
issue({ file, start: 0, end: 0, line: 1, column: 1 }, 'Native descriptor has no matching extern C declaration ' + fn.module + '.' + fn.name);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
result.functions.set(def.node, fn);
|
|
53
|
+
result.providers.set(def.node, source.identity);
|
|
54
|
+
if (fn.error) {
|
|
55
|
+
const dot = fn.error.lastIndexOf('.'), errorFile = resolve(source.sourceRoot, fn.error.slice(0, dot) + '.aug');
|
|
56
|
+
const error = project.scopes.get(errorFile)?.get(fn.error.slice(dot + 1));
|
|
57
|
+
if (!error || error.file !== errorFile || error.node.kind !== 'class')
|
|
58
|
+
issue(def.node.span, 'Native descriptor has no matching checked error declaration ' + fn.error);
|
|
59
|
+
else
|
|
60
|
+
result.errors.set(def.node, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const def of project.definitions.values())
|
|
65
|
+
if (def.node.kind === 'resource' && !result.resources.has(def.node))
|
|
66
|
+
issue(def.node.span, 'extern C resource requires a release identity in its package native.abi.json');
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Keep checked binding facts separate from promises made by foreign code. */
|
|
2
|
+
export function nativeFact(checked, node) {
|
|
3
|
+
const provider = checked.native.providers.get(node), manifest = provider && checked.native.providerMetadata.get(provider);
|
|
4
|
+
if (!provider || !manifest)
|
|
5
|
+
return;
|
|
6
|
+
const common = { provider, profile: manifest.profile, descriptor: manifest.bindings, descriptorSha256: manifest.bindingsSha256,
|
|
7
|
+
upstream: manifest.upstream, supportedTargets: manifest.artifacts.map(artifact => artifact.target) };
|
|
8
|
+
if (node.kind === 'resource') {
|
|
9
|
+
const contract = checked.native.resources.get(node);
|
|
10
|
+
return contract ? { ...common, kind: 'resource', contract, cleanup: 'scope-exit' } : undefined;
|
|
11
|
+
}
|
|
12
|
+
const contract = checked.native.functions.get(node);
|
|
13
|
+
return contract ? { ...common, kind: 'function', contract,
|
|
14
|
+
compilerChecks: ['provider identity', 'descriptor digest', 'August signature', 'ownership at August call sites'],
|
|
15
|
+
nativeAuthorPromises: ['inputs are not retained', 'boundary entry stays on the caller thread', 'foreign threads do not enter August', 'C ABI boundary does not unwind into August'] } : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Resolve only standalone calls. Member dispatch is not claimed as complete. */
|
|
18
|
+
export function nativeDependencies(checked, method) {
|
|
19
|
+
const found = new Map(), seen = new Set();
|
|
20
|
+
const visit = (current) => {
|
|
21
|
+
if (seen.has(current))
|
|
22
|
+
return;
|
|
23
|
+
seen.add(current);
|
|
24
|
+
const fact = nativeFact(checked, current);
|
|
25
|
+
if (fact?.kind === 'function') {
|
|
26
|
+
found.set(current, fact);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const walk = (value) => {
|
|
30
|
+
if (!value || typeof value !== 'object')
|
|
31
|
+
return;
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
value.forEach(walk);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const call = value;
|
|
37
|
+
if (call.kind === 'call' && call.callee?.kind === 'name' && call.span) {
|
|
38
|
+
const target = checked.project.scopes.get(call.span.file)?.get(call.callee.name);
|
|
39
|
+
if (target?.node.kind === 'function')
|
|
40
|
+
visit(target.node);
|
|
41
|
+
}
|
|
42
|
+
for (const [key, child] of Object.entries(value))
|
|
43
|
+
if (!['span', 'nameSpan', 'sourceSpan'].includes(key))
|
|
44
|
+
walk(child);
|
|
45
|
+
};
|
|
46
|
+
walk(current.body);
|
|
47
|
+
};
|
|
48
|
+
visit(method);
|
|
49
|
+
return [...found.values()].sort((a, b) => (a.provider + '/' + a.contract.symbol).localeCompare(b.provider + '/' + b.contract.symbol, 'en'));
|
|
50
|
+
}
|
|
51
|
+
export function nativeDescription(fact, contractLink) {
|
|
52
|
+
const quote = (text) => '`' + text + '`';
|
|
53
|
+
const targets = fact.supportedTargets.map(target => target.os + ' ' + target.arch + (target.minimumOS ? ' ' + target.minimumOS + '+' : '') + (target.minimumLibc ? ' glibc ' + target.minimumLibc + '+' : '') + (target.cxxABI ? ' ' + target.cxxABI : '')).join(', ');
|
|
54
|
+
const origin = `Native implementation: ${quote(fact.provider)}, ${quote(fact.upstream.version)}. Supported targets: ${targets}. Binding contract: ${contractLink ?? quote(fact.descriptor)} (SHA-256 ${quote(fact.descriptorSha256)}).`;
|
|
55
|
+
if (fact.kind === 'resource')
|
|
56
|
+
return `${origin} An owned value releases its opaque handle through ${quote(fact.contract.release)} when its scope ends, including error and return paths.`;
|
|
57
|
+
const contract = fact.contract;
|
|
58
|
+
const loans = contract.params.filter(param => param.kind === 'resource').map(param => quote(param.name) + (param.ownership === 'consume' ? ' transfers ownership' : param.ownership === 'borrow' ? ' lends mutable access for this call' : ' lends read access for this call'));
|
|
59
|
+
const result = contract.result.kind === 'resource' ? 'The caller owns the returned handle.' : contract.result.release ?
|
|
60
|
+
`August copies the returned buffer, then calls ${quote(contract.result.release)} to release it.` : '';
|
|
61
|
+
return `${origin} It calls ${quote(contract.symbol)} through the C ABI on the caller thread; a blocking native call blocks that thread. ${loans.length ? loans.join('; ') + '. ' : ''}${result ? result + ' ' : ''}` +
|
|
62
|
+
'The compiler checks the provider, descriptor digest, signature and ownership at August call sites. The native author promises not to retain inputs, enter August from foreign threads, or unwind across the C boundary; internal native workers may run. The compiler does not prove those promises.';
|
|
63
|
+
}
|
package/src/package-locking.js
CHANGED
|
@@ -43,3 +43,46 @@ export function withPackageLock(path, action) {
|
|
|
43
43
|
rmSync(path, { recursive: true, force: true });
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
/** Network-backed installs keep the writer lock until the asynchronous action completes. */
|
|
47
|
+
export async function withPackageLockAsync(path, action) {
|
|
48
|
+
const deadline = Date.now() + 30000;
|
|
49
|
+
while (true) {
|
|
50
|
+
try {
|
|
51
|
+
mkdirSync(path);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST')
|
|
56
|
+
throw error;
|
|
57
|
+
let stale = false;
|
|
58
|
+
try {
|
|
59
|
+
const pid = Number(readFileSync(join(path, 'pid'), 'utf8'));
|
|
60
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
61
|
+
throw new Error('Invalid lock owner');
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
stale = !!e && typeof e === 'object' && 'code' in e && e.code === 'ESRCH';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
stale = existsSync(path) && Date.now() - statSync(path).mtimeMs > 180000;
|
|
71
|
+
}
|
|
72
|
+
if (stale) {
|
|
73
|
+
rmSync(path, { recursive: true, force: true });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (Date.now() >= deadline)
|
|
77
|
+
throw new Error('Another August installation is still running. Lock: ' + path);
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(join(path, 'pid'), String(process.pid));
|
|
83
|
+
return await action();
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
rmSync(path, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/package-manager.js
CHANGED
|
@@ -8,6 +8,7 @@ import { parse } from "./parser.js";
|
|
|
8
8
|
import { isGitSource, materializeGit, sourceAlias } from "./git-packages.js";
|
|
9
9
|
import { agentInstructions } from "./project-init.js";
|
|
10
10
|
import { withPackageLock } from "./package-locking.js";
|
|
11
|
+
import { validateNativeManifest, readNativeDescriptor } from "./native-contracts.js";
|
|
11
12
|
export const compilerVersion = () => JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
12
13
|
export const packageAlias = (name) => /^[a-z][a-z0-9_]*$/.test(name) && name !== 'august';
|
|
13
14
|
/** Repository imports declare their dependencies beside the code that uses them. */
|
|
@@ -71,9 +72,15 @@ export function readPackage(directory) {
|
|
|
71
72
|
format: 1, name: 'aug-' + sourceAlias(realpathSync(directory)), version: '0.0.0', compiler: compilerVersion(),
|
|
72
73
|
source: existsSync(join(directory, 'export.aug')) ? '.' : 'src', dependencies: {}
|
|
73
74
|
};
|
|
74
|
-
if (manifest.format
|
|
75
|
+
if (![1, 2].includes(manifest.format) || !npmName.test(manifest.name ?? '') || !version.test(manifest.version ?? '') ||
|
|
75
76
|
manifest.compiler !== compilerVersion())
|
|
76
77
|
throw new Error(`Invalid August package or compiler mismatch in ${directory}; expected compiler ${compilerVersion()}`);
|
|
78
|
+
if (manifest.format === 1 && manifest.native !== undefined)
|
|
79
|
+
throw new Error('Native packages require manifest format 2');
|
|
80
|
+
if (manifest.format === 2) {
|
|
81
|
+
manifest.native = validateNativeManifest(manifest.native);
|
|
82
|
+
readNativeDescriptor(directory, manifest.native);
|
|
83
|
+
}
|
|
77
84
|
if (typeof manifest.source !== 'string' || !manifest.source || isAbsolute(manifest.source))
|
|
78
85
|
throw new Error('Package source must be a relative folder');
|
|
79
86
|
const sourceRoot = realpathSync(resolve(directory, manifest.source));
|
|
@@ -127,7 +134,7 @@ function normalizeSpecifier(spec, root, requirePath = true) {
|
|
|
127
134
|
}
|
|
128
135
|
function packageDigest(directory, sourceRoot) {
|
|
129
136
|
const hash = createHash('sha256');
|
|
130
|
-
for (const file of [join(directory, 'aug-package.json'), join(directory, 'package.json'), ...sourcePaths(sourceRoot)].filter(existsSync).sort()) {
|
|
137
|
+
for (const file of [join(directory, 'aug-package.json'), join(directory, 'package.json'), join(directory, 'native.abi.json'), join(directory, 'THIRD_PARTY_NOTICES.md'), ...sourcePaths(sourceRoot)].filter(existsSync).sort()) {
|
|
131
138
|
hash.update(relative(directory, file).replaceAll('\\', '/'));
|
|
132
139
|
hash.update('\0');
|
|
133
140
|
hash.update(readFileSync(file));
|
|
@@ -156,10 +163,12 @@ export function projectPackages(root, specifications, sourceFolder = root) {
|
|
|
156
163
|
if (manifest.name !== entry.name || manifest.version !== entry.version || manifest.source !== entry.source ||
|
|
157
164
|
packageDigest(directory, sourceRoot) !== entry.digest)
|
|
158
165
|
throw new Error(`Installed ${entry.name} changed; run aug install`);
|
|
166
|
+
if (JSON.stringify(entry.native) !== JSON.stringify(manifest.native))
|
|
167
|
+
throw new Error(`NATIVE_LOCK: Native metadata for ${entry.name} differs from its verified source manifest; run aug install`);
|
|
159
168
|
if (result.scopes.has(entry.path))
|
|
160
169
|
throw new Error('Duplicate package lock path');
|
|
161
170
|
manifests.set(entry.path, manifest);
|
|
162
|
-
result.scopes.set(entry.path, { ...entry, directory, sourceRoot, specifications: manifest.dependencies ?? {} });
|
|
171
|
+
result.scopes.set(entry.path, { ...entry, native: manifest.native, directory, sourceRoot, specifications: manifest.dependencies ?? {} });
|
|
163
172
|
}
|
|
164
173
|
verifyIdentities(lock.packages);
|
|
165
174
|
for (const scope of result.scopes.values()) {
|
|
@@ -212,14 +221,12 @@ function npm(args, cwd) {
|
|
|
212
221
|
return process.stdout;
|
|
213
222
|
}
|
|
214
223
|
/** Running an application prepares its declared packages; checking remains read-only. */
|
|
215
|
-
export function prepareRunPackages(root, offline = false) {
|
|
224
|
+
export function prepareRunPackages(root, offline = false, frozen = false) {
|
|
216
225
|
const loaded = loadConfig(root);
|
|
217
226
|
if (loaded.diagnostics.length)
|
|
218
227
|
return; // The checker renders the configuration's source diagnostics.
|
|
219
228
|
const library = isLibrary(root) ? readPackage(root) : undefined;
|
|
220
229
|
const specifications = packageSpecifications(library?.sourceRoot ?? root, library?.manifest.dependencies ?? loaded.config.packages);
|
|
221
|
-
if (!Object.keys(specifications).length)
|
|
222
|
-
return;
|
|
223
230
|
const lockPath = join(root, 'aug.lock.json');
|
|
224
231
|
let lock;
|
|
225
232
|
if (existsSync(lockPath)) {
|
|
@@ -234,6 +241,10 @@ export function prepareRunPackages(root, offline = false) {
|
|
|
234
241
|
}
|
|
235
242
|
const matching = lock?.compiler === compilerVersion() && sameSpecifications(lock.specifications, specifications);
|
|
236
243
|
if (!matching) {
|
|
244
|
+
if (!lock && !Object.keys(specifications).length)
|
|
245
|
+
return;
|
|
246
|
+
if (frozen)
|
|
247
|
+
throw new Error('PACKAGE_LOCK: Frozen run requires a matching aug.lock.json for the current imports, configuration and compiler. Run aug install before retrying.');
|
|
237
248
|
process.stderr.write('Installing August packages declared by imports and main.yaml…\n');
|
|
238
249
|
installPackages(root, false, offline);
|
|
239
250
|
return;
|
|
@@ -272,7 +283,7 @@ function installLocked(root, frozen, offline, update) {
|
|
|
272
283
|
}
|
|
273
284
|
const isLibrary = (root) => existsSync(join(root, 'aug-package.json')) ||
|
|
274
285
|
!existsSync(join(root, 'main.aug')) && (existsSync(join(root, 'export.aug')) || existsSync(join(root, 'src/export.aug')));
|
|
275
|
-
const selectedSource = (file) => file.endsWith('.aug') || ['aug-package.json', 'package.json', 'main.yaml', 'README.md', 'LICENSE'].includes(file);
|
|
286
|
+
const selectedSource = (file) => file.endsWith('.aug') || ['aug-package.json', 'package.json', 'main.yaml', 'README.md', 'LICENSE', 'native.abi.json', 'THIRD_PARTY_NOTICES.md'].includes(file);
|
|
276
287
|
function copySource(origin, destination) {
|
|
277
288
|
let bytes = 0, count = 0;
|
|
278
289
|
const copy = (folder) => {
|
|
@@ -381,7 +392,7 @@ function installSourceGraph(root, specifications, frozen, offline, previous) {
|
|
|
381
392
|
writeJson(join(directory, 'package.json'), { name: manifest.name, version: manifest.version, private: true, dependencies });
|
|
382
393
|
const { sourceRoot } = readPackage(directory);
|
|
383
394
|
const entry = { path, name: manifest.name, version: manifest.version, source: manifest.source,
|
|
384
|
-
digest: packageDigest(directory, sourceRoot), dependencies: {} };
|
|
395
|
+
digest: packageDigest(directory, sourceRoot), dependencies: {}, ...(manifest.native ? { native: manifest.native } : {}) };
|
|
385
396
|
packages.set(path, entry);
|
|
386
397
|
for (const [alias, request] of Object.entries(dependencies)) {
|
|
387
398
|
entry.dependencies[alias] = visit(request, origin);
|
|
@@ -395,6 +406,8 @@ function installSourceGraph(root, specifications, frozen, offline, previous) {
|
|
|
395
406
|
const lock = { format: 1, compiler: compilerVersion(), specifications, roots, npm: registry,
|
|
396
407
|
git: [...gitSources.values()].sort((a, b) => a.request.localeCompare(b.request)),
|
|
397
408
|
packages: [...packages.values()].sort((a, b) => a.path.localeCompare(b.path)) };
|
|
409
|
+
if (previous?.native && JSON.stringify(lock.packages) === JSON.stringify(previous.packages))
|
|
410
|
+
lock.native = previous.native;
|
|
398
411
|
if (frozen && JSON.stringify(lock.packages) !== JSON.stringify(previous?.packages))
|
|
399
412
|
throw new Error('Frozen package contents changed; use aug install --update to choose new revisions.');
|
|
400
413
|
rmSync(join(stage, 'transport'), { recursive: true, force: true });
|
|
@@ -446,6 +459,8 @@ export function preparePackage(root) {
|
|
|
446
459
|
transport.version = manifest.version;
|
|
447
460
|
transport.dependencies = manifest.dependencies ?? {};
|
|
448
461
|
transport.files = [...new Set([...(Array.isArray(transport.files) ? transport.files : ['README.md', 'LICENSE']), manifest.source, '.aug-spec', 'aug-package.json'])];
|
|
462
|
+
if (manifest.native)
|
|
463
|
+
transport.files.push('native.abi.json', 'THIRD_PARTY_NOTICES.md');
|
|
449
464
|
writeJson(join(root, 'package.json'), transport);
|
|
450
465
|
readPackage(root);
|
|
451
466
|
}
|
package/src/parser.js
CHANGED
|
@@ -218,8 +218,14 @@ class Parser {
|
|
|
218
218
|
return { kind: 'composition', name, typeParams: [], bindings, span: this.span(start) };
|
|
219
219
|
}
|
|
220
220
|
if (this.at('extern')) {
|
|
221
|
-
this.take();
|
|
221
|
+
const start = this.take().span;
|
|
222
222
|
this.expect('C');
|
|
223
|
+
if (this.current().value === 'resource') {
|
|
224
|
+
this.take();
|
|
225
|
+
const name = this.expect('identifier').value;
|
|
226
|
+
this.endStatement();
|
|
227
|
+
return { kind: 'resource', name, typeParams: [], span: this.span(start) };
|
|
228
|
+
}
|
|
223
229
|
return this.parseFunction(true);
|
|
224
230
|
}
|
|
225
231
|
if (this.looksLikeBareClass())
|
|
@@ -395,6 +401,11 @@ class Parser {
|
|
|
395
401
|
this.parseTypeParams();
|
|
396
402
|
if (this.at('('))
|
|
397
403
|
this.parseParams(true);
|
|
404
|
+
if (this.match('unless')) {
|
|
405
|
+
this.parseType();
|
|
406
|
+
while (this.match('and') || this.match(','))
|
|
407
|
+
this.parseType();
|
|
408
|
+
}
|
|
398
409
|
if (this.match('=>')) {
|
|
399
410
|
return true;
|
|
400
411
|
}
|
|
@@ -497,6 +508,13 @@ class Parser {
|
|
|
497
508
|
const name = this.expect('identifier').value;
|
|
498
509
|
const { typeParams, typeConstraints, typeVariance } = this.parseTypeParams();
|
|
499
510
|
const fields = this.at('(') ? this.parseParams(true) : [];
|
|
511
|
+
const validationErrors = [];
|
|
512
|
+
const validationDeclared = !!this.match('unless');
|
|
513
|
+
if (validationDeclared) {
|
|
514
|
+
validationErrors.push(this.parseType());
|
|
515
|
+
while (this.match('and') || this.match(','))
|
|
516
|
+
validationErrors.push(this.parseType());
|
|
517
|
+
}
|
|
500
518
|
let constructorBody = this.match('=>') ? this.parseBlock(start) : undefined;
|
|
501
519
|
const implemented = [];
|
|
502
520
|
if (!this.at('implements'))
|
|
@@ -542,7 +560,7 @@ class Parser {
|
|
|
542
560
|
}
|
|
543
561
|
this.closeBrace();
|
|
544
562
|
return { kind: 'class', name, typeParams, typeConstraints, typeVariance, fields, stateFields, constructorBody,
|
|
545
|
-
implements: implemented, methods, headerEnd, span: this.span(start) };
|
|
563
|
+
validationErrors, validationDeclared, implements: implemented, methods, headerEnd, span: this.span(start) };
|
|
546
564
|
}
|
|
547
565
|
parseInterface() {
|
|
548
566
|
const keyword = this.take();
|