Compare commits

...

32 Commits

Author SHA1 Message Date
Olivia Brooks
3636916ac3 Add documentation. 2026-02-22 15:18:09 -05:00
Olivia Brooks
4e961dac40 Add documentation. 2026-02-22 13:46:03 -05:00
Olivia Brooks
fee63a2d4b Add documentation. 2026-02-22 13:45:53 -05:00
Cutieguwu
6ad82c3339 Update Cargo.lock 2026-02-16 20:41:34 -05:00
Cutieguwu
f0d22e6b79 Speed up brute force building with Prey.lock hashing and caching. 2026-02-16 20:41:31 -05:00
Cutieguwu
1009a84c06 Improve javac version fetching. 2026-02-16 20:40:36 -05:00
Cutieguwu
dcbf67e203 Update README.md 2026-02-15 20:41:56 -05:00
Cutieguwu
cfb6f5fb34 Fix raven clean 2026-02-15 20:36:54 -05:00
Cutieguwu
a3c208555a Add better handling of undefined entry points. 2026-02-15 20:27:57 -05:00
Cutieguwu
4404aba65f Update Cargo.lock 2026-02-15 20:22:12 -05:00
Cutieguwu
68743619f2 Bump java crate version. 2026-02-15 20:16:47 -05:00
Cutieguwu
e1827b13f4 Fix some issues with clap using the cli crate's versioning. 2026-02-15 20:16:32 -05:00
Cutieguwu
17f7b9dca9 Resolve some pathing issues; cleanup. 2026-02-15 20:15:26 -05:00
Cutieguwu
16accb8ab8 Shed old reference materials. 2026-02-15 19:46:59 -05:00
Cutieguwu
b338f76e06 Bump subprocess and toml. Also remove the demo project I accidentally
pushed.
2026-02-15 19:38:20 -05:00
Cutieguwu
79629391c5 I think I finished it... 2026-02-15 17:39:48 -05:00
Cutieguwu
0fad1b74bc Try to work on build some more. 2026-02-15 12:25:03 -05:00
Cutieguwu
a9fb52d8d7 Bump version numbers. 2026-02-15 09:50:36 -05:00
Olivia Brooks
e41d4bcd76 Most of the refactor. Need to switch machines. 2026-02-15 09:36:04 -05:00
Olivia Brooks
dda863e512 Make us Zed users happy. 2026-01-28 19:38:39 -05:00
Olivia Brooks
de7c0e6409 Add pathsub; Add a bunch of metadata. 2026-01-28 19:38:22 -05:00
Olivia Brooks
7020cfb8b6 Update templates (including future ones) 2026-01-28 19:37:40 -05:00
Olivia Brooks
54e6350d42 Code cleanup. 2026-01-27 22:01:41 -05:00
Olivia Brooks
f3f79a12df Zed mucked something up with the merge. Fix it. 2026-01-27 21:32:18 -05:00
Olivia Brooks
649507bbcb Fix build; Correct target/ path in generated .gitignore 2026-01-27 21:30:45 -05:00
676b0b606b Merge pull request 'fix: raven test' (#2) from AdrianLong/raven:master into master
Reviewed-on: #2
2026-01-27 16:33:41 -05:00
82b45d9c66 Removed Fence denugging statements 2026-01-27 14:09:30 -05:00
05390a8f0b Fixed raven test. The raven test system needs to be redesigned in order to run multiple different test classes. Cyclic dependencies cannot be resolved the curent build system so a parser needs to be implemented to get the import statements. 2026-01-27 13:47:57 -05:00
6da7586c3e Refactored assets to use propper java syntax. Found error in the raven test command. Impropper formating when passing arguments to the java command. 2026-01-27 13:47:57 -05:00
3deedb2f7f Fixed raven build to build from anywhere inside of a raven project. 2026-01-27 13:47:57 -05:00
Olivia Brooks
3db0f53943 Correct commands. 2026-01-27 06:15:03 -05:00
Olivia Brooks
b5c4f61c8a Use a bug tracker, instead of README. 2026-01-27 06:13:37 -05:00
45 changed files with 2905 additions and 905 deletions

18
.zed/debug.json Normal file
View File

@@ -0,0 +1,18 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Build & Debug raven new",
"build": {
"command": "cargo",
"args": ["run", "--", "new", "JavaProjectTest"],
},
"program": "$ZED_WORKTREE_ROOT/target/debug/binary",
// sourceLanguages is required for CodeLLDB (not GDB) when using Rust
"sourceLanguages": ["rust"],
"request": "launch",
"adapter": "CodeLLDB",
},
]

9
.zed/settings.json Normal file
View File

@@ -0,0 +1,9 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"file_types": {
"TOML": ["lock"],
},
}

289
Cargo.lock generated
View File

@@ -54,9 +54,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "async-trait"
@@ -66,17 +66,14 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.116",
]
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
dependencies = [
"serde_core",
]
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
@@ -89,9 +86,9 @@ dependencies = [
[[package]]
name = "bytes"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "bytesize"
@@ -107,9 +104,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.5.54"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
dependencies = [
"clap_builder",
"clap_derive",
@@ -117,9 +114,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.54"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
dependencies = [
"anstream",
"anstyle",
@@ -129,21 +126,28 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.49"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.116",
]
[[package]]
name = "clap_lex"
version = "0.7.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "cli"
version = "0.2.1"
dependencies = [
"clap",
]
[[package]]
name = "colorchoice"
@@ -151,6 +155,31 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core"
version = "0.1.4"
dependencies = [
"anyhow",
"derive_more",
"fs",
"java",
"pathsub",
"semver",
"serde",
"sha256",
"subprocess",
"toml",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -170,6 +199,29 @@ dependencies = [
"typenum",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.116",
"unicode-xid",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -186,6 +238,13 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fs"
version = "0.1.0"
dependencies = [
"derive_more",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -196,6 +255,31 @@ dependencies = [
"version_check",
]
[[package]]
name = "hard-xml"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b07b8ba970e18a03dbb79f6786b6e4d6f198a0ac839aa5182017001bb8dee17"
dependencies = [
"hard-xml-derive",
"jetscii",
"lazy_static",
"memchr",
"xmlparser",
]
[[package]]
name = "hard-xml-derive"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c43e7c3212bd992c11b6b9796563388170950521ae8487f5cdf6f6e792f1c8"
dependencies = [
"bitflags",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
@@ -231,16 +315,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
name = "java"
version = "0.2.2"
dependencies = [
"bytesize",
"derive_more",
"fs",
"semver",
"subprocess",
]
[[package]]
name = "once_cell"
version = "1.21.3"
name = "jetscii"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell_polyfill"
@@ -248,12 +355,29 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "pathsub"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dadd38133bcbe43264410412c48614bab4ef899f0792ffc4530dc19ec000a970"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pom"
version = "0.1.0"
dependencies = [
"derive_more",
"hard-xml",
"semver",
"serde",
"strum",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -265,40 +389,33 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.43"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "raven"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"anyhow",
"bytesize",
"clap",
"ron",
"semver",
"serde",
"sha256",
"subprocess",
"cli",
"core",
"fs",
"java",
"toml",
]
[[package]]
name = "ron"
version = "0.12.0"
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"bitflags",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
"semver",
]
[[package]]
@@ -338,7 +455,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.116",
]
[[package]]
@@ -381,10 +498,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subprocess"
version = "0.2.13"
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f75238edb5be30a9ea3035b945eb9c319dde80e879411cdc9a8978e1ac822960"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.116",
]
[[package]]
name = "subprocess"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98e067360c7f5a302e35c9f9cd24cb583c378fbedc73d0237284dbdd0650fcc5"
dependencies = [
"libc",
"winapi",
@@ -392,9 +530,20 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.114"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
@@ -413,9 +562,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
version = "1.0.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220"
dependencies = [
"indexmap",
"serde_core",
@@ -428,18 +577,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
version = "1.0.0+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
version = "1.0.8+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
dependencies = [
"winnow",
]
@@ -450,12 +599,6 @@ version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.19.0"
@@ -464,9 +607,21 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "utf8parse"
@@ -522,3 +677,9 @@ name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
[[package]]
name = "xmlparser"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"

View File

@@ -1,28 +1,48 @@
[package]
name = "raven"
version = "0.1.0"
[workspace]
resolver = "3"
members = ["crates/*"]
default-members = ["crates/*"]
[workspace.package]
edition = "2024"
authors = ["Olivia Brooks", "Adrian Long"]
repository = "https://gitea.cutieguwu.ca/Cutieguwu/raven"
license = "MIT"
repository = "https://gitea.cutieguwu.ca/Cutieguwu/raven"
publish = false
[dependencies]
[workspace.dependencies]
#
# Workspace member crates
#
cli = { path = "crates/cli" }
fs = { path = "crates/fs" }
java = { path = "crates/java" }
core = { path = "crates/core" }
pom = { path = "crates/pom" }
raven = { path = "crates/raven" }
#
# External crates
#
anyhow = "1.0"
bytesize = "2.3"
ron = "0.12"
const_format = "0.2.35"
hard-xml = "1.41"
lenient_semver = "0.4.2"
pathsub = "0.1.1"
ron = "0.12.0"
sha256 = "1.6"
subprocess = "0.2"
toml = "0.9"
subprocess = "1.0"
toml = "1.0"
[dependencies.clap]
version = "4.5"
features = ["cargo", "derive"]
derive_more = { version = "2.1", features = ["display", "from"] }
semver = { version = "1.0",features = ["serde"] }
serde = { version = "1.0",features = ["derive"] }
strum = { version = "0.27.2", features = ["derive"] }
[dependencies.semver]
version = "1.0"
features = ["serde"]
[workspace.lints.rust]
unsafe_code = "forbid"
[dependencies.serde]
version = "1.0"
features =["derive"]
[profile.dev]
incremental = true

View File

@@ -29,18 +29,35 @@ WSL *may* affect the behaviour of raven. Quirks to be worked out.
```bash
raven new demo
cd demo
raven run main.Main
raven run main
```
## Raven commands
```bash
Usage: raven <COMMAND>
Commands:
new Create a new raven project
init Create a new raven project in an existing directory
build Compile the current project
run Run the current project
test !!! BORKED !!! Run the tests
clean Remove the target directory and caching
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
## Future plans
- [ ] Fix `raven test`. Totally borked.
- [ ] Make `nest run` fall back to an entry point specified in Nest.toml, when
- [ ] Make `raven run` fall back to an entry point specified in Nest.toml, when
none provided.
- [ ] Fix project structure, eliminate inter-dependencies.
- [ ] Separate out `java.rs` into a dedicated wrapper library.
- [ ] Possibly add support for pulling remote packages via `nest add`. This
- [ ] Possibly add support for pulling remote packages via `raven add`. This
will be implemented should there arise a need.
- [ ] Wrap errors properly, instead of hap-hazardly using `anyhow`
- [ ] Maybe proper logging?
- [ ] Fix using hashes to avoid unnesscesary recompilation.

14
crates/cli/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "cli"
version = "0.2.1"
edition.workspace = true
license.workspace = true
description = "Raven's CLI"
repository.workspace = true
publish.workspace = true
[dependencies.clap]
version = "4.5"
features = ["cargo", "derive"]

152
crates/cli/src/lib.rs Normal file
View File

@@ -0,0 +1,152 @@
use std::sync::LazyLock;
use std::{fmt::Display, path::PathBuf};
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
pub static CLI_ARGS: LazyLock<CliArgs> = LazyLock::new(|| CliArgs::parse());
#[derive(Debug, Parser)]
#[clap(name = "Raven", version = "0.2.0")]
pub struct CliArgs {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Command {
/// Create a new raven project
New {
#[clap(flatten)]
type_: ProjectFlag,
project_name: String,
},
/// Create a new raven project in an existing directory
Init,
/// Compile the current project
Build,
/// Run the current project
Run {
#[clap(value_hint = clap::ValueHint::DirPath)]
entry_point: Option<PathBuf>,
#[clap(flatten)]
assertions: Assertions,
},
/// !!! BORKED !!! Run the tests
Test {
#[clap(flatten)]
assertions: Assertions,
},
/// Remove the target directory and caching
Clean,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Args)]
pub struct Assertions {
/// Disable assertions.
#[arg(short, long = "no-assert", action = ArgAction::SetFalse)]
assertions: bool,
}
impl Into<bool> for &Assertions {
fn into(self) -> bool {
self.assertions
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Args)]
#[group(multiple = false)]
pub struct ProjectFlag {
#[arg(long, action = ArgAction::SetTrue)]
_nest: (),
#[arg(long, action = ArgAction::SetTrue)]
_package: (),
#[arg(
hide = true,
required = false,
short,
long,
default_value_ifs = [
("_nest", "true", "nest"),
("_package", "true", "package"),
("_nest", "false", "nest"),
],
)]
pub project_type: ProjectType,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum ProjectType {
#[default]
Nest,
Package,
}
impl Display for ProjectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Self::to_string(&self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn args_new_w_arg() {
let args: CliArgs = Parser::try_parse_from(["raven", "new", "demo"]).unwrap();
let type_ = match args.command {
Command::New { type_, .. } => type_,
_ => unreachable!(),
};
assert!(type_.project_type == ProjectType::Nest);
}
#[test]
fn args_new_nest() {
let args: CliArgs = Parser::try_parse_from(["raven", "new", "--nest", "demo"]).unwrap();
let type_ = match args.command {
Command::New { type_, .. } => type_,
_ => unreachable!(),
};
assert!(type_.project_type == ProjectType::Nest);
}
#[test]
fn args_new_package() {
let args: CliArgs = Parser::try_parse_from(["raven", "new", "--package", "demo"]).unwrap();
let type_ = match args.command {
Command::New { type_, .. } => type_,
_ => unreachable!(),
};
assert!(type_.project_type == ProjectType::Package);
}
#[test]
fn args_run_assert() {
let args: CliArgs = Parser::try_parse_from(["raven", "run", "Main"]).unwrap();
let assertions: bool = match args.command {
Command::Run { assertions, .. } => assertions.assertions,
_ => unreachable!(),
};
assert!(assertions == true)
}
#[test]
fn args_run_no_assert() {
let args: CliArgs =
Parser::try_parse_from(["raven", "run", "--no-assert", "Main"]).unwrap();
let assertions: bool = match args.command {
Command::Run { assertions, .. } => assertions.assertions,
_ => unreachable!(),
};
assert!(assertions == false)
}
}

28
crates/core/Cargo.toml Normal file
View File

@@ -0,0 +1,28 @@
[package]
name = "core"
version = "0.1.4"
edition.workspace = true
license.workspace = true
description = "Raven's core, including metadata tooling and resources"
repository.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
fs.workspace = true
java.workspace = true
pathsub.workspace = true
semver.workspace = true
serde.workspace = true
sha256.workspace = true
subprocess.workspace = true
toml.workspace = true
[dependencies.anyhow]
workspace = true
optional = true
[features]
into_anyhow = ["dep:anyhow"]

View File

@@ -1,5 +1,3 @@
package main;
public class Main {
public static void main(String[] args) {

View File

@@ -1,12 +1,10 @@
package test;
public class Main {
public class MainTest {
public static void main(String[] args) {
testAdd();
}
public static void testAdd() {
assert main.Main.add(2, 2) == 4;
assert Main.add(2, 2) == 4;
}
}

74
crates/core/src/class.rs Normal file
View File

@@ -0,0 +1,74 @@
use std::path::{Path, PathBuf};
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
/// [`Class`] represents a source file. It is a handler.
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Class {
/// Path relative to PACKAGE/java, without file extension.
pub path: PathBuf,
pub checksum: String,
}
impl Class {
/// Creates a new [`Class`].
///
/// # Error
///
/// * [`Error::MismatchedPackage`] if the provided `file_path` is absolute, and cannot be
/// subtracted to just its relative path within its parent package.
/// * [`Error::Io`] if the source file cannot be loaded and digested.
pub fn new<P: AsRef<Path>>(package_src_root: P, file_path: P) -> Result<Self> {
let mut file_path = file_path.as_ref().to_path_buf();
if file_path.is_absolute() {
file_path = pathsub::sub_paths(file_path.as_path(), package_src_root.as_ref())
.ok_or(Error::MismatchedPackage)?;
}
Ok(Self {
path: dbg!(file_path.with_extension("")),
checksum: sha256::try_digest(package_src_root.as_ref().join(file_path))?,
})
}
/// Returns a boolean representing if the class is up to date.
///
/// # Criteria
///
/// * The class path is local.
/// * The class has been compiled to `target/`
/// * The class' source file has not been updated.
///
/// # Errors
///
/// * [`Error::Io`]
pub fn is_updated<P: AsRef<Path>>(&self, class_path: P) -> Result<bool> {
// If the path is local and that file has not been updated.
Ok(class_path
.as_ref()
.join(self.path.as_path())
.with_extension(JAVA_EXT_CLASS)
.exists()
&& self.checksum == sha256::try_digest(self.path.clone())?)
}
/// Updates the class' checksum.
///
/// Only call this if you know that the class has been compiled successfully.
///
/// # Errors
///
/// * [`Error::Io`] if the source file cannot be loaded and digested.
pub fn update<P: AsRef<Path>>(&mut self, package_src_root: P) -> Result<()> {
self.checksum = sha256::try_digest(dbg!(
package_src_root
.as_ref()
.join(self.path.as_path())
.with_extension(JAVA_EXT_SOURCE),
))?;
Ok(())
}
}

View File

@@ -0,0 +1,51 @@
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::Result;
use crate::package::PackageHandler;
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Dependency {
name: String,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
version: Option<Version>,
pub checksum: String,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
source: Option<String>, // Path / URL
}
impl Dependency {
/// Returns a path to the dependency in local storage
/// if there is one.
pub fn local_path(&self) -> String {
if self.source.as_ref().is_some_and(|path| !is_url(path)) {
return self.source.clone().unwrap();
}
// TODO: Convert from reverse domain name to path.
return self.name.clone();
}
pub fn is_updated(&self) -> Result<bool> {
// If the path is local and that file has not been updated.
Ok(self.source.as_ref().is_some_and(|path| is_url(path))
&& self.checksum == sha256::try_digest(self.source.as_ref().unwrap())?)
}
}
impl From<PackageHandler> for Dependency {
fn from(value: PackageHandler) -> Self {
Dependency {
name: value.name(),
version: Some(value.version()),
checksum: String::new(),
source: None,
}
}
}
/// TODO: This is just a placeholder at present.
fn is_url<S: ToString>(_path: S) -> bool {
return false;
}

30
crates/core/src/error.rs Normal file
View File

@@ -0,0 +1,30 @@
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
/// Attempted to replace a value in a hash collection,
/// but there was no prime present when one was expected.
AbsentPrimeHashingError,
#[from]
Io(std::io::Error),
#[from]
Java(java::Error),
MissingFileName,
MismatchedPackage,
#[from]
TomlDeserialize(toml::de::Error),
#[from]
TomlSerialize(toml::ser::Error),
UndefinedEntryPoint,
UnknownPackage,
}

11
crates/core/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod class;
pub mod dependency;
pub mod error;
pub mod meta;
pub mod nest;
pub mod package;
pub mod prelude;
pub mod prey;
pub mod workspace;
pub use error::{Error, Result};

40
crates/core/src/meta.rs Normal file
View File

@@ -0,0 +1,40 @@
use std::path::PathBuf;
use semver::Version;
use serde::{Deserialize, Serialize};
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Meta {
pub name: String,
pub version: Version,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub authors: Option<Vec<String>>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub repository: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license: Option<String>,
#[serde(skip_serializing_if = "<Option<_>>::is_none")]
pub license_file: Option<PathBuf>,
}
impl Meta {
pub fn new<S: ToString>(name: S) -> Self {
let mut meta = Self::default();
meta.name = name.to_string();
meta
}
}
impl Default for Meta {
fn default() -> Self {
Meta {
name: String::from("Main"),
version: Version::new(0, 1, 0),
authors: None,
repository: None,
license: None,
license_file: None,
}
}
}

110
crates/core/src/nest.rs Normal file
View File

@@ -0,0 +1,110 @@
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::dependency::Dependency;
use crate::meta::Meta;
use crate::workspace::Workspace;
pub const F_NEST_TOML: &str = "Nest.toml";
pub const F_NEST_LOCK: &str = "Nest.lock";
/// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct Nest {
workspace: Workspace,
meta: Meta,
dependencies: HashSet<Dependency>,
}
impl Nest {
pub fn new<S: ToString>(name: S) -> Self {
Self {
workspace: Workspace::default(),
meta: Meta::new(name),
dependencies: Default::default(),
}
}
pub fn write<P: AsRef<Path>>(&self, project_root: P) -> crate::Result<()> {
let mut project_root = project_root.as_ref().to_path_buf();
if project_root.is_dir() {
project_root = project_root.join(F_NEST_TOML);
}
Ok(OpenOptions::new()
.write(true)
.create(true)
.open(project_root)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?)
}
pub fn default_package(&self) -> PathBuf {
self.workspace.default_package.clone()
}
pub fn name(&self) -> String {
self.meta.name.clone()
}
pub fn set_default_package<P: AsRef<Path>>(&mut self, package: P) {
self.workspace.default_package = package.as_ref().to_path_buf();
}
}
impl TryFrom<PathBuf> for Nest {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for Nest {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}
/// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct NestLock {
pub dependencies: Vec<Dependency>,
}
impl NestLock {
pub fn write<P: AsRef<Path>>(&self, path: P) -> crate::Result<()> {
Ok(OpenOptions::new()
.write(true)
.create(true)
.open(path)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?)
}
}
impl TryFrom<PathBuf> for NestLock {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for NestLock {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}

View File

@@ -0,0 +1,85 @@
use std::hash::Hash;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::class::Class;
use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock};
pub const DIR_JAVA: &str = "java/";
/// Hashing is only based off the Prey.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageHandler {
prey: Prey,
prey_lock: PreyLock,
/// Path relative to WORKSPACE/src
package_root: PathBuf,
target_dir: PathBuf,
}
impl PackageHandler {
pub fn new<P: AsRef<Path>>(src_dir: P, package_root: P, target_dir: P) -> crate::Result<Self> {
let package_root = src_dir.as_ref().join(package_root.as_ref());
let prey_lock = if let Ok(prey_lock) = PreyLock::try_from(package_root.join(F_PREY_LOCK)) {
prey_lock
} else {
PreyLock::new(package_root.clone(), package_root.join(DIR_JAVA))?
};
Ok(Self {
prey: Prey::try_from(package_root.join(F_PREY_TOML))?,
prey_lock,
package_root,
target_dir: target_dir.as_ref().to_path_buf(),
})
}
pub fn entry_point(&self) -> PathBuf {
self.prey.entry_point()
}
pub fn name(&self) -> String {
self.prey.name()
}
pub fn version(&self) -> semver::Version {
self.prey.version()
}
pub fn prey_lock(&mut self) -> &mut PreyLock {
&mut self.prey_lock
}
pub fn get_outdated<P: AsRef<Path>>(&self, class_path: P) -> Vec<Class> {
self.prey_lock
.clone()
.classes()
.iter()
.filter(|class| {
class
.is_updated(class_path.as_ref())
.is_ok_and(|b| b == false)
})
.cloned()
.collect()
}
pub fn write_lock(&self) -> crate::Result<()> {
self.prey_lock.write(self.package_root.as_path())
}
}
impl Hash for PackageHandler {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.prey.hash(state);
}
}
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Package {
pub entry_point: PathBuf,
}
//impl Into<Dependency> for Package {}

View File

@@ -0,0 +1,9 @@
#![allow(unused_imports)]
pub use crate::class::Class;
pub use crate::dependency::Dependency;
pub use crate::meta::Meta;
pub use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock};
pub use crate::package::{Package, PackageHandler};
pub use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey, PreyLock};
pub use crate::workspace::{Workspace, WorkspaceHandler};

141
crates/core/src/prey.rs Normal file
View File

@@ -0,0 +1,141 @@
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use fs::expand_files;
use serde::{Deserialize, Serialize};
use crate::class::Class;
use crate::meta::Meta;
use crate::package::Package;
pub const F_PREY_TOML: &str = "Prey.toml";
pub const F_PREY_LOCK: &str = "Prey.lock";
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)]
pub struct Prey {
package: Package,
meta: Meta,
}
impl Prey {
pub fn new<S: ToString>(name: S) -> Self {
Self {
package: Package {
entry_point: PathBuf::from(""),
},
meta: Meta::new(name),
}
}
pub fn with_entry_point<P: AsRef<Path>>(&mut self, entry_point: P) -> &mut Self {
self.package.entry_point = entry_point.as_ref().to_path_buf();
self
}
pub fn entry_point(&self) -> PathBuf {
self.package.entry_point.clone()
}
pub fn name(&self) -> String {
self.meta.name.clone()
}
pub fn version(&self) -> semver::Version {
self.meta.version.clone()
}
}
impl TryFrom<PathBuf> for Prey {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for Prey {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}
/// Data struct
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct PreyLock {
classes: HashSet<Class>,
}
impl PreyLock {
pub fn new<P: AsRef<Path>>(package_root: P, package_src_root: P) -> crate::Result<Self> {
Ok(Self::from_paths(
expand_files(package_root)?,
package_src_root,
))
}
pub fn from_paths<P: AsRef<Path>>(paths: Vec<PathBuf>, package_src_root: P) -> Self {
let mut lock = Self::default();
lock.classes = paths
.iter()
.filter_map(|f| {
let dep = Class::new(package_src_root.as_ref().to_path_buf(), f.to_owned());
if dep.is_ok() {
Some(dep.unwrap())
} else {
None
}
})
.collect();
lock
}
pub fn write<P: AsRef<Path>>(&self, package_root: P) -> crate::Result<()> {
let mut package_root = package_root.as_ref().to_path_buf();
if package_root.is_dir() {
package_root = package_root.join(F_PREY_LOCK);
}
Ok(OpenOptions::new()
.write(true)
.create(true)
.open(package_root)?
.write_all(toml::to_string_pretty(&self)?.as_bytes())?)
}
pub fn with_class(&mut self, class: Class) -> &mut Self {
self.classes.insert(class);
self
}
pub fn classes(&mut self) -> &mut HashSet<Class> {
&mut self.classes
}
}
/// Load the PreyLock from Prey.lock file.
impl TryFrom<PathBuf> for PreyLock {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let f = OpenOptions::new().read(true).open(value)?;
Self::try_from(f)
}
}
impl TryFrom<File> for PreyLock {
type Error = crate::Error;
fn try_from(mut value: File) -> Result<Self, Self::Error> {
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(toml::from_str(buf.as_str())?)
}
}

View File

@@ -0,0 +1,461 @@
use std::collections::HashMap;
use std::fs::{OpenOptions, read_dir};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::time::Duration;
use fs::expand_files;
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use serde::{Deserialize, Serialize};
use subprocess::Exec;
use crate::nest::{F_NEST_LOCK, F_NEST_TOML, Nest, NestLock};
use crate::package::PackageHandler;
use crate::prey::{F_PREY_LOCK, F_PREY_TOML, Prey};
use crate::{Error, package};
/// Represents a raven workspace.
///
/// [`WorkspaceHandler`] manages project compilation, cache writing,
/// and tracking of all packages that make up the workspace.
#[derive(Debug)]
pub struct WorkspaceHandler {
nest: Nest,
nest_lock: Option<NestLock>,
project_root: PathBuf,
packages: HashMap<PathBuf, PackageHandler>,
}
impl WorkspaceHandler {
const DIR_SRC: &str = "src/";
const DIR_TARGET: &str = "target/";
/// Creates a new [`WorkspaceHandler`] instance at the provided path.
///
/// # Errors
///
/// * [`Error::Io`] if the `project_root` cannot be canonicalized.
/// * [`Error::MissingFileName`] if a name for the workspace cannot be derived
/// from the `project_root`
pub fn new<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> {
let project_root = project_root.as_ref().canonicalize()?;
Ok(Self {
nest: Nest::new(
project_root
.file_name()
.ok_or(Error::MissingFileName)?
.display(),
),
nest_lock: None,
packages: HashMap::new(),
project_root,
})
}
/// Loads an existing workspace from the provided path.
///
/// # Errors
///
/// * [`Error::Io`] for any number of IO-related errors.
/// Unwrap the [`std::io::Error`] for further information.
/// * [`Error::MissingFileName`] if a name for the workspace cannot be derived
/// from the `project_root`
pub fn load<P: AsRef<Path>>(project_root: P) -> crate::Result<Self> {
let project_root = project_root.as_ref().canonicalize()?;
let mut workspace_manager = Self {
nest: Nest::try_from(project_root.join(F_NEST_TOML))?,
nest_lock: NestLock::try_from(project_root.join(F_NEST_LOCK)).ok(),
packages: HashMap::new(),
project_root,
};
workspace_manager.discover_packages()?;
Ok(workspace_manager)
}
/// Writes all lock files (Nest.toml, src/**/Prey.toml) to the disk.
pub fn write_locks(&self) -> crate::Result<()> {
if let Option::Some(lock) = self.nest_lock.clone() {
lock.write(self.project_root.join(F_NEST_LOCK))?;
}
for handler in self.packages.values() {
handler.write_lock()?;
}
Ok(())
}
/// Initializes a new workspace.
///
/// # Behaviour
///
/// Init will avoid overwriting or adding an example project if raven is being integrated into
/// an existing workspace codebase.
///
/// Init will always write:
///
/// * Nest.toml
/// * .java-version
/// * Attempt to append to .gitignore
///
/// If this is an empty workspace, init will include:
///
/// * Calling `git init`
/// * Writing an example project.
/// * Automatic package discovery.
///
/// # Errors
pub fn init(&mut self) -> crate::Result<&mut Self> {
// ORDER MATTERS.
let is_empty = read_dir(self.project_root.as_path()).is_ok_and(|tree| tree.count() == 0);
// Make config file.
self.write_nest()?;
// Make .java-version
self.write_java_version()?;
if is_empty {
self.write_example_project()?;
self.discover_packages()?;
Exec::cmd("git")
.arg("init")
.arg(".")
.start()?
.wait_timeout(Duration::from_secs(10))?;
}
// Append to .gitignore
if let Result::Ok(mut f) = OpenOptions::new()
.append(true)
.create(true)
.read(true)
.open(".gitignore")
{
let mut buf = String::new();
f.read_to_string(&mut buf)?;
for ignored in [
"# Automatically added by Raven".to_string(),
Self::DIR_TARGET.to_string(),
format!("*.{}", JAVA_EXT_CLASS),
] {
if !buf.contains(&ignored) {
f.write(format!("{}\n", ignored).as_bytes())?;
}
}
}
Ok(self)
}
/// This is a naive build; it attempts to build every source file individually.
///
/// It calls [`PackageHandler::get_outdated()`] on all known packages to determine
/// compile targets.
///
/// It will then attempt to write all locks to update the caches and avoid attempting to
/// needlessly recompile targets.
///
/// # Errors
///
/// * [`Error::AbsentPrimeError`]
/// * [`Error::Io`]
pub fn build(&mut self) -> crate::Result<&mut Self> {
let compiler = java::compiler::CompilerBuilder::new()
.class_path(Self::DIR_TARGET)
.destination(Self::DIR_TARGET)
.build();
// No unintentional deep copying anywhere, right?
// Probably not. We'll see.
for handler in self.packages.values_mut() {
let package_src_root = self
.project_root
.join(Self::DIR_SRC)
.join(handler.name())
.join(package::DIR_JAVA);
let targets = handler.get_outdated(self.project_root.join(Self::DIR_TARGET));
for mut target in targets {
dbg!(&target);
if let Ok(true) = compiler
.clone()
.compile(package_src_root.join(target.path.as_path()))
{
target.update(package_src_root.as_path())?;
handler
.prey_lock()
.classes()
.replace(target)
.ok_or(Error::AbsentPrimeHashingError)?;
}
}
}
self.write_locks()?;
Ok(self)
}
/// Attempts to call the JVM to run, in order of precedence:
///
/// 1. Provided entry point which includes a specific source file.
/// 2. Provided entry point to a package, using that package's default entry point
/// defined in it's `Prey.toml`
/// 3. The workspace's default package defined in `Nest.toml`, and that package's default
/// entry point as defined in it's `Prey.toml`.
///
/// # Errors
///
/// * [`Error::UnknownPackage`] if the package cannot be found.
/// * [`Error::UndefinedEntryPoint`] if no fallback entry point can be found.
/// * [`Error::Io`]
/// * [`Error::Java`]
pub fn run<P: AsRef<Path>>(
&mut self,
entry_point: Option<P>,
assertions: bool,
) -> crate::Result<&mut Self> {
let mut entry_point = if entry_point.is_none() {
self.nest.default_package()
} else {
entry_point.unwrap().as_ref().to_path_buf()
};
if !entry_point.is_file() {
// Use is_file to skip messing with pathing for src/
// If target is not a file (explicit entry point), check if it's a known package
// and use that's package's default entry point.
entry_point = self
.packages
.get(&entry_point)
.ok_or(Error::UnknownPackage)?
.entry_point();
}
if entry_point.file_name().is_none() {
return Err(Error::UndefinedEntryPoint);
}
java::runtime::JVMBuilder::new(Self::DIR_TARGET)
.assertions(assertions)
.monitor(true)
.build()
.run(entry_point)?;
Ok(self)
}
/// Cleans the workspace of it's caches:
///
/// * `target/`
/// * Nest.lock
/// * Prey.lock
pub fn clean(&mut self) -> crate::Result<&mut Self> {
// Clear Nest.lock
if let Err(err) = std::fs::remove_file(self.project_root.join(F_NEST_LOCK)) {
if err.kind() != std::io::ErrorKind::NotFound {
return Err(Error::from(err));
}
}
// Clear src/**/Prey.lock
for handler in self.packages.values() {
if let Err(err) = std::fs::remove_file(
self.project_root
.join(Self::DIR_SRC)
.join(handler.name())
.join(F_PREY_LOCK),
) {
if err.kind() != std::io::ErrorKind::NotFound {
return Err(Error::from(err));
}
}
}
// Clear target/
let _ = std::fs::remove_dir_all(Self::DIR_TARGET);
Ok(self)
}
/// Scan for newly created packages, wrapping them in [`PackageHandler`]s
/// and adding them to the [`WorkspaceHandler`]'s package map.
///
/// Packages are identified by their `Prey.toml` manifests.
///
/// # Errors
///
/// * [`Error::Io`]
fn discover_packages(&mut self) -> crate::Result<()> {
// Scan the src/ directory for entries,
// filter out the files,
// then construct PackageManagers for each package
// Promote *not* using reverse domain name tree structures
// by improving the speed of package discovery by using read_dir
// and checking for an immediate Prey.toml before expanding the
// whole subtree.
//
// Yes, I know this looks like shit.
// That's because it is.
for file in read_dir(self.project_root.join(Self::DIR_SRC))?
// Get directories
.filter_map(|entry| {
if entry.as_ref().is_ok_and(|entry| entry.path().is_dir()) {
Some(entry.unwrap().path())
} else {
None
}
})
// Get Prey.toml files
.filter_map(|dir| {
Some(if dir.join(F_PREY_TOML).exists() {
vec![dir.join(F_PREY_TOML)]
} else {
expand_files(dir)
.ok()?
.iter()
.filter_map(|file| {
if file.ends_with(PathBuf::from(F_PREY_TOML)) {
Some(file.to_owned())
} else {
None
}
})
.collect()
})
})
.flatten()
{
let package_root = pathsub::sub_paths(
file.as_path(),
self.project_root.join(Self::DIR_SRC).as_path(),
)
.unwrap()
.parent()
.unwrap()
.to_path_buf();
self.packages.insert(
package_root.to_path_buf(),
PackageHandler::new(
self.project_root.join(Self::DIR_SRC),
package_root,
self.project_root.join(Self::DIR_TARGET),
)?,
);
}
Ok(())
}
/// Writes the [`WorkspaceHandler`]'s `Nest.toml` to disk.
fn write_nest(&self) -> crate::Result<()> {
Ok(self.nest.write(self.project_root.clone())?)
}
/// Writes a `.java-version` file for `jenv` to the disk with an automatically parsed
/// java version from [`java::get_javac_version()`].
///
/// # Errors
///
/// * [`Error::Io`]
/// * [`Error::Java`]
fn write_java_version(&self) -> crate::Result<()> {
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(java::F_JAVA_VERSION)
{
f.write_all(format!("{}\n", java::get_javac_version()?.major.to_string()).as_bytes())?;
}
Ok(())
}
/// Writes an example project tree to disk.
fn write_dir_tree(&self) -> std::io::Result<()> {
for dir in [
format!("{}main/java", Self::DIR_SRC),
format!("{}test/java", Self::DIR_SRC),
Self::DIR_TARGET.to_string(),
] {
std::fs::create_dir_all(std::env::current_dir()?.join(dir))?;
}
Ok(())
}
/// Writes an example project to disk.
fn write_example_project(&self) -> crate::Result<()> {
let main: PathBuf = PathBuf::from(Self::DIR_SRC).join("main/");
let test: PathBuf = PathBuf::from(Self::DIR_SRC).join("test/");
// Make src/, target/, test/
self.write_dir_tree()?;
// Make src/main/Prey.toml
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(main.join(F_PREY_TOML))
{
f.write_all(
toml::to_string_pretty(&Prey::new("main").with_entry_point("Main"))?.as_bytes(),
)?;
}
// Make src/main/Main.java
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(main.join("java/Main").with_extension(JAVA_EXT_SOURCE))
{
f.write_all(include_bytes!("../assets/Main.java"))?;
}
// Make src/test/Prey.toml
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(test.join(F_PREY_TOML))
{
f.write_all(
toml::to_string_pretty(&Prey::new("test").with_entry_point("MainTest"))?.as_bytes(),
)?;
}
// Make src/test/MainTest.java
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(test.join("java/MainTest").with_extension(JAVA_EXT_SOURCE))
{
f.write_all(include_bytes!("../assets/MainTest.java"))?;
}
Ok(())
}
}
/// Data struct
#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Workspace {
pub default_package: PathBuf,
}
impl Default for Workspace {
fn default() -> Self {
Workspace {
default_package: PathBuf::from("main"),
}
}
}

13
crates/fs/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "fs"
version = "0.1.0"
edition.workspace = true
license.workspace = true
description = "Raven's FS utilities"
repository.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true

View File

@@ -1,6 +1,9 @@
use std::path::{Path, PathBuf};
pub fn expand_files<P: AsRef<Path>>(path: P) -> anyhow::Result<Vec<PathBuf>> {
pub const EXT_TOML: &str = ".toml";
pub const EXT_LOCK: &str = ".lock";
pub fn expand_files<P: AsRef<Path>>(path: P) -> std::io::Result<Vec<PathBuf>> {
let path = path.as_ref();
if path.is_file() {

20
crates/java/Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
# May want to find a better name, more reflective of the JDK part
# than the entire Java language.
[package]
name = "java"
version = "0.2.2"
edition.workspace = true
license.workspace = true
description = "Tools for interfacing with the Java Development Kit"
repository.workspace = true
publish.workspace = true
[dependencies]
bytesize.workspace = true
derive_more.workspace = true
fs.workspace = true
semver.workspace = true
subprocess.workspace = true

View File

@@ -0,0 +1,96 @@
use std::fmt::Display;
use std::path::{Path, PathBuf};
use subprocess::Exec;
use subprocess::Redirection;
use crate::JAVA_BIN_COMPILER;
use crate::JAVA_EXT_SOURCE;
use crate::Result;
#[derive(Debug, Default, Clone)]
pub struct CompilerBuilder {
class_path: Option<PathBuf>,
destination: Option<PathBuf>,
}
impl CompilerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn class_path<S: AsRef<Path>>(&mut self, class_path: S) -> &mut Self {
self.class_path = Some(class_path.as_ref().to_path_buf());
self
}
pub fn destination<S: AsRef<Path>>(&mut self, destination: S) -> &mut Self {
self.destination = Some(destination.as_ref().to_path_buf());
self
}
pub fn build(&self) -> Compiler {
let mut flags = vec![];
if let Option::Some(path) = self.destination.to_owned() {
flags.push(CompilerFlag::Destination { path });
}
if let Option::Some(path) = self.class_path.to_owned() {
flags.push(CompilerFlag::Classpath { path });
}
Compiler { flags }
}
}
#[derive(Debug, Clone)]
pub struct Compiler {
flags: Vec<CompilerFlag>,
}
impl Compiler {
pub fn compile<P: AsRef<Path>>(self, path: P) -> Result<bool> {
Ok(Exec::cmd(JAVA_BIN_COMPILER)
.args(
self.flags
.clone()
.into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)),
)
.arg(path.as_ref().with_extension(JAVA_EXT_SOURCE))
.stdout(Redirection::Pipe)
.detached()
.start()?
.wait()?
.success())
}
}
#[derive(Debug, Clone)]
pub enum CompilerFlag {
Classpath { path: PathBuf },
Destination { path: PathBuf },
Version,
}
impl Into<Vec<String>> for CompilerFlag {
fn into(self) -> Vec<String> {
match self {
Self::Classpath { path } => vec!["-classpath".to_string(), path.display().to_string()],
Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()],
Self::Version => vec!["--version".to_string()],
}
}
}
// TODO: Clean this up a bit?
impl Display for CompilerFlag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Classpath { path } => write!(f, "-classpath {}", path.display()),
Self::Destination { path } => write!(f, "-d {}", path.display()),
Self::Version => write!(f, "--version"),
}
}
}

16
crates/java/src/error.rs Normal file
View File

@@ -0,0 +1,16 @@
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
EmptyStdout,
#[from]
Io(std::io::Error),
NthOutOfBounds,
#[from]
Semver(semver::Error),
}

55
crates/java/src/lib.rs Normal file
View File

@@ -0,0 +1,55 @@
pub mod compiler;
pub mod error;
pub mod runtime;
use std::str::FromStr;
pub use error::{Error, Result};
use runtime::VMFlag;
use subprocess::Exec;
use crate::compiler::CompilerFlag;
pub const JAVA_BIN_VM: &str = "java";
pub const JAVA_BIN_COMPILER: &str = "javac";
pub const JAVA_EXT_SOURCE: &str = "java";
pub const JAVA_EXT_CLASS: &str = "class";
pub const F_JAVA_VERSION: &str = ".java-version";
/// Uses the javac binary to get information about the install compiler.
pub fn get_javac_version() -> Result<semver::Version> {
// Try to pull from javac first.
if let Ok(version) = semver::Version::from_str(
Exec::cmd(JAVA_BIN_COMPILER)
.arg(CompilerFlag::Version.to_string())
.capture()?
.stdout_str()
.replace("javac", "")
.trim(),
) {
return Ok(version);
}
Ok(semver::Version::from_str(
get_java_version_info()?
.split_ascii_whitespace()
.nth(1)
.ok_or(Error::NthOutOfBounds)?,
)?)
}
/// Calls the java binary, returning the complete stdout version information.
fn get_java_version_info() -> Result<String> {
/*
* $ java --version
* openjdk 21.0.9 2025-10-21
* OpenJDK Runtime Environment (build 21.0.9+10)
* OpenJDK 64-Bit Server VM (build 21.0.9+10, mixed mode, sharing)
*/
Ok(Exec::cmd(JAVA_BIN_VM)
.arg(VMFlag::Version.to_string())
.capture()?
.stdout_str())
}

141
crates/java/src/runtime.rs Normal file
View File

@@ -0,0 +1,141 @@
use std::fmt;
use std::path::{Path, PathBuf};
use crate::JAVA_BIN_VM;
use crate::Result;
use bytesize::ByteSize;
use subprocess::Exec;
#[derive(Debug, Default, Clone)]
pub struct JVMBuilder {
assertions: bool,
monitor: bool,
ram_min: Option<ByteSize>,
ram_max: Option<ByteSize>,
class_path: PathBuf,
}
impl JVMBuilder {
pub fn new<P: AsRef<Path>>(class_path: P) -> Self {
Self {
class_path: class_path.as_ref().to_path_buf(),
..Default::default()
}
}
pub fn assertions(&mut self, assertions: bool) -> &mut Self {
self.assertions = assertions;
self
}
pub fn ram_min(&mut self, ram_min: ByteSize) -> &mut Self {
self.ram_min = Some(ram_min);
self
}
pub fn ram_max(&mut self, ram_max: ByteSize) -> &mut Self {
self.ram_max = Some(ram_max);
self
}
/// Monitor stdout and stderr in raven's process.
pub fn monitor(&mut self, monitor: bool) -> &mut Self {
self.monitor = monitor;
self
}
pub fn build(&self) -> JVM {
let mut flags = vec![VMFlag::Classpath {
path: self.class_path.to_owned(),
}];
if self.assertions {
flags.push(VMFlag::EnableAssert);
}
if let Option::Some(size) = self.ram_min {
flags.push(VMFlag::HeapMin { size });
}
if let Option::Some(size) = self.ram_max {
flags.push(VMFlag::HeapMax { size });
}
JVM {
monitor: self.monitor,
flags,
}
}
}
pub struct JVM {
monitor: bool,
flags: Vec<VMFlag>,
}
impl JVM {
pub fn run<P: AsRef<Path>>(self, entry_point: P) -> Result<()> {
let capture = Exec::cmd(JAVA_BIN_VM)
.args(
self.flags
.clone()
.into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)),
)
.arg(entry_point.as_ref())
.capture()?;
if !self.monitor {
return Ok(());
}
let stdout = capture.stdout_str();
if stdout.len() > 0 {
println!("{stdout}");
}
let stderr = capture.stderr_str();
if stderr.len() > 0 {
eprintln!("{stderr}");
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum VMFlag {
Classpath { path: PathBuf },
EnableAssert,
HeapMax { size: ByteSize },
HeapMin { size: ByteSize },
Version,
}
impl Into<Vec<String>> for VMFlag {
fn into(self) -> Vec<String> {
self.to_string()
.split_ascii_whitespace()
.map(|s| s.to_string())
.collect()
}
}
// Currently being kept around because it's fine for the current branches,
// and is currently serving a to_string() method for the Java version fetch.
impl fmt::Display for VMFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"-{}",
match self {
Self::Classpath { path } => format!("classpath {}", path.display()),
Self::EnableAssert => String::from("ea"),
Self::HeapMax { size } => format!("Xmx{}", size.as_u64()),
Self::HeapMin { size } => format!("Xms{}", size.as_u64()),
Self::Version => String::from("-version"), // --version
}
)
}
}

17
crates/pom/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "pom"
version = "0.1.0"
edition.workspace = true
license.workspace = true
description = "Library for serializing and deserializing Maven's POM"
repository.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
hard-xml.workspace = true
serde.workspace = true
semver.workspace = true
strum.workspace = true

12
crates/pom/src/error.rs Normal file
View File

@@ -0,0 +1,12 @@
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
#[from]
Io(std::io::Error),
#[from]
Xml(hard_xml::XmlError),
}

25
crates/pom/src/lib.rs Normal file
View File

@@ -0,0 +1,25 @@
pub mod error;
pub mod xml;
pub use error::{Error, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct Pom {
model_version: semver::Version,
}
impl Pom {
pub fn is_empty(&self) -> bool {
self == &Self::default()
}
}
impl Default for Pom {
fn default() -> Self {
Self {
model_version: semver::Version::new(4, 0, 0),
}
}
}

118
crates/pom/src/xml.rs Normal file
View File

@@ -0,0 +1,118 @@
use std::{fs::File, io::Read};
use crate::Error;
use hard_xml::{XmlRead, XmlWrite};
use serde::{Deserialize, Serialize};
use strum::EnumString;
// start with the super POM idiot.
#[derive(Debug, Clone, Deserialize, Serialize, XmlRead, XmlWrite)]
#[xml(tag = "project")]
pub struct Project {
#[xml(attr = "xmlns")]
xmlns: String,
#[xml(attr = "xlmns:xsi")]
xmlns_xsi: String,
#[xml(attr = "xsi:schemaLocation")]
xsi_schema_location: String,
#[xml(flatten_text = "modelVersion")]
model_version: semver::Version,
#[xml(flatten_text = "groupId")]
group_id: String,
#[xml(flatten_text = "artifactId")]
artifact_id: String,
#[xml(flatten_text = "version")]
version: String,
#[xml(flatten_text = "packaging")]
packaging: String,
#[xml(child = "properties")]
properties: Properties,
#[xml(child = "dependencyManagement")]
dependency_management: DependencyManagement,
#[xml(child = "dependencies")]
dependencies: Vec<Dependency>,
}
#[derive(Debug, Clone, Deserialize, Serialize, XmlRead, XmlWrite)]
#[xml(tag = "properties")]
pub struct Properties {
#[xml(flatten_text = "maven.compiler.source")]
source: String,
#[xml(flatten_text = "maven.compiler.target")]
target: String,
// lwjgl.version
// lwjgl.natives
}
#[derive(Debug, Clone, Deserialize, Serialize, XmlRead, XmlWrite)]
#[xml(tag = "dependencyManagement")]
pub struct DependencyManagement {
#[xml(child = "dependencies")]
dependencies: Vec<Dependency>,
}
#[derive(Debug, Clone, Deserialize, Serialize, XmlRead, XmlWrite)]
#[xml(tag = "dependency")]
pub struct Dependency {
#[xml(flatten_text = "groupId")]
group_id: String,
#[xml(flatten_text = "artifactId")]
artifact_id: String,
#[xml(flatten_text = "version")]
version: semver::Version,
#[xml(flatten_text = "scope")]
scope: String,
#[xml(flatten_text = "type")]
type_: String,
#[xml(flatten_text = "optional")]
optional: bool,
#[xml(child = "exclusions")]
exclusions: Vec<Exclusion>,
}
#[derive(Debug, Clone, Deserialize, Serialize, XmlRead, XmlWrite)]
#[xml(tag = "exclusion")]
pub struct Exclusion {
#[xml(flatten_text = "groupId")]
group_id: String,
#[xml(flatten_text = "artifactId")]
artifact_id: String,
}
#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, EnumString)]
#[strum(serialize_all = "camelCase")]
pub enum Packaging {
#[default]
Pom,
Jar,
}
impl TryFrom<File> for Project {
type Error = Error;
fn try_from(value: File) -> Result<Self, Self::Error> {
let mut value = value;
let mut buf = String::new();
value.read_to_string(&mut buf)?;
Ok(Project::from_str(&buf)?)
}
}

671
crates/raven/Cargo.lock generated Normal file
View File

@@ -0,0 +1,671 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
dependencies = [
"serde_core",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "bytesize"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "clap_lex"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "const_format"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hard-xml"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b07b8ba970e18a03dbb79f6786b6e4d6f198a0ac839aa5182017001bb8dee17"
dependencies = [
"hard-xml-derive",
"jetscii",
"lazy_static",
"memchr",
"xmlparser",
]
[[package]]
name = "hard-xml-derive"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c43e7c3212bd992c11b6b9796563388170950521ae8487f5cdf6f6e792f1c8"
dependencies = [
"bitflags",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "jetscii"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lenient_semver"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de8de3f4f3754c280ce1c8c42ed8dd26a9c8385c2e5ad4ec5a77e774cea9c1ec"
dependencies = [
"lenient_semver_parser",
"semver",
]
[[package]]
name = "lenient_semver_parser"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f650c1d024ddc26b4bb79c3076b30030f2cf2b18292af698c81f7337a64d7d6"
dependencies = [
"lenient_semver_version_builder",
"semver",
]
[[package]]
name = "lenient_semver_version_builder"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9049f8ff49f75b946f95557148e70230499c8a642bf2d6528246afc7d0282d17"
dependencies = [
"semver",
]
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "pathsub"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dadd38133bcbe43264410412c48614bab4ef899f0792ffc4530dc19ec000a970"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "raven"
version = "0.1.0"
dependencies = [
"anyhow",
"bytesize",
"clap",
"const_format",
"hard-xml",
"lenient_semver",
"pathsub",
"ron",
"semver",
"serde",
"sha256",
"strum",
"subprocess",
"toml",
]
[[package]]
name = "ron"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
dependencies = [
"bitflags",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "serde_spanned"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha256"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6"
dependencies = [
"async-trait",
"bytes",
"hex",
"sha2",
"tokio",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "subprocess"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f75238edb5be30a9ea3035b945eb9c319dde80e879411cdc9a8978e1ac822960"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"pin-project-lite",
]
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
[[package]]
name = "xmlparser"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"

21
crates/raven/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "raven"
version = "0.2.0"
edition.workspace = true
license.workspace = true
description = "A simple build tool for Java"
keywords = ["java", "tool"]
categories = ["development-tools::build-utils"]
repository.workspace = true
publish.workspace = true
[dependencies]
anyhow.workspace = true
bytesize.workspace = true
cli.workspace = true
core.workspace = true
fs.workspace = true
java.workspace = true
toml.workspace = true

53
crates/raven/src/main.rs Normal file
View File

@@ -0,0 +1,53 @@
use core::prelude::WorkspaceHandler;
use anyhow::anyhow;
use cli::{CLI_ARGS, Command};
fn main() -> anyhow::Result<()> {
// type_ is yet unused.
// `raven new` requires special behaviour.
// This will call the "new" part of the command,
// changing the cwd, before calling WorkspaceHandler.init()
// on the actual workspace directory.
if let Command::New { project_name, .. } = &CLI_ARGS.command {
new(project_name.to_owned())?;
}
let project_root = std::env::current_dir()?;
let mut wh: WorkspaceHandler = match &CLI_ARGS.command {
Command::New { .. } | Command::Init => WorkspaceHandler::new(project_root),
_ => WorkspaceHandler::load(project_root),
}
.map_err(|err| anyhow!(err))?;
match &CLI_ARGS.command {
Command::New { .. } | Command::Init => wh.init(),
Command::Build => wh.build(),
Command::Run {
entry_point,
assertions,
} => wh
.build()
.map_err(|err| anyhow!(err))?
.run(entry_point.to_owned(), assertions.into()),
Command::Clean => wh.clean(),
Command::Test { .. } => unimplemented!(),
}
.map_err(|err| anyhow!(err))?;
Ok(())
}
/// Creates a new project directory and enters it.
fn new(project_name: String) -> anyhow::Result<()> {
// Because this messes around with the CWD, this should live external to any
// instance of a `WorkspaceHandler`.
let cwd = std::env::current_dir()?.join(project_name);
std::fs::create_dir(&cwd)?;
std::env::set_current_dir(&cwd)?;
Ok(())
}

View File

@@ -1,61 +0,0 @@
use std::sync::LazyLock;
use clap::{ArgAction, Parser, Subcommand};
pub static CONFIG: LazyLock<Args> = LazyLock::new(|| Args::parse());
#[derive(Debug, Parser)]
#[clap(author, version)]
#[command(help_template = "{author-section}\n{usage-heading} {usage}\n\n{all-args}")]
pub struct Args {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
/// Create a new raven project
New { project_name: String },
/// Create a new raven project in an existing directory
Init,
/// Compile the current project
Build,
/// Run the current project
#[command(arg_required_else_help = true)]
Run {
#[arg(required = true)]
entry_point: String,
#[clap(flatten)]
assertions: Assertions,
},
/// !!! BORKED !!! Run the tests
Test {
#[clap(flatten)]
assertions: Assertions,
},
/// Remove the target directory and caching
Clean,
}
impl Command {
pub fn depends_on_nest(&self) -> bool {
match self {
Self::Init | Self::New { .. } => false,
_ => true,
}
}
}
#[derive(Debug, clap::Args)]
pub struct Assertions {
/// Disable assertions.
#[arg(short, long = "no-assert", action = ArgAction::SetFalse)]
disable_assert: bool,
}
impl Into<bool> for &Assertions {
fn into(self) -> bool {
!self.disable_assert
}
}

View File

@@ -1,41 +0,0 @@
use std::io;
use std::path::Path;
use std::str::FromStr;
use std::sync::LazyLock;
use crate::java::{JAVA_VM, VMFlag};
use anyhow::Context;
static JAVA_VERSION: LazyLock<String> = LazyLock::new(|| {
crate::io::run_process(&[JAVA_VM, VMFlag::Version.to_string().as_str()])
.expect("Failed to call java")
.0
.expect("Failed to read java version from stdout")
});
pub fn get_javac_ver() -> anyhow::Result<semver::Version> {
/*
* $ java --version
* openjdk 21.0.9 2025-10-21
* OpenJDK Runtime Environment (build 21.0.9+10)
* OpenJDK 64-Bit Server VM (build 21.0.9+10, mixed mode, sharing)
*/
semver::Version::from_str(
JAVA_VERSION
.lines()
.nth(0)
.context("stdout from Java is all f-cked up")?
.split_ascii_whitespace()
.nth(1)
.context("Java version position is all f-cked up")?,
)
.context("Failed to produce a version struct from string")
}
pub fn set_cwd<P: AsRef<Path>>(path: P) -> io::Result<()> {
let mut cwd = crate::PROJECT_ROOT.clone();
cwd.push(path.as_ref());
std::env::set_current_dir(cwd.as_path())
}

View File

@@ -1,30 +0,0 @@
use std::ffi;
use anyhow::Context;
pub fn run_process<S>(argv: &[S]) -> anyhow::Result<(Option<String>, Option<String>)>
where
S: AsRef<ffi::OsStr>,
{
let mut process = subprocess::Popen::create(
argv,
subprocess::PopenConfig {
stdout: subprocess::Redirection::Pipe,
..Default::default()
},
)?;
let result = process
.communicate(None)
.context("Failed to communicate with subprocess");
if process
.wait_timeout(std::time::Duration::from_secs(5))
.is_err()
|| process.exit_status().is_none()
{
process.terminate()?;
}
result
}

View File

@@ -1,227 +0,0 @@
use std::fmt;
use std::path::{Path, PathBuf};
use anyhow::Context;
use bytesize::ByteSize;
pub const JAVA_VM: &str = "java";
pub const JAVA_COMPILER: &str = "javac";
pub const JAVA_EXT_SOURCE: &str = "java";
pub const JAVA_EXT_CLASS: &str = "class";
// TODO: Builder-pattern JVM wrapper.
#[derive(Debug, Default, Clone)]
pub struct JVMBuilder {
assertions: bool,
monitor: bool,
ram_min: Option<ByteSize>,
ram_max: Option<ByteSize>,
}
impl JVMBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn assertions(&mut self, assertions: bool) -> &mut Self {
self.assertions = assertions;
self
}
pub fn ram_min(&mut self, ram_min: ByteSize) -> &mut Self {
self.ram_min = Some(ram_min);
self
}
pub fn ram_max(&mut self, ram_max: ByteSize) -> &mut Self {
self.ram_max = Some(ram_max);
self
}
/// Monitor stdout and stderr in raven's process.
pub fn monitor(&mut self, monitor: bool) -> &mut Self {
self.monitor = monitor;
self
}
pub fn build(&self) -> JVM {
let mut flags = vec![];
if self.assertions {
flags.push(VMFlag::EnableAssert);
}
if let Option::Some(size) = self.ram_min {
flags.push(VMFlag::HeapMin { size });
}
if let Option::Some(size) = self.ram_max {
flags.push(VMFlag::HeapMax { size });
}
JVM {
monitor: self.monitor,
flags,
}
}
}
pub struct JVM {
monitor: bool,
flags: Vec<VMFlag>,
}
impl JVM {
pub fn run<P: AsRef<Path>>(
self,
entry_point: P,
) -> anyhow::Result<(Option<String>, Option<String>)> {
let mut cmd = vec![JAVA_VM.to_string()];
cmd.extend(
self.flags
.clone()
.into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)),
);
cmd.push(entry_point.as_ref().display().to_string());
let result = crate::io::run_process(cmd.as_slice());
if self.monitor
&& let Result::Ok((stdout, stderr)) = &result
{
if let Option::Some(stdout) = stdout
&& stdout.len() > 0
{
print!("{stdout}");
}
if let Option::Some(stderr) = stderr
&& stderr.len() > 0
{
eprintln!("{stderr}");
}
}
result
}
}
#[derive(Debug, Clone)]
pub enum VMFlag {
EnableAssert,
HeapMax { size: ByteSize },
HeapMin { size: ByteSize },
Version,
}
impl Into<Vec<String>> for VMFlag {
fn into(self) -> Vec<String> {
match self {
Self::EnableAssert => vec![String::from("-ea")],
Self::HeapMax { size } => vec![format!("Xmx{}", size)],
Self::HeapMin { size } => vec![format!("Xms{}", size)],
Self::Version => vec![String::from("--version")],
}
}
}
// Currently being kept around because it's fine for the current branches,
// and is currently serving a to_string() method for the Java version fetch.
impl fmt::Display for VMFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"-{}",
match self {
Self::EnableAssert => String::from("ea"),
Self::HeapMax { size } => format!("Xmx{}", size),
Self::HeapMin { size } => format!("Xms{}", size),
Self::Version => String::from("-version"), // --version
}
)
}
}
#[derive(Debug, Default, Clone)]
pub struct CompilerBuilder {
class_path: Option<PathBuf>,
destination: Option<PathBuf>,
}
impl CompilerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn class_path<S: AsRef<Path>>(&mut self, class_path: S) -> &mut Self {
self.class_path = Some(class_path.as_ref().to_path_buf());
self
}
pub fn destination<S: AsRef<Path>>(&mut self, destination: S) -> &mut Self {
self.destination = Some(destination.as_ref().to_path_buf());
self
}
pub fn build(&self) -> Compiler {
let mut flags = vec![];
if let Option::Some(path) = self.destination.to_owned() {
flags.push(CompilerFlag::Destination { path });
}
if let Option::Some(path) = self.class_path.to_owned() {
flags.push(CompilerFlag::Classpath { path });
}
Compiler { flags }
}
}
#[derive(Debug, Clone)]
pub struct Compiler {
flags: Vec<CompilerFlag>,
}
impl Compiler {
pub fn compile<P: AsRef<Path>>(
self,
path: P,
) -> anyhow::Result<(Option<String>, Option<String>)> {
let mut cmd: Vec<String> = vec![JAVA_COMPILER.to_string()];
cmd.extend(
self.flags
.clone()
.into_iter()
.flat_map(|f| Into::<Vec<String>>::into(f)),
);
cmd.extend(crate::fs::expand_files(path)?.into_iter().filter_map(|f| {
Some(
f.to_str()
.context("Failed to cast PathBuf to &str")
.ok()?
.to_string(),
)
}));
crate::io::run_process(cmd.as_slice())
}
}
#[derive(Debug, Clone)]
pub enum CompilerFlag {
Destination { path: PathBuf },
Classpath { path: PathBuf },
}
impl Into<Vec<String>> for CompilerFlag {
fn into(self) -> Vec<String> {
match self {
Self::Classpath { path } => vec!["-cp".to_string(), path.display().to_string()],
Self::Destination { path } => vec!["-d".to_string(), path.display().to_string()],
}
}
}

View File

@@ -1,272 +0,0 @@
mod cli;
mod env;
mod fs;
mod io;
mod java;
mod nest;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use cli::{CONFIG, Command};
use java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use nest::{Class, NEST, NestLock};
use anyhow::Context;
use bytesize::ByteSize;
pub static PROJECT_ROOT: LazyLock<PathBuf> = LazyLock::new(|| {
// Start from CWD
let cwd = std::env::current_dir().expect("Failed to get current working directory");
let mut probe = cwd.clone();
while !probe.join(F_NEST_TOML.as_path()).exists() {
if !probe.pop() {
// This is easier than having a Result everywhere. For now.
panic!(
"No {} found in current directory or any ancestor. (cwd: {})",
F_NEST_TOML.display(),
cwd.display()
)
}
}
probe
});
const DIR_TARGET: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("target/"));
const DIR_SRC: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_ROOT.join("src/"));
const DIR_MAIN: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("main/"));
const DIR_TEST: LazyLock<PathBuf> = LazyLock::new(|| DIR_SRC.join("test/"));
const F_NEST_TOML: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.toml"));
const F_NEST_LOCK: LazyLock<PathBuf> = LazyLock::new(|| PathBuf::from("Nest.lock"));
fn main() -> anyhow::Result<()> {
// Ensure that Nest.toml exists, if the requested command depends upon it.
if CONFIG.command.depends_on_nest() && NEST.is_err() {
println!("No Nest.toml found in project directory");
println!("Aborting...");
return Ok(());
}
match &CONFIG.command {
Command::Init => init()?,
Command::New { project_name } => {
new(project_name.to_owned())?;
init()?;
}
Command::Build => {
build()?;
}
Command::Run {
entry_point,
assertions,
} => {
build()?;
run(entry_point, assertions.into())?;
}
Command::Test { assertions } => {
test(assertions.into())?;
}
Command::Clean => clean(),
}
println!("Done.");
Ok(())
}
fn init() -> anyhow::Result<()> {
let cwd = std::env::current_dir()?;
let is_empty = std::fs::read_dir(cwd.as_path()).is_ok_and(|tree| tree.count() == 0);
let project_name = cwd
.file_name()
.context("Invalid directory name")?
.to_str()
.context("Unable to convert OsStr to str")?
.to_owned();
// ORDER MATTERS. THIS MUST COME FIRST.
// Make config file.
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(F_NEST_TOML.as_path())
{
f.write_all(
toml::to_string_pretty(&nest::Nest {
package: nest::Package {
name: project_name.to_owned(),
..Default::default()
},
})?
.as_bytes(),
)?;
}
// Make .java-version
if let Result::Ok(mut f) = OpenOptions::new()
.write(true)
.create_new(true)
.open(PathBuf::from(".java-version"))
{
f.write_all(
{
let mut version = crate::env::get_javac_ver()?.major.to_string();
version.push('\n');
version
}
.as_bytes(),
)?;
}
// Make src, target, tests
for dir in [DIR_SRC, DIR_MAIN, DIR_TARGET, DIR_TEST] {
std::fs::create_dir_all(dir.clone())?;
}
// Make src/main/Main.java
if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
DIR_MAIN
.clone()
.join("Main")
.with_extension(JAVA_EXT_SOURCE),
) {
f.write_all(include_bytes!("../assets/src/main/Main.java"))?;
}
// Make src/test/Main.java
if let Result::Ok(mut f) = OpenOptions::new().write(true).create_new(is_empty).open(
DIR_TEST
.clone()
.join("Main")
.with_extension(JAVA_EXT_SOURCE),
) {
f.write_all(include_bytes!("../assets/src/test/Main.java"))?;
}
// git init .
crate::io::run_process(&["git", "init", "."])?;
// Append to .gitignore
if let Result::Ok(mut f) = OpenOptions::new()
.append(true)
.create(true)
.read(true)
.open(".gitignore")
{
let mut buf = String::new();
f.read_to_string(&mut buf)?;
for ignored in [
DIR_TARGET.as_path().display().to_string(),
format!("*.{}", JAVA_EXT_CLASS),
] {
if !buf.contains(&ignored) {
f.write(format!("{}\n", ignored).as_bytes())?;
}
}
}
Ok(())
}
fn new(project_name: String) -> anyhow::Result<()> {
let cwd = std::env::current_dir()?.join(project_name);
std::fs::create_dir(&cwd)?;
std::env::set_current_dir(&cwd)?;
Ok(())
}
fn build() -> anyhow::Result<()> {
let mut targets: Vec<PathBuf> = crate::fs::expand_files(DIR_SRC.as_path())?
.into_iter()
.filter(|f| {
f.extension()
.is_some_and(|ext| ext.to_str().is_some_and(|ext| ext == JAVA_EXT_SOURCE))
})
.collect();
let mut nest_lock = NestLock::load().unwrap_or_default();
nest_lock.update();
let mut retained_targets = vec![];
for path in targets.into_iter() {
if let Option::Some((_path, class)) = nest_lock.0.get_key_value(&path)
&& class.is_updated()
{
continue;
}
retained_targets.push(path);
}
targets = retained_targets;
let javac = java::CompilerBuilder::new()
.class_path(DIR_TARGET.as_path())
.destination(DIR_TARGET.as_path())
.build();
for target in targets {
println!("Compiling {}", target.display());
if javac.clone().compile(dbg!(target.as_path())).is_ok()
&& let Result::Ok(class) = Class::try_from(target.clone())
{
nest_lock.0.insert(target, class);
}
}
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(F_NEST_LOCK.as_path())?
.write_all(toml::to_string_pretty(&nest_lock)?.as_bytes())
.with_context(|| format!("Failed to write {}", F_NEST_LOCK.display()))
}
fn run<P: AsRef<Path>>(
entry_point: P,
assertions: bool,
) -> anyhow::Result<(Option<String>, Option<String>)> {
// JRE pathing will be messed up without this.
crate::env::set_cwd(DIR_TARGET.as_path())?;
java::JVMBuilder::new()
.assertions(assertions)
.monitor(true)
.build()
.run(entry_point)
}
fn test(assertions: bool) -> anyhow::Result<(Option<String>, Option<String>)> {
java::CompilerBuilder::new()
.class_path(DIR_TARGET.as_path())
.destination(DIR_TARGET.as_path())
.build()
.compile(DIR_TEST.as_path())?;
// Change cwd to avoid Java pathing issues.
crate::env::set_cwd(DIR_TARGET.as_path())?;
java::JVMBuilder::new()
.assertions(assertions)
.ram_min(ByteSize::mib(128))
.ram_max(ByteSize::mib(512))
.monitor(true)
.build()
.run(DIR_TEST.as_path())
}
fn clean() {
let _ = std::fs::remove_file(PROJECT_ROOT.join(F_NEST_LOCK.as_path()));
let _ = std::fs::remove_dir_all(DIR_TARGET.join("/*").as_path());
}

View File

@@ -1,163 +0,0 @@
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::path::PathBuf;
use std::sync::LazyLock;
use crate::java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE};
use crate::{DIR_SRC, DIR_TARGET, DIR_TEST, F_NEST_LOCK, F_NEST_TOML, PROJECT_ROOT};
use anyhow::Context;
use semver::Version;
use serde::{Deserialize, Serialize};
pub static NEST: LazyLock<anyhow::Result<Nest>> =
LazyLock::new(|| Nest::try_from(PROJECT_ROOT.join(F_NEST_TOML.as_path())));
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Nest {
pub package: Package,
}
impl TryFrom<File> for Nest {
type Error = anyhow::Error;
fn try_from(value: File) -> Result<Self, Self::Error> {
let mut value = value;
let mut buf = String::new();
value
.read_to_string(&mut buf)
.context("Failed to read Nest")?;
toml::from_str(buf.as_str()).context("Failed to deserialize Nest")
}
}
impl TryFrom<PathBuf> for Nest {
type Error = anyhow::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
Nest::try_from(
OpenOptions::new()
.read(true)
.open(value)
.expect("Failed to load Nest.toml"),
)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Package {
pub name: String,
pub version: semver::Version,
}
impl Default for Package {
fn default() -> Self {
Self {
name: String::from("MyPackage"),
version: Version::new(0, 1, 0),
}
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct NestLock(pub HashMap<PathBuf, Class>);
impl NestLock {
pub fn load() -> anyhow::Result<NestLock> {
NestLock::try_from(F_NEST_LOCK.clone())
}
/// Update, retaining all classes that still exist and whose paths are still files, rather than
/// being shifted into a package.
pub fn update(&mut self) {
self.0 = self
.0
.clone()
.into_iter()
.filter_map(|(path, class)| {
if path.exists() && path.is_file() {
return Some((path, class));
}
None
})
.collect();
}
}
impl TryFrom<File> for NestLock {
type Error = anyhow::Error;
fn try_from(value: File) -> Result<Self, Self::Error> {
let mut value = value;
let mut buf = String::new();
value.read_to_string(&mut buf)?;
toml::from_str(buf.as_str()).context("Failed to deserialize NestLock")
}
}
impl TryFrom<PathBuf> for NestLock {
type Error = anyhow::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
NestLock::try_from(
OpenOptions::new()
.read(true)
.open(&value)
.with_context(|| format!("Failed to open {}", value.display()))?,
)
.context("")
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Class {
pub name: PathBuf,
pub checksum: String,
pub test: bool,
}
impl Class {
/// Returns true if the class needs updating.
/// This may also cautionarily return true if it cannot digest the file.
pub fn is_updated(&self) -> bool {
// If the source file exists, hasn't been moved to a package (path is a file) and
// the associated compiled class file exists in DIR_TARGET
PROJECT_ROOT
.join(DIR_TARGET.as_path())
.join(self.name.as_path())
.with_extension(JAVA_EXT_CLASS)
.exists()
&& sha256::try_digest(
PROJECT_ROOT
.join(if self.test {
DIR_TEST.clone()
} else {
DIR_SRC.clone()
})
.join(self.name.as_path())
.with_extension(JAVA_EXT_SOURCE),
)
.is_ok_and(|hash| self.checksum == hash)
}
}
impl TryFrom<PathBuf> for Class {
type Error = anyhow::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
Ok(Self {
name: PathBuf::from(
value
.file_name()
.context("Failed to get file name from PathBuf for class")?,
)
.with_extension(""),
checksum: sha256::try_digest(&value)?,
test: value.is_relative() && value.starts_with(DIR_TEST.as_path()),
})
}
}

26
templates/demo/Nest.toml Normal file
View File

@@ -0,0 +1,26 @@
[workspace]
default_package = "main" # PathBuf || String ?
[meta]
name = "demo" # String
version = "0.1.0" # semver::Version
authors = ["Olivia Brooks", "Adrian Long"] # Option<Vec<String>>
repository = "https://gitea.cutieguwu.ca/Cutieguwu/raven" # Option<URL> struct?
license = "MIT" # Option<enum License>
license-file = "LICENSE"
[dependencies]
anyhow = "1.0" # semver::VersionReq
bytesize = "2.3"
pathsub = "0.1.1"
ron = "0.12"
sha256 = "1.6"
subprocess = "0.2"
toml = "0.9"
[dependencies.clap]
version = "4.5"
#features = ["cargo", "derive"] # Is this a concept in POM?
[pom] # Only POM-specific data
model_version = "4.0.0" # semver::Version

View File

@@ -0,0 +1,7 @@
[[classes]]
path = "Main" # PathBuf to .class
checksum = "24dffb40073ff21878cf879bf8c67d189ad600115f9a8ecead11a3ca6c086767"
[[classes]]
path = "Cli"
checksum = "24dffb40073ff21878cf879bf8c67d189ad600115f9a8ecead11a3ca6c086767"

View File

@@ -0,0 +1,6 @@
[package]
entry_point = "Main" # PathBuf
[meta]
name = "main"
version = "0.1.0"

View File

@@ -1,17 +0,0 @@
// Acknowledge sister/child
mod module;
// std
use std::*;
// sister/child
use module1::*;
// parent
use super::*;
// ancestor of parent
use crate::*;
// external
use external::*;

84
templates/pom.xml Normal file
View File

@@ -0,0 +1,84 @@
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>com.viffx</groupId>
<artifactId>GameEngine</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<lwjgl.version>3.4.0</lwjgl.version>
<lwjgl.natives>natives-windows</lwjgl.natives>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-bom</artifactId>
<version>${lwjgl.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-assimp</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-vulkan</artifactId>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-assimp</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-openal</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
</dependencies>
</project>