mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c00ae3492 | ||
|
|
c1dd60ffd6 | ||
|
|
bec28d6b87 | ||
|
|
d3e6111cf1 | ||
|
|
dd1f0531ac | ||
|
|
37150774c6 | ||
|
|
9cf115e808 | ||
|
|
9d8f16c825 | ||
|
|
f043540d13 | ||
|
|
78decf80f7 | ||
|
|
8ad9a3ce15 | ||
|
|
251fddb658 | ||
|
|
51db2d52e4 | ||
|
|
9621dcbd94 | ||
|
|
5648a06ac4 | ||
|
|
f344cf4460 | ||
|
|
e61e328edb | ||
|
|
611eb9f5a9 | ||
|
|
1977a493c7 | ||
|
|
a08fd7c1d1 | ||
|
|
fa6bb0351b | ||
|
|
48b4165591 |
@@ -14,6 +14,17 @@ updates:
|
||||
- "*"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
# craft-ui is still on egui 0.36, and two eguis in the graph don't build (#1990). Remove this
|
||||
# once craft-ui moves to egui 0.37 (storytold/craft-libs#199).
|
||||
ignore:
|
||||
- dependency-name: "egui"
|
||||
versions: [">=0.37"]
|
||||
- dependency-name: "eframe"
|
||||
versions: [">=0.37"]
|
||||
- dependency-name: "egui-wgpu"
|
||||
versions: [">=0.37"]
|
||||
- dependency-name: "egui_kittest"
|
||||
versions: [">=0.37"]
|
||||
|
||||
# Maintain GitHub Actions workflows
|
||||
- package-ecosystem: "github-actions"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: CI
|
||||
|
||||
# Quality gates for every push to main and every pull request (`cargo xtask ci`), through the
|
||||
# suite's shared gate workflow in craft-common-actions.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# A newer push to a PR branch cancels its older run; every push to main runs to the end.
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
# Tracks @main so suite-wide CI fixes reach every app without a PR in each repository.
|
||||
uses: storytold/craft-common-actions/.github/workflows/gate.yml@main
|
||||
with:
|
||||
# Same craft-fonts commit as release.yml.
|
||||
craft-fonts-ref: 3d9ab3a132ed0eff457e424226a435a901da30d3
|
||||
timeout-minutes: 90
|
||||
packages: >-
|
||||
pkg-config libxkbcommon-dev libwayland-dev libx11-dev libxcb1-dev libxcursor-dev
|
||||
libxrandr-dev libxi-dev libfontconfig1-dev libfreetype-dev mesa-vulkan-drivers
|
||||
libgl1-mesa-dev
|
||||
@@ -2,6 +2,9 @@
|
||||
# (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter.
|
||||
name: FreeBSD
|
||||
|
||||
# The VM build is slow, so it runs nightly on main and on pushes to `release`, not on every pull
|
||||
# request: a drifted dependency still shows up within a day, and always before a release ships.
|
||||
# PRs that change the FreeBSD workflow or packaging itself still run it.
|
||||
on:
|
||||
push:
|
||||
branches: [release]
|
||||
|
||||
@@ -21,3 +21,41 @@ jobs:
|
||||
nix:
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/nix.yml@main
|
||||
|
||||
git-sources:
|
||||
name: Git dependencies come from storytold
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Git dependencies come from storytold
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
|
||||
# Source replacement, patches and path overrides in cargo config never show up
|
||||
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
|
||||
# Cargo still reads the legacy .cargo/config.
|
||||
python3 - <<'PY'
|
||||
import os, sys, tomllib
|
||||
for path in (".cargo/config", ".cargo/config.toml"):
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
config = tomllib.load(open(path, "rb"))
|
||||
replaced = [name for name, source in config.get("source", {}).items()
|
||||
if isinstance(source, dict) and "replace-with" in source]
|
||||
if replaced or "patch" in config or "paths" in config:
|
||||
print(f"{path} replaces, patches or overrides a source")
|
||||
sys.exit(1)
|
||||
PY
|
||||
# Fails when Cargo.lock is out of date with the manifests.
|
||||
cargo metadata --locked --format-version 1 > /dev/null
|
||||
python3 - <<'PY'
|
||||
import sys, tomllib
|
||||
lock = tomllib.load(open("Cargo.lock", "rb"))
|
||||
bad = sorted({p["source"] for p in lock.get("package", [])
|
||||
if p.get("source", "").startswith("git+")
|
||||
and not p["source"].startswith("git+https://github.com/storytold/")})
|
||||
if bad:
|
||||
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
@@ -11,14 +11,57 @@ on:
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
git-sources:
|
||||
name: Git dependencies come from storytold
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Git dependencies come from storytold
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
|
||||
# Source replacement, patches and path overrides in cargo config never show up
|
||||
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
|
||||
# Cargo still reads the legacy .cargo/config.
|
||||
python3 - <<'PY'
|
||||
import os, sys, tomllib
|
||||
for path in (".cargo/config", ".cargo/config.toml"):
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
config = tomllib.load(open(path, "rb"))
|
||||
replaced = [name for name, source in config.get("source", {}).items()
|
||||
if isinstance(source, dict) and "replace-with" in source]
|
||||
if replaced or "patch" in config or "paths" in config:
|
||||
print(f"{path} replaces, patches or overrides a source")
|
||||
sys.exit(1)
|
||||
PY
|
||||
# Fails when Cargo.lock is out of date with the manifests.
|
||||
cargo metadata --locked --format-version 1 > /dev/null
|
||||
python3 - <<'PY'
|
||||
import sys, tomllib
|
||||
lock = tomllib.load(open("Cargo.lock", "rb"))
|
||||
bad = sorted({p["source"] for p in lock.get("package", [])
|
||||
if p.get("source", "").startswith("git+")
|
||||
and not p["source"].startswith("git+https://github.com/storytold/")})
|
||||
if bad:
|
||||
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
release:
|
||||
needs: git-sources
|
||||
permissions:
|
||||
# The reusable release workflow tags and uploads the draft release.
|
||||
contents: write
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/release.yml@main
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,8 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
group: sonarcloud-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
# A push to main is the run that publishes coverage; superseded runs there would never finish.
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
sonarcloud:
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
# binaries are ARM64, run the CLI natively, and uninstall.
|
||||
name: Windows ARM64
|
||||
|
||||
# Nightly on main and on pushes to `release` (dependency and runner-image drift shows up within a
|
||||
# day, and always before a release ships), and on PRs that change the Windows packaging itself.
|
||||
on:
|
||||
schedule:
|
||||
- cron: "3 5 * * *"
|
||||
push:
|
||||
branches: [release]
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/windows-arm64.yml
|
||||
- packaging/windows/**
|
||||
schedule:
|
||||
- cron: "41 3 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
Generated
+69
-134
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94862189cab6e282ed0433a801a216cd1a712953813284a48fa81c67223e38f6"
|
||||
checksum = "5eaa72520f70eb6f7f09b06f44e1ed0d8fad5fa743364f2c3715c30df962ec7c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.2",
|
||||
@@ -209,9 +209,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.1"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||
checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"image",
|
||||
@@ -223,7 +223,7 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
@@ -650,8 +650,8 @@ name = "cadcraft-fonts"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"cadcraft-geom",
|
||||
"harfrust",
|
||||
"skrifa 0.47.0",
|
||||
"harfrust 0.14.0",
|
||||
"skrifa 0.48.0",
|
||||
"unicode-bidi",
|
||||
]
|
||||
|
||||
@@ -674,8 +674,8 @@ dependencies = [
|
||||
"cadcraft-fonts",
|
||||
"cadcraft-geom",
|
||||
"cadcraft-render",
|
||||
"miniz_oxide 0.8.9",
|
||||
"quick-xml",
|
||||
"miniz_oxide 0.9.1",
|
||||
"quick-xml 0.42.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
@@ -689,7 +689,7 @@ dependencies = [
|
||||
"cadcraft-engine",
|
||||
"cadcraft-io",
|
||||
"cadcraft-render",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.3",
|
||||
"log",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
||||
"emath",
|
||||
"epaint_default_fonts",
|
||||
"font-types",
|
||||
"harfrust",
|
||||
"harfrust 0.12.0",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"parking_lot",
|
||||
@@ -1441,9 +1441,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.12.6"
|
||||
version = "0.12.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274"
|
||||
checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
@@ -1734,6 +1734,18 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "harfrust"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86aa776d6d32b072119e1f1650f4db843ff4382a4aaa6a2c9c4e35703cd377cb"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"bytemuck",
|
||||
"read-fonts 0.45.1",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.3.1"
|
||||
@@ -1914,7 +1926,7 @@ dependencies = [
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"png",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
@@ -2100,9 +2112,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
version = "0.2.191"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -2271,9 +2283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.21.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c"
|
||||
checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
@@ -2282,8 +2294,7 @@ dependencies = [
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"once_cell",
|
||||
"png 0.18.1",
|
||||
"png",
|
||||
"thiserror 2.0.21",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -2945,19 +2956,6 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
@@ -3081,6 +3079,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
@@ -3159,13 +3166,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.44.0"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8"
|
||||
checksum = "4edc8db0d972de88e53a085b1f01fb3fbdae7bb0cb7a0ce2b2f8285a412fe3ef"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"foldhash",
|
||||
"font-types",
|
||||
"once_cell",
|
||||
"yoke",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3244,9 +3253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rstar"
|
||||
version = "0.12.2"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
|
||||
checksum = "5912b862fa5ffb462607bfd1e35036c458c537921f508c8235a83d5f3987edfe"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"num-traits",
|
||||
@@ -3377,9 +3386,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -3464,12 +3473,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "526e654b303ff4ad73a5a40b25de2b3ce3d039c5e3b6da5f89ae6e57004cb40f"
|
||||
checksum = "a5213b4825bac3587046330052d61bfac7bd3bd0d203c758d7c2ef726fcea034"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts 0.44.0",
|
||||
"read-fonts 0.45.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3715,24 +3724,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"png 0.17.16",
|
||||
"png",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
@@ -4153,7 +4162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quick-xml 0.41.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
@@ -4510,7 +4519,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4519,16 +4528,7 @@ version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4546,31 +4546,14 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4588,96 +4571,48 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winit"
|
||||
version = "0.30.13"
|
||||
@@ -4740,9 +4675,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.31"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
|
||||
checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"version_check",
|
||||
|
||||
+8
-6
@@ -37,17 +37,17 @@ chrono = { version = "0.4.45", default-features = false, features = ["std"] }
|
||||
rayon = "1"
|
||||
|
||||
# rendering / formats
|
||||
tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd", "png-format"] }
|
||||
tiny-skia = { version = "0.12", default-features = false, features = ["std", "simd", "png-format"] }
|
||||
lyon_tessellation = "1.0.22"
|
||||
skrifa = "0.47"
|
||||
# Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; the version egui's epaint uses)
|
||||
# and the Unicode Bidirectional Algorithm for right-to-left drawing text.
|
||||
harfrust = "0.12"
|
||||
skrifa = "0.48"
|
||||
# Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; egui's epaint is still on 0.12,
|
||||
# so the graph has both) and the Unicode Bidirectional Algorithm for right-to-left drawing text.
|
||||
harfrust = "0.14"
|
||||
unicode-bidi = "0.3.18"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
||||
encoding_rs = "0.8.35"
|
||||
# XML for the SVG reader (streaming parser, no entity expansion beyond the five predefined ones)
|
||||
quick-xml = "0.41"
|
||||
quick-xml = "0.42"
|
||||
|
||||
# ui
|
||||
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
|
||||
@@ -69,9 +69,11 @@ type_complexity = "allow"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
debug = false
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
debug = "line-tables-only"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
mod cli;
|
||||
mod drafting_spacing;
|
||||
mod help;
|
||||
mod mcp_connect;
|
||||
mod skipped_records;
|
||||
mod undrawn_entities;
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>CADCraft</title>
|
||||
<link data-trunk rel="rust" data-bin="cadcraft-web" data-wasm-opt="z" data-weak-refs />
|
||||
<style>
|
||||
|
||||
@@ -48,6 +48,8 @@ impl Default for Limits {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the loopback server with a fresh session token and a bounded request queue.
|
||||
/// Binding or token-issuance failure returns a disconnected receiver.
|
||||
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
|
||||
match TcpListener::bind(("127.0.0.1", port)) {
|
||||
Ok(l) => {
|
||||
@@ -101,6 +103,7 @@ impl Drop for Slot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept clients within the connection limit and give each worker the same session token.
|
||||
fn accept_loop(listener: TcpListener, tx: SyncSender<ControlRequest>, ctx: egui::Context, limits: Limits, token: Arc<String>) {
|
||||
let open = Arc::new(AtomicUsize::new(0));
|
||||
for mut stream in accepted(listener.incoming(), || std::thread::sleep(ACCEPT_RETRY)) {
|
||||
@@ -162,6 +165,7 @@ enum Line {
|
||||
Reject(String),
|
||||
}
|
||||
|
||||
/// Parse a UTF-8 JSON request and its optional token, distinguishing blank lines from rejected input.
|
||||
fn parse_line(bytes: &[u8]) -> Line {
|
||||
let Ok(text) = std::str::from_utf8(bytes) else {
|
||||
return Line::Reject("not a control request (invalid UTF-8); closing the connection".into());
|
||||
@@ -198,6 +202,8 @@ fn is_http_request_line(text: &str) -> bool {
|
||||
|| text.rsplit_once(' ').is_some_and(|(_, version)| version.starts_with("HTTP/"))
|
||||
}
|
||||
|
||||
/// Serve bounded JSON lines, authenticating each request before enqueueing it for the UI.
|
||||
/// Invalid credentials close the connection; queue and reply limits report whether a request ran.
|
||||
fn serve(stream: TcpStream, tx: &SyncSender<ControlRequest>, ctx: &egui::Context, limits: Limits, token: &str) {
|
||||
if stream.set_read_timeout(Some(limits.idle_timeout)).is_err() || stream.set_write_timeout(Some(limits.write_timeout)).is_err() {
|
||||
return;
|
||||
@@ -385,6 +391,7 @@ mod tests {
|
||||
assert!(t.elapsed() < Duration::from_secs(4));
|
||||
}
|
||||
|
||||
/// Authenticated requests rejected by a full queue retain their IDs and report that they did not run.
|
||||
#[test]
|
||||
fn a_full_queue_answers_busy() {
|
||||
let (addr, rx) = serve_with(short());
|
||||
@@ -407,6 +414,7 @@ mod tests {
|
||||
drop(rx);
|
||||
}
|
||||
|
||||
/// Replies on one authenticated connection preserve the ID of each request.
|
||||
#[test]
|
||||
fn requests_are_answered_with_their_ids() {
|
||||
let addr = serve_echo();
|
||||
@@ -423,6 +431,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Missing and incorrect session tokens are rejected before dispatch.
|
||||
#[test]
|
||||
fn a_request_without_the_token_is_unauthorized() {
|
||||
// A local peer that does not hold the session token cannot drive the app (CWE-306).
|
||||
@@ -437,6 +446,7 @@ mod tests {
|
||||
assert!(wrong[0]["error"].as_str().unwrap().contains("unauthorized"), "{wrong:?}");
|
||||
}
|
||||
|
||||
/// A request carrying the session token reaches the control dispatcher.
|
||||
#[test]
|
||||
fn a_request_with_the_token_is_dispatched() {
|
||||
let addr = serve_echo();
|
||||
|
||||
@@ -89,6 +89,8 @@ struct CompDof {
|
||||
objects: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
/// Compute component and object freedom only when rank and residual-evaluation work fit their budgets.
|
||||
/// Return unknown freedom before building the Jacobian when either work limit is exceeded.
|
||||
fn analyse(comp: &Comp) -> CompDof {
|
||||
let free = comp.free(None);
|
||||
let rows = comp.rows(None);
|
||||
@@ -213,6 +215,7 @@ mod budget_tests {
|
||||
use cadcraft_doc::{Common, ConstraintKind, EntityKind, GeomRef, Space};
|
||||
use cadcraft_geom::{Spline, Vec2};
|
||||
|
||||
/// An expensive fit-spline endpoint residual produces unknown freedom before numerical differentiation.
|
||||
#[test]
|
||||
fn endpoint_analysis_rejects_cubic_fit_work_before_differentiation() {
|
||||
let mut d = Drawing::default();
|
||||
|
||||
@@ -328,6 +328,8 @@ pub(crate) fn build(d: &Drawing, cons: &[&Constraint], opts: &SolveOptions) -> R
|
||||
build_budgeted(d, cons, opts, &mut members_left)
|
||||
}
|
||||
|
||||
/// Build constraint components with one caller-owned rigid-member sampling budget shared by every model.
|
||||
/// Return constraints that cannot be interpreted as skipped entries.
|
||||
pub(crate) fn build_budgeted(
|
||||
d: &Drawing,
|
||||
cons: &[&Constraint],
|
||||
|
||||
@@ -117,6 +117,8 @@ pub fn rigid_points(d: &Drawing, ins: &Insert) -> Vec<Vec2> {
|
||||
rigid_points_budgeted(d, ins, &mut members_left)
|
||||
}
|
||||
|
||||
/// Sample finite block points within per-insert and caller-shared member limits.
|
||||
/// Members that contribute no points still consume traversal work; nested blocks are not entered.
|
||||
fn rigid_points_budgeted(d: &Drawing, ins: &Insert, members_left: &mut usize) -> Vec<Vec2> {
|
||||
let Some(block) = d.block(&ins.block) else { return Vec::new() };
|
||||
let m = ins.transform(block.base.xy());
|
||||
@@ -187,6 +189,7 @@ impl Model {
|
||||
Self::build_budgeted(d, handles, &mut members_left)
|
||||
}
|
||||
|
||||
/// Build solver parameters for supported entities while sharing rigid-member sampling work with other models.
|
||||
pub(crate) fn build_budgeted(d: &Drawing, handles: &[Handle], members_left: &mut usize) -> Model {
|
||||
let mut m = Model::default();
|
||||
for &h in handles {
|
||||
@@ -620,6 +623,7 @@ mod budget_tests {
|
||||
use cadcraft_geom::Vec3;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Writing an unchanged model back leaves partial rational weights and the original spline geometry intact.
|
||||
#[test]
|
||||
fn write_back_keeps_partial_rational_weights() {
|
||||
let mut drawing = Drawing::default();
|
||||
@@ -635,6 +639,7 @@ mod budget_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A second model receives no sampled block points after the first model spends the shared member budget.
|
||||
#[test]
|
||||
fn rigid_sampling_budget_is_shared_between_models() {
|
||||
let mut d = Drawing::default();
|
||||
|
||||
@@ -116,6 +116,7 @@ fn r_at(c: &Constraint, i: usize) -> Option<&GeomRef> {
|
||||
}
|
||||
|
||||
impl Built {
|
||||
/// Estimate spline reconstruction work for endpoint and join residuals; other residual forms add no such cost.
|
||||
pub(crate) fn spline_reconstruct_cost(&self, m: &Model) -> f64 {
|
||||
let cost = |i: usize| self.refs.get(i).map_or(0.0, |r| m.spline_reconstruct_cost(r.handle));
|
||||
match self.form {
|
||||
|
||||
@@ -364,6 +364,7 @@ fn hostile_new_geometry_never_panics() {
|
||||
let _ = dof(&d, &[Handle(0), Handle(u64::MAX)]);
|
||||
}
|
||||
|
||||
/// Fit-point vertex constraints retain a known freedom count because they do not reconstruct the spline.
|
||||
#[test]
|
||||
fn vertex_constraints_on_large_fit_spline_do_not_charge_reconstruction() {
|
||||
let mut d = Drawing::default();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
mod lin_pat_parse;
|
||||
mod sub_segment_alias;
|
||||
@@ -10,4 +10,4 @@ workspace = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
# MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE).
|
||||
acadrust = "0.6.4"
|
||||
acadrust = "0.6.5"
|
||||
|
||||
+12
-19
@@ -107,7 +107,6 @@ mod native {
|
||||
current_style_names(&mut doc);
|
||||
table_merges(&mut doc);
|
||||
table_style_rows(&mut doc, false);
|
||||
dimension_flags(&mut doc, false);
|
||||
let orphans = drop_orphaned_dgn_components(&mut doc);
|
||||
if orphans > 0 {
|
||||
let (s, were) = if orphans == 1 { ("", "was") } else { ("s", "were") };
|
||||
@@ -291,24 +290,14 @@ mod native {
|
||||
}
|
||||
}
|
||||
|
||||
/// The DWG dimension flags byte. Its bit 0 is the opposite of DXF group 70's bit 128 (set: the
|
||||
/// text is at its default position) and its bit 1 is group 70's bit 32 (ODA DWG
|
||||
/// specification, common dimension data), while acadrust 0.6.4 reads and writes bit 0 as
|
||||
/// `text_user_positioned` itself and keeps no bit 32 from DXF. Without this, every dimension
|
||||
/// of a DWG CADCraft writes is user-positioned for AutoCAD (#1094), and every dimension of an
|
||||
/// AutoCAD DWG is read as user-positioned. `to_dwg` sets the fields up for the writer:
|
||||
/// bit 32 as CADCraft's DXF writer always sets it, and bit 0 through the inverted field;
|
||||
/// otherwise the field is read from bit 0 after a DWG is read.
|
||||
fn dimension_flags(doc: &mut acadrust::CadDocument, to_dwg: bool) {
|
||||
/// The DWG dimension flags byte. Its bit 1 is DXF group 70's bit 32 (ODA DWG specification,
|
||||
/// common dimension data), which acadrust keeps no copy of from DXF: it is set here as
|
||||
/// CADCraft's DXF writer always sets it (#1094). Bit 0, the opposite of group 70's bit 128,
|
||||
/// acadrust 0.6.5 reads and writes itself.
|
||||
fn dimension_flags(doc: &mut acadrust::CadDocument) {
|
||||
for e in doc.entities_mut() {
|
||||
let acadrust::entities::EntityType::Dimension(d) = e else { continue };
|
||||
let b = d.base_mut();
|
||||
if to_dwg {
|
||||
b.dwg_flags_byte |= 0x02;
|
||||
b.text_user_positioned = !b.text_user_positioned;
|
||||
} else {
|
||||
b.text_user_positioned = b.dwg_flags_byte & 0x01 == 0;
|
||||
}
|
||||
d.base_mut().dwg_flags_byte |= 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +518,11 @@ mod native {
|
||||
// interrupted once it starts (CWE-789). This bounds the large-input variant; the decoder
|
||||
// runs inside `catch_unwind` so a decode failure is reported rather than crashing.
|
||||
if dxf.len() > Limits::DEFAULT.max_dxf_bytes {
|
||||
return Err(format!("DXF: the input is {}, larger than the {} CADCraft converts to DWG", mb(dxf.len() as u64), mb(Limits::DEFAULT.max_dxf_bytes as u64)));
|
||||
return Err(format!(
|
||||
"DXF: the input is {}, larger than the {} CADCraft converts to DWG",
|
||||
mb(dxf.len() as u64),
|
||||
mb(Limits::DEFAULT.max_dxf_bytes as u64)
|
||||
));
|
||||
}
|
||||
let data = dxf.to_vec();
|
||||
let mut doc = std::panic::catch_unwind(move || acadrust::DxfReader::from_reader(Cursor::new(data)).and_then(|r| r.read()))
|
||||
@@ -545,7 +538,7 @@ mod native {
|
||||
}
|
||||
other_layout_owners(&mut doc);
|
||||
table_style_rows(&mut doc, true);
|
||||
dimension_flags(&mut doc, true);
|
||||
dimension_flags(&mut doc);
|
||||
drop_unwritable(&mut doc);
|
||||
current_dimstyle(&mut doc);
|
||||
current_text_style(&mut doc);
|
||||
|
||||
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
# R-tree spatial index for picking, window selection and object snaps (MIT OR Apache-2.0).
|
||||
rstar = { version = "0.12", default-features = false }
|
||||
rstar = { version = "0.13", default-features = false }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn read_capped(path: &Path, max: usize) -> io::Result<Vec<u8>> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Regular-file reads accept the byte boundary and reject any larger payload.
|
||||
#[test]
|
||||
fn reads_within_limit_and_rejects_oversize() {
|
||||
let dir = std::env::temp_dir().join(format!("cadcraft-cr-{}", std::process::id()));
|
||||
@@ -57,6 +58,7 @@ mod tests {
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
/// An endless device is rejected before its contents can be buffered.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn rejects_non_regular_file() {
|
||||
@@ -66,6 +68,7 @@ mod tests {
|
||||
assert!(read_capped(p, 8 << 20).is_err());
|
||||
}
|
||||
}
|
||||
/// Opening a FIFO without a writer returns an error without waiting for a peer.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn rejects_fifo_without_a_writer() {
|
||||
|
||||
@@ -337,6 +337,7 @@ fn opened(s: &Session, i: usize, entities: bool) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a drawing from bounded regular-file bytes or base64 data and resolve its external references.
|
||||
fn run_open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(_path) = str_param(p, "path") {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -18,8 +18,7 @@ pub use craft_docpath::Refused;
|
||||
/// on other platforms.
|
||||
pub fn resolve(stored: &str, document: Option<&Path>) -> Result<PathBuf, Refused> {
|
||||
let document = document.filter(|d| !d.as_os_str().is_empty()).map(|d| std::path::absolute(d).unwrap_or_else(|_| d.to_path_buf()));
|
||||
let path = craft_docpath::resolve(stored.trim(), document.as_deref())?;
|
||||
if path.is_absolute() { Ok(path) } else { Err(Refused::Invalid) }
|
||||
craft_docpath::resolve_absolute(stored.trim(), document.as_deref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -698,11 +698,14 @@ fn wcmatch(a: &[Value]) -> R {
|
||||
/// cover), but finite so truly exponential `*`-heavy patterns are rejected rather than hanging.
|
||||
const WCMATCH_BUDGET: u64 = 200_000_000;
|
||||
|
||||
/// Spend one wildcard work unit, returning an error when the shared budget is exhausted.
|
||||
fn charge_match(budget: &mut u64) -> Result<(), ()> {
|
||||
*budget = budget.checked_sub(1).ok_or(())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Split comma-separated wildcard alternatives while preserving escapes and bracket sets.
|
||||
/// Every consumed character spends from the same budget used for matching.
|
||||
fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
|
||||
let mut out = vec![Vec::new()];
|
||||
let mut chars = pat.chars().peekable();
|
||||
@@ -742,6 +745,7 @@ fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Match one wildcard alternative, including leading `~` negation, within the shared budget.
|
||||
fn match_pattern(text: &[char], pat: &[char], budget: &mut u64) -> Result<bool, ()> {
|
||||
if let Some(('~', rest)) = pat.split_first().map(|(c, r)| (*c, r))
|
||||
&& !rest.is_empty()
|
||||
@@ -757,6 +761,7 @@ fn glob(text: &[char], pat: &[char], depth: usize, budget: &mut u64) -> Result<b
|
||||
glob_cached(text, pat, depth, budget, &mut std::collections::HashSet::new())
|
||||
}
|
||||
|
||||
/// Match suffixes while memoizing failures; both matching work and retained failure entries are bounded.
|
||||
fn glob_cached(
|
||||
text: &[char],
|
||||
pat: &[char],
|
||||
@@ -778,6 +783,7 @@ fn glob_cached(
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
/// Evaluate wildcard tokens and backtracking branches under shared work and recursion limits.
|
||||
fn glob_inner(
|
||||
text: &[char],
|
||||
pat: &[char],
|
||||
@@ -1060,6 +1066,7 @@ fn findfile(a: &[Value]) -> R {
|
||||
Ok(if file_exists(&p) { Value::Str(p) } else { Value::Nil })
|
||||
}
|
||||
|
||||
/// Read a regular text file up to 64 MiB, replacing invalid UTF-8 and returning `None` on read failure.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn read_text_file(path: &str) -> Option<String> {
|
||||
// Read through a byte-limited, regular-file-only reader so a path naming an endless device
|
||||
@@ -1068,6 +1075,7 @@ pub fn read_text_file(path: &str) -> Option<String> {
|
||||
let bytes = crate::capped_read::read_capped(std::path::Path::new(path), MAX).ok()?;
|
||||
Some(String::from_utf8_lossy(&bytes).into_owned())
|
||||
}
|
||||
/// Return `None` on the web, where native filesystem paths are unavailable.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn read_text_file(_path: &str) -> Option<String> {
|
||||
None
|
||||
@@ -1588,6 +1596,7 @@ pub fn wild_match(text: &str, pattern: &str) -> bool {
|
||||
mod wildcard_budget_tests {
|
||||
use super::*;
|
||||
|
||||
/// Pattern splitting, literal matching, and bracket scans all stop when their shared fuel runs out.
|
||||
#[test]
|
||||
fn all_native_scans_consume_fuel() {
|
||||
assert!(split_patterns("abcdefgh", &mut 4).is_err());
|
||||
|
||||
@@ -158,6 +158,8 @@ pub fn from_json(v: &serde_json::Value) -> Value {
|
||||
const TO_JSON_BUDGET: u64 = 2_000_000;
|
||||
const TO_JSON_MAX_DEPTH: usize = 256;
|
||||
|
||||
/// Convert a LISP value to owned JSON within element and nesting limits.
|
||||
/// Return `None` before a shared cons structure can expand beyond the remaining budget.
|
||||
fn to_json_bounded(v: &Value, depth: usize, budget: &mut u64) -> Option<serde_json::Value> {
|
||||
if depth > TO_JSON_MAX_DEPTH || *budget == 0 {
|
||||
return None;
|
||||
@@ -185,6 +187,7 @@ pub fn getvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
|
||||
Ok(Flow::Value(crate::sysvars::get(ctx.s, &name).map(|v| from_json(&v)).unwrap_or_default()))
|
||||
}
|
||||
|
||||
/// Set a system variable after bounded JSON conversion, reporting invalid or oversized values as LISP errors.
|
||||
pub fn setvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
|
||||
arity(&a, 2, 2)?;
|
||||
let name = string(&a[0])?;
|
||||
|
||||
@@ -131,6 +131,7 @@ pub(crate) struct Limits {
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
/// Provide finite cons-cell, string-byte, and synchronous-callback limits for a LISP run.
|
||||
fn default() -> Self {
|
||||
Limits { cells: value::MAX_CELLS, string_bytes: value::STRING_BUDGET, callback_steps: 500_000_000 }
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ fn runaway_routines_are_errors_not_crashes() {
|
||||
assert_eq!(r, Ok("3000000".into()));
|
||||
}
|
||||
|
||||
/// An infinite comparator stops at its callback cap, and a later finite comparator still succeeds.
|
||||
#[test]
|
||||
fn synchronous_callback_loop_is_an_error_not_a_hang() {
|
||||
// A comparator with an infinite loop used to run with u64::MAX fuel and hang the thread; it
|
||||
@@ -232,6 +233,7 @@ fn synchronous_callback_loop_is_an_error_not_a_hang() {
|
||||
assert_eq!(ok(&mut s, "(vl-sort '(3 1 2) '<)"), "(1 2 3)");
|
||||
}
|
||||
|
||||
/// A zero-step callback limit prevents even the first callback side effect.
|
||||
#[test]
|
||||
fn a_zero_callback_budget_does_not_run_the_callback() {
|
||||
let mut s = session();
|
||||
@@ -242,6 +244,7 @@ fn a_zero_callback_budget_does_not_run_the_callback() {
|
||||
assert_eq!(ok(&mut s, "callback-ran"), "nil");
|
||||
}
|
||||
|
||||
/// A callback stops at a cap smaller than one scheduling slice without completing all its assignments.
|
||||
#[test]
|
||||
fn a_callback_must_fit_within_a_partial_slice() {
|
||||
let mut s = session();
|
||||
@@ -252,6 +255,7 @@ fn a_callback_must_fit_within_a_partial_slice() {
|
||||
assert_eq!(ok(&mut s, "(and (> callback-count 0) (< callback-count 1000))"), "T");
|
||||
}
|
||||
|
||||
/// Crossing a scheduling boundary grants only the additional configured step, rather than another slice.
|
||||
#[test]
|
||||
fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
|
||||
let counts: Vec<u64> = [399_999, 400_000]
|
||||
@@ -270,6 +274,7 @@ fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
|
||||
assert!(counts[1] >= counts[0] && counts[1] - counts[0] <= 1, "{counts:?}");
|
||||
}
|
||||
|
||||
/// System-variable conversion rejects a shared list whose owned JSON expansion would exceed its budget.
|
||||
#[test]
|
||||
fn setvar_rejects_an_oversized_shared_structure() {
|
||||
// A list repeatedly sharing its own contents expands exponentially; setvar's bounded JSON
|
||||
|
||||
@@ -353,6 +353,7 @@ pub fn equal(a: &Value, b: &Value, fuzz: f64) -> bool {
|
||||
/// Largest number of node comparisons one `equal` call may perform.
|
||||
const EQUAL_BUDGET: u64 = 50_000_000;
|
||||
|
||||
/// Compare nested values within depth and node-work limits, returning false when either limit is reached.
|
||||
fn equal_depth(a: &Value, b: &Value, fuzz: f64, depth: usize, budget: &mut u64) -> bool {
|
||||
if depth > MAX_DEPTH || *budget == 0 {
|
||||
return false;
|
||||
|
||||
@@ -208,7 +208,7 @@ impl SpatialIndex {
|
||||
let pad = (b.width() + b.height()) * 1e-9 + (b.min.x.abs() + b.min.y.abs() + b.max.x.abs() + b.max.y.abs()) * 1e-12 + 1e-300;
|
||||
let env = AABB::from_corners([b.min.x - pad, b.min.y - pad], [b.max.x + pad, b.max.y + pad]);
|
||||
let mut out: Vec<u32> =
|
||||
self.tree.locate_in_envelope_intersecting(&env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect();
|
||||
self.tree.locate_in_envelope_intersecting(env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect();
|
||||
if with_infinite {
|
||||
out.extend_from_slice(&self.infinite);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ fn session() -> (Session, Handle) {
|
||||
/// As [`session`], with the fixture's text changed by `edit` first.
|
||||
fn session_with(edit: impl Fn(String) -> String) -> (Session, Handle) {
|
||||
let grip = "0 BLOCKLINEARGRIP\n5 68\n330 61\n100 AcDbEvalExpr\n90 9\n98 33\n99 232\n100 AcDbBlockElement\n300 End Grip\n98 33\n99 232\n1071 0\n";
|
||||
let src = include_str!("../../io/tests/data/dynblock_door.txt")
|
||||
let src = include_str!("../../../io/tests/data/dynblock_door.txt")
|
||||
.replace("\r\n", "\n")
|
||||
.replace("170 4\n91 0\n91 0\n", "170 4\n91 8\n91 9\n")
|
||||
.replace("1030 0\n93 0\n170 0\n171 0\n100 AcDbBlockVisibilityParameter", "1030 0\n93 10\n170 0\n171 0\n100 AcDbBlockVisibilityParameter")
|
||||
@@ -2,7 +2,6 @@
|
||||
//! leave exactly the expected entities, layers and printed text. The corpus lives in its own
|
||||
//! repository; point `CADCRAFT_LISP_CORPUS` at its `cases` directory (or the repository) to run
|
||||
//! it. Without it the test only says so.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user