Merge pull request #4562 from roc-lang/https-packages

URL-based packages
This commit is contained in:
Richard Feldman 2022-11-26 15:50:09 -05:00 committed by GitHub
commit bb89344eaa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 2031 additions and 237 deletions

583
Cargo.lock generated
View file

@ -53,6 +53,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "alsa"
version = "0.6.0"
@ -100,6 +115,12 @@ dependencies = [
"roc_error_macros",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -159,6 +180,21 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64-url"
version = "1.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a99c239d0c7e77c85dddfa9cebce48704b3c49550fcd3b84dd637e4484899f"
dependencies = [
"base64",
]
[[package]]
name = "bincode"
version = "1.3.3"
@ -241,6 +277,20 @@ dependencies = [
"wyz 0.5.0",
]
[[package]]
name = "blake3"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f"
dependencies = [
"arrayref",
"arrayvec 0.7.2",
"cc",
"cfg-if 1.0.0",
"constant_time_eq",
"digest",
]
[[package]]
name = "block"
version = "0.1.6"
@ -256,6 +306,27 @@ dependencies = [
"generic-array",
]
[[package]]
name = "brotli"
version = "3.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bstr"
version = "0.2.17"
@ -646,6 +717,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "copyless"
version = "0.1.5"
@ -1105,6 +1182,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -1249,6 +1327,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "endian-type"
version = "0.1.2"
@ -1363,6 +1450,18 @@ dependencies = [
"windows-sys 0.36.1",
]
[[package]]
name = "filetime"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"windows-sys 0.42.0",
]
[[package]]
name = "find-crate"
version = "0.6.3"
@ -1403,6 +1502,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "fs_extra"
version = "1.2.0"
@ -1656,6 +1765,25 @@ dependencies = [
"bitflags",
]
[[package]]
name = "h2"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
@ -1708,6 +1836,77 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
[[package]]
name = "http"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"fnv",
"itoa 1.0.2",
]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa 1.0.2",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
dependencies = [
"http",
"hyper",
"rustls",
"tokio",
"tokio-rustls",
]
[[package]]
name = "iced-x86"
version = "1.17.0"
@ -1724,6 +1923,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "im"
version = "15.1.0"
@ -1829,6 +2039,12 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb"
[[package]]
name = "ipnet"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
[[package]]
name = "itertools"
version = "0.9.0"
@ -2091,6 +2307,12 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.5.0"
@ -2147,6 +2369,12 @@ dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -3236,6 +3464,60 @@ dependencies = [
"wasmer-wasi",
]
[[package]]
name = "reqwest"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-rustls",
"ipnet",
"js-sys",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rkyv"
version = "0.7.39"
@ -3297,6 +3579,7 @@ dependencies = [
"roc_error_macros",
"roc_load",
"roc_module",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -3411,6 +3694,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_region",
"roc_repl_cli",
@ -3517,6 +3801,7 @@ dependencies = [
"roc_highlight",
"roc_load",
"roc_module",
"roc_packaging",
"roc_parse",
"roc_region",
"roc_reporting",
@ -3563,6 +3848,7 @@ dependencies = [
"roc_collections",
"roc_load",
"roc_module",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -3695,6 +3981,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_reporting",
"roc_std",
"roc_target",
@ -3759,6 +4046,7 @@ dependencies = [
"roc_error_macros",
"roc_load",
"roc_mono",
"roc_packaging",
"roc_reporting",
"serde",
"target-lexicon",
@ -3775,6 +4063,7 @@ dependencies = [
"roc_collections",
"roc_load_internal",
"roc_module",
"roc_packaging",
"roc_reporting",
"roc_target",
"roc_types",
@ -3802,6 +4091,7 @@ dependencies = [
"roc_late_solve",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -3813,6 +4103,7 @@ dependencies = [
"roc_tracing",
"roc_types",
"roc_unify",
"tempfile",
"ven_pretty",
]
@ -3857,6 +4148,25 @@ dependencies = [
"ven_pretty",
]
[[package]]
name = "roc_packaging"
version = "0.0.1"
dependencies = [
"base64-url",
"blake3",
"brotli",
"bumpalo",
"flate2",
"indoc",
"pretty_assertions",
"reqwest",
"roc_error_macros",
"roc_parse",
"tar",
"tempfile",
"walkdir",
]
[[package]]
name = "roc_parse"
version = "0.0.1"
@ -3934,6 +4244,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_region",
"roc_reporting",
@ -3962,6 +4273,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_region",
"roc_repl_eval",
@ -4019,6 +4331,7 @@ dependencies = [
"roc_fmt",
"roc_load",
"roc_module",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -4059,6 +4372,7 @@ dependencies = [
"roc_exhaustive",
"roc_load",
"roc_module",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -4231,6 +4545,27 @@ dependencies = [
"windows-sys 0.36.1",
]
[[package]]
name = "rustls"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
dependencies = [
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
dependencies = [
"base64",
]
[[package]]
name = "rustversion"
version = "1.0.7"
@ -4296,6 +4631,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "seahash"
version = "4.1.0"
@ -4403,6 +4748,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa 1.0.2",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.25"
@ -4642,6 +4999,22 @@ dependencies = [
"syn",
]
[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spirv"
version = "0.2.0+1.5.4"
@ -4771,6 +5144,12 @@ dependencies = [
"syn",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.99"
@ -4788,6 +5167,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.4"
@ -4844,6 +5234,7 @@ dependencies = [
"roc_derive_key",
"roc_load_internal",
"roc_module",
"roc_packaging",
"roc_region",
"roc_reporting",
"roc_solve",
@ -4876,6 +5267,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@ -4905,6 +5297,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_reporting",
"roc_target",
"roc_tracing",
@ -5054,6 +5447,48 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"pin-project-lite",
"socket2",
"winapi",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
name = "tokio-util"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "toml"
version = "0.5.9"
@ -5063,6 +5498,12 @@ dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.36"
@ -5137,6 +5578,12 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "ttf-parser"
version = "0.15.2"
@ -5181,12 +5628,27 @@ dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
@ -5205,6 +5667,23 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf8parse"
version = "0.2.0"
@ -5280,6 +5759,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -5789,6 +6278,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
dependencies = [
"webpki",
]
[[package]]
name = "wgpu"
version = "0.12.0"
@ -5960,6 +6468,27 @@ dependencies = [
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.33.0"
@ -5972,6 +6501,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.33.0"
@ -5984,6 +6519,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.33.0"
@ -5996,6 +6537,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.33.0"
@ -6008,6 +6555,18 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.33.0"
@ -6020,6 +6579,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "winit"
version = "0.26.1"
@ -6053,6 +6618,15 @@ dependencies = [
"x11-dl",
]
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "wyhash"
version = "0.5.0"
@ -6100,6 +6674,15 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
dependencies = [
"libc",
]
[[package]]
name = "xcb"
version = "1.1.1"

View file

@ -10,6 +10,7 @@ members = [
"crates/highlight",
"crates/error_macros",
"crates/reporting",
"crates/packaging",
"crates/repl_cli",
"crates/repl_eval",
"crates/repl_test",

View file

@ -20,18 +20,19 @@ roc_solve = { path = "../compiler/solve"}
roc_load = { path = "../compiler/load" }
roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" }
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" }
ven_graph = { path = "../vendor/pathfinding" }
arrayvec.workspace = true
bumpalo.workspace = true
page_size.workspace = true
snafu.workspace = true
libc.workspace = true
arrayvec.workspace = true
bumpalo.workspace = true
page_size.workspace = true
snafu.workspace = true
libc.workspace = true
[dev-dependencies]
indoc.workspace = true
indoc.workspace = true
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["memoryapi"]}

View file

@ -1,10 +1,15 @@
use bumpalo::Bump;
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, Threading};
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::DEFAULT_PALETTE;
use roc_target::TargetInfo;
use std::path::Path;
pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule {
pub fn load_module(
src_file: &Path,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> LoadedModule {
let subs_by_module = Default::default();
let load_config = LoadConfig {
@ -16,8 +21,13 @@ pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule {
};
let arena = Bump::new();
let loaded =
roc_load::load_and_typecheck(&arena, src_file.to_path_buf(), subs_by_module, load_config);
let loaded = roc_load::load_and_typecheck(
&arena,
src_file.to_path_buf(),
subs_by_module,
roc_cache_dir,
load_config,
);
match loaded {
Ok(x) => x,

View file

@ -54,6 +54,7 @@ roc_load = { path = "../compiler/load" }
roc_build = { path = "../compiler/build" }
roc_fmt = { path = "../compiler/fmt" }
roc_target = { path = "../compiler/roc_target" }
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" }
roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
@ -79,7 +80,7 @@ strum.workspace = true
libloading.workspace = true
signal-hook.workspace = true
inkwell.workspace = true
inkwell.workspace = true
# for now, uses unix/libc functions that windows does not support
[target.'cfg(not(windows))'.dependencies]

View file

@ -12,6 +12,7 @@ use roc_load::{
LoadingProblem, Threading,
};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_reporting::{
cli::Problems,
report::{RenderTarget, DEFAULT_PALETTE},
@ -66,9 +67,10 @@ pub fn build_file<'a>(
emit_timings: bool,
link_type: LinkType,
linking_strategy: LinkingStrategy,
prebuilt: bool,
prebuilt_requested: bool,
threading: Threading,
wasm_dev_stack_bytes: Option<u32>,
roc_cache_dir: RocCacheDir<'_>,
order: BuildOrdering,
) -> Result<BuiltFile<'a>, BuildFileError<'a>> {
let compilation_start = Instant::now();
@ -94,6 +96,7 @@ pub fn build_file<'a>(
arena,
app_module_path.clone(),
subs_by_module,
roc_cache_dir,
load_config,
);
let loaded = match load_result {
@ -109,6 +112,9 @@ pub fn build_file<'a>(
}
};
// For example, if we're loading the platform from a URL, it's automatically prebuilt
// even if the --prebuilt-platform=true CLI flag wasn't set.
let is_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
let (app_extension, extension, host_filename) = {
use roc_target::OperatingSystem::*;
@ -142,7 +148,7 @@ pub fn build_file<'a>(
let host_input_path = if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point
{
cwd.join(platform_path).with_file_name(host_filename)
platform_path.with_file_name(host_filename)
} else {
unreachable!();
};
@ -180,12 +186,19 @@ pub fn build_file<'a>(
};
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if prebuilt {
let rebuild_thread = if is_prebuilt {
if !preprocessed_host_path.exists() {
eprintln!(
"\nBecause I was run with --prebuilt-platform=true, I was expecting this file to exist:\n\n {}\n\nHowever, it was not there!\n\nIf you have the platform's source code locally, you may be able to regenerate it by re-running this command with --prebuilt-platform=false\n",
preprocessed_host_path.to_string_lossy()
);
if prebuilt_requested {
eprintln!(
"\nBecause I was run with --prebuilt-platform=true, I was expecting this file to exist:\n\n {}\n\nHowever, it was not there!\n\nIf you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false\n",
preprocessed_host_path.to_string_lossy()
);
} else {
eprintln!(
"\nI was expecting this file to exist:\n\n {}\n\nHowever, it was not there!\n\nIf you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false\n",
preprocessed_host_path.to_string_lossy()
);
}
std::process::exit(1);
}
@ -272,7 +285,8 @@ pub fn build_file<'a>(
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !prebuilt {
if emit_timings && !is_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
@ -309,7 +323,6 @@ pub fn build_file<'a>(
);
let compilation_end = compilation_start.elapsed();
let size = roc_app_bytes.len();
if emit_timings {
@ -327,7 +340,8 @@ pub fn build_file<'a>(
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !prebuilt {
if emit_timings && !is_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
@ -484,12 +498,13 @@ fn spawn_rebuild_thread(
}
#[allow(clippy::too_many_arguments)]
pub fn check_file(
arena: &Bump,
pub fn check_file<'a>(
arena: &'a Bump,
roc_file_path: PathBuf,
emit_timings: bool,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> Result<(Problems, Duration), LoadingProblem> {
) -> Result<(Problems, Duration), LoadingProblem<'a>> {
let compilation_start = Instant::now();
// only used for generating errors. We don't do code generation, so hardcoding should be fine
@ -507,8 +522,13 @@ pub fn check_file(
threading,
exec_mode: ExecutionMode::Check,
};
let mut loaded =
roc_load::load_and_typecheck(arena, roc_file_path, subs_by_module, load_config)?;
let mut loaded = roc_load::load_and_typecheck(
arena,
roc_file_path,
subs_by_module,
roc_cache_dir,
load_config,
)?;
let buf = &mut String::with_capacity(1024);

View file

@ -11,6 +11,8 @@ use roc_build::program::{CodeGenBackend, CodeGenOptions};
use roc_error_macros::{internal_error, user_error};
use roc_load::{ExpectMetadata, LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression;
use roc_reporting::cli::Problems;
use std::env;
use std::ffi::{CString, OsStr};
@ -19,6 +21,7 @@ use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::process;
use std::time::Instant;
use strum::{EnumIter, IntoEnumIterator, IntoStaticStr};
use target_lexicon::BinaryFormat;
use target_lexicon::{
@ -49,6 +52,7 @@ pub const CMD_GLUE: &str = "glue";
pub const CMD_GEN_STUB_LIB: &str = "gen-stub-lib";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_BUNDLE: &str = "bundle";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_MAX_THREADS: &str = "max-threads";
@ -165,6 +169,14 @@ pub fn build_app<'a>() -> Command<'a> {
.help("Build a C library instead of an executable")
.required(false),
)
.arg(
Arg::new(FLAG_BUNDLE)
.long(FLAG_BUNDLE)
.help("Create an archive of a package (for example, a .tar, .tar.gz, or .tar.br file), so others can add it as a HTTPS dependency.")
.conflicts_with(FLAG_TARGET)
.possible_values([".tar", ".tar.gz", ".tar.br"])
.required(false),
)
.arg(
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
@ -349,8 +361,8 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result<i32> {
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExecutionMode, LoadConfig};
use roc_packaging::cache;
use roc_target::TargetInfo;
use std::time::Instant;
let start_time = Instant::now();
let arena = Bump::new();
@ -413,9 +425,14 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
threading,
exec_mode: ExecutionMode::Test,
};
let loaded =
roc_load::load_and_monomorphize(arena, path.to_path_buf(), subs_by_module, load_config)
.unwrap();
let loaded = roc_load::load_and_monomorphize(
arena,
path.to_path_buf(),
subs_by_module,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
load_config,
)
.unwrap();
let mut loaded = loaded;
let mut expectations = std::mem::take(&mut loaded.expectations);
@ -481,11 +498,79 @@ pub fn build(
matches: &ArgMatches,
config: BuildConfig,
triple: Triple,
roc_cache_dir: RocCacheDir<'_>,
link_type: LinkType,
) -> io::Result<i32> {
use build::build_file;
use BuildConfig::*;
let filename = matches.value_of_os(ROC_FILE).unwrap();
let path_buf = {
let path = Path::new(filename);
// Spawn the root task
if !path.exists() {
let path_string = path.to_string_lossy();
// TODO these should use roc_reporting to display nicer error messages.
match matches.value_source(ROC_FILE) {
Some(ValueSource::DefaultValue) => {
eprintln!(
"\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
DEFAULT_ROC_FILENAME
)
}
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string),
}
process::exit(1);
}
if config == BuildConfig::BuildOnly && matches.is_present(FLAG_BUNDLE) {
let start_time = Instant::now();
let compression =
Compression::try_from(matches.value_of(FLAG_BUNDLE).unwrap()).unwrap();
// Print a note of advice. This is mainly here because brotli takes so long but produces
// such smaller output files; the idea is to encourage people to wait for brotli,
// so that downloads go faster. The compression only happens once, but the network
// transfer and decompression will happen many more times!
match compression {
Compression::Brotli => {
println!("Compressing with Brotli at maximum quality level…\n\n(Note: Brotli compression can take awhile! Using --{FLAG_BUNDLE} .tar.gz takes less time, but usually produces a significantly larger output file. Brotli is generally worth the up-front wait if this is a file people will be downloading!)\n");
}
Compression::Gzip => {
println!("Compressing with gzip at minimum quality…\n\n(Note: Gzip usually runs faster than Brotli but typically produces significantly larger output files. Consider using --{FLAG_BUNDLE} .tar.br if this is a file people will be downloading!)\n");
}
Compression::Uncompressed => {
println!("Building .tar archive without compression…\n\n(Note: Compression takes more time to run but typically produces much smaller output files. Consider using --{FLAG_BUNDLE} .tar.br if this is a file people will be downloading!)\n");
}
}
// Rather than building an executable or library, we're building
// a tarball so this code can be distributed via a HTTPS
let filename = roc_packaging::tarball::build(path, compression)?;
let total_time_ms = start_time.elapsed().as_millis();
let total_time = if total_time_ms > 1000 {
format!("{}s {}ms", total_time_ms / 1000, total_time_ms % 1000)
} else {
format!("{total_time_ms} ms")
};
let created_path = path.with_file_name(&filename);
println!(
"\nBundled \x1B[33m{}\x1B[39m and its dependent files into the following archive in {total_time}:\n\n\t\x1B[33m{}\x1B[39m\n\nTo distribute this archive as a package, upload this to some URL and then add it as a dependency with:\n\n\t\x1B[32m\"https://your-url-goes-here/{filename}\"\x1B[39m\n",
path.to_string_lossy(),
created_path.to_string_lossy()
);
return Ok(0);
}
path.to_path_buf()
};
// the process will end after this function,
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
@ -549,27 +634,6 @@ pub fn build(
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
};
let filename = matches.value_of_os(ROC_FILE).unwrap();
let path = Path::new(filename);
// Spawn the root task
if !path.exists() {
let path_string = path.to_string_lossy();
// TODO these should use roc_reporting to display nicer error messages.
match matches.value_source(ROC_FILE) {
Some(ValueSource::DefaultValue) => {
eprintln!(
"\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
DEFAULT_ROC_FILENAME
)
}
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string),
}
process::exit(1);
}
let wasm_dev_stack_bytes: Option<u32> = matches
.try_get_one::<&str>(FLAG_WASM_STACK_SIZE_KB)
.ok()
@ -591,7 +655,7 @@ pub fn build(
let res_binary_path = build_file(
&arena,
&triple,
path.to_path_buf(),
path_buf,
code_gen_options,
emit_timings,
link_type,
@ -599,6 +663,7 @@ pub fn build(
prebuilt,
threading,
wasm_dev_stack_bytes,
roc_cache_dir,
build_ordering,
);

View file

@ -10,6 +10,7 @@ use roc_cli::{
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
use roc_load::{LoadingProblem, Threading};
use roc_packaging::cache::{self, RocCacheDir};
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
@ -37,6 +38,7 @@ fn main() -> io::Result<()> {
&matches,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
} else {
@ -51,6 +53,7 @@ fn main() -> io::Result<()> {
matches,
BuildConfig::BuildAndRun,
Triple::host(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
} else {
@ -74,6 +77,7 @@ fn main() -> io::Result<()> {
matches,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LinkType::Executable,
)
} else {
@ -97,12 +101,14 @@ fn main() -> io::Result<()> {
Some((CMD_GEN_STUB_LIB, matches)) => {
let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap());
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
roc_linker::generate_stub_lib(input_path, &target.to_triple())
roc_linker::generate_stub_lib(
input_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
&target.to_triple(),
)
}
Some((CMD_BUILD, matches)) => {
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
@ -117,6 +123,7 @@ fn main() -> io::Result<()> {
matches,
BuildConfig::BuildOnly,
target.to_triple(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
link_type,
)?)
}
@ -136,7 +143,13 @@ fn main() -> io::Result<()> {
Some(n) => Threading::AtMost(n),
};
match check_file(&arena, roc_file_path, emit_timings, threading) {
match check_file(
&arena,
roc_file_path,
emit_timings,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
threading,
) {
Ok((problems, total_time)) => {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",

View file

@ -13,18 +13,20 @@ roc_can = { path = "../can" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
roc_collections = { path = "../collections" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
bumpalo.workspace = true
bumpalo.workspace = true
[build-dependencies]
roc_builtins = { path = "../builtins" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
roc_can = { path = "../can" }
bumpalo.workspace = true
bumpalo.workspace = true
[target.'cfg(not(windows))'.build-dependencies]
roc_load_internal = { path = "../load_internal" }

View file

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
#[cfg(not(windows))]
use bumpalo::Bump;
use roc_module::symbol::ModuleId;
use roc_packaging::cache::RocCacheDir;
const SKIP_SUBS_CACHE: bool = {
match option_env!("ROC_SKIP_SUBS_CACHE") {
@ -72,7 +73,7 @@ fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path:
use roc_reporting::cli::report_problems;
let arena = Bump::new();
let src_dir = PathBuf::from(".");
let cwd = std::env::current_dir().unwrap();
let source = roc_builtins::roc::module_source(module_id);
let target_info = roc_target::TargetInfo::default_x86_64();
@ -80,11 +81,12 @@ fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path:
&arena,
PathBuf::from(filename),
source,
src_dir,
cwd,
Default::default(),
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
roc_reporting::report::DEFAULT_PALETTE,
RocCacheDir::Disallowed,
Threading::AllAvailable,
);

View file

@ -4,6 +4,7 @@ use bumpalo::Bump;
use roc_can::module::{ExposedByModule, TypeState};
use roc_collections::all::MutMap;
use roc_module::symbol::ModuleId;
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::{Palette, RenderTarget};
use roc_target::TargetInfo;
use std::path::PathBuf;
@ -26,14 +27,23 @@ fn load<'a>(
arena: &'a Bump,
load_start: LoadStart<'a>,
exposed_types: ExposedByModule,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_types = read_cached_types();
roc_load_internal::file::load(arena, load_start, exposed_types, cached_types, load_config)
roc_load_internal::file::load(
arena,
load_start,
exposed_types,
cached_types,
roc_cache_dir,
load_config,
)
}
/// Load using only a single thread; used when compiling to webassembly
#[allow(clippy::too_many_arguments)]
pub fn load_single_threaded<'a>(
arena: &'a Bump,
load_start: LoadStart<'a>,
@ -41,6 +51,7 @@ pub fn load_single_threaded<'a>(
target_info: TargetInfo,
render: RenderTarget,
palette: Palette,
roc_cache_dir: RocCacheDir<'_>,
exec_mode: ExecutionMode,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_subs = read_cached_types();
@ -54,6 +65,7 @@ pub fn load_single_threaded<'a>(
render,
palette,
exec_mode,
roc_cache_dir,
)
}
@ -87,47 +99,60 @@ pub fn load_and_monomorphize_from_str<'a>(
src: &'a str,
src_dir: PathBuf,
exposed_types: ExposedByModule,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src, src_dir)?;
let load_start = LoadStart::from_str(arena, filename, src, roc_cache_dir, src_dir)?;
match load(arena, load_start, exposed_types, load_config)? {
match load(arena, load_start, exposed_types, roc_cache_dir, load_config)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
}
}
pub fn load_and_monomorphize(
arena: &Bump,
pub fn load_and_monomorphize<'a>(
arena: &'a Bump,
filename: PathBuf,
exposed_types: ExposedByModule,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<MonomorphizedModule<'_>, LoadMonomorphizedError<'_>> {
) -> Result<MonomorphizedModule<'a>, LoadMonomorphizedError<'a>> {
use LoadResult::*;
let load_start =
LoadStart::from_path(arena, filename, load_config.render, load_config.palette)?;
let load_start = LoadStart::from_path(
arena,
filename,
load_config.render,
roc_cache_dir,
load_config.palette,
)?;
match load(arena, load_start, exposed_types, load_config)? {
match load(arena, load_start, exposed_types, roc_cache_dir, load_config)? {
Monomorphized(module) => Ok(module),
TypeChecked(module) => Err(LoadMonomorphizedError::ErrorModule(module)),
}
}
pub fn load_and_typecheck(
arena: &Bump,
pub fn load_and_typecheck<'a>(
arena: &'a Bump,
filename: PathBuf,
exposed_types: ExposedByModule,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<LoadedModule, LoadingProblem<'_>> {
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start =
LoadStart::from_path(arena, filename, load_config.render, load_config.palette)?;
let load_start = LoadStart::from_path(
arena,
filename,
load_config.render,
roc_cache_dir,
load_config.palette,
)?;
match load(arena, load_start, exposed_types, load_config)? {
match load(arena, load_start, exposed_types, roc_cache_dir, load_config)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
}
@ -142,11 +167,12 @@ pub fn load_and_typecheck_str<'a>(
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
roc_cache_dir: RocCacheDir<'_>,
palette: Palette,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, source, src_dir)?;
let load_start = LoadStart::from_str(arena, filename, source, roc_cache_dir, src_dir)?;
// NOTE: this function is meant for tests, and so we use single-threaded
// solving so we don't use too many threads per-test. That gives higher
@ -158,6 +184,7 @@ pub fn load_and_typecheck_str<'a>(
target_info,
render,
palette,
roc_cache_dir,
ExecutionMode::Check,
)? {
Monomorphized(_) => unreachable!(""),

View file

@ -27,6 +27,7 @@ roc_mono = { path = "../mono" }
roc_intern = { path = "../intern" }
roc_target = { path = "../roc_target" }
roc_tracing = { path = "../../tracing" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_debug_flags = { path = "../debug_flags" }
@ -35,6 +36,7 @@ ven_pretty = { path = "../../vendor/pretty" }
bumpalo.workspace = true
parking_lot.workspace = true
crossbeam.workspace = true
tempfile.workspace = true
[dev-dependencies]
roc_test_utils = { path = "../../test_utils" }

View file

@ -7,8 +7,7 @@ use parking_lot::Mutex;
use roc_builtins::roc::module_source;
use roc_can::abilities::{AbilitiesStore, PendingAbilitiesStore, ResolvedImpl};
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints, TypeOrVar};
use roc_can::expr::{DbgLookup, PendingDerives};
use roc_can::expr::{Declarations, ExpectLookup};
use roc_can::expr::{DbgLookup, Declarations, ExpectLookup, PendingDerives};
use roc_can::module::{
canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module,
ResolvedImplementations, TypeState,
@ -37,6 +36,9 @@ use roc_mono::ir::{
use roc_mono::layout::{
CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem, STLayoutInterner,
};
use roc_packaging::cache::{self, RocCacheDir};
#[cfg(not(target_family = "wasm"))]
use roc_packaging::https::PackageMetadata;
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
@ -707,6 +709,7 @@ pub struct MonomorphizedModule<'a> {
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub expectations: VecMap<ModuleId, Expectations>,
pub uses_prebuilt_platform: bool,
}
/// Values used to render expect output
@ -721,7 +724,7 @@ pub enum EntryPoint<'a> {
Executable {
symbol: Symbol,
layout: ProcLayout<'a>,
platform_path: Box<Path>,
platform_path: PathBuf,
},
Test,
}
@ -869,6 +872,7 @@ enum PlatformPath<'a> {
struct PlatformData {
module_id: ModuleId,
provides: Symbol,
is_prebuilt: bool,
}
#[derive(Debug, Clone, Copy)]
@ -899,6 +903,7 @@ impl MakeSpecializationsPass {
struct State<'a> {
pub root_id: ModuleId,
pub root_subs: Option<Subs>,
pub cache_dir: PathBuf,
pub platform_data: Option<PlatformData>,
pub exposed_types: ExposedByModule,
pub output_path: Option<&'a str>,
@ -917,7 +922,7 @@ struct State<'a> {
/// From now on, these will be used by multiple threads; time to make an Arc<Mutex<_>>!
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
#[allow(unused)]
pub derived_module: SharedDerivedModule,
@ -972,12 +977,13 @@ impl<'a> State<'a> {
exec_mode: ExecutionMode,
) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
let cache_dir = roc_packaging::cache::roc_cache_dir();
let dependencies = Dependencies::new(exec_mode.goal_phase());
Self {
root_id,
root_subs: None,
cache_dir,
target_info,
platform_data: None,
output_path: None,
@ -1085,7 +1091,7 @@ enum BuildTask<'a> {
LoadModule {
module_name: PQModuleName<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
ident_ids_by_module: SharedIdentIdsByModule,
},
Parse {
@ -1173,6 +1179,7 @@ pub enum LoadingProblem<'a> {
ImportCycle(PathBuf, Vec<ModuleId>),
IncorrectModuleName(FileError<'a, IncorrectModuleName<'a>>),
CouldNotFindCacheDir,
}
pub enum Phases {
@ -1211,11 +1218,12 @@ pub fn load_and_typecheck_str<'a>(
target_info: TargetInfo,
render: RenderTarget,
palette: Palette,
roc_cache_dir: RocCacheDir<'_>,
threading: Threading,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, source, src_dir)?;
let load_start = LoadStart::from_str(arena, filename, source, roc_cache_dir, src_dir)?;
// this function is used specifically in the case
// where we want to regenerate the cached data
@ -1229,7 +1237,14 @@ pub fn load_and_typecheck_str<'a>(
exec_mode: ExecutionMode::Check,
};
match load(arena, load_start, exposed_types, cached_subs, load_config)? {
match load(
arena,
load_start,
exposed_types,
cached_subs,
roc_cache_dir,
load_config,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
}
@ -1254,6 +1269,7 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump,
filename: PathBuf,
render: RenderTarget,
roc_cache_dir: RocCacheDir<'_>,
palette: Palette,
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
@ -1273,6 +1289,7 @@ impl<'a> LoadStart<'a> {
None,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
roc_cache_dir,
root_start_time,
);
@ -1384,6 +1401,7 @@ impl<'a> LoadStart<'a> {
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
roc_cache_dir: RocCacheDir<'_>,
src_dir: PathBuf,
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
@ -1400,6 +1418,7 @@ impl<'a> LoadStart<'a> {
src,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
roc_cache_dir,
root_start_time,
)?
};
@ -1482,6 +1501,7 @@ pub fn load<'a>(
load_start: LoadStart<'a>,
exposed_types: ExposedByModule,
cached_types: MutMap<ModuleId, TypeState>,
roc_cache_dir: RocCacheDir<'_>,
load_config: LoadConfig,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
enum Threads {
@ -1518,6 +1538,7 @@ pub fn load<'a>(
load_config.render,
load_config.palette,
load_config.exec_mode,
roc_cache_dir,
),
Threads::Many(threads) => load_multi_threaded(
arena,
@ -1529,6 +1550,7 @@ pub fn load<'a>(
load_config.palette,
threads,
load_config.exec_mode,
roc_cache_dir,
),
}
}
@ -1544,6 +1566,7 @@ pub fn load_single_threaded<'a>(
render: RenderTarget,
palette: Palette,
exec_mode: ExecutionMode,
roc_cache_dir: RocCacheDir<'_>,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart {
arc_modules,
@ -1587,7 +1610,15 @@ pub fn load_single_threaded<'a>(
// now we just manually interleave stepping the state "thread" and the worker "thread"
loop {
match state_thread_step(arena, state, worker_listeners, &injector, &msg_tx, &msg_rx) {
match state_thread_step(
arena,
state,
&src_dir,
worker_listeners,
&injector,
&msg_tx,
&msg_rx,
) {
Ok(ControlFlow::Break(done)) => return Ok(done),
Ok(ControlFlow::Continue(new_state)) => {
state = new_state;
@ -1604,6 +1635,7 @@ pub fn load_single_threaded<'a>(
&worker_msg_rx,
&msg_tx,
&src_dir,
roc_cache_dir,
target_info,
);
@ -1620,6 +1652,7 @@ pub fn load_single_threaded<'a>(
fn state_thread_step<'a>(
arena: &'a Bump,
state: State<'a>,
src_dir: &Path,
worker_listeners: &'a [Sender<WorkerMsg>],
injector: &Injector<BuildTask<'a>>,
msg_tx: &crossbeam::channel::Sender<Msg<'a>>,
@ -1715,6 +1748,7 @@ fn state_thread_step<'a>(
let res_state = update(
state,
src_dir,
msg,
msg_tx.clone(),
injector,
@ -1798,6 +1832,7 @@ fn load_multi_threaded<'a>(
palette: Palette,
available_threads: usize,
exec_mode: ExecutionMode,
roc_cache_dir: RocCacheDir<'_>,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart {
arc_modules,
@ -1901,6 +1936,7 @@ fn load_multi_threaded<'a>(
worker_msg_rx,
msg_tx,
src_dir,
roc_cache_dir,
target_info,
)
});
@ -1931,8 +1967,15 @@ fn load_multi_threaded<'a>(
// The root module will have already queued up messages to process,
// and processing those messages will in turn queue up more messages.
loop {
match state_thread_step(arena, state, worker_listeners, &injector, &msg_tx, &msg_rx)
{
match state_thread_step(
arena,
state,
&src_dir,
worker_listeners,
&injector,
&msg_tx,
&msg_rx,
) {
Ok(ControlFlow::Break(load_result)) => {
shut_down_worker_threads!();
@ -1963,6 +2006,7 @@ fn worker_task_step<'a>(
worker_msg_rx: &crossbeam::channel::Receiver<WorkerMsg>,
msg_tx: &MsgSender<'a>,
src_dir: &Path,
roc_cache_dir: RocCacheDir<'_>,
target_info: TargetInfo,
) -> Result<ControlFlow<(), ()>, LoadingProblem<'a>> {
match worker_msg_rx.try_recv() {
@ -1986,8 +2030,14 @@ fn worker_task_step<'a>(
// added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message.
if let Some(task) = find_task(worker, injector, stealers) {
let result =
run_task(task, worker_arena, src_dir, msg_tx.clone(), target_info);
let result = run_task(
task,
worker_arena,
src_dir,
msg_tx.clone(),
roc_cache_dir,
target_info,
);
match result {
Ok(()) => {}
@ -2031,6 +2081,7 @@ fn worker_task<'a>(
worker_msg_rx: crossbeam::channel::Receiver<WorkerMsg>,
msg_tx: MsgSender<'a>,
src_dir: &Path,
roc_cache_dir: RocCacheDir<'_>,
target_info: TargetInfo,
) -> Result<(), LoadingProblem<'a>> {
// Keep listening until we receive a Shutdown msg
@ -2054,7 +2105,14 @@ fn worker_task<'a>(
// added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message.
if let Some(task) = find_task(&worker, injector, stealers) {
let result = run_task(task, worker_arena, src_dir, msg_tx.clone(), target_info);
let result = run_task(
task,
worker_arena,
src_dir,
msg_tx.clone(),
roc_cache_dir,
target_info,
);
match result {
Ok(()) => {}
@ -2168,6 +2226,7 @@ fn extend_header_with_builtin(header: &mut ModuleHeader, module: ModuleId) {
fn update<'a>(
mut state: State<'a>,
src_dir: &Path,
msg: Msg<'a>,
msg_tx: MsgSender<'a>,
injector: &Injector<BuildTask<'a>>,
@ -2195,49 +2254,127 @@ fn update<'a>(
let mut work = MutSet::default();
// Register the package's path under its shorthand
// (e.g. for { pf: "blah" }, register that "pf" should resolve to "blah")
{
let mut shorthands = (*state.arc_shorthands).lock();
for (shorthand, package_name) in header.packages.iter() {
shorthands.insert(shorthand, *package_name);
let package_str = package_name.as_str();
let shorthand_path = if package_str.starts_with("https://") {
#[cfg(not(target_family = "wasm"))]
{
let url = package_str;
match PackageMetadata::try_from(url) {
Ok(url_metadata) => {
// This was a valid URL
let root_module_dir = state
.cache_dir
.join(url_metadata.cache_subdir)
.join(url_metadata.content_hash);
let root_module = root_module_dir.join(
url_metadata.root_module_filename.unwrap_or("main.roc"),
);
ShorthandPath::FromHttpsUrl {
root_module_dir,
root_module,
}
}
Err(url_err) => {
todo!(
"Gracefully report URL error for {:?} - {:?}",
url,
url_err
);
}
}
}
#[cfg(target_family = "wasm")]
{
panic!("Specifying packages via URLs is curently unsupported in wasm.");
}
} else {
// This wasn't a URL, so it must be a filesystem path.
let root_module: PathBuf = src_dir.join(package_str);
let root_module_dir = root_module.parent().unwrap_or_else(|| {
if root_module.is_file() {
// Files must have parents!
internal_error!("Somehow I got a file path to a real file on the filesystem that has no parent!");
} else {
// TODO make this a nice report
todo!(
"platform module {:?} was not a file.",
package_str
)
}
}).into();
ShorthandPath::RelativeToSrc {
root_module_dir,
root_module,
}
};
shorthands.insert(shorthand, shorthand_path);
}
if let Platform {
config_shorthand, ..
} = header.header_for
{
work.extend(state.dependencies.notify_package(config_shorthand));
}
}
match header.header_for {
App { to_platform } => {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform);
}
Platform { main_for_host, .. } => {
debug_assert!(matches!(state.platform_data, None));
state.platform_data = Some(PlatformData {
module_id: header.module_id,
provides: main_for_host,
});
if header.is_root_module {
match header.header_for {
App { to_platform } => {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsPlatformModule;
state.platform_path = PlatformPath::Valid(to_platform);
}
}
Builtin { .. } | Interface => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsInterface;
Platform {
main_for_host,
config_shorthand,
..
} => {
debug_assert!(matches!(state.platform_data, None));
work.extend(state.dependencies.notify_package(config_shorthand));
let is_prebuilt = if header.is_root_module {
debug_assert!(matches!(
state.platform_path,
PlatformPath::NotSpecified
));
state.platform_path = PlatformPath::RootIsPlatformModule;
// If the root module is a platform, then the platform is the very
// thing we're rebuilding!
false
} else {
// platforms from HTTPS URLs are always prebuilt
matches!(
shorthands.get(config_shorthand),
Some(ShorthandPath::FromHttpsUrl { .. })
)
};
state.platform_data = Some(PlatformData {
module_id: header.module_id,
provides: main_for_host,
is_prebuilt,
});
}
}
Hosted { .. } => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsHosted;
Builtin { .. } | Interface => {
if header.is_root_module {
debug_assert!(matches!(
state.platform_path,
PlatformPath::NotSpecified
));
state.platform_path = PlatformPath::RootIsInterface;
}
}
Hosted { .. } => {
if header.is_root_module {
debug_assert!(matches!(
state.platform_path,
PlatformPath::NotSpecified
));
state.platform_path = PlatformPath::RootIsHosted;
}
}
}
}
@ -2267,17 +2404,19 @@ fn update<'a>(
let mut header = header;
if !header.module_id.is_builtin() {
extend_header_with_builtin(&mut header, ModuleId::NUM);
extend_header_with_builtin(&mut header, ModuleId::BOOL);
extend_header_with_builtin(&mut header, ModuleId::STR);
extend_header_with_builtin(&mut header, ModuleId::LIST);
extend_header_with_builtin(&mut header, ModuleId::RESULT);
extend_header_with_builtin(&mut header, ModuleId::DICT);
extend_header_with_builtin(&mut header, ModuleId::SET);
extend_header_with_builtin(&mut header, ModuleId::BOX);
extend_header_with_builtin(&mut header, ModuleId::ENCODE);
extend_header_with_builtin(&mut header, ModuleId::DECODE);
extend_header_with_builtin(&mut header, ModuleId::HASH);
let header = &mut header;
extend_header_with_builtin(header, ModuleId::NUM);
extend_header_with_builtin(header, ModuleId::BOOL);
extend_header_with_builtin(header, ModuleId::STR);
extend_header_with_builtin(header, ModuleId::LIST);
extend_header_with_builtin(header, ModuleId::RESULT);
extend_header_with_builtin(header, ModuleId::DICT);
extend_header_with_builtin(header, ModuleId::SET);
extend_header_with_builtin(header, ModuleId::BOX);
extend_header_with_builtin(header, ModuleId::ENCODE);
extend_header_with_builtin(header, ModuleId::DECODE);
extend_header_with_builtin(header, ModuleId::HASH);
}
state
@ -2984,26 +3123,21 @@ fn finish_specialization<'a>(
match exec_mode {
ExecutionMode::Test => EntryPoint::Test,
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
let path_to_platform = {
use PlatformPath::*;
let package_name = match platform_path {
Valid(To::ExistingPackage(shorthand)) => {
match (*state.arc_shorthands).lock().get(shorthand) {
Some(p_or_p) => *p_or_p,
None => unreachable!(),
}
use PlatformPath::*;
let platform_path = match platform_path {
Valid(To::ExistingPackage(shorthand)) => {
match (*state.arc_shorthands).lock().get(shorthand) {
Some(shorthand_path) => shorthand_path.root_module().to_path_buf(),
None => unreachable!(),
}
Valid(To::NewPackage(p_or_p)) => p_or_p,
other => {
let buf = to_missing_platform_report(state.root_id, other);
return Err(LoadingProblem::FormattedReport(buf));
}
};
package_name.into()
}
Valid(To::NewPackage(p_or_p)) => PathBuf::from(p_or_p.as_str()),
other => {
let buf = to_missing_platform_report(state.root_id, other);
return Err(LoadingProblem::FormattedReport(buf));
}
};
let platform_path = Path::new(path_to_platform).into();
let symbol = match platform_data {
None => {
debug_assert_eq!(exposed_to_host.values.len(), 1);
@ -3042,6 +3176,13 @@ fn finish_specialization<'a>(
None => current_dir().unwrap().join(DEFAULT_APP_OUTPUT_PATH).into(),
};
let uses_prebuilt_platform = match platform_data {
Some(data) => data.is_prebuilt,
// If there's no platform data (e.g. because we're building an interface module)
// then there's no prebuilt platform either!
None => false,
};
Ok(MonomorphizedModule {
can_problems,
type_problems,
@ -3057,6 +3198,7 @@ fn finish_specialization<'a>(
sources,
timings: state.timings,
toplevel_expects,
uses_prebuilt_platform,
})
}
@ -3122,7 +3264,7 @@ fn finish(
}
}
/// Load a `platform` module
/// Load a `platform` module from disk
fn load_platform_module<'a>(
arena: &'a Bump,
filename: &Path,
@ -3269,7 +3411,8 @@ fn load_module<'a>(
src_dir: &Path,
module_name: PQModuleName<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
roc_cache_dir: RocCacheDir<'_>,
ident_ids_by_module: SharedIdentIdsByModule,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let module_start_time = Instant::now();
@ -3327,14 +3470,52 @@ fn load_module<'a>(
Some(module_name),
module_ids,
ident_ids_by_module,
roc_cache_dir,
module_start_time,
)
}
#[derive(Debug)]
enum ShorthandPath {
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
FromHttpsUrl {
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
root_module_dir: PathBuf,
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz/main.roc"
root_module: PathBuf,
},
RelativeToSrc {
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/"
root_module_dir: PathBuf,
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/main.roc"
root_module: PathBuf,
},
}
impl ShorthandPath {
pub fn root_module(&self) -> &Path {
match self {
ShorthandPath::FromHttpsUrl { root_module, .. }
| ShorthandPath::RelativeToSrc { root_module, .. } => root_module.as_path(),
}
}
pub fn root_module_dir(&self) -> &Path {
match self {
ShorthandPath::FromHttpsUrl {
root_module_dir, ..
}
| ShorthandPath::RelativeToSrc {
root_module_dir, ..
} => root_module_dir.as_path(),
}
}
}
fn module_name_to_path<'a>(
src_dir: &Path,
module_name: &PQModuleName<'a>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
) -> (PathBuf, Option<&'a str>) {
let mut filename;
let opt_shorthand;
@ -3352,20 +3533,11 @@ fn module_name_to_path<'a>(
PQModuleName::Qualified(shorthand, name) => {
opt_shorthand = Some(*shorthand);
let shorthands = arc_shorthands.lock();
match shorthands.get(shorthand) {
Some(path) => {
let parent = Path::new(path.as_str()).parent().unwrap_or_else(|| {
panic!(
"platform module {:?} did not have a parent directory.",
path
)
});
filename = src_dir.join(parent)
}
None => unreachable!("there is no shorthand named {:?}", shorthand),
}
filename = shorthands
.get(shorthand)
.expect("All shorthands should have been validated by now.")
.root_module_dir()
.to_path_buf();
// Convert dots in module name to directories
for part in name.split(MODULE_SEPARATOR) {
@ -3456,6 +3628,7 @@ fn parse_header<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
src_bytes: &'a [u8],
roc_cache_dir: RocCacheDir<'_>,
start_time: Instant,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = Instant::now();
@ -3606,14 +3779,50 @@ fn parse_header<'a>(
shorthand,
package_name:
Loc {
value: package_name,
value: package_path,
..
},
..
}) = opt_base_package
{
// check whether we can find a `platform` module file
let platform_module_path = app_file_dir.join(package_name.to_str());
let src = package_path.to_str();
// check whether we can find a `platform` module file on disk
let platform_module_path = if src.starts_with("https://") {
#[cfg(not(target_family = "wasm"))]
{
// If this is a HTTPS package, synchronously download it
// to the cache before proceeding.
// TODO we should do this async; however, with the current
// architecture of file.rs (which doesn't use async/await),
// this would be very difficult!
let (package_dir, opt_root_module) = cache::install_package(
roc_cache_dir,
src,
)
.unwrap_or_else(|err| {
todo!("TODO gracefully handle package install error {:?}", err);
});
// You can optionally specify the root module using the URL fragment,
// e.g. #foo.roc
// (defaults to main.roc)
match opt_root_module {
Some(root_module) => package_dir.join(root_module),
None => package_dir.join("main.roc"),
}
}
#[cfg(target_family = "wasm")]
{
panic!(
"Specifying packages via URLs is curently unsupported in wasm."
);
}
} else {
app_file_dir.join(src)
};
if platform_module_path.as_path().exists() {
let load_platform_module_msg = load_platform_module(
@ -3671,6 +3880,7 @@ fn load_filename<'a>(
opt_expected_module_name: Option<PackageQualified<'a, ModuleName>>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
roc_cache_dir: RocCacheDir<'_>,
module_start_time: Instant,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = Instant::now();
@ -3688,6 +3898,7 @@ fn load_filename<'a>(
module_ids,
ident_ids_by_module,
arena.alloc(bytes),
roc_cache_dir,
module_start_time,
),
Err(err) => Err(LoadingProblem::FileProblem {
@ -3706,6 +3917,7 @@ fn load_from_str<'a>(
src: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
roc_cache_dir: RocCacheDir<'_>,
module_start_time: Instant,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let file_io_start = Instant::now();
@ -3721,6 +3933,7 @@ fn load_from_str<'a>(
module_ids,
ident_ids_by_module,
src.as_bytes(),
roc_cache_dir,
module_start_time,
)
}
@ -5577,6 +5790,7 @@ fn run_task<'a>(
arena: &'a Bump,
src_dir: &Path,
msg_tx: MsgSender<'a>,
roc_cache_dir: RocCacheDir<'_>,
target_info: TargetInfo,
) -> Result<(), LoadingProblem<'a>> {
use BuildTask::*;
@ -5593,6 +5807,7 @@ fn run_task<'a>(
module_name,
module_ids,
shorthands,
roc_cache_dir,
ident_ids_by_module,
)
.map(|(_, msg)| msg),

View file

@ -21,6 +21,7 @@ use roc_load_internal::file::{ExecutionMode, LoadConfig, Threading};
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem};
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem;
use roc_region::all::LineInfo;
use roc_reporting::report::RenderTarget;
@ -40,7 +41,13 @@ fn load_and_typecheck(
) -> Result<LoadedModule, LoadingProblem> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic, DEFAULT_PALETTE)?;
let load_start = LoadStart::from_path(
arena,
filename,
RenderTarget::Generic,
RocCacheDir::Disallowed,
DEFAULT_PALETTE,
)?;
let load_config = LoadConfig {
target_info,
render: RenderTarget::Generic,
@ -54,6 +61,7 @@ fn load_and_typecheck(
load_start,
exposed_types,
Default::default(), // these tests will re-compile the builtins
RocCacheDir::Disallowed,
load_config,
)? {
Monomorphized(_) => unreachable!(""),

View file

@ -4,12 +4,14 @@ use bumpalo::Bump;
use roc_region::all::{Loc, Position, Region};
use Progress::*;
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Either<First, Second> {
First(First),
Second(Second),
}
impl<F: Copy, S: Copy> Copy for Either<F, S> {}
pub type ParseResult<'a, Output, Error> = Result<(Progress, Output, State<'a>), (Progress, Error)>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -10,6 +10,7 @@ description = "The entry point of Roc's type inference system. Implements type i
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_exhaustive = { path = "../exhaustive" }
roc_packaging = { path = "../../packaging" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }

View file

@ -18,6 +18,7 @@ mod solve_expr {
};
use roc_load::LoadedModule;
use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem;
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region};
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
@ -108,6 +109,7 @@ mod solve_expr {
exposed_types,
roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::Generic,
RocCacheDir::Disallowed,
roc_reporting::report::DEFAULT_PALETTE,
);
@ -7767,7 +7769,7 @@ mod solve_expr {
indoc!(
r#"
f : { x ? Str, y ? Str } -> {}
f {x : ""}
"#
),

View file

@ -20,6 +20,7 @@ roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols", "open-extension-vars"] }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_constrain = { path = "../constrain" }
roc_region = { path = "../region" }

View file

@ -2,6 +2,7 @@ use std::fmt::Write as _; // import without risk of name clashing
use std::path::PathBuf;
use bumpalo::Bump;
use roc_packaging::cache::RocCacheDir;
use ven_pretty::DocAllocator;
use crate::pretty_print::{pretty_print_def, Ctx};
@ -490,6 +491,7 @@ where
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
roc_reporting::report::DEFAULT_PALETTE,
RocCacheDir::Disallowed,
Threading::AllAvailable,
)
.unwrap();

View file

@ -31,6 +31,7 @@ roc_unify = { path = "../unify" }
roc_utils = { path = "../../utils" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_load = { path = "../load" }
roc_can = { path = "../can" }

View file

@ -2,6 +2,7 @@ use libloading::Library;
use roc_build::link::{link, LinkType};
use roc_builtins::bitcode;
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_packaging::cache::RocCacheDir;
use roc_region::all::LineInfo;
use tempfile::tempdir;
@ -63,6 +64,7 @@ pub fn helper(
module_src,
src_dir,
Default::default(),
RocCacheDir::Disallowed,
load_config,
);

View file

@ -9,6 +9,7 @@ use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult};
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_mono::ir::{CrashTag, OptLevel};
use roc_packaging::cache::RocCacheDir;
use roc_region::all::LineInfo;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_utils::zig;
@ -80,6 +81,7 @@ fn create_llvm_module<'a>(
module_src,
src_dir,
Default::default(),
RocCacheDir::Disallowed,
load_config,
);

View file

@ -4,6 +4,7 @@ use roc_collections::all::MutSet;
use roc_gen_wasm::wasm32_result::Wasm32Result;
use roc_gen_wasm::DEBUG_SETTINGS;
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::DEFAULT_PALETTE_HTML;
use roc_std::RocStr;
use roc_wasm_module::{Export, ExportType};
@ -99,6 +100,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
module_src,
src_dir,
Default::default(),
RocCacheDir::Disallowed,
load_config,
);

View file

@ -18,6 +18,7 @@ roc_load = { path = "../load" }
roc_can = { path = "../can" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_tracing = { path = "../../tracing" }

View file

@ -13,16 +13,15 @@ extern crate indoc;
#[allow(dead_code)]
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
use roc_collections::all::MutMap;
use roc_load::ExecutionMode;
use roc_load::LoadConfig;
use test_mono_macros::*;
use roc_collections::all::MutMap;
use roc_load::Threading;
use roc_module::symbol::Symbol;
use roc_mono::ir::Proc;
use roc_mono::ir::ProcLayout;
use roc_mono::layout::STLayoutInterner;
use test_mono_macros::*;
const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64();
@ -76,6 +75,7 @@ fn promote_expr_to_module(src: &str) -> String {
fn compiles_to_ir(test_name: &str, src: &str) {
use bumpalo::Bump;
use roc_packaging::cache::RocCacheDir;
use std::path::PathBuf;
let arena = &Bump::new();
@ -107,6 +107,7 @@ fn compiles_to_ir(test_name: &str, src: &str) {
module_src,
src_dir,
Default::default(),
RocCacheDir::Disallowed,
load_config,
);
@ -1945,7 +1946,7 @@ fn unreachable_void_constructor() {
x : []
main = if Bool.true then Ok x else Err "abc"
main = if Bool.true then Ok x else Err "abc"
"#
)
}

View file

@ -20,10 +20,11 @@ roc_parse = { path = "../compiler/parse" }
roc_target = { path = "../compiler/roc_target" }
roc_collections = { path = "../compiler/collections" }
roc_highlight = { path = "../highlight"}
roc_packaging = { path = "../packaging"}
roc_reporting = { path = "../reporting"}
bumpalo.workspace = true
snafu.workspace = true
peg.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true
pretty_assertions.workspace = true

View file

@ -14,6 +14,7 @@ use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{Documentation, ModuleDocumentation, RecordField};
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId};
use roc_packaging::cache::{self, RocCacheDir};
use roc_parse::ident::{parse_ident, Ident};
use roc_parse::state::State;
use roc_region::all::Region;
@ -462,7 +463,13 @@ pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
threading: Threading::AllAvailable,
exec_mode: ExecutionMode::Check,
};
match roc_load::load_and_typecheck(&arena, filename, Default::default(), load_config) {
match roc_load::load_and_typecheck(
&arena,
filename,
Default::default(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
load_config,
) {
Ok(loaded) => modules.push(loaded),
Err(LoadingProblem::FormattedReport(report)) => {
eprintln!("{}", report);

View file

@ -30,6 +30,7 @@ roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }
roc_utils = { path = "../utils"}
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" }
roc_solve = { path = "../compiler/solve" }
ven_graph = { path = "../vendor/pathfinding" }

View file

@ -28,6 +28,7 @@ use roc_ast::mem_pool::pool::Pool;
use roc_ast::module::load_module;
use roc_load::Threading;
use roc_module::symbol::IdentIds;
use roc_packaging::cache::{self, RocCacheDir};
use roc_types::subs::VarStore;
use std::collections::HashSet;
use std::env;
@ -127,8 +128,11 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
println!("Loading file {:?}...", file_path_str);
let file_path = Path::new(&file_path_str);
let loaded_module = load_module(file_path, Threading::AllAvailable);
let loaded_module = load_module(
file_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
Threading::AllAvailable,
);
let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8);

View file

@ -237,6 +237,7 @@ pub mod test_ed_model {
use roc_load::{LoadedModule, Threading};
use roc_module::symbol::IdentIds;
use roc_module::symbol::ModuleIds;
use roc_packaging::cache::RocCacheDir;
use roc_types::subs::VarStore;
use std::fs;
use std::fs::File;
@ -329,7 +330,11 @@ pub mod test_ed_model {
writeln!(file, "{}", clean_code_str)
.unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file));
let loaded_module = load_module(&temp_file_full_path, Threading::AllAvailable);
let loaded_module = load_module(
&temp_file_full_path,
RocCacheDir::Disallowed,
Threading::AllAvailable,
);
let mut ed_model = init_dummy_model(
clean_code_str,

View file

@ -13,6 +13,7 @@ roc_intern = { path = "../compiler/intern" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_reporting = { path = "../reporting" }
roc_packaging = { path = "../packaging" }
roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" }
roc_module = { path = "../compiler/module" }

View file

@ -3,6 +3,7 @@ use crate::types::{Env, Types};
use bumpalo::Bump;
use roc_intern::GlobalInterner;
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
use roc_packaging::cache::{self, RocCacheDir};
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_target::{Architecture, OperatingSystem, TargetInfo};
use std::fs::File;
@ -82,7 +83,6 @@ pub fn load_types(
ignore_errors: IgnoreErrors,
) -> Result<Vec<(Types, TargetInfo)>, io::Error> {
let target_info = (&Triple::host()).into();
let arena = &Bump::new();
let subs_by_module = Default::default();
let LoadedModule {
@ -97,6 +97,7 @@ pub fn load_types(
arena,
full_file_path,
subs_by_module,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LoadConfig {
target_info,
render: RenderTarget::Generic,

View file

@ -17,6 +17,7 @@ roc_build = { path = "../compiler/build" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_load = { path = "../compiler/load" }
roc_packaging = { path = "../packaging" }
roc_reporting = { path = "../reporting" }
bumpalo.workspace = true

View file

@ -9,6 +9,7 @@ use roc_build::link::{rebuild_host, LinkType};
use roc_error_macros::internal_error;
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use std::cmp::Ordering;
use std::mem;
@ -102,7 +103,11 @@ pub fn link_preprocessed_host(
}
// Exposed function to load a platform file and generate a stub lib for it.
pub fn generate_stub_lib(input_path: &Path, triple: &Triple) -> std::io::Result<i32> {
pub fn generate_stub_lib(
input_path: &Path,
roc_cache_dir: RocCacheDir<'_>,
triple: &Triple,
) -> std::io::Result<i32> {
// Note: this should theoretically just be able to load the host, I think.
// Instead, I am loading an entire app because that was simpler and had example code.
// If this was expected to stay around for the the long term, we should change it.
@ -114,6 +119,7 @@ pub fn generate_stub_lib(input_path: &Path, triple: &Triple) -> std::io::Result<
arena,
input_path.to_path_buf(),
subs_by_module,
roc_cache_dir,
LoadConfig {
target_info,
render: RenderTarget::Generic,

View file

@ -0,0 +1,32 @@
[package]
name = "roc_packaging"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "Functionality for packaging Roc source code - e.g. for distribution over the network"
[dependencies]
roc_parse = { path = "../compiler/parse" }
roc_error_macros = { path = "../error_macros" }
tar = "0.4.38" # used for `roc build --tar`
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
flate2 = "1.0.24"
walkdir = "2.3.2"
blake3 = "1.3.1"
base64-url = "1.4.13"
bumpalo.workspace = true
tempfile.workspace = true
[target.'cfg(not(target_family = "wasm"))'.dependencies]
# default-features=false removes libopenssl as a dependency on Linux, which might not be available!
reqwest = { version = "0.11.13", default-features = false, features = [ "blocking", "rustls-tls" ] }
[dev-dependencies]
pretty_assertions = "1.3.0"
indoc = "1.0.7"
tempfile.workspace = true

View file

@ -0,0 +1,170 @@
#[cfg(not(target_family = "wasm"))]
use crate::https::{self, PackageMetadata, Problem};
use roc_error_macros::internal_error;
use std::{
fs,
path::{Path, PathBuf},
};
const MAX_DOWNLOAD_BYTES: u64 = 32 * 1_000_000_000; // GB
#[derive(Copy, Clone, Debug)]
pub enum RocCacheDir<'a> {
/// Normal scenario: reading from the user's cache dir on disk
Persistent(&'a Path),
/// For build.rs and tests where we never want to be downloading anything - yell loudly if we try!
Disallowed,
/// For tests only; we don't want to write to the real cache during a test!
#[cfg(test)]
Temp(&'a tempfile::TempDir),
}
/// Accepts either a path to the Roc cache dir, or else a TempDir. If a TempDir, always download
/// into that dir. If the cache dir on the filesystem, then look into it to see if we already
/// have an entry for the given URL. If we do, return its info. If we don't already have it, then:
///
/// - Download and decompress the compressed tarball from the given URL
/// - Verify its bytes against the hash in the URL
/// - Extract the tarball's contents into the appropriate cache directory
///
/// Returns the path to the installed package (which will be in the cache dir somewhere), as well
/// as the requested root module filename (optionally specified via the URL fragment).
#[cfg(not(target_family = "wasm"))]
pub fn install_package<'a>(
roc_cache_dir: RocCacheDir<'_>,
url: &'a str,
) -> Result<(PathBuf, Option<&'a str>), Problem> {
let PackageMetadata {
cache_subdir,
content_hash,
root_module_filename,
} = PackageMetadata::try_from(url).map_err(Problem::InvalidUrl)?;
match roc_cache_dir {
RocCacheDir::Persistent(cache_dir) => {
// e.g. ~/.cache/roc/example.com/roc-packages/
let parent_dir = cache_dir.join(cache_subdir);
// e.g. ~/.cache/roc/example.com/roc-packages/jDRlAFAA3738vu3-vMpLUoyxtA86Z7CaZneoOKrihbE
let dest_dir = parent_dir.join(content_hash);
if dest_dir.exists() {
// If the cache dir exists already, we assume it has the correct contents
// (it's a cache, after all!) and return without downloading anything.
Ok((dest_dir, root_module_filename))
} else {
// Download into a tempdir; only move it to dest_dir if hash verification passes.
println!(
"Downloading \u{001b}[36m{url}\u{001b}[0m\n into {}\n",
cache_dir.display()
);
let tempdir = tempfile::tempdir().map_err(Problem::IoErr)?;
let tempdir_path = tempdir.path();
let downloaded_hash =
https::download_and_hash(url, tempdir_path, MAX_DOWNLOAD_BYTES)?;
// Download the tarball into memory and verify it.
// The tarball name is the hash of its contents.
if downloaded_hash == content_hash {
// Now that we've verified the hash, rename the tempdir to the real dir.
// Create the destination dir's parent dir, since it may not exist yet.
fs::create_dir_all(parent_dir).map_err(Problem::IoErr)?;
// This should be super cheap - just an inode change.
fs::rename(tempdir_path, &dest_dir).map_err(Problem::IoErr)?;
// The package's files are now in the cache. We're done!
Ok((dest_dir, root_module_filename))
} else {
Err(Problem::InvalidContentHash {
expected: content_hash.to_string(),
actual: downloaded_hash,
})
}
}
}
RocCacheDir::Disallowed => {
internal_error!(
"Tried to download a package ({:?}) via RocCacheDir::Disallowed - which was explicitly used in order to disallow downloading packages in the current context!",
url
)
}
#[cfg(test)]
RocCacheDir::Temp(temp_dir) => Ok((temp_dir.path().to_path_buf(), None)),
}
}
#[cfg(windows)]
// e.g. the "Roc" in %APPDATA%\\Roc
const ROC_CACHE_DIR_NAME: &str = "Roc";
#[cfg(not(windows))]
// e.g. the "roc" in ~/.cache/roc
const ROC_CACHE_DIR_NAME: &str = "roc";
/// This looks up environment variables, so it should ideally be called once and then cached!
///
/// Returns a path of the form cache_dir_path.join(ROC_CACHE_DIR_NAME).join("packages")
/// where cache_dir_path is:
/// - The XDG_CACHE_HOME environment varaible, if it's set.
/// - Otherwise, ~/.cache on UNIX and %APPDATA% on Windows.
///
/// ROC_CACHE_DIR_NAME is "roc" on UNIX and "Roc" on Windows.
///
/// So ~/.cache/roc will be typical on UNIX, and %APPDATA%\\Roc will be typical on Windows.
///
/// Returns None if XDG_CACHE_HOME is not set, and also we can't determine the home directory
/// (or if %APPDATA% is missing on Windows) on this system.
#[cfg(not(target_family = "wasm"))]
pub fn roc_cache_dir() -> PathBuf {
use std::{env, process};
const PACKAGES_DIR_NAME: &str = "packages";
// Respect XDG, if the system appears to be using it.
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
match env::var_os("XDG_CACHE_HOME") {
Some(xdg_cache_home) => Path::new(&xdg_cache_home)
.join(ROC_CACHE_DIR_NAME)
.join(PACKAGES_DIR_NAME),
None => {
#[cfg(windows)]
{
// e.g. %APPDATA%\\Roc
if let Some(appdata) =
// CSIDL_APPDATA is the same as APPDATA, according to:
// https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables
env::var_os("APPDATA").or_else(|| env::var_os("CSIDL_APPDATA"))
{
Path::new(&appdata)
.join(ROC_CACHE_DIR_NAME)
.join(PACKAGES_DIR_NAME)
} else {
eprintln!("roc needs either the %APPDATA% or else the %XDG_CACHE_HOME% environment variables set. Please set one of these environment variables and re-run roc!");
process::exit(1);
}
}
#[cfg(unix)]
{
// e.g. $HOME/.cache/roc
if let Some(home) = env::var_os("HOME") {
Path::new(&home)
.join(".cache")
.join(ROC_CACHE_DIR_NAME)
.join(PACKAGES_DIR_NAME)
} else {
eprintln!("roc needs either the $HOME or else the $XDG_CACHE_HOME environment variables set. Please set one of these environment variables and re-run roc!");
process::exit(1);
}
}
}
}
}
/// WASI doesn't have a home directory, so just make the cache dir in the current directory
/// https://github.com/WebAssembly/wasi-filesystem/issues/59
#[cfg(target_family = "wasm")]
pub fn roc_cache_dir() -> PathBuf {
PathBuf::from(".cache").join(ROC_CACHE_DIR_NAME)
}

View file

@ -0,0 +1,282 @@
use std::{
io::{self, Read},
path::Path,
};
use crate::tarball::Compression;
// gzip should be the most widely supported, and brotli offers the highest compression.
// flate2 gets us both gzip and deflate, so there's no harm in offering deflate too.
//
// Here are all the officially supported options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
// We can consider supporting more, but that would bloat the `roc` binary more, so
// let's try to avoid doing that.
const BROTLI_BUFFER_BYTES: usize = 8 * 1_000_000; // MB
pub struct PackageMetadata<'a> {
/// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk.
pub content_hash: &'a str,
/// On disk, this will be the subfolder inside the cache dir where the package lives
pub cache_subdir: &'a str,
/// Other code will default this to main.roc, but this module isn't concerned with that default.
pub root_module_filename: Option<&'a str>,
}
/// Valid URLs must end in one of these:
///
/// - .tar
/// - .tar.gz
/// - .tar.br
const VALID_EXTENSION_SUFFIXES: [&str; 2] = [".gz", ".br"];
#[derive(Debug)]
pub enum UrlProblem {
InvalidExtensionSuffix(String),
MissingTarExt,
InvalidFragment(String),
MissingHash,
MissingHttps,
}
impl<'a> TryFrom<&'a str> for PackageMetadata<'a> {
type Error = UrlProblem;
fn try_from(url: &'a str) -> Result<Self, Self::Error> {
PackageMetadata::new(url)
}
}
impl<'a> PackageMetadata<'a> {
fn new(url: &'a str) -> Result<Self, UrlProblem> {
// First, verify that the URL starts with https://
let without_protocol = match url.split_once("https://") {
Some((_, without_protocol)) => without_protocol,
None => {
return Err(UrlProblem::MissingHttps);
}
};
// Next, get the (optional) URL fragment, which must be a .roc filename
let (without_fragment, fragment) = match without_protocol.rsplit_once('#') {
Some((before_fragment, fragment)) => {
const EXT: &str = ".roc";
// The fragment must be a .roc file, and the part before ".roc" can't be empty
if fragment.ends_with(EXT) && fragment.len() > EXT.len() {
(before_fragment, Some(fragment))
} else {
return Err(UrlProblem::InvalidFragment(fragment.to_string()));
}
}
None => (without_protocol, None),
};
// The tarball name is everything after the "/" (without the .tar extension)
// The URL must end in .tar followed optionally by ".gz", ".br", etc. (excluding the fragment)
let without_ext = match without_fragment.rsplit_once(".tar") {
Some((before_ext, after_ext)) => {
if after_ext.is_empty() || VALID_EXTENSION_SUFFIXES.contains(&after_ext) {
before_ext
} else {
return Err(UrlProblem::InvalidExtensionSuffix(after_ext.to_string()));
}
}
None => {
// The URL didn't end in .tar at all
return Err(UrlProblem::MissingTarExt);
}
};
let (path, tarball_name) = match without_ext.rsplit_once('/') {
Some((path, hash)) if !hash.is_empty() => (path, hash),
_ => {
return Err(UrlProblem::MissingHash);
}
};
Ok(PackageMetadata {
cache_subdir: path,
content_hash: tarball_name,
root_module_filename: fragment,
})
}
}
#[derive(Debug)]
pub enum Problem {
UnsupportedEncoding(String),
MultipleEncodings(String),
InvalidContentHash {
expected: String,
actual: String,
},
IoErr(io::Error),
HttpErr(reqwest::Error),
InvalidUrl(UrlProblem),
/// The Content-Length header of the response exceeded max_download_bytes
DownloadTooBig(u64),
}
pub fn download_and_hash(
url: &str,
dest_dir: &Path,
max_download_bytes: u64,
) -> Result<String, Problem> {
// TODO apparently it really improves performance to construct a Client once and then reuse it,
// instead of making a new Client for every request.
// Per https://github.com/seanmonstar/reqwest/issues/1454#issuecomment-1026076701
let resp = reqwest::blocking::Client::new()
.get(url)
.send()
.map_err(Problem::HttpErr)?;
// Some servers don't return Content-Length - e.g. Netlify seems to only sometimes return it.
// If they do, and if it says the file is going to be too big, don't bother downloading it!
if let Some(content_len) = resp.content_length() {
if content_len > max_download_bytes {
return Err(Problem::DownloadTooBig(content_len));
}
}
// The server can respond with multiple encodings, per
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
// ...but we don't support that.
let encoding = {
let content_encoding = match resp.headers().get("content-encoding") {
Some(header) => header.to_str().unwrap_or_default(),
None => "",
};
Encoding::new(content_encoding, url)?
};
// Use .take to prevent a malicious server from sending back bytes
// until system resources are exhausted!
decompress_into(dest_dir, encoding, resp.take(max_download_bytes))
}
/// The content encodings we support
#[derive(Debug, Clone, Copy, PartialEq)]
enum Encoding {
Gzip,
Brotli,
Deflate,
Uncompressed,
}
impl Encoding {
pub fn new(content_encoding: &str, url: &str) -> Result<Self, Problem> {
use Encoding::*;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives
match content_encoding {
"br" => Ok(Brotli),
"gzip" => Ok(Gzip),
"deflate" => Ok(Deflate),
"" => {
// There was no Content-Encoding header, but we can infer the encoding
// from the file extension in the URL.
let end_of_ext = url.rfind('#').unwrap_or(url.len());
// Drop the URL fragment when determining file extension
match url[0..end_of_ext].rsplit_once('.') {
Some((_, after_dot)) => match Compression::from_file_ext(after_dot) {
Some(Compression::Brotli) => Ok(Self::Brotli),
Some(Compression::Gzip) => Ok(Self::Gzip),
Some(Compression::Uncompressed) | None => Ok(Self::Uncompressed),
},
None => Ok(Uncompressed),
}
}
other => {
if other.contains(',') {
// We don't support multiple encodings (although the spec for the HTTP header
// permits a comma-separated list)
Err(Problem::MultipleEncodings(other.to_string()))
} else {
// We don't support other encodings
Err(Problem::UnsupportedEncoding(other.to_string()))
}
}
}
}
}
#[test]
fn encoding_from_tar_br() {
let actual = Encoding::new(
"",
"https://example.com/jDRlAFAA3738vu3-vMpLUoyxtA86Z7CaZneoOKrihbE.tar.br",
)
.unwrap();
assert_eq!(Encoding::Brotli, actual);
}
fn hash_and_unpack(dest_dir: &Path, reader: impl Read) -> Result<String, Problem> {
let mut hash_reader = HashReader::new(reader);
tar::Archive::new(&mut hash_reader)
.unpack(dest_dir)
.map_err(Problem::IoErr)?;
let mut buf = Vec::with_capacity(1024);
// Archive::new() doesn't always read all the bytes, but we need to read them all
// in order to get the correct hash!
hash_reader.read_to_end(&mut buf).map_err(Problem::IoErr)?;
Ok(base64_url::encode(hash_reader.finalize().as_bytes()))
}
/// Read from the given reader, decompress the bytes using the given Content-Encoding string,
/// write them to the given writer, and return the base64url-encoded BLAKE3 hash of what was written.
/// This both writes and hashes incrementally as it reads, so the only extra work that's done
/// at the end is base64url-encoding the final hash.
fn decompress_into(
dest_dir: &Path,
encoding: Encoding,
reader: impl Read,
) -> Result<String, Problem> {
match encoding {
Encoding::Brotli => hash_and_unpack(
dest_dir,
brotli::Decompressor::new(reader, BROTLI_BUFFER_BYTES),
),
Encoding::Gzip => {
// Note: GzDecoder::new immediately parses the gzip header (so, calls read())
hash_and_unpack(dest_dir, flate2::read::GzDecoder::new(reader))
}
Encoding::Deflate => hash_and_unpack(dest_dir, flate2::read::DeflateDecoder::new(reader)),
Encoding::Uncompressed => hash_and_unpack(dest_dir, reader),
}
}
/// Read something while calculating its BLAKE3 hash
struct HashReader<R: Read> {
reader: R,
hasher: blake3::Hasher,
}
impl<R: Read> HashReader<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
hasher: blake3::Hasher::new(),
}
}
pub fn finalize(&self) -> blake3::Hash {
self.hasher.finalize()
}
}
impl<R: Read> Read for HashReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_read = self.reader.read(buf)?;
self.hasher.update(&buf[0..bytes_read]);
Ok(bytes_read)
}
}

View file

@ -0,0 +1,4 @@
pub mod cache;
#[cfg(not(target_family = "wasm"))]
pub mod https;
pub mod tarball;

View file

@ -0,0 +1,272 @@
use brotli::enc::BrotliEncoderParams;
use bumpalo::Bump;
use flate2::write::GzEncoder;
use roc_parse::ast::Module;
use roc_parse::header::PlatformHeader;
use roc_parse::module::parse_header;
use roc_parse::state::State;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use tar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compression {
Brotli,
Gzip,
Uncompressed,
}
impl Compression {
const fn file_ext(&self) -> &'static str {
match self {
Compression::Brotli => ".tar.br",
Compression::Gzip => ".tar.gz",
Compression::Uncompressed => ".tar",
}
}
pub fn from_file_ext(ext: &str) -> Option<Self> {
match ext {
"tar" => Some(Self::Uncompressed),
"gz" => Some(Self::Gzip),
"br" => Some(Self::Brotli),
_ => None,
}
}
}
impl<'a> TryFrom<&'a str> for Compression {
type Error = ();
fn try_from(extension: &'a str) -> Result<Self, Self::Error> {
if extension.ends_with(".br") {
Ok(Compression::Brotli)
} else if extension.ends_with(".gz") {
Ok(Compression::Gzip)
} else if extension.ends_with(".tar") {
Ok(Compression::Uncompressed)
} else {
Err(())
}
}
}
/// Given a path to a .roc file, write a .tar file to disk.
///
/// The .tar file will be in the same directory, and its filename
/// will be the hash of its contents. This function returns
/// the name of that filename (including the .tar extension),
/// so the caller can obtain the path to the file by calling
/// Path::with_file_name(returned_string) on the Path argument it provided.
pub fn build(path_to_main: &Path, compression: Compression) -> io::Result<String> {
let mut archive_bytes = Vec::new();
write_archive(path_to_main, &mut archive_bytes)?;
// Now that we have our compressed archive, get its BLAKE3 hash
// and base64url encode it. Use base64url encoding because:
// - It's more concise than hex encoding, so the URL can be shorter
// - Unlike base64 encoding, it's URL-frienly (e.g. won't include slashes)
let hash = base64_url::encode(blake3::hash(&archive_bytes).as_bytes());
let mut filename = hash;
filename.push_str(compression.file_ext());
// Write the bytes to disk.
{
let dest_path = path_to_main.with_file_name(&filename);
let mut file = File::create(&dest_path).unwrap_or_else(|err| {
panic!(
"Unable to open {} for writing - error was: {:?}",
dest_path.to_string_lossy(),
err
);
});
match compression {
Compression::Brotli => {
brotli::BrotliCompress(
&mut archive_bytes.as_slice(),
&mut file,
&BrotliEncoderParams {
quality: 11,
use_dictionary: true,
..Default::default()
},
)?;
}
Compression::Gzip => {
let mut encoder = GzEncoder::new(&mut file, flate2::Compression::fast());
encoder.write_all(&archive_bytes)?;
encoder.finish()?;
}
Compression::Uncompressed => file.write_all(&archive_bytes)?,
};
}
Ok(filename)
}
/// Write an uncompressed tar archive to the given writer.
fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
let root_dir = if let Some(parent) = path.parent() {
parent
} else {
eprintln!(
"{} is a directory, not a .roc file. Please specify a .roc file!",
path.to_string_lossy()
);
std::process::exit(1);
};
let mut builder = tar::Builder::new(writer);
let arena = Bump::new();
let mut buf = Vec::new();
let _other_modules: &[Module<'_>] = match read_header(&arena, &mut buf, path)? {
Module::Interface { .. } => {
todo!();
// TODO report error
}
Module::App { .. } => {
todo!();
// TODO report error
}
Module::Hosted { .. } => {
todo!();
// TODO report error
}
Module::Platform {
header: PlatformHeader { imports: _, .. },
} => {
use walkdir::WalkDir;
// Add all the prebuilt host files to the archive.
// These should all be in the same directory as the platform module.
for entry in std::fs::read_dir(root_dir)? {
let path = entry?.path();
if [
// surgical linker format
Some("rh1"),
// legacy linker formats
Some("o"),
Some("obj"),
Some("wasm"),
// optimized wasm builds compile to .zig for now,
// because zig can't emit .bc for wasm yet.
Some("zig"),
]
.contains(&path.extension().and_then(OsStr::to_str))
{
builder.append_path_with_name(
&path,
// Store it without the root path, so that (for example) we don't store
// `examples/cli/main.roc` and therefore end up with the root of the tarball
// being an `examples/cli/` dir instead of having `main.roc` in the root.
path.strip_prefix(root_dir).unwrap(),
)?;
}
}
// Recursively find all the .roc files and add them.
// TODO we can do this more efficiently by parsing the platform module and finding
// all of its dependencies. See below for a commented-out WIP sketch of this.
//
// The WalkDir approach is easier to implement, but has the downside of doing things
// like traversing target/ and zig-cache/ which can be large but will never have
// any .roc files in them!
for entry in WalkDir::new(root_dir).into_iter().filter_entry(|entry| {
let path = entry.path();
// We already got the prebuilt host files, so the only other things
// we care about are .roc files.
path.is_dir() || path.extension().and_then(OsStr::to_str) == Some("roc")
}) {
let entry = entry?;
// Only include files, not directories or symlinks.
// Symlinks may not work on Windows, and directories will get automatically
// added based on the paths of the files inside anyway. (In fact, if we don't
// filter out directories in this step, then empty ones will end up getting added!)
if entry.path().is_file() {
builder.append_path_with_name(
entry.path(),
// Store it without the root path, so that (for example) we don't store
// `examples/cli/main.roc` and therefore end up with the root of the tarball
// being an `examples/cli/` dir instead of having `main.roc` in the root.
entry.path().strip_prefix(root_dir).unwrap(),
)?;
}
}
&[]
}
};
// TODO: This will be necessary when bundling packages (not platforms, since platforms just
// slurp up the whole directory at the moment) and also platforms in a future where they
// have precompiled hosts, and we only need to grab the .roc files and the precompiled hostfiles!
// {
// // Repeat this process on each of the root module's imports.
// let mut stack = Vec::from_iter_in(other_modules, &arena);
// let mut visited_paths = HashSet::from_iter([path]);
// // We could do this all in parallel, but a simple stack seems fast enough for this use case.
// while let Some(path) = stack.pop() {
// let other_modules = match read_header(&arena, &mut buf, path) {
// Module::Interface { .. } => {
// // TODO use header.imports
// builder.append_path(path)?;
// }
// Module::App { .. } => {
// // TODO report error
// }
// Module::Hosted { header } => {
// // TODO report error
// }
// Module::Platform { header } => {
// // TODO report error
// }
// };
// let other_paths = todo!("infer from other_modules");
// // Recurse on the other paths in the header
// for other_path in other_paths {
// if !visited_paths.contains(other_path) {
// stack.push(other_path);
// }
// }
// paths_visited.insert(path);
// }
// }
builder.finish()
}
fn read_header<'a>(
arena: &'a Bump,
buf: &'a mut Vec<u8>,
path: &'a Path,
) -> io::Result<Module<'a>> {
// Read all the bytes into the buffer.
{
let mut file = File::open(path)?;
buf.clear();
file.read_to_end(buf)?;
}
// TODO avoid copying the contents of the file into a Bumpalo arena by doing multiple
// https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read calls instead of
// using the much more convenient file.read_to_end - which requires a std::vec::Vec.
// (We can't use that for the parser state and still return Module<'a> unfortunately.)
let arena_buf = bumpalo::collections::Vec::from_iter_in(buf.iter().copied(), arena);
let parse_state = State::new(arena_buf.into_bump_slice());
let (module, _) = parse_header(arena, parse_state).unwrap_or_else(|_err| {
todo!(); // TODO report a nice error and exit 1 - or maybe just return Err, for better testability?
});
Ok(module)
}

View file

@ -9,7 +9,7 @@ description = "Provides the functionality for the REPL to evaluate Roc expressio
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bumpalo.workspace = true
bumpalo.workspace = true
roc_builtins = {path = "../compiler/builtins"}
roc_can = {path = "../compiler/can"}
@ -21,6 +21,7 @@ roc_module = {path = "../compiler/module"}
roc_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"}
roc_region = {path = "../compiler/region"}
roc_packaging = {path = "../packaging"}
roc_reporting = {path = "../reporting"}
roc_std = {path = "../roc_std"}
roc_target = {path = "../compiler/roc_target"}

View file

@ -1,5 +1,6 @@
use bumpalo::Bump;
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_packaging::cache::{self, RocCacheDir};
use roc_reporting::report::{Palette, Severity};
use std::path::PathBuf;
@ -59,6 +60,7 @@ pub fn compile_to_mono<'a, 'i, I: Iterator<Item = &'i str>>(
module_src,
src_dir,
exposed_types,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
LoadConfig {
target_info,
render: roc_reporting::report::RenderTarget::ColorTerminal,

View file

@ -12,7 +12,7 @@ target-lexicon.workspace = true
libloading.workspace = true
signal-hook.workspace = true
libc.workspace = true
inkwell.workspace = true
inkwell.workspace = true
roc_builtins = {path = "../compiler/builtins"}
roc_can = {path = "../compiler/can"}
@ -23,6 +23,7 @@ roc_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"}
roc_module = {path = "../compiler/module"}
roc_repl_eval = {path = "../repl_eval"}
roc_packaging = {path = "../packaging"}
roc_reporting = {path = "../reporting"}
roc_std = {path = "../roc_std"}
roc_target = {path = "../compiler/roc_target"}

View file

@ -88,6 +88,7 @@ mod test {
use pretty_assertions::assert_eq;
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult, run_roc_dylib};
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use target_lexicon::Triple;
@ -124,6 +125,7 @@ mod test {
source,
src_dir.path().to_path_buf(),
Default::default(),
RocCacheDir::Disallowed,
load_config,
)
.unwrap();

View file

@ -1,5 +1,6 @@
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_test_utils::assert_multiline_str_eq;
#[cfg(not(feature = "wasm"))]

View file

@ -33,6 +33,7 @@ roc_parse = { path = "../compiler/parse" }
roc_target = { path = "../compiler/roc_target" }
roc_test_utils = { path = "../test_utils" }
roc_solve = { path = "../compiler/solve" }
roc_packaging = { path = "../packaging" }
pretty_assertions.workspace = true
indoc.workspace = true

View file

@ -1267,9 +1267,8 @@ fn to_bad_ident_expr_report<'b>(
lines.convert_region(surroundings),
lines.convert_region(region),
),
alloc.concat([alloc.reflow(
r"I recommend using camelCase, it is the standard in the Roc ecosystem.",
)]),
alloc.concat([alloc
.reflow(r"I recommend using camelCase. It's the standard style in Roc code!")]),
])
}

View file

@ -549,8 +549,8 @@ impl<'a> RocDocAllocator<'a> {
let this_line_number_length = line_number.len();
let line = self.src_lines[i as usize];
let rest_of_line = if !line.trim().is_empty() {
let is_line_empty = line.trim().is_empty();
let rest_of_line = if !is_line_empty {
self.text(line).indent(indent)
} else {
self.nil()
@ -572,11 +572,17 @@ impl<'a> RocDocAllocator<'a> {
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(rest_of_line)
} else {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
let up_to_gutter = self
.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(" "))
.append(rest_of_line)
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar));
if is_line_empty {
// Don't put an trailing space after the gutter
up_to_gutter
} else {
up_to_gutter.append(self.text(" ")).append(rest_of_line)
}
};
result = result.append(source_line);
@ -667,8 +673,8 @@ impl<'a> RocDocAllocator<'a> {
let this_line_number_length = line_number.len();
let line: &str = self.src_lines.get(i as usize).unwrap_or(&"");
let rest_of_line = if !line.trim().is_empty() {
let is_line_empty = line.trim().is_empty();
let rest_of_line = if !is_line_empty {
self.text(line)
.annotate(Annotation::CodeBlock)
.indent(indent)
@ -691,11 +697,17 @@ impl<'a> RocDocAllocator<'a> {
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(rest_of_line)
} else {
self.text(" ".repeat(max_line_number_length - this_line_number_length))
let up_to_gutter = self
.text(" ".repeat(max_line_number_length - this_line_number_length))
.append(self.text(line_number).annotate(Annotation::LineNumber))
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
.append(self.text(" "))
.append(rest_of_line)
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar));
if is_line_empty {
// Don't put an trailing space after the gutter
up_to_gutter
} else {
up_to_gutter.append(self.text(" ")).append(rest_of_line)
}
};
result = result.append(source_line);

View file

@ -15,6 +15,7 @@ mod test_reporting {
use roc_can::expr::PendingDerives;
use roc_load::{self, ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_region::all::LineInfo;
use roc_reporting::report::{
can_problem, parse_problem, type_problem, RenderTarget, Report, Severity, ANSI_STYLE_CODES,
@ -90,8 +91,13 @@ mod test_reporting {
threading: Threading::Single,
exec_mode: ExecutionMode::Check,
};
let result =
roc_load::load_and_typecheck(arena, full_file_path, exposed_types, load_config);
let result = roc_load::load_and_typecheck(
arena,
full_file_path,
exposed_types,
RocCacheDir::Disallowed,
load_config,
);
drop(file);
result
@ -4387,16 +4393,20 @@ mod test_reporting {
test_report!(
comment_with_tab,
"# comment with a \t\n4",
@r###"
TAB CHARACTER tmp/comment_with_tab/Test.roc
|golden| pretty_assertions::assert_eq!(
golden,
&format!(
r###"── TAB CHARACTER ─────────────────────────────── tmp/comment_with_tab/Test.roc ─
I encountered a tab character
I encountered a tab character
4 # comment with a
^
4 # comment with a {}
^
Tab characters are not allowed.
"###
Tab characters are not allowed."###,
"\t"
)
)
);
// TODO bad error message
@ -5675,23 +5685,27 @@ All branches in an `if` must have the same type!
main = 5 -> 3
"#
),
@r###"
UNKNOWN OPERATOR tmp/wild_case_arrow/Test.roc
|golden| pretty_assertions::assert_eq!(
golden,
&format!(
r###"── UNKNOWN OPERATOR ───────────────────────────── tmp/wild_case_arrow/Test.roc ─
This looks like an operator, but it's not one I recognize!
This looks like an operator, but it's not one I recognize!
1 app "test" provides [main] to "./platform"
2
3 main =
4 main = 5 -> 3
^^
1 app "test" provides [main] to "./platform"
2
3 main =
4 main = 5 -> 3
^^
Looks like you are trying to define a function.
Looks like you are trying to define a function.{}
In roc, functions are always written as a lambda, like
In roc, functions are always written as a lambda, like{}
increment = \n -> n + 1
"###
increment = \n -> n + 1"###,
' ', ' '
)
)
);
#[test]
@ -9933,16 +9947,21 @@ All branches in an `if` must have the same type!
f 1 _ 1
"#
),
@r###"
SYNTAX PROBLEM /code/proj/Main.roc
|golden| pretty_assertions::assert_eq!(
golden,
&format!(
r###"── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
Underscores are not allowed in identifier names:
Underscores are not allowed in identifier names:
6 f 1 _ 1
6 f 1 _ 1
{}
I recommend using camelCase, it is the standard in the Roc ecosystem.
"###
I recommend using camelCase. It's the standard style in Roc code!
"###,
" " // TODO make the reporter not insert extraneous spaces here in the first place!
),
)
);
test_report!(
@ -10355,7 +10374,7 @@ All branches in an `if` must have the same type!
u8 : [Good (List U8), Bad [DecodeProblem]]
fromBytes =
fromBytes =
when u8 is
Good _ _ ->
Ok "foo"
@ -10372,7 +10391,7 @@ All branches in an `if` must have the same type!
6> when u8 is
7 Good _ _ ->
8 Ok "foo"
9
9
10 Bad _ ->
11 Ok "foo"
@ -11115,7 +11134,7 @@ All branches in an `if` must have the same type!
indoc!(
r#"
x : U8
when x is
'☃' -> ""
_ -> ""
@ -11662,7 +11681,7 @@ All branches in an `if` must have the same type!
list_pattern_not_terminated,
indoc!(
r#"
when [] is
when [] is
[1, 2, -> ""
"#
),
@ -11683,7 +11702,7 @@ All branches in an `if` must have the same type!
list_pattern_weird_indent,
indoc!(
r#"
when [] is
when [] is
[1, 2,
3] -> ""
"#

View file

@ -19,7 +19,7 @@ path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../crates/roc_std" }
libc = "0.2"
reqwest = { version="0.11.11", default-features=false, features=["blocking", "rustls-tls"] }
backtrace = "0.3"
reqwest = { version="0.11.11", default-features=false, features=["blocking", "rustls-tls"] }
[workspace]

View file

@ -15,3 +15,5 @@ components = [
# for usages of rust-analyzer or similar tools inside `nix develop`
"rust-src"
]
# for test_wasm.sh
targets = ["wasm32-wasi"]

View file

@ -1 +1 @@
(built from source)
built-from-source