aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml11
-rw-r--r--Cargo.lock788
-rw-r--r--Cargo.toml29
-rw-r--r--LICENSE.md2
-rw-r--r--LICENSE.txt51
-rw-r--r--README.md21
-rw-r--r--src/args.rs230
-rw-r--r--src/main.rs22
-rw-r--r--src/makefile/command_line.rs91
-rw-r--r--src/makefile/conditional.rs4
-rw-r--r--src/makefile/eval_context.rs53
-rw-r--r--src/makefile/functions.rs549
-rw-r--r--src/makefile/inference_rules.rs138
-rw-r--r--src/makefile/input.rs635
-rw-r--r--src/makefile/lookup_internal.rs164
-rw-r--r--src/makefile/macro.rs323
-rw-r--r--src/makefile/macro_scope.rs229
-rw-r--r--src/makefile/mod.rs210
-rw-r--r--src/makefile/parse.rs19
-rw-r--r--src/makefile/target.rs20
-rw-r--r--src/makefile/token.rs64
-rw-r--r--tests/conditional_assignment_inheritance.rs29
-rw-r--r--tests/rule_specific_macros.rs85
-rw-r--r--tests/update_logic.rs14
-rw-r--r--tests/utils/mod.rs13
25 files changed, 2543 insertions, 1251 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f467b90..1c079af 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -75,14 +75,9 @@ full-test-xbps:
script:
- cargo install --path .
- cd
- - wget https://www.libarchive.org/downloads/libarchive-3.5.1.tar.xz
- - tar xf libarchive-*.tar.xz
- - cd libarchive-*/
- - ./configure
- - makers
- - makers install
- git clone https://github.com/void-linux/xbps.git ~/xbps
- cd ~/xbps
+ - apt update && apt install -y libarchive-dev
- ./configure
- makers
@@ -92,13 +87,13 @@ full-test-linux:
- cargo install --path .
- mkdir ~/linux
- cd ~/linux
- - wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.11.tar.xz
+ - wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.5.tar.xz
- tar xf linux-*.tar.xz
- cd linux-*/
- (makers -p 2>&1 || true) | grep 'Configuration file ".config" not found!'
- makers help
- makers mrproper
- - apt update && apt install -y flex bison
+ - apt update && apt install -y flex bison libelf-dev
- makers defconfig
- makers
allow_failure: true
diff --git a/Cargo.lock b/Cargo.lock
index 3d60803..e9cb31b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,10 +1,12 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
+version = 4
+
[[package]]
name = "addr2line"
-version = "0.14.1"
+version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
@@ -17,58 +19,70 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
-version = "0.7.15"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
-name = "ansi_term"
-version = "0.11.0"
+name = "anstream"
+version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
- "winapi",
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
]
[[package]]
-name = "arrayref"
-version = "0.3.6"
+name = "anstyle"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
-name = "arrayvec"
-version = "0.5.2"
+name = "anstyle-parse"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
[[package]]
-name = "atty"
-version = "0.2.14"
+name = "anstyle-query"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
+ "windows-sys 0.59.0",
]
[[package]]
-name = "autocfg"
-version = "1.0.1"
+name = "anstyle-wincon"
+version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.59.0",
+]
[[package]]
name = "backtrace"
-version = "0.3.56"
+version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
+ "cc",
"cfg-if",
"libc",
"miniz_oxide",
@@ -77,38 +91,18 @@ dependencies = [
]
[[package]]
-name = "base64"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
-
-[[package]]
name = "bitflags"
-version = "1.2.1"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
-name = "bitvec"
-version = "0.19.5"
+name = "cc"
+version = "1.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
+checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
dependencies = [
- "funty",
- "radium",
- "tap",
- "wyz",
-]
-
-[[package]]
-name = "blake2b_simd"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
-dependencies = [
- "arrayref",
- "arrayvec",
- "constant_time_eq",
+ "shlex",
]
[[package]]
@@ -119,24 +113,50 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
-version = "2.33.3"
+version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
- "ansi_term",
- "atty",
- "bitflags",
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
"strsim",
- "textwrap",
- "unicode-width",
- "vec_map",
+ "terminal_size",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
name = "color-eyre"
-version = "0.5.10"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b29030875fd8376e4a28ef497790d5b4a7843d8d1396bf08ce46f5eec562c5c"
+checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
dependencies = [
"backtrace",
"color-spantrace",
@@ -149,9 +169,9 @@ dependencies = [
[[package]]
name = "color-spantrace"
-version = "0.1.6"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1"
+checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
dependencies = [
"once_cell",
"owo-colors",
@@ -160,122 +180,109 @@ dependencies = [
]
[[package]]
-name = "constant_time_eq"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.3"
+name = "colorchoice"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
-dependencies = [
- "autocfg",
- "cfg-if",
- "lazy_static",
-]
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "dirs"
-version = "3.0.1"
+version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
-version = "0.3.5"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
+ "option-ext",
"redox_users",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
-name = "env_logger"
-version = "0.8.3"
+name = "env_filter"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
+checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
- "atty",
- "humantime",
"log",
"regex",
- "termcolor",
]
[[package]]
-name = "eyre"
-version = "0.6.5"
+name = "env_logger"
+version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
+checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
- "indenter",
- "once_cell",
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "humantime",
+ "log",
]
[[package]]
-name = "funty"
-version = "1.1.0"
+name = "errno"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
[[package]]
-name = "getrandom"
-version = "0.1.16"
+name = "eyre"
+version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
+ "indenter",
+ "once_cell",
]
[[package]]
+name = "fastrand"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
+
+[[package]]
name = "getrandom"
-version = "0.2.2"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.10.2+wasi-snapshot-preview1",
+ "wasi",
]
[[package]]
name = "gimli"
-version = "0.23.0"
+version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "glob"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "heck"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
-dependencies = [
- "unicode-segmentation",
-]
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.18"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
-dependencies = [
- "libc",
-]
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "humantime"
@@ -290,519 +297,498 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
-name = "jane-eyre"
-version = "0.3.0"
+name = "is_terminal_polyfill"
+version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ccd957c837e264dcbc6e75b58fcf85fd329a66e4836f820779c40563a2d0be0"
-dependencies = [
- "color-eyre",
-]
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
-name = "lexical-core"
-version = "0.7.5"
+name = "libc"
+version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
+checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "arrayvec",
"bitflags",
- "cfg-if",
- "ryu",
- "static_assertions",
+ "libc",
]
[[package]]
-name = "libc"
-version = "0.2.91"
+name = "linux-raw-sys"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
-version = "0.4.14"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "makers"
-version = "0.7.0"
+version = "0.8.0"
dependencies = [
+ "clap",
+ "color-eyre",
"dirs",
"env_logger",
"eyre",
"glob",
- "jane-eyre",
"lazy_static",
"log",
"nom",
"regex",
- "structopt",
+ "shlex",
"tempfile",
]
[[package]]
name = "memchr"
-version = "2.3.4"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.4.4"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
- "autocfg",
]
[[package]]
name = "nom"
-version = "6.1.2"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
- "bitvec",
- "funty",
- "lexical-core",
"memchr",
- "version_check",
+ "minimal-lexical",
]
[[package]]
name = "object"
-version = "0.23.0"
+version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "once_cell"
-version = "1.7.2"
+version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "owo-colors"
-version = "1.3.0"
+version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "pin-project-lite"
-version = "0.2.6"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
-name = "ppv-lite86"
-version = "0.2.10"
+name = "proc-macro2"
+version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "quote"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
- "proc-macro-error-attr",
"proc-macro2",
- "quote",
- "syn",
- "version_check",
]
[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+name = "redox_users"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
+ "getrandom",
+ "libredox",
+ "thiserror",
]
[[package]]
-name = "proc-macro2"
-version = "1.0.24"
+name = "regex"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
- "unicode-xid",
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
]
[[package]]
-name = "quote"
-version = "1.0.9"
+name = "regex-automata"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
- "proc-macro2",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
]
[[package]]
-name = "radium"
-version = "0.5.3"
+name = "regex-syntax"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
-name = "rand"
-version = "0.8.3"
+name = "rustc-demangle"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
-]
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
-name = "rand_chacha"
-version = "0.3.0"
+name = "rustix"
+version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [
- "ppv-lite86",
- "rand_core",
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "rand_core"
-version = "0.6.2"
+name = "sharded-slab"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
- "getrandom 0.2.2",
+ "lazy_static",
]
[[package]]
-name = "rand_hc"
-version = "0.3.0"
+name = "shlex"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
-dependencies = [
- "rand_core",
-]
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
-name = "redox_syscall"
-version = "0.1.57"
+name = "strsim"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
-name = "redox_syscall"
-version = "0.2.5"
+name = "syn"
+version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
- "bitflags",
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
]
[[package]]
-name = "redox_users"
-version = "0.3.5"
+name = "tempfile"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
- "getrandom 0.1.16",
- "redox_syscall 0.1.57",
- "rust-argon2",
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
]
[[package]]
-name = "regex"
-version = "1.4.5"
+name = "terminal_size"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
+checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
+ "rustix",
+ "windows-sys 0.59.0",
]
[[package]]
-name = "regex-syntax"
-version = "0.6.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
-
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
+name = "thiserror"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "winapi",
+ "thiserror-impl",
]
[[package]]
-name = "rust-argon2"
-version = "0.8.3"
+name = "thiserror-impl"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
- "base64",
- "blake2b_simd",
- "constant_time_eq",
- "crossbeam-utils",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
-name = "rustc-demangle"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
-
-[[package]]
-name = "ryu"
-version = "1.0.5"
+name = "thread_local"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
[[package]]
-name = "sharded-slab"
-version = "0.1.1"
+name = "tracing"
+version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
- "lazy_static",
+ "pin-project-lite",
+ "tracing-core",
]
[[package]]
-name = "static_assertions"
-version = "1.1.0"
+name = "tracing-core"
+version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
[[package]]
-name = "strsim"
-version = "0.8.0"
+name = "tracing-error"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
[[package]]
-name = "structopt"
-version = "0.3.21"
+name = "tracing-subscriber"
+version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
- "clap",
- "lazy_static",
- "structopt-derive",
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
]
[[package]]
-name = "structopt-derive"
-version = "0.4.14"
+name = "unicode-ident"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
-dependencies = [
- "heck",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
-name = "syn"
-version = "1.0.64"
+name = "utf8parse"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
-name = "tap"
-version = "1.0.1"
+name = "valuable"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
-name = "tempfile"
-version = "3.2.0"
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
-dependencies = [
- "cfg-if",
- "libc",
- "rand",
- "redox_syscall 0.2.5",
- "remove_dir_all",
- "winapi",
-]
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
-name = "termcolor"
-version = "1.1.2"
+name = "windows-sys"
+version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "winapi-util",
+ "windows-targets 0.48.5",
]
[[package]]
-name = "textwrap"
-version = "0.11.0"
+name = "windows-sys"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "unicode-width",
+ "windows-targets 0.52.6",
]
[[package]]
-name = "thread_local"
-version = "1.1.3"
+name = "windows-sys"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
- "once_cell",
+ "windows-targets 0.52.6",
]
[[package]]
-name = "tracing"
-version = "0.1.25"
+name = "windows-targets"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
- "cfg-if",
- "pin-project-lite",
- "tracing-attributes",
- "tracing-core",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
]
[[package]]
-name = "tracing-attributes"
-version = "0.1.15"
+name = "windows-targets"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
]
[[package]]
-name = "tracing-core"
-version = "0.1.17"
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
-dependencies = [
- "lazy_static",
-]
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
-name = "tracing-error"
-version = "0.1.2"
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
-dependencies = [
- "tracing",
- "tracing-subscriber",
-]
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
-name = "tracing-subscriber"
-version = "0.2.17"
+name = "windows_aarch64_msvc"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "705096c6f83bf68ea5d357a6aa01829ddbdac531b357b45abeca842938085baa"
-dependencies = [
- "sharded-slab",
- "thread_local",
- "tracing-core",
-]
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
-name = "unicode-segmentation"
-version = "1.7.1"
+name = "windows_aarch64_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
-name = "unicode-width"
-version = "0.1.8"
+name = "windows_i686_gnu"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
-name = "unicode-xid"
-version = "0.2.1"
+name = "windows_i686_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
-name = "vec_map"
-version = "0.8.2"
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
-name = "version_check"
-version = "0.9.3"
+name = "windows_i686_msvc"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
-name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
+name = "windows_i686_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
-name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+name = "windows_x86_64_gnu"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
-name = "winapi"
-version = "0.3.9"
+name = "windows_x86_64_gnu"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
-name = "winapi-util"
-version = "0.1.5"
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows_x86_64_msvc"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
-name = "wyz"
-version = "0.2.0"
+name = "windows_x86_64_msvc"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
index 60f9c04..4677d37 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "makers"
-version = "0.7.0"
+version = "0.8.0"
authors = ["boringcactus / Melody Horn <melody@boringcactus.com>"]
-edition = "2018"
+edition = "2021"
description = "a POSIX-compatible make implemented in Rust"
readme = "README.md"
repository = "https://code.boringcactus.com/makers/"
-license-file = "LICENSE.md"
+license-file = "LICENSE.txt"
keywords = ["build", "make"]
categories = ["development-tools"]
@@ -15,19 +15,20 @@ default = ['full']
full = ['dirs', 'glob']
[dependencies]
-dirs = { version = "3.0.1", optional = true }
-env_logger = "0.8.3"
-eyre = "0.6.5"
-glob = { version = "0.3.0", optional = true }
-jane-eyre = "0.3.0"
-lazy_static = "1.4.0"
-log = "0.4.14"
-nom = "6.1.2"
-regex = "1.4.5"
-structopt = "0.3.21"
+clap = { version = "4.5.20", features = ["derive", "wrap_help"] }
+color-eyre = "0.6.3"
+dirs = { version = "5.0.1", optional = true }
+env_logger = "0.11.5"
+eyre = "0.6.12"
+glob = { version = "0.3.1", optional = true }
+lazy_static = "1.5.0"
+log = "0.4.22"
+nom = "7.1.3"
+regex = "1.11.0"
+shlex = "1.3.0"
[dev-dependencies]
-tempfile = "3.2.0"
+tempfile = "3.13.0"
# [target.'cfg(unix)'.dependencies]
# signal-hook = "0.3.7"
diff --git a/LICENSE.md b/LICENSE.md
deleted file mode 100644
index 26874f0..0000000
--- a/LICENSE.md
+++ /dev/null
@@ -1,2 +0,0 @@
-Released under the [Indie Code Catalog Standard Deal](https://indiecc.com/deal/3.0.0),
-version 3.0.0 or later.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..5ad7e33
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,51 @@
+ ===========================
+ BIG BILL HELL'S LICENSE
+ ===========================
+
+ FUCK YOU, OPEN-SOURCE!
+
+If you're dumb enough to want to copy this software, you're a big enough
+schmuck to abide by BIG BILL HELL'S LICENSE (BBHL):
+
+| 1. BAD TERMS
+| 2. CODE THAT BREAKS DOWN
+| 3. THIEVES
+
+--------------
+1. BAD TERMS
+--------------
+
+If you think you're going to find permissive terms in BIG BILL'S, you can
+kiss my ASS. It's our belief that you're such a stupid motherfucker,
+you'll depend on this bullshit, GUARANTEED. If you make a derivative work:
+Shove it up your ugly ASS. You heard us right: Shove it up your ugly ASS.
+
+--------------------------
+2. CODE THAT BREAKS DOWN
+--------------------------
+
+Bring your CHANGE; bring your REPRO; bring your SPOUSE--we'll fuck them!
+That's right, we'll fuck your SPOUSE. Because under the BBHL, you're fucked
+six ways from Sunday.
+
+------------
+3. THIEVES
+------------
+
+Make a hack with BBHL, home of CHALLENGE GITTING. That's right:
+
+3.a. CHALLENGE GITTING
+----------------------
+How does it work? If you submit six PRs straight into main that don't need
+feedback, you get no Contributor License Agreement (CLA).
+
+
+DON'T SELL; DON'T DISTRIBUTE; DON'T FORK FROM US, OR WE'LL RIP YOUR BITS OFF.
+
+ONLY WITH BBHL: THE ONLY LICENSE THAT TELLS YOU TO FUCK OFF.
+
+HURRY UP, ASSHOLE, THESE TERMS TAKE EFFECT THE MINUTE YOU WRITE A LINE OF
+CODE, AND IT BETTER NOT BREAK, OR YOU'RE A DEAD MOTHERFUCKER.
+
+GO TO HELL. BIG BILL HELL'S LICENSE: OPEN-SOURCE'S FILTHIEST AND EXCLUSIVE
+TERMS OF THE MEANEST (SONS OF) BITCHES IN THE WHOLE COMMUNITY, GUARANTEED.
diff --git a/README.md b/README.md
index 83fb0de..933fe6c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A (mostly) [POSIX-compatible](https://pubs.opengroup.org/onlinepubs/9699919799/u
Not explicitly aiming for full support for [every GNU make feature](https://www.gnu.org/software/make/manual/html_node/index.html), but adding whichever features are strictly necessary to be compatible with existing GNUish makefiles.
You can get slightly more info by running with `RUST_LOG=makers=warn`, much more info with `RUST_LOG=makers=debug`, and an avalanche of info (probably only useful in small doses for debugging your makefile or makers itself) with `RUST_LOG=makers=trace`.
-For more specific configuration, see [the documentation for the `env_logger` Rust crate](https://docs.rs/env_logger/0.8.3/env_logger/).
+For more specific configuration, see [the documentation for the `env_logger` Rust crate](https://docs.rs/env_logger/0.11.5/env_logger/).
## conformance
@@ -31,12 +31,27 @@ specific projects tested:
list of features that are needed for version 1.0:
+- actual documentation
- tested with Linux
- unit tests for most of the things
- no TODOs left
## changelog
+### next - TBD
+
+- propagate command line macros through `MAKEFLAGS` for recursive calls
+
+### v0.8.0 - 2024-11-15
+
+- license is worse
+- implement `-C`/`--directory` flag
+- pass flags into recursive calls via `MAKEFLAGS` environment variable
+- implement GNU-style `export EGG=bug`
+- rebuild out-of-date makefiles (but not with as much environment preservation as GNU make provides)
+- implement GNU-style rule-specific macros `target: EGG=bug`
+- implement GNU-style `.SECONDEXPANSION` (but not properly deferred like in GNU make)
+
### v0.7.0 - 2021-04-13
- implement GNU-style functions `abspath`, `firstword`
@@ -95,9 +110,9 @@ list of features that are needed for version 1.0:
## license
-Released under the [Indie Code Catalog Standard Deal](https://indiecc.com/deal/3.0.0), version 3.0.0 or later.
+Released under [Big Bill Hell’s License](https://lifning.info/BBHL).
-Commercial licenses are available for purchase [through Indie Code Catalog](https://indiecc.com/~boringcactus/makers).
+Exceptions may be available on request.
## minuteæ
diff --git a/src/args.rs b/src/args.rs
index 9318202..8489c2d 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -1,17 +1,18 @@
use std::env;
use std::ffi::OsString;
use std::iter;
+use std::ops::AddAssign;
use std::path::PathBuf;
-use structopt::StructOpt;
+use clap::Parser;
-#[derive(StructOpt, Debug, PartialEq, Eq, Clone)]
-#[structopt(author, about)]
+#[derive(clap::Parser, Debug, PartialEq, Eq, Clone)]
+#[clap(author, about)]
#[allow(clippy::struct_excessive_bools)]
pub struct Args {
/// Cause environment variables, including those with null values, to override macro
/// assignments within makefiles.
- #[structopt(short, long)]
+ #[clap(short, long)]
pub environment_overrides: bool,
/// Specify a different makefile (or '-' for standard input).
@@ -21,12 +22,12 @@ pub struct Args {
/// be multiple instances of this option, and they shall be processed in the order
/// specified. The effect of specifying the same option-argument more than once is
/// unspecified.
- #[structopt(
- short = "f",
+ #[clap(
+ short = 'f',
long = "file",
visible_alias = "makefile",
number_of_values = 1,
- parse(from_os_str)
+ value_parser
)]
pub makefile: Vec<PathBuf>,
@@ -34,17 +35,17 @@ pub struct Args {
///
/// This mode is the same as if the special target .IGNORE were specified without
/// prerequisites.
- #[structopt(short, long)]
+ #[clap(short, long)]
pub ignore_errors: bool,
/// Continue to update other targets that do not depend on the current target if a
/// non-ignored error occurs while executing the commands to bring a target
/// up-to-date.
- #[structopt(
+ #[clap(
short,
long,
- overrides_with = "keep-going",
- overrides_with = "no-keep-going"
+ overrides_with = "keep_going",
+ overrides_with = "no_keep_going"
)]
pub keep_going: bool,
@@ -54,8 +55,8 @@ pub struct Args {
/// However, lines with a <plus-sign> ( '+' ) prefix shall be executed. In this mode,
/// lines with an at-sign ( '@' ) character prefix shall be written to standard
/// output.
- #[structopt(
- short = "n",
+ #[clap(
+ short = 'n',
long,
visible_alias = "just-print",
visible_alias = "recon"
@@ -66,7 +67,7 @@ pub struct Args {
/// descriptions.
///
/// The output format is unspecified.
- #[structopt(short, long, visible_alias = "print-data-base")]
+ #[clap(short, long, visible_alias = "print-data-base")]
pub print_everything: bool,
/// Return a zero exit value if the target file is up-to-date; otherwise, return an
@@ -75,11 +76,11 @@ pub struct Args {
/// Targets shall not be updated if this option is specified. However, a makefile
/// command line (associated with the targets) with a <plus-sign> ( '+' ) prefix
/// shall be executed.
- #[structopt(short, long)]
+ #[clap(short, long)]
pub question: bool,
/// Clear the suffix list and do not use the built-in rules.
- #[structopt(short = "r", long)]
+ #[clap(short = 'r', long)]
pub no_builtin_rules: bool,
/// Terminate make if an error occurs while executing the commands to bring a target
@@ -87,13 +88,13 @@ pub struct Args {
/// reason).
///
/// This shall be the default and the opposite of -k.
- #[structopt(
- short = "S",
+ #[clap(
+ short = 'S',
long,
visible_alias = "stop",
- hidden = true,
- overrides_with = "keep-going",
- overrides_with = "no-keep-going"
+ hide = true,
+ overrides_with = "keep_going",
+ overrides_with = "no_keep_going"
)]
pub no_keep_going: bool,
@@ -102,7 +103,7 @@ pub struct Args {
///
/// This mode shall be the same as if the special target .SILENT were specified
/// without prerequisites.
- #[structopt(short, long, visible_alias = "quiet")]
+ #[clap(short, long, visible_alias = "quiet")]
pub silent: bool,
/// Update the modification time of each target as though a touch target had been
@@ -113,14 +114,35 @@ pub struct Args {
/// target file indicating the name of the file and that it was touched. Normally,
/// the makefile command lines associated with each target are not executed. However,
/// a command line with a <plus-sign> ( '+' ) prefix shall be executed.
- #[structopt(short, long)]
+ #[clap(short, long)]
pub touch: bool,
/// Change to the given directory before running.
#[cfg(feature = "full")]
- #[structopt(short = "C", long, parse(from_os_str))]
+ #[clap(short = 'C', long, value_parser)]
pub directory: Option<PathBuf>,
+ /// Print the working directory when starting.
+ // TODO implement
+ // TODO automatically with -C or recursion or decide that this is a bad GNU feature
+ #[cfg(feature = "full")]
+ #[clap(
+ short = 'w',
+ long,
+ overrides_with = "print_directory",
+ overrides_with = "no_print_directory"
+ )]
+ pub print_directory: bool,
+
+ /// Do not print the working directory when starting, even when running with -C or recursively.
+ #[cfg(feature = "full")]
+ #[clap(
+ long,
+ overrides_with = "print_directory",
+ overrides_with = "no_print_directory"
+ )]
+ pub no_print_directory: bool,
+
/// Target names or macro definitions.
///
/// If no target is specified, while make is processing the makefiles, the first
@@ -137,14 +159,13 @@ impl Args {
// POSIX spec says "Any options specified in the MAKEFLAGS environment variable
// shall be evaluated before any options specified on the make utility command
// line."
- // TODO allow macro definitions in MAKEFLAGS
// POSIX says we have to accept
// > The characters are option letters without the leading <hyphen-minus>
// > characters or <blank> separation used on a make utility command line.
let makeflags_given = !env_makeflags.is_empty();
let makeflags_spaces = env_makeflags.contains(' ');
let makeflags_leading_dash = env_makeflags.starts_with('-');
- let makeflags_has_equals = env_makeflags.starts_with('=');
+ let makeflags_has_equals = env_makeflags.contains('=');
let makeflags_obviously_full =
makeflags_spaces || makeflags_leading_dash || makeflags_has_equals;
let env_makeflags = if makeflags_given && !makeflags_obviously_full {
@@ -152,16 +173,17 @@ impl Args {
} else {
env_makeflags
};
- let env_makeflags = env_makeflags.split_whitespace().map(OsString::from);
+ let env_makeflags = shlex::split(&env_makeflags)
+ .expect("Bad args?")
+ .into_iter()
+ .map(OsString::from);
// per the structopt docs, the first argument will be used as the binary name,
// so we need to make sure it goes in before MAKEFLAGS
let arg_0 = args.next().unwrap_or_else(|| env!("CARGO_PKG_NAME").into());
- let args = iter::once(arg_0)
- .chain(env_makeflags.into_iter())
- .chain(args);
+ let args = iter::once(arg_0).chain(env_makeflags).chain(args);
- Self::from_iter(args)
+ Self::parse_from(args)
}
pub fn from_env_and_args() -> Self {
@@ -192,37 +214,66 @@ impl Args {
}
pub fn makeflags(&self) -> String {
- let mut result = String::new();
+ let mut flags = String::new();
if self.environment_overrides {
- result.push('e');
+ flags.push('e');
}
if self.ignore_errors {
- result.push('i');
+ flags.push('i');
}
if self.keep_going {
- result.push('k');
+ flags.push('k');
}
if self.dry_run {
- result.push('n');
+ flags.push('n');
}
if self.print_everything {
- result.push('p');
+ flags.push('p');
}
if self.question {
- result.push('q');
+ flags.push('q');
}
if self.no_builtin_rules {
- result.push('r');
+ flags.push('r');
}
if self.no_keep_going {
- result.push('S');
+ flags.push('S');
}
if self.silent {
- result.push('s');
+ flags.push('s');
}
if self.touch {
- result.push('t');
+ flags.push('t');
}
+
+ let macros = self
+ .targets_or_macros
+ .iter()
+ .map(|x| shlex::try_quote(x).expect("Bad quoting?"))
+ .filter(|x| x.contains('='))
+ .collect::<Vec<_>>()
+ .join(" ");
+
+ let mut result = String::new();
+ if !flags.is_empty() && !macros.is_empty() {
+ result.push('-');
+ }
+ result.add_assign(&flags);
+
+ #[cfg(feature = "full")]
+ if self.no_print_directory {
+ if !result.is_empty() {
+ result.push(' ');
+ }
+ result.push_str("--no-print-directory");
+ }
+
+ // TODO consider -- to separate flags from macros - GNU does it but it would require
+ // gnarly splicing to not override recursive macros
+ if !result.is_empty() && !macros.is_empty() {
+ result += " ";
+ }
+ result.add_assign(&macros);
result
}
}
@@ -232,6 +283,12 @@ mod test {
use super::*;
#[test]
+ fn clap_validate() {
+ use clap::CommandFactory;
+ Args::command().debug_assert();
+ }
+
+ #[test]
fn no_args() {
let args: Vec<OsString> = vec!["makers".into()];
let args = Args::from_given_args_and_given_env(args.into_iter(), String::new());
@@ -251,6 +308,10 @@ mod test {
touch: false,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
targets_or_macros: vec![],
}
);
@@ -279,6 +340,10 @@ mod test {
touch: true,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
targets_or_macros: vec!["bar".into(), "baz=yeet".into()],
}
);
@@ -307,6 +372,10 @@ mod test {
touch: false,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
targets_or_macros: vec![],
}
);
@@ -335,6 +404,43 @@ mod test {
touch: false,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
+ targets_or_macros: vec![],
+ }
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "full")]
+ fn print_directory_wrestling() {
+ let args = "makers -w --no-print-directory -w -w --no-print-directory --no-print-directory -w --no-print-directory";
+ let args = Args::from_given_args_and_given_env(
+ args.split_whitespace().map(OsString::from),
+ String::new(),
+ );
+ assert_eq!(
+ args,
+ Args {
+ environment_overrides: false,
+ makefile: vec![],
+ ignore_errors: false,
+ keep_going: false,
+ dry_run: false,
+ print_everything: false,
+ question: false,
+ no_builtin_rules: false,
+ no_keep_going: false,
+ silent: false,
+ touch: false,
+ #[cfg(feature = "full")]
+ directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: true,
targets_or_macros: vec![],
}
);
@@ -361,6 +467,10 @@ mod test {
touch: false,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
targets_or_macros: vec![],
}
);
@@ -387,6 +497,10 @@ mod test {
touch: false,
#[cfg(feature = "full")]
directory: None,
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
targets_or_macros: vec![],
}
);
@@ -394,7 +508,7 @@ mod test {
#[test]
fn nightmare() {
- let makeflags = "-nrs -k foo=bar";
+ let makeflags = "-nrs -k -- foo=bar";
let args = "makers -eipqtSf foo -f bruh bar baz=yeet";
let args = Args::from_given_args_and_given_env(
args.split_whitespace().map(OsString::from),
@@ -416,8 +530,36 @@ mod test {
touch: true,
#[cfg(feature = "full")]
directory: None,
- targets_or_macros: vec!["foo=bar".into(), "bar".into(), "baz=yeet".into()],
+ #[cfg(feature = "full")]
+ print_directory: false,
+ #[cfg(feature = "full")]
+ no_print_directory: false,
+ targets_or_macros: vec!["bar".into(), "baz=yeet".into(), "foo=bar".into()],
}
);
+ assert_eq!(args.makeflags(), "-einpqrSst -- baz=yeet foo=bar");
+ }
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn makeflags_no_print_directory() {
+ let args = Args {
+ environment_overrides: false,
+ makefile: vec![],
+ ignore_errors: false,
+ keep_going: false,
+ dry_run: false,
+ print_everything: false,
+ question: false,
+ no_builtin_rules: false,
+ no_keep_going: false,
+ silent: false,
+ touch: false,
+ directory: None,
+ print_directory: false,
+ no_print_directory: true,
+ targets_or_macros: vec!["V=1".into()],
+ };
+ assert_eq!(args.makeflags(), "--no-print-directory 'V=1'");
}
}
diff --git a/src/main.rs b/src/main.rs
index 8fa4efd..0b3e9f7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,7 @@
clippy::nursery,
clippy::str_to_string,
clippy::unwrap_used,
- clippy::integer_arithmetic,
+ clippy::arithmetic_side_effects,
clippy::panic,
clippy::unimplemented,
clippy::todo,
@@ -32,7 +32,7 @@ mod args;
mod makefile;
use args::Args;
-use makefile::{Makefile, MakefileReader};
+use makefile::{MacroScopeStack, MacroSet, Makefile, MakefileReader};
const DEFAULT_PATHS: &[&str] = &[
#[cfg(feature = "full")]
@@ -43,7 +43,7 @@ const DEFAULT_PATHS: &[&str] = &[
fn main() -> Result<()> {
env_logger::init();
- jane_eyre::install()?;
+ color_eyre::install()?;
let mut args = Args::from_env_and_args();
#[cfg(feature = "full")]
@@ -66,15 +66,16 @@ fn main() -> Result<()> {
let mut makefile = Makefile::new(&args);
let paths = Default::default();
for filename in &args.makefile {
+ let stack = MacroScopeStack::default().with_scope(&makefile.macros);
if filename == &PathBuf::from("-") {
- let macros = makefile.macros.with_overlay();
- let file = MakefileReader::read(&args, macros, stdin().lock(), "-", Rc::clone(&paths))?
- .finish();
+ let macros = MacroSet::new();
+ let file =
+ MakefileReader::read(&args, stack, macros, stdin().lock(), "-", Rc::clone(&paths))?
+ .finish();
makefile.extend(file)?;
} else {
- let macros = makefile.macros.with_overlay();
let file =
- MakefileReader::read_file(&args, macros, filename, Rc::clone(&paths))?.finish();
+ MakefileReader::read_file(&args, stack, filename, Rc::clone(&paths))?.finish();
makefile.extend(file)?;
};
}
@@ -92,9 +93,8 @@ fn main() -> Result<()> {
for outdated in makefiles_outdated {
eprintln!("makefile {} out of date, rebuilding", outdated);
makefile.update_target(&outdated)?;
- let macros = makefile.macros.with_overlay();
- let file =
- MakefileReader::read_file(&args, macros, &outdated, Default::default())?.finish();
+ let stack = MacroScopeStack::default().with_scope(&makefile.macros);
+ let file = MakefileReader::read_file(&args, stack, &outdated, Default::default())?.finish();
// TODO forget the stale data
// TODO reread all the things, not just this one
makefile.extend(file)?;
diff --git a/src/makefile/command_line.rs b/src/makefile/command_line.rs
index 7d4915d..9641941 100644
--- a/src/makefile/command_line.rs
+++ b/src/makefile/command_line.rs
@@ -3,9 +3,12 @@ use std::fmt;
use std::process::{Command, ExitStatus};
use eyre::{bail, Error};
+#[cfg(feature = "full")]
use lazy_static::lazy_static;
+#[cfg(feature = "full")]
use regex::Regex;
+#[cfg(feature = "full")]
use super::r#macro::Set as MacroSet;
use super::target::Target;
use super::token::{Token, TokenString};
@@ -15,7 +18,7 @@ use super::Makefile;
fn execute_command_line(
command_line: &str,
ignore_errors: bool,
- macros: &MacroSet,
+ #[cfg(feature = "full")] macros: &MacroSet,
) -> Result<ExitStatus, Error> {
let (program, args) = if cfg!(windows) {
let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into());
@@ -33,50 +36,18 @@ fn execute_command_line(
let mut command = Command::new(program);
command.args(args);
#[cfg(feature = "full")]
- command.envs(macros.resolve_exports()?);
+ command.envs(macros.resolve_exports::<&[u8]>(None)?);
Ok(command.status()?)
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CommandLine {
- /// If the command prefix contains a <hyphen-minus>, or the -i option is present, or
- /// the special target .IGNORE has either the current target as a prerequisite or has
- /// no prerequisites, any error found while executing the command shall be ignored.
- ignore_errors: bool,
- /// If the command prefix contains an at-sign and the make utility command line -n
- /// option is not specified, or the -s option is present, or the special target
- /// .SILENT has either the current target as a prerequisite or has no prerequisites,
- /// the command shall not be written to standard output before it is executed.
- silent: bool,
- /// If the command prefix contains a <plus-sign>, this indicates a makefile command
- /// line that shall be executed even if -n, -q, or -t is specified.
- always_execute: bool,
execution_line: TokenString,
}
impl CommandLine {
- pub fn from(mut line: TokenString) -> Self {
- let mut ignore_errors = false;
- let mut silent = false;
- let mut always_execute = false;
-
- if let Token::Text(text) = line.first_token_mut() {
- let mut text_chars = text.chars().peekable();
- while let Some(x) = text_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
- match x {
- '-' => ignore_errors = true,
- '@' => silent = true,
- '+' => always_execute = true,
- _ => unreachable!(),
- }
- }
- *text = text_chars.collect();
- }
-
+ pub const fn from(line: TokenString) -> Self {
Self {
- ignore_errors,
- silent,
- always_execute,
execution_line: line,
}
}
@@ -91,14 +62,13 @@ impl CommandLine {
#[cfg(feature = "full")]
{
let is_just_one_macro_expansion = self.execution_line.tokens().count() == 1
- && self.execution_line.tokens().all(|x| match x {
- Token::MacroExpansion { .. } => true,
- Token::FunctionCall { .. } => true,
- _ => false,
+ && self.execution_line.tokens().all(|x| {
+ matches!(x, Token::MacroExpansion { .. } | Token::FunctionCall { .. })
});
// unfortunately, if we had a multiline macro somewhere with non-escaped newlines, now we have to run each of them as separate lines
lazy_static! {
- static ref UNESCAPED_NEWLINE: Regex = Regex::new(r"([^\\])\n").unwrap();
+ static ref UNESCAPED_NEWLINE: Regex = #[allow(clippy::unwrap_used)]
+ Regex::new(r"([^\\])\n").unwrap();
}
if is_just_one_macro_expansion && UNESCAPED_NEWLINE.is_match(&execution_line) {
let lines = UNESCAPED_NEWLINE
@@ -111,28 +81,31 @@ impl CommandLine {
}
}
log::trace!("executing {}", &execution_line);
- let mut self_ignore_errors = self.ignore_errors;
- let mut self_silent = self.silent;
- let mut self_always_execute = self.always_execute;
+ let mut ignore_errors = false;
+ let mut silent = false;
+ let mut always_execute = false;
- // apparently some makefiles will just throw this shit in in macros? bruh moment tbh
+ // sometimes this is defined in macros rather than statically
let execution_line: String = {
- let mut line_chars = execution_line.chars().peekable();
+ let mut line_chars = execution_line
+ .chars()
+ .skip_while(char::is_ascii_whitespace)
+ .peekable();
while let Some(x) = line_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
match x {
- '-' => self_ignore_errors = true,
- '@' => self_silent = true,
- '+' => self_always_execute = true,
+ '-' => ignore_errors = true,
+ '@' => silent = true,
+ '+' => always_execute = true,
_ => unreachable!(),
}
}
line_chars.collect()
};
- let ignore_error = self_ignore_errors
+ let ignore_error = ignore_errors
|| file.args.ignore_errors
|| file.special_target_has_prereq(".IGNORE", &target.name);
- let silent = (self_silent && !file.args.dry_run)
+ let silent = (silent && !file.args.dry_run)
|| file.args.silent
|| file.special_target_has_prereq(".SILENT", &target.name);
@@ -140,14 +113,19 @@ impl CommandLine {
println!("{}", execution_line);
}
- let should_execute = self_always_execute
+ let should_execute = always_execute
|| is_recursive
|| !(file.args.dry_run || file.args.question || file.args.touch);
if !should_execute {
return Ok(());
}
- let return_value = execute_command_line(&execution_line, ignore_error, &file.macros);
+ let return_value = execute_command_line(
+ &execution_line,
+ ignore_error,
+ #[cfg(feature = "full")]
+ &file.macros,
+ );
let errored = return_value.map_or(true, |status| !status.success());
if errored {
// apparently there was an error. do we care?
@@ -162,15 +140,6 @@ impl CommandLine {
impl fmt::Display for CommandLine {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if self.ignore_errors {
- write!(f, "-")?;
- }
- if self.silent {
- write!(f, "@")?;
- }
- if self.always_execute {
- write!(f, "+")?;
- }
let execution_line = format!("{}", &self.execution_line);
let execution_line = execution_line.replace("\n", "↵\n");
write!(f, "{}", execution_line)?;
diff --git a/src/makefile/conditional.rs b/src/makefile/conditional.rs
index 98400e6..6eed14a 100644
--- a/src/makefile/conditional.rs
+++ b/src/makefile/conditional.rs
@@ -99,7 +99,7 @@ fn decode_condition_args(line_body: &str) -> Option<(TokenString, TokenString)>
impl Line {
pub fn from(
line: &str,
- expand_macro: impl Fn(&TokenString) -> Result<String>,
+ mut expand_macro: impl FnMut(&TokenString) -> Result<String>,
) -> Result<Option<Self>> {
let line = line.trim_start();
Ok(Some(if let Some(line) = line.strip_prefix("ifeq ") {
@@ -134,7 +134,7 @@ impl Line {
&self,
current_state: Option<&State>,
is_macro_defined: impl Fn(&str) -> bool,
- expand_macro: impl Fn(&TokenString) -> Result<String>,
+ mut expand_macro: impl FnMut(&TokenString) -> Result<String>,
) -> Result<StateAction> {
Ok(match self {
Self::IfEqual(arg1, arg2) => {
diff --git a/src/makefile/eval_context.rs b/src/makefile/eval_context.rs
new file mode 100644
index 0000000..87edafd
--- /dev/null
+++ b/src/makefile/eval_context.rs
@@ -0,0 +1,53 @@
+use eyre::{Result, WrapErr};
+use std::io::{BufRead, Cursor};
+use std::rc::Rc;
+
+use super::{FinishedMakefileReader, MacroSet, MakefileReader};
+
+pub struct DeferredEvalContext<'parent, 'args, 'grandparent, R: BufRead> {
+ parent: &'parent MakefileReader<'args, 'grandparent, R>,
+ children: Vec<FinishedMakefileReader>,
+}
+
+impl<'parent, 'args, 'grandparent, R: BufRead>
+ DeferredEvalContext<'parent, 'args, 'grandparent, R>
+{
+ pub const fn new(parent: &'parent MakefileReader<'args, 'grandparent, R>) -> Self {
+ Self {
+ parent,
+ children: Vec::new(),
+ }
+ }
+
+ pub fn push(&mut self, child: FinishedMakefileReader) {
+ self.children.push(child);
+ }
+
+ pub fn eval(&mut self, to_eval: String) -> Result<()> {
+ let child_stack = self.parent.stack.with_scope(&self.parent.macros);
+ let child_macros = MacroSet::new();
+ let child = MakefileReader::read(
+ self.parent.args,
+ child_stack,
+ child_macros,
+ Cursor::new(to_eval),
+ "<eval>",
+ Rc::clone(&self.parent.file_names),
+ )
+ .context("while evaling")?
+ .finish();
+ self.push(child);
+ Ok(())
+ }
+}
+
+impl<'parent, 'args, 'grandparent, R: BufRead> IntoIterator
+ for DeferredEvalContext<'parent, 'args, 'grandparent, R>
+{
+ type Item = FinishedMakefileReader;
+ type IntoIter = std::vec::IntoIter<Self::Item>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.children.into_iter()
+ }
+}
diff --git a/src/makefile/functions.rs b/src/makefile/functions.rs
index a78d582..a6a2db1 100644
--- a/src/makefile/functions.rs
+++ b/src/makefile/functions.rs
@@ -1,128 +1,142 @@
-use std::cell::RefCell;
use std::env;
+use std::io::BufRead;
use std::process::{Command, Stdio};
-use std::rc::Rc;
use eyre::{bail, Result, WrapErr};
+use super::eval_context::DeferredEvalContext;
use super::pattern::r#match;
-use super::r#macro::{Macro, Set as MacroSet};
-use super::token::TokenString;
-use super::ItemSource;
+use super::r#macro::Macro;
+use super::{ItemSource, MacroScopeStack, MacroSet, TokenString};
+pub const NO_EVAL: Option<&mut DeferredEvalContext<&[u8]>> = None;
+
+#[allow(clippy::cognitive_complexity)]
pub fn expand_call(
name: &str,
args: &[TokenString],
- macros: &MacroSet,
- to_eval: Option<Rc<RefCell<Vec<String>>>>,
+ stack: &MacroScopeStack,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
match name {
"subst" => {
assert_eq!(args.len(), 3);
- text::subst(macros, &args[0], &args[1], &args[2])
+ text::subst(stack, &args[0], &args[1], &args[2], eval_context)
}
"patsubst" => {
assert_eq!(args.len(), 3);
- text::patsubst(macros, &args[0], &args[1], &args[2])
+ text::patsubst(stack, &args[0], &args[1], &args[2], eval_context)
}
"strip" => {
assert_eq!(args.len(), 1);
- text::strip(macros, &args[0])
+ text::strip(stack, &args[0], eval_context)
}
"findstring" => {
assert_eq!(args.len(), 2);
- text::findstring(macros, &args[0], &args[1])
+ text::findstring(stack, &args[0], &args[1], eval_context)
}
"filter" => {
assert_eq!(args.len(), 2);
- text::filter(macros, &args[0], &args[1])
+ text::filter(stack, &args[0], &args[1], eval_context)
}
"filter-out" => {
assert_eq!(args.len(), 2);
- text::filter_out(macros, &args[0], &args[1])
+ text::filter_out(stack, &args[0], &args[1], eval_context)
}
"sort" => {
assert_eq!(args.len(), 1);
- text::sort(macros, &args[0])
+ text::sort(stack, &args[0], eval_context)
}
"word" => {
assert_eq!(args.len(), 2);
- text::word(macros, &args[0], &args[1])
+ text::word(stack, &args[0], &args[1], eval_context)
}
"words" => {
assert_eq!(args.len(), 1);
- text::words(macros, &args[0])
+ text::words(stack, &args[0], eval_context)
}
"firstword" => {
assert_eq!(args.len(), 1);
- text::firstword(macros, &args[0])
+ text::firstword(stack, &args[0], eval_context)
}
"lastword" => {
assert_eq!(args.len(), 1);
- text::lastword(macros, &args[0])
+ text::lastword(stack, &args[0], eval_context)
}
"dir" => {
assert_eq!(args.len(), 1);
- file_name::dir(macros, &args[0])
+ file_name::dir(stack, &args[0], eval_context)
}
"notdir" => {
assert_eq!(args.len(), 1);
- file_name::notdir(macros, &args[0])
+ file_name::notdir(stack, &args[0], eval_context)
}
"basename" => {
assert_eq!(args.len(), 1);
- file_name::basename(macros, &args[0])
+ file_name::basename(stack, &args[0], eval_context)
}
"addsuffix" => {
assert_eq!(args.len(), 2);
- file_name::addsuffix(macros, &args[0], &args[1])
+ file_name::addsuffix(stack, &args[0], &args[1], eval_context)
}
"addprefix" => {
assert_eq!(args.len(), 2);
- file_name::addprefix(macros, &args[0], &args[1])
+ file_name::addprefix(stack, &args[0], &args[1], eval_context)
}
"wildcard" => {
assert_eq!(args.len(), 1);
- file_name::wildcard(macros, &args[0])
+ file_name::wildcard(stack, &args[0], eval_context)
}
"realpath" => {
assert_eq!(args.len(), 1);
- file_name::realpath(macros, &args[0])
+ file_name::realpath(stack, &args[0], eval_context)
}
"abspath" => {
assert_eq!(args.len(), 1);
- file_name::abspath(macros, &args[0])
+ file_name::abspath(stack, &args[0], eval_context)
}
"if" => {
assert!(args.len() == 2 || args.len() == 3);
- conditional::r#if(macros, &args[0], &args[1], args.get(2))
+ conditional::r#if(stack, &args[0], &args[1], args.get(2), eval_context)
}
"or" => {
assert!(!args.is_empty());
- conditional::or(macros, args.iter())
+ conditional::or(stack, args.iter(), eval_context)
}
"and" => {
assert!(!args.is_empty());
- conditional::and(macros, args.iter())
+ conditional::and(stack, args.iter(), eval_context)
+ }
+ "intcmp" => {
+ assert!(2 <= args.len() && args.len() <= 5);
+ conditional::intcmp(
+ stack,
+ &args[0],
+ &args[1],
+ args.get(2),
+ args.get(3),
+ args.get(4),
+ eval_context,
+ )
}
"foreach" => {
assert_eq!(args.len(), 3);
- foreach(macros, &args[0], &args[1], &args[2])
+ foreach(stack, &args[0], &args[1], &args[2], eval_context)
}
"call" => {
assert!(!args.is_empty());
- call(macros, args.iter())
+ call(stack, args.iter(), eval_context)
}
"eval" => {
assert_eq!(args.len(), 1);
- let should_eval = eval(macros, &args[0])?;
- if let Some(to_eval) = to_eval {
- to_eval.borrow_mut().push(should_eval);
+ let should_eval = eval(stack, &args[0], eval_context.as_deref_mut())?;
+ if let Some(eval_context) = eval_context {
+ eval_context.eval(should_eval)?;
} else {
bail!("tried to eval something but no eval back-channel was available");
}
@@ -131,17 +145,17 @@ pub fn expand_call(
"origin" => {
assert_eq!(args.len(), 1);
- origin(macros, &args[0])
+ origin(stack, &args[0], eval_context)
}
"error" => {
assert_eq!(args.len(), 1);
- meta::error(macros, &args[0])
+ meta::error(stack, &args[0], eval_context)
}
"shell" => {
assert_eq!(args.len(), 1);
- shell(macros, &args[0])
+ shell(stack, &args[0], eval_context)
}
// fallback
@@ -154,54 +168,58 @@ mod text {
use super::*;
pub fn subst(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
from: &TokenString,
to: &TokenString,
text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let from = macros.expand(from)?;
- let to = macros.expand(to)?;
- let text = macros.expand(text)?;
+ let from = stack.expand(from, eval_context.as_deref_mut())?;
+ let to = stack.expand(to, eval_context.as_deref_mut())?;
+ let text = stack.expand(text, eval_context)?;
Ok(text.replace(&from, &to))
}
pub fn patsubst(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
from: &TokenString,
to: &TokenString,
text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let from = macros.expand(from)?;
- let to = macros.expand(to)?;
- let text = macros.expand(text)?;
- let words = text
- .split_whitespace()
- .map(|word| {
- let pattern_match = r#match(&from, word)?.and_then(|x| x.get(1));
- Ok(if let Some(pm) = pattern_match {
- to.replace('%', pm.as_str())
- } else {
- word.to_owned()
+ let from = stack.expand(from, eval_context.as_deref_mut())?;
+ let to = stack.expand(to, eval_context.as_deref_mut())?;
+ let text = stack.expand(text, eval_context)?;
+ let words =
+ text.split_whitespace()
+ .map(|word| {
+ let pattern_match = r#match(&from, word)?.and_then(|x| x.get(1));
+ Ok(pattern_match
+ .map_or_else(|| word.to_owned(), |pm| to.replace('%', pm.as_str())))
})
- })
- .collect::<Result<Vec<_>>>()?;
+ .collect::<Result<Vec<_>>>()?;
Ok(words.join(" "))
}
- pub fn strip(macros: &MacroSet, text: &TokenString) -> Result<String> {
- let text = macros.expand(text)?;
+ pub fn strip(
+ stack: &MacroScopeStack,
+ text: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let text = stack.expand(text, eval_context)?;
// TODO don't allocate this vec
let words = text.split_whitespace().collect::<Vec<_>>();
Ok(words.join(" "))
}
pub fn findstring(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
needle: &TokenString,
haystack: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let needle = macros.expand(needle)?;
- let haystack = macros.expand(haystack)?;
+ let needle = stack.expand(needle, eval_context.as_deref_mut())?;
+ let haystack = stack.expand(haystack, eval_context)?;
if haystack.contains(&needle) {
Ok(needle)
} else {
@@ -209,10 +227,15 @@ mod text {
}
}
- pub fn filter(macros: &MacroSet, patterns: &TokenString, text: &TokenString) -> Result<String> {
- let patterns = macros.expand(patterns)?;
+ pub fn filter(
+ stack: &MacroScopeStack,
+ patterns: &TokenString,
+ text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let patterns = stack.expand(patterns, eval_context.as_deref_mut())?;
let patterns = patterns.split_whitespace().collect::<Vec<_>>();
- let text = macros.expand(text)?;
+ let text = stack.expand(text, eval_context)?;
let text = text.split_whitespace();
let mut result_pieces = vec![];
for word in text {
@@ -227,13 +250,14 @@ mod text {
}
pub fn filter_out(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
patterns: &TokenString,
text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let patterns = macros.expand(patterns)?;
+ let patterns = stack.expand(patterns, eval_context.as_deref_mut())?;
let patterns = patterns.split_whitespace().collect::<Vec<_>>();
- let text = macros.expand(text)?;
+ let text = stack.expand(text, eval_context)?;
let text = text.split_whitespace();
let mut result_pieces = vec![];
for word in text {
@@ -247,18 +271,27 @@ mod text {
Ok(result_pieces.join(" "))
}
- pub fn sort(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn sort(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
let mut words = words.split_whitespace().collect::<Vec<_>>();
words.sort_unstable();
words.dedup();
Ok(words.join(" "))
}
- pub fn word(macros: &MacroSet, n: &TokenString, text: &TokenString) -> Result<String> {
- let n = macros.expand(n)?;
+ pub fn word(
+ stack: &MacroScopeStack,
+ n: &TokenString,
+ text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let n = stack.expand(n, eval_context.as_deref_mut())?;
let n: usize = n.parse().wrap_err("while calling `word`")?;
- let text = macros.expand(text)?;
+ let text = stack.expand(text, eval_context)?;
Ok(text
.split_whitespace()
.nth(n.saturating_add(1))
@@ -266,18 +299,30 @@ mod text {
.to_owned())
}
- pub fn words(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn words(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
Ok(words.split_whitespace().count().to_string())
}
- pub fn firstword(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
- Ok(words.split_whitespace().nth(0).unwrap_or("").to_owned())
+ pub fn firstword(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
+ Ok(words.split_whitespace().next().unwrap_or("").to_owned())
}
- pub fn lastword(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn lastword(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
Ok(words.split_whitespace().last().unwrap_or("").to_owned())
}
}
@@ -287,14 +332,19 @@ mod file_name {
use std::env;
use std::ffi::OsStr;
use std::fs;
+ use std::io::BufRead;
use std::path::{Path, MAIN_SEPARATOR};
- use eyre::WrapErr;
-
use super::*;
+ use crate::makefile::eval_context::DeferredEvalContext;
+ use eyre::WrapErr;
- pub fn dir(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn dir(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
let words = words
.split_whitespace()
.map(|word| {
@@ -309,8 +359,12 @@ mod file_name {
Ok(words.join(" "))
}
- pub fn notdir(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn notdir(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
let words = words
.split_whitespace()
.map(|word| {
@@ -323,8 +377,12 @@ mod file_name {
Ok(words.join(" "))
}
- pub fn basename(macros: &MacroSet, words: &TokenString) -> Result<String> {
- let words = macros.expand(words)?;
+ pub fn basename(
+ stack: &MacroScopeStack,
+ words: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let words = stack.expand(words, eval_context)?;
let words = words
.split_whitespace()
.map(|word| {
@@ -338,12 +396,13 @@ mod file_name {
}
pub fn addsuffix(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
suffix: &TokenString,
targets: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let suffix = macros.expand(suffix)?;
- let targets = macros.expand(targets)?;
+ let suffix = stack.expand(suffix, eval_context.as_deref_mut())?;
+ let targets = stack.expand(targets, eval_context)?;
let results = targets
.split_whitespace()
.map(|t| format!("{}{}", t, suffix))
@@ -352,12 +411,13 @@ mod file_name {
}
pub fn addprefix(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
prefix: &TokenString,
targets: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let prefix = macros.expand(prefix)?;
- let targets = macros.expand(targets)?;
+ let prefix = stack.expand(prefix, eval_context.as_deref_mut())?;
+ let targets = stack.expand(targets, eval_context)?;
let results = targets
.split_whitespace()
.map(|t| format!("{}{}", prefix, t))
@@ -365,8 +425,12 @@ mod file_name {
Ok(results.join(" "))
}
- pub fn wildcard(macros: &MacroSet, pattern: &TokenString) -> Result<String> {
- let pattern = macros.expand(pattern)?;
+ pub fn wildcard(
+ stack: &MacroScopeStack,
+ pattern: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let pattern = stack.expand(pattern, eval_context)?;
let home_dir = env::var("HOME")
.ok()
.or_else(|| dirs::home_dir().and_then(|p| p.to_str().map(String::from)));
@@ -385,8 +449,12 @@ mod file_name {
Ok(results.join(" "))
}
- pub fn realpath(macros: &MacroSet, targets: &TokenString) -> Result<String> {
- let targets = macros.expand(targets)?;
+ pub fn realpath(
+ stack: &MacroScopeStack,
+ targets: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let targets = stack.expand(targets, eval_context)?;
let results = targets
.split_whitespace()
.map(|x| {
@@ -398,43 +466,52 @@ mod file_name {
Ok(results.join(" "))
}
- pub fn abspath(macros: &MacroSet, targets: &TokenString) -> Result<String> {
+ pub fn abspath(
+ stack: &MacroScopeStack,
+ targets: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
// TODO don't resolve symlinks
- realpath(macros, targets)
+ realpath(stack, targets, eval_context)
}
}
// Functions for Conditionals
mod conditional {
+ use std::borrow::Cow;
+ use std::cmp::Ordering;
+
use super::*;
+ use eyre::eyre;
pub fn r#if(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
condition: &TokenString,
if_true: &TokenString,
if_false: Option<&TokenString>,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
let mut condition = condition.clone();
condition.trim_start();
condition.trim_end();
- let condition = macros.expand(&condition)?;
+ let condition = stack.expand(&condition, eval_context.as_deref_mut())?;
if condition.is_empty() {
- if let Some(if_false) = if_false {
- macros.expand(if_false)
- } else {
- Ok(String::new())
- }
+ if_false.map_or_else(
+ || Ok(String::new()),
+ |if_false| stack.expand(if_false, eval_context),
+ )
} else {
- macros.expand(if_true)
+ stack.expand(if_true, eval_context)
}
}
pub fn or<'a>(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
args: impl Iterator<Item = &'a TokenString>,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
for arg in args {
- let arg = macros.expand(arg)?;
+ let arg = stack.expand(arg, eval_context.as_deref_mut())?;
if !arg.is_empty() {
return Ok(arg);
}
@@ -443,33 +520,80 @@ mod conditional {
}
pub fn and<'a>(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
args: impl Iterator<Item = &'a TokenString>,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
let mut last = String::new();
for arg in args {
- last = macros.expand(arg)?;
+ last = stack.expand(arg, eval_context.as_deref_mut())?;
if last.is_empty() {
return Ok(String::new());
}
}
Ok(last)
}
+
+ pub fn intcmp<'a>(
+ stack: &MacroScopeStack,
+ lhs: &TokenString,
+ rhs: &TokenString,
+ lt_part: Option<&TokenString>,
+ eq_part: Option<&TokenString>,
+ gt_part: Option<&TokenString>,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let raw_lhs_value = stack.expand(lhs, eval_context.as_deref_mut())?;
+ let raw_rhs_value = stack.expand(rhs, eval_context.as_deref_mut())?;
+ let lhs_value: i64 = raw_lhs_value.parse()?;
+ let rhs_value: i64 = raw_rhs_value.parse()?;
+ let cmp = lhs_value.cmp(&rhs_value);
+
+ // defaults are a bit of a mess
+ let mut lt_part = lt_part.map(Cow::Borrowed);
+ let mut eq_part = eq_part.map(Cow::Borrowed);
+ let mut gt_part = gt_part.map(Cow::Borrowed);
+ if lt_part.is_none() && eq_part.is_none() && gt_part.is_none() {
+ lt_part = Some(Cow::Owned(TokenString::empty()));
+ // not just reusing lhs param since expansion could have a side effect
+ eq_part = Some(Cow::Owned(TokenString::text(raw_lhs_value)));
+ gt_part = Some(Cow::Owned(TokenString::empty()));
+ }
+ if eq_part.is_none() {
+ eq_part = Some(Cow::Owned(TokenString::empty()));
+ }
+ if gt_part.is_none() {
+ gt_part = eq_part.clone();
+ }
+
+ let lt_part = lt_part.ok_or_else(|| eyre!("intcmp defaults failed"))?;
+ let eq_part = eq_part.ok_or_else(|| eyre!("intcmp defaults failed"))?;
+ let gt_part = gt_part.ok_or_else(|| eyre!("intcmp defaults failed"))?;
+
+ let result = match cmp {
+ Ordering::Less => lt_part,
+ Ordering::Equal => eq_part,
+ Ordering::Greater => gt_part,
+ };
+
+ stack.expand(&result, eval_context.as_deref_mut())
+ }
}
pub fn foreach(
- macros: &MacroSet,
+ stack: &MacroScopeStack,
var: &TokenString,
list: &TokenString,
text: &TokenString,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
) -> Result<String> {
- let var = macros.expand(var)?;
- let list = macros.expand(list)?;
+ let var = stack.expand(var, eval_context.as_deref_mut())?;
+ let list = stack.expand(list, eval_context.as_deref_mut())?;
let words = list.split_whitespace();
- let mut macros = macros.with_overlay();
let results = words
.map(|word| {
+ let mut macros = MacroSet::new();
macros.set(
var.clone(),
Macro {
@@ -479,20 +603,26 @@ pub fn foreach(
eagerly_expanded: false,
},
);
- macros.expand(text)
+ stack
+ .with_scope(&macros)
+ .expand(text, eval_context.as_deref_mut())
})
.collect::<Result<Vec<_>, _>>()?;
Ok(results.join(" "))
}
-pub fn call<'a>(macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>) -> Result<String> {
+pub fn call<'a>(
+ stack: &MacroScopeStack,
+ args: impl Iterator<Item = &'a TokenString>,
+ mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+) -> Result<String> {
let args = args
- .map(|arg| macros.expand(arg))
+ .map(|arg| stack.expand(arg, eval_context.as_deref_mut()))
.collect::<Result<Vec<_>, _>>()?;
let function = args[0].clone();
// TODO if function is a builtin, call the builtin instead
- let mut macros = macros.with_overlay();
+ let mut macros = MacroSet::new();
for (i, x) in args.into_iter().enumerate() {
macros.set(
i.to_string(),
@@ -504,31 +634,49 @@ pub fn call<'a>(macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>)
},
);
}
- macros.expand(&TokenString::r#macro(function))
+ stack
+ .with_scope(&macros)
+ .expand(&TokenString::r#macro(function), eval_context)
}
// TODO consider bringing eval logic in here since we put the Vec in MacroSet IIRC
-pub fn eval(macros: &MacroSet, arg: &TokenString) -> Result<String> {
- macros.expand(arg)
+pub fn eval(
+ stack: &MacroScopeStack,
+ arg: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+) -> Result<String> {
+ stack.expand(arg, eval_context)
}
-pub fn origin(macros: &MacroSet, variable: &TokenString) -> Result<String> {
- let variable = macros.expand(variable)?;
- Ok(macros.origin(&variable).to_owned())
+pub fn origin(
+ stack: &MacroScopeStack,
+ variable: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+) -> Result<String> {
+ let variable = stack.expand(variable, eval_context)?;
+ Ok(stack.origin(&variable).to_owned())
}
mod meta {
use super::*;
- pub fn error(macros: &MacroSet, text: &TokenString) -> Result<String> {
- let text = macros.expand(text)?;
+ pub fn error(
+ stack: &MacroScopeStack,
+ text: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+ ) -> Result<String> {
+ let text = stack.expand(text, eval_context)?;
bail!("{}", text);
}
}
-pub fn shell(macros: &MacroSet, command: &TokenString) -> Result<String> {
+pub fn shell(
+ stack: &MacroScopeStack,
+ command: &TokenString,
+ eval_context: Option<&mut DeferredEvalContext<impl BufRead>>,
+) -> Result<String> {
// TODO bring this in from command_line
- let command = macros.expand(command)?;
+ let command = stack.expand(command, eval_context)?;
let (program, args) = if cfg!(windows) {
let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into());
let args = vec!["/c", &command];
@@ -557,7 +705,8 @@ mod test {
type R = Result<()>;
fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> Result<String> {
- super::expand_call(name, args, macros, None)
+ let stack = MacroScopeStack::default().with_scope(macros);
+ expand_call(name, args, &stack, NO_EVAL)
}
macro_rules! call {
@@ -766,6 +915,148 @@ mod test {
}
#[test]
+ fn intcmp() -> R {
+ assert_eq!(
+ call(
+ "intcmp",
+ &[TokenString::text("1"), TokenString::text("2")],
+ &MacroSet::new()
+ )?,
+ ""
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[TokenString::text("2"), TokenString::text("2")],
+ &MacroSet::new()
+ )?,
+ "2"
+ );
+
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("1"),
+ TokenString::text("2"),
+ TokenString::text("a")
+ ],
+ &MacroSet::new()
+ )?,
+ "a"
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("2"),
+ TokenString::text("2"),
+ TokenString::text("a")
+ ],
+ &MacroSet::new()
+ )?,
+ ""
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("3"),
+ TokenString::text("2"),
+ TokenString::text("a")
+ ],
+ &MacroSet::new()
+ )?,
+ ""
+ );
+
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("1"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b")
+ ],
+ &MacroSet::new()
+ )?,
+ "a"
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("2"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b")
+ ],
+ &MacroSet::new()
+ )?,
+ "b"
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("3"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b")
+ ],
+ &MacroSet::new()
+ )?,
+ "b"
+ );
+
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("1"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b"),
+ TokenString::text("c")
+ ],
+ &MacroSet::new()
+ )?,
+ "a"
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("2"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b"),
+ TokenString::text("c")
+ ],
+ &MacroSet::new()
+ )?,
+ "b"
+ );
+ assert_eq!(
+ call(
+ "intcmp",
+ &[
+ TokenString::text("3"),
+ TokenString::text("2"),
+ TokenString::text("a"),
+ TokenString::text("b"),
+ TokenString::text("c")
+ ],
+ &MacroSet::new()
+ )?,
+ "c"
+ );
+
+ Ok(())
+ }
+
+ #[test]
fn foreach() -> R {
let mut macros = MacroSet::new();
macros.set(
diff --git a/src/makefile/inference_rules.rs b/src/makefile/inference_rules.rs
index 368d72b..f841797 100644
--- a/src/makefile/inference_rules.rs
+++ b/src/makefile/inference_rules.rs
@@ -1,18 +1,18 @@
-use std::fmt;
+use std::collections::HashMap;
+use std::{fmt, mem};
-use eyre::{eyre, Result};
-use regex::Captures;
-
-use super::command_line::CommandLine;
use super::pattern::r#match;
-use super::ItemSource;
+use super::{CommandLine, ItemSource, MacroSet};
+use eyre::{eyre, OptionExt, Result};
+use regex::Captures;
-#[derive(PartialEq, Eq, Clone, Debug)]
+#[derive(Clone, Debug)]
pub struct InferenceRule {
pub source: ItemSource,
pub products: Vec<String>,
pub prerequisites: Vec<String>,
pub commands: Vec<CommandLine>,
+ pub macros: MacroSet,
}
impl InferenceRule {
@@ -22,18 +22,23 @@ impl InferenceRule {
s1: String,
s2: String,
commands: Vec<CommandLine>,
+ macros: MacroSet,
) -> Self {
Self {
source,
products: vec![format!("%{}", s1)],
prerequisites: vec![format!("%{}", s2)],
commands,
+ macros,
}
}
pub fn first_match<'s, 't: 's>(&'s self, target_name: &'t str) -> Result<Option<Captures<'t>>> {
self.products
.iter()
+ // TODO find a better way to make the self_subdir_match test pass
+ .flat_map(|pattern| [pattern.strip_prefix("./"), Some(pattern.as_str())])
+ .filter_map(|x| x)
.map(|pattern| r#match(pattern, target_name))
.try_fold(None, |x, y| y.map(|y| x.or(y)))
}
@@ -49,12 +54,39 @@ impl InferenceRule {
let capture = self
.first_match(target_name)?
.ok_or_else(|| eyre!("asked non-matching inference rule for prerequisites"))?;
- let percent_expansion = capture.get(1).expect("should've matched the %").as_str();
+ let percent_expansion = capture
+ .get(1)
+ .ok_or_eyre("should've matched the %")?
+ .as_str();
Ok(self
.prerequisites
.iter()
.map(move |p| p.replace('%', percent_expansion)))
}
+
+ fn extend(&mut self, other: Self) {
+ assert_eq!(&self.products, &other.products);
+ match (self.commands.is_empty(), other.commands.is_empty()) {
+ (false, false) => {
+ // both rules have commands, so replace this entirely
+ *self = other;
+ }
+ (true, false) => {
+ // this rule doesn't have commands, but the other one does,
+ // so it's the real one
+ let mut other = other;
+ mem::swap(self, &mut other);
+ self.extend(other);
+ }
+ (false, true) | (true, true) => {
+ // this rule might have commands, but the other one doesn't,
+ // so append non-command stuff
+ // TODO decide something smart about sources
+ self.prerequisites.extend(other.prerequisites);
+ self.macros.extend(other.macros);
+ }
+ }
+ }
}
impl fmt::Display for InferenceRule {
@@ -72,11 +104,68 @@ impl fmt::Display for InferenceRule {
}
}
+#[derive(Clone, Default)]
+pub struct InferenceRuleSet {
+ /// Maps from products to a map from prerequisites to rules.
+ data: HashMap<Vec<String>, HashMap<Vec<String>, InferenceRule>>,
+}
+
+impl InferenceRuleSet {
+ pub fn get(&self, products: &[String], prerequisites: &[String]) -> Option<&InferenceRule> {
+ self.data.get(products).and_then(|x| x.get(prerequisites))
+ }
+
+ fn get_mut(
+ &mut self,
+ products: &[String],
+ prerequisites: &[String],
+ ) -> Option<&mut InferenceRule> {
+ self.data
+ .get_mut(products)
+ .and_then(|x| x.get_mut(prerequisites))
+ }
+
+ pub fn put(&mut self, rule: InferenceRule) {
+ if let Some(existing_rule) = self.get_mut(&rule.products, &rule.prerequisites) {
+ existing_rule.extend(rule);
+ } else {
+ self.data
+ .entry(rule.products.clone())
+ .or_default()
+ .insert(rule.prerequisites.clone(), rule);
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ pub fn extend(&mut self, other: Self) {
+ for other in other.data.into_values().flat_map(HashMap::into_values) {
+ self.put(other);
+ }
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = &InferenceRule> {
+ self.data.values().flat_map(HashMap::values)
+ }
+}
+
+impl From<Vec<InferenceRule>> for InferenceRuleSet {
+ fn from(value: Vec<InferenceRule>) -> Self {
+ let mut result = Self::default();
+ for rule in value {
+ result.put(rule);
+ }
+ result
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
- type R = eyre::Result<()>;
+ type R = Result<()>;
#[test]
fn suffix_match() -> R {
@@ -85,6 +174,7 @@ mod test {
".o".to_owned(),
".c".to_owned(),
vec![],
+ MacroSet::new(),
);
assert!(rule.matches("foo.o")?);
assert!(rule.matches("dir/foo.o")?);
@@ -104,8 +194,38 @@ mod test {
"goneall.gpg".to_owned(),
],
commands: vec![],
+ macros: MacroSet::new(),
};
assert!(rule.matches("licenseListPublisher-2.2.1.jar-valid")?);
Ok(())
}
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn subdir_match() -> R {
+ let rule = InferenceRule {
+ source: ItemSource::Builtin,
+ products: vec!["a/%.o".to_owned()],
+ prerequisites: vec!["a/%.c".to_owned()],
+ commands: vec![],
+ macros: MacroSet::new(),
+ };
+ assert!(rule.matches("a/foo.o")?);
+ Ok(())
+ }
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn self_subdir_match() -> R {
+ let rule = InferenceRule {
+ source: ItemSource::Builtin,
+ products: vec!["./%.o".to_owned()],
+ prerequisites: vec!["./%.c".to_owned()],
+ commands: vec![],
+ macros: MacroSet::new(),
+ };
+ assert!(rule.matches("foo.o")?);
+ assert!(rule.matches("./foo.o")?);
+ Ok(())
+ }
}
diff --git a/src/makefile/input.rs b/src/makefile/input.rs
index c2f8e7b..345f465 100644
--- a/src/makefile/input.rs
+++ b/src/makefile/input.rs
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fs::File;
-use std::io::{BufRead, BufReader, Cursor, Error as IoError, ErrorKind as IoErrorKind, Lines};
+use std::io::{BufRead, BufReader, Error as IoError, ErrorKind as IoErrorKind, Lines};
use std::iter::Peekable;
use std::path::Path;
use std::rc::Rc;
@@ -13,19 +13,24 @@ use regex::Regex;
use crate::args::Args;
-use super::command_line::CommandLine;
#[cfg(feature = "full")]
use super::conditional::{Line as ConditionalLine, State as ConditionalState};
-use super::inference_rules::InferenceRule;
+#[cfg(feature = "full")]
+use super::eval_context::DeferredEvalContext;
+use super::parse::{MacroAssignment, MacroAssignmentOutcome};
#[cfg(feature = "full")]
use super::r#macro::ExportConfig;
-use super::r#macro::{Macro, Set as MacroSet};
-use super::target::{StaticTargetSet, Target};
-use super::token::{Token, TokenString};
-use super::ItemSource;
+use super::r#macro::Macro;
+use super::target::StaticTargetSet;
+use super::{
+ builtin_targets, CommandLine, InferenceRule, InferenceRuleSet, ItemSource, LookupInternal,
+ MacroScopeStack, MacroSet, Target, TokenString,
+};
enum LineType {
Rule,
+ #[cfg(feature = "full")]
+ RuleMacro,
Macro,
Include,
#[cfg(feature = "full")]
@@ -52,32 +57,31 @@ impl LineType {
if line_tokens.starts_with("unexport ") || line_tokens == "unexport" {
return Self::Unexport;
}
- for token in line_tokens.tokens() {
- if let Token::Text(text) = token {
- let colon_idx = text.find(':');
- #[cfg(not(feature = "full"))]
- let equals_idx = text.find('=');
+ let colon_idx = line_tokens.find(":");
+ #[cfg(not(feature = "full"))]
+ let equals_idx = line_tokens.find("=");
+ #[cfg(feature = "full")]
+ let equals_idx = ["=", ":=", "::=", "?=", "+="]
+ .iter()
+ .filter_map(|p| line_tokens.find(p))
+ .min();
+ match (colon_idx, equals_idx) {
+ (Some(_), None) => {
+ return Self::Rule;
+ }
+ (Some(c), Some(e)) if c < e => {
#[cfg(feature = "full")]
- let equals_idx = ["=", ":=", "::=", "?=", "+="]
- .iter()
- .filter_map(|p| text.find(p))
- .min();
- match (colon_idx, equals_idx) {
- (Some(_), None) => {
- return Self::Rule;
- }
- (Some(c), Some(e)) if c < e => {
- return Self::Rule;
- }
- (None, Some(_)) => {
- return Self::Macro;
- }
- (Some(c), Some(e)) if e <= c => {
- return Self::Macro;
- }
- _ => {}
- }
+ return Self::RuleMacro;
+ #[cfg(not(feature = "full"))]
+ return Self::Rule;
}
+ (None, Some(_)) => {
+ return Self::Macro;
+ }
+ (Some(c), Some(e)) if e <= c => {
+ return Self::Macro;
+ }
+ _ => {}
}
Self::Unknown
}
@@ -94,9 +98,11 @@ fn inference_match<'a>(
prerequisites: &[String],
) -> Option<InferenceMatch<'a>> {
lazy_static! {
- static ref INFERENCE_RULE: Regex =
- Regex::new(r"^(?P<s2>(\.[^/.]+)?)(?P<s1>\.[^/.]+)$").unwrap();
- static ref SPECIAL_TARGET: Regex = Regex::new(r"^\.[A-Z]+$").unwrap();
+ static ref INFERENCE_RULE: Regex = #[allow(clippy::unwrap_used)]
+ Regex::new(r"^(?P<s2>(\.[^/.]+)?)(?P<s1>\.[^/.]+)$")
+ .unwrap();
+ static ref SPECIAL_TARGET: Regex = #[allow(clippy::unwrap_used)]
+ Regex::new(r"^\.[A-Z]+$").unwrap();
}
let inference_match = INFERENCE_RULE.captures(targets[0]);
@@ -107,6 +113,7 @@ fn inference_match<'a>(
&& inference_match.is_some()
&& special_target_match.is_none();
if inference_rule {
+ #[allow(clippy::unwrap_used)]
inference_match.map(|x| InferenceMatch {
s1: x.name("s1").unwrap().as_str(),
s2: x.name("s2").unwrap().as_str(),
@@ -126,7 +133,7 @@ where
E: StdError + Send + Sync + 'static,
Inner: Iterator<Item = Result<T, E>>,
{
- fn new(inner: Inner) -> Self {
+ const fn new(inner: Inner) -> Self {
Self(inner, 0)
}
}
@@ -181,30 +188,33 @@ impl Default for NextLineSettings {
pub struct MakefileReader<'a, 'parent, R: BufRead> {
file_name: String,
- pub inference_rules: Vec<InferenceRule>,
- pub macros: MacroSet<'parent, 'static>,
+ pub inference_rules: InferenceRuleSet,
+ pub stack: MacroScopeStack<'parent>,
+ pub macros: MacroSet,
pub targets: StaticTargetSet,
built_in_targets: HashMap<String, Target>,
pub first_non_special_target: Option<String>,
pub failed_includes: Vec<String>,
- args: &'a Args,
+ pub args: &'a Args,
lines_iter: Peekable<LineNumbers<String, IoError, Lines<R>>>,
// join with escaped_newline_replacement to get the actual line
pending_line: Option<(usize, Vec<String>)>,
#[cfg(feature = "full")]
conditional_stack: Vec<ConditionalState>,
- file_names: Rc<RefCell<Vec<String>>>,
+ pub file_names: Rc<RefCell<Vec<String>>>,
}
impl<'a, 'parent> MakefileReader<'a, 'parent, BufReader<File>> {
pub fn read_file(
args: &'a Args,
- mut macros: MacroSet<'parent, 'static>,
+ stack: MacroScopeStack<'parent>,
path: impl AsRef<Path>,
file_names: Rc<RefCell<Vec<String>>>,
) -> Result<Self> {
+ let mut macros = MacroSet::new();
#[cfg(feature = "full")]
- if let Some(mut old_makefile_list) = macros.pop("MAKEFILE_LIST") {
+ if let Some(old_makefile_list) = stack.get("MAKEFILE_LIST") {
+ let mut old_makefile_list = old_makefile_list.into_owned();
old_makefile_list.text.extend(TokenString::text(format!(
" {}",
path.as_ref().to_string_lossy()
@@ -227,14 +237,15 @@ impl<'a, 'parent> MakefileReader<'a, 'parent, BufReader<File>> {
// TODO handle errors
let file = file.context("couldn't open makefile!")?;
let file_reader = BufReader::new(file);
- Self::read(args, macros, file_reader, file_name, file_names)
+ Self::read(args, stack, macros, file_reader, file_name, file_names)
}
}
impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
pub fn read(
args: &'a Args,
- macros: MacroSet<'parent, 'static>,
+ stack: MacroScopeStack<'parent>,
+ macros: MacroSet,
source: R,
name: impl Into<String>,
file_names: Rc<RefCell<Vec<String>>>,
@@ -242,7 +253,8 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
let name = name.into();
let mut reader = Self {
file_name: name.clone(),
- inference_rules: Vec::new(),
+ inference_rules: InferenceRuleSet::default(),
+ stack,
macros,
targets: Default::default(),
built_in_targets: HashMap::new(),
@@ -257,18 +269,10 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
};
// TODO be smart about this instead, please
if !args.no_builtin_rules {
- reader.built_in_targets.insert(
- ".SUFFIXES".to_owned(),
- Target {
- name: ".SUFFIXES".into(),
- prerequisites: vec![".o", ".c", ".y", ".l", ".a", ".sh", ".f"]
- .into_iter()
- .map(String::from)
- .collect(),
- commands: vec![],
- stem: None,
- already_updated: Cell::new(false),
- },
+ reader.built_in_targets.extend(
+ builtin_targets()
+ .into_iter()
+ .map(|target| (target.name.clone(), target)),
);
}
reader
@@ -300,30 +304,12 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
let line_type = LineType::of(&line_tokens);
// before we actually test it, see if it's only visible after expanding macros
- let (line_tokens, line_type) = if let LineType::Unknown = line_type {
+ let (line_tokens, line_type) = if matches!(line_type, LineType::Unknown) {
let line_tokens = TokenString::text(
self.expand_macros(&line_tokens)
.wrap_err_with(|| format!("while parsing line {}", line_number))?
.trim(),
);
- // and let's eval whatever bullshit needs evaling
- #[cfg(feature = "full")]
- {
- let eval = self.macros.to_eval.take();
- for eval in eval {
- let child_macros = self.macros.with_overlay();
- let child = MakefileReader::read(
- self.args,
- child_macros,
- Cursor::new(eval),
- "<eval>",
- Rc::clone(&self.file_names),
- )
- .context("while evaling")?
- .finish();
- self.extend(child);
- }
- }
let line_type = LineType::of(&line_tokens);
(line_tokens, line_type)
} else {
@@ -339,6 +325,16 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
)
})?;
}
+ #[cfg(feature = "full")]
+ LineType::RuleMacro => {
+ self.read_rule_macro(line_tokens, line_number)
+ .wrap_err_with(|| {
+ format!(
+ "while parsing rule-specific macro definition starting on line {}",
+ line_number
+ )
+ })?;
+ }
LineType::Macro => {
self.read_macro(line_tokens, line_number)
.wrap_err_with(|| {
@@ -363,8 +359,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
} else {
let exported = if line_tokens.contains_text("=") {
// that's an assignment!
- let new_macro = self.read_macro(line_tokens, line_number)?;
- new_macro
+ self.read_macro(line_tokens, line_number)?
} else {
self.expand_macros(&line_tokens)?
};
@@ -399,7 +394,8 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
fn next_line(&mut self, settings: NextLineSettings) -> Option<(usize, Result<String>)> {
lazy_static! {
- static ref COMMENT: Regex = Regex::new(r"(^|[^\\])#.*$").unwrap();
+ static ref COMMENT: Regex = #[allow(clippy::unwrap_used)]
+ Regex::new(r"(^|[^\\])#.*$").unwrap();
}
let escaped_newline_replacement = settings.escaped_newline_replacement;
if let Some((line_number, line)) = self.pending_line.take() {
@@ -443,15 +439,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
Ok(x) => x,
Err(err) => return Some((n, Err(err))),
};
- let line = if settings.strip_comments {
- COMMENT
- .replace(&line, "$1")
- .replace(r"\#", "#")
- .trim_end()
- .to_owned()
- } else {
- line
- };
+ // TODO strip comments if it's correct to
line_pieces.push(line.trim_start().to_owned());
}
}
@@ -466,15 +454,19 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
Err(err) => return Some((line_number, Err(err))),
};
if let Some(line) = cond_line {
+ let mut deferred_eval_context = DeferredEvalContext::new(self);
let action = line
.action(
self.conditional_stack.last(),
- |name| self.macros.is_defined(name),
- |t| self.expand_macros(t),
+ |name| self.stack.with_scope(&self.macros).is_defined(name),
+ |t| self.expand_macros_deferred_eval(t, &mut deferred_eval_context),
)
.wrap_err_with(|| {
format!("while applying conditional on line {}", line_number)
});
+ for child in deferred_eval_context {
+ self.extend(child);
+ }
let action = match action {
Ok(x) => x,
Err(err) => return Some((line_number, Err(err))),
@@ -520,17 +512,13 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
}
fn special_target_has_prereq(&self, target: &str, name: &str, empty_counts: bool) -> bool {
- match self
- .targets
+ self.targets
.get(target)
.or_else(|| self.built_in_targets.get(target))
- {
- Some(target) => {
+ .map_or(false, |target| {
(empty_counts && target.prerequisites.is_empty())
|| target.prerequisites.iter().any(|e| e == name)
- }
- None => false,
- }
+ })
}
fn read_include(&mut self, mut line_tokens: TokenString, line_number: usize) -> Result<()> {
@@ -545,10 +533,10 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
// handles arbitrarily many filenames, and it's not like that's more work
for field in fields {
log::trace!("{}:{}: including {}", &self.file_name, line_number, field);
- let child_macros = self.macros.with_overlay();
+ let child_stack = self.stack.with_scope(&self.macros);
let child = MakefileReader::read_file(
self.args,
- child_macros,
+ child_stack,
field,
Rc::clone(&self.file_names),
)
@@ -605,45 +593,39 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
None => (not_targets, vec![]),
};
if prerequisites.contains_text("=") {
- log::error!("rule-specific macros are not implemented yet");
- return Ok(());
+ bail!("handling rule-specific macro as rule");
}
+ #[cfg(feature = "full")]
+ let mut deferred_eval_context = DeferredEvalContext::new(self);
let prerequisites = self
- .macros
- .with_lookup(&|macro_name: &str| {
- let macro_pieces = if macro_name.starts_with('@') {
- // The $@ shall evaluate to the full target name of the
- // current target.
- targets.iter()
- } else {
- bail!("unknown internal macro")
- };
-
- let macro_pieces = if macro_name.ends_with('D') {
- macro_pieces
- .map(|x| {
- Path::new(x)
- .parent()
- .ok_or_else(|| eyre!("no parent"))
- .map(|x| x.to_string_lossy().into())
- })
- .collect::<Result<Vec<String>, _>>()?
- } else if macro_name.ends_with('F') {
- macro_pieces
- .map(|x| {
- Path::new(x)
- .file_name()
- .ok_or_else(|| eyre!("no filename"))
- .map(|x| x.to_string_lossy().into())
- })
- .collect::<Result<Vec<String>, _>>()?
- } else {
- macro_pieces.map(|&x| x.to_owned()).collect::<Vec<String>>()
- };
-
- Ok(macro_pieces.join(" "))
- })
- .expand(&prerequisites)?;
+ .stack
+ .with_scope(&self.macros)
+ .with_scope(&LookupInternal::new_partial(&targets))
+ .expand(
+ &prerequisites,
+ #[cfg(feature = "full")]
+ Some(&mut deferred_eval_context),
+ )?;
+ // https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html
+ // this is supposed to be deferred but maybe i can get away with this
+ // TODO move this to run at runtime
+ #[cfg(feature = "full")]
+ let prerequisites = if self.targets.has(".SECONDEXPANSION") {
+ self.stack
+ .with_scope(&self.macros)
+ .with_scope(&LookupInternal::new_partial(&targets))
+ .expand(
+ &prerequisites.parse()?,
+ #[cfg(feature = "full")]
+ Some(&mut deferred_eval_context),
+ )?
+ } else {
+ prerequisites
+ };
+ #[cfg(feature = "full")]
+ for child in deferred_eval_context {
+ self.extend(child);
+ }
let prerequisites = prerequisites
.split_whitespace()
.map(|x| x.into())
@@ -697,6 +679,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
products: targets.into_iter().map(|x| x.to_owned()).collect(),
prerequisites,
commands,
+ macros: MacroSet::new(),
};
if let Some(static_targets) = static_targets {
@@ -712,12 +695,14 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
.first_match(real_target)?
.and_then(|x| x.get(1).map(|x| x.as_str().to_owned())),
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
};
self.targets.put(new_target);
}
}
} else {
- self.inference_rules.push(new_rule);
+ log::debug!("pattern-based inference rule defined: {:?}", &new_rule,);
+ self.inference_rules.put(new_rule);
}
return Ok(());
}
@@ -753,20 +738,17 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
inference_match.s1.to_owned(),
inference_match.s2.to_owned(),
commands,
+ MacroSet::new(),
);
- log::trace!(
+ log::debug!(
"suffix-based inference rule defined by {:?} - {:?}",
&inference_match,
&new_rule,
);
- self.inference_rules.retain(|existing_rule| {
- (&existing_rule.prerequisites, &existing_rule.products)
- != (&new_rule.prerequisites, &new_rule.products)
- });
- self.inference_rules.push(new_rule);
+ self.inference_rules.put(new_rule);
} else {
- log::trace!(
+ log::debug!(
"{}:{}: new target {:?} based on {:?}",
&self.file_name,
line_number,
@@ -784,6 +766,135 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
commands: commands.clone(),
stem: None,
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
+ };
+ self.targets.put(new_target);
+ }
+ }
+
+ Ok(())
+ }
+
+ #[cfg(feature = "full")]
+ fn read_rule_macro(&mut self, line_tokens: TokenString, line_number: usize) -> Result<()> {
+ let (targets, macro_def) = line_tokens
+ .split_once(":")
+ .ok_or_else(|| eyre!("read_rule couldn't find a ':' on line {}", line_number))?;
+ lazy_static! {
+ // my kingdom for lookahead
+ static ref NON_EAGER_EXPANSION_ASSIGNMENT_COLON: Regex = #[allow(clippy::unwrap_used)] Regex::new(":[^:=]").unwrap();
+ }
+ if macro_def.matches_regex(&NON_EAGER_EXPANSION_ASSIGNMENT_COLON) {
+ bail!("GNUful static patterns not yet implemented in rule-specific macros");
+ };
+ let targets = self.expand_macros(&targets)?;
+ let targets = targets.split_whitespace().collect::<Vec<_>>();
+
+ let (name, value) = macro_def
+ .split_once("=")
+ .ok_or_else(|| eyre!("read_macro couldn't find a '=' on line {}", line_number))?;
+ let macro_assignment = self.parse_macro_assignment(name, value, line_number)?;
+
+ if targets.is_empty() {
+ return Ok(());
+ }
+
+ // we don't know yet if it's a target rule or an inference rule (or a GNUish "pattern rule")
+ let inference_match = inference_match(&targets, &[]);
+ let is_pattern = targets.iter().all(|x| x.contains('%'));
+
+ // TODO resolve against existing stack
+ let mut macro_set = MacroSet::new();
+ if let Some(outcome) = self
+ .check_macro_assignment_outcome(&macro_assignment, self.stack.with_scope(&self.macros))
+ {
+ let (name, value) = self.resolve_macro_value(macro_assignment, outcome, line_number)?;
+ // TODO trace
+ macro_set.set(name, value);
+ }
+
+ if is_pattern {
+ let new_rule = InferenceRule {
+ source: ItemSource::File {
+ name: self.file_name.clone(),
+ line: line_number,
+ },
+ products: targets.into_iter().map(|x| x.to_owned()).collect(),
+ prerequisites: vec![],
+ commands: vec![],
+ macros: macro_set,
+ };
+
+ log::error!(
+ "{}:{}: inference rule specific macros not yet working",
+ &self.file_name,
+ line_number
+ );
+
+ self.inference_rules.put(new_rule);
+ return Ok(());
+ }
+
+ // don't interpret things like `.tmp: ; mkdir -p $@` as single-suffix rules
+ let inference_match = inference_match.and_then(|inference| {
+ if self.special_target_has_prereq(".SUFFIXES", inference.s1, false)
+ && (inference.s2.is_empty()
+ || self.special_target_has_prereq(".SUFFIXES", inference.s2, false))
+ {
+ Some(inference)
+ } else {
+ log::info!(
+ "{}:{}: looks like {:?} is not a suffix rule because .SUFFIXES is {:?}",
+ &self.file_name,
+ line_number,
+ inference,
+ self.targets
+ .get(".SUFFIXES")
+ .or_else(|| self.built_in_targets.get(".SUFFIXES"))
+ .map(|x| &x.prerequisites)
+ );
+ None
+ }
+ });
+
+ if let Some(inference_match) = inference_match {
+ let new_rule = InferenceRule::new_suffix(
+ ItemSource::File {
+ name: self.file_name.clone(),
+ line: line_number,
+ },
+ inference_match.s1.to_owned(),
+ inference_match.s2.to_owned(),
+ vec![],
+ macro_set,
+ );
+ log::error!(
+ "{}:{}: inference rule specific macros not yet working",
+ &self.file_name,
+ line_number
+ );
+
+ self.inference_rules.put(new_rule);
+ } else {
+ log::trace!(
+ "{}:{}: target {:?} gets macros {:?}",
+ &self.file_name,
+ line_number,
+ &targets,
+ &macro_set
+ );
+ for target in targets {
+ if self.first_non_special_target.is_none() && !target.starts_with('.') {
+ self.first_non_special_target = Some(target.into());
+ }
+ // TODO handle appending to built-in (it's Complicated)
+ let new_target = Target {
+ name: target.into(),
+ prerequisites: vec![],
+ commands: vec![],
+ stem: None,
+ already_updated: Cell::new(false),
+ macros: macro_set.clone(),
};
self.targets.put(new_target);
}
@@ -794,7 +905,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
/// If successful, returns the name of the macro which was read.
fn read_macro(&mut self, mut line_tokens: TokenString, line_number: usize) -> Result<String> {
- let (name, mut value) = if cfg!(feature = "full") && line_tokens.starts_with("define ") {
+ let (name, value) = if cfg!(feature = "full") && line_tokens.starts_with("define ") {
line_tokens.strip_prefix("define ");
if line_tokens.ends_with("=") {
line_tokens.strip_suffix("=");
@@ -822,10 +933,36 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
.split_once("=")
.ok_or_else(|| eyre!("read_macro couldn't find a '=' on line {}", line_number))?
};
+ let macro_assignment = self.parse_macro_assignment(name, value, line_number)?;
+ let macro_name = macro_assignment.name.clone();
+ if let Some(outcome) = self
+ .check_macro_assignment_outcome(&macro_assignment, self.stack.with_scope(&self.macros))
+ {
+ let (name, value) = self.resolve_macro_value(macro_assignment, outcome, line_number)?;
+ log::trace!(
+ "{}:{}: setting macro {} to {:?}",
+ &self.file_name,
+ line_number,
+ &name,
+ &value
+ );
+ self.macros.set(name, value);
+ }
+ Ok(macro_name)
+ }
+
+ fn parse_macro_assignment(
+ &mut self,
+ name: TokenString,
+ mut value: TokenString,
+ line_number: usize,
+ ) -> Result<MacroAssignment> {
let name = self.expand_macros(&name)?;
- // GNUisms are annoying, but popular
+ #[cfg(feature = "full")]
let mut expand_value = false;
+ #[cfg(feature = "full")]
let mut skip_if_defined = false;
+ #[cfg(feature = "full")]
let mut append = false;
#[cfg(feature = "full")]
@@ -848,6 +985,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
let name = name.trim();
value.trim_start();
+ #[cfg(feature = "full")]
let value = if expand_value {
TokenString::text(
self.expand_macros(&value)
@@ -857,73 +995,124 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
value
};
- let skipped = match self.macros.get(name) {
+ Ok(MacroAssignment {
+ name: name.to_owned(),
+ value,
+ #[cfg(feature = "full")]
+ expand_value,
+ #[cfg(feature = "full")]
+ skip_if_defined,
+ #[cfg(feature = "full")]
+ append,
+ })
+ }
+
+ /// For aliasing reasons, applying a macro assignment is done in three steps:
+ /// 1. Determine what the assignment will do, and if it will append, fetch and clone the original value. Reads both self and the stack.
+ /// 2. Resolve the new value of the macro, eagerly expanding if appending to an eagerly expanded macro. May write to self due to eval, and since the stack will include `&self.macros`, must be separate from 1.
+ /// 3. Actually perform the assignment in a [MacroSet]. Since the [MacroSet] may be `&self.macros`, must be separate from 1 and 2.
+ fn check_macro_assignment_outcome(
+ &self,
+ macro_assignment: &MacroAssignment,
+ stack: MacroScopeStack,
+ ) -> Option<MacroAssignmentOutcome> {
+ let skipped = match stack.get(&macro_assignment.name).map(|x| x.source.clone()) {
// We always let command line or MAKEFLAGS macros override macros from the file.
- Some(Macro {
- source: ItemSource::CommandLineOrMakeflags,
- ..
- }) => true,
+ Some(ItemSource::CommandLineOrMakeflags) => true,
// We let environment variables override macros from the file only if the command-line argument to do that was given
- Some(Macro {
- source: ItemSource::Environment,
- ..
- }) => self.args.environment_overrides,
- Some(_) => skip_if_defined,
+ Some(ItemSource::Environment) => self.args.environment_overrides,
+ #[cfg(feature = "full")]
+ Some(_) => macro_assignment.skip_if_defined,
+ #[cfg(not(feature = "full"))]
+ Some(_) => false,
None => false,
};
if skipped {
- return Ok(name.to_owned());
+ None
+ } else {
+ Some(match stack.get(&macro_assignment.name) {
+ #[cfg(feature = "full")]
+ Some(old_value) if macro_assignment.append => {
+ MacroAssignmentOutcome::AppendedTo(old_value.into_owned())
+ }
+ _ => MacroAssignmentOutcome::Set,
+ })
}
+ }
- log::trace!(
- "{}:{}: setting macro {} to {}",
- &self.file_name,
- line_number,
- name,
- &value
- );
-
- let value = match self.macros.pop(name) {
- Some(mut old_value) if append => {
+ fn resolve_macro_value(
+ &mut self,
+ macro_assignment: MacroAssignment,
+ outcome: MacroAssignmentOutcome,
+ line_number: usize,
+ ) -> Result<(String, Macro)> {
+ match outcome {
+ MacroAssignmentOutcome::AppendedTo(mut old_value) => {
+ let value = macro_assignment.value;
#[cfg(feature = "full")]
let value = if old_value.eagerly_expanded {
TokenString::text(self.expand_macros(&value).wrap_err_with(|| {
- format!("while defining {} on line {}", name, line_number)
+ format!(
+ "while defining {} on line {}",
+ macro_assignment.name, line_number
+ )
})?)
} else {
value
};
old_value.text.extend(TokenString::text(" "));
old_value.text.extend(value);
- old_value
+ Ok((macro_assignment.name, old_value))
}
- _ => Macro {
- source: ItemSource::File {
- name: self.file_name.clone(),
- line: line_number,
+ MacroAssignmentOutcome::Set => Ok((
+ macro_assignment.name,
+ Macro {
+ source: ItemSource::File {
+ name: self.file_name.clone(),
+ line: line_number,
+ },
+ text: macro_assignment.value,
+ #[cfg(feature = "full")]
+ eagerly_expanded: macro_assignment.expand_value,
},
- text: value,
- #[cfg(feature = "full")]
- eagerly_expanded: expand_value,
- },
- };
- self.macros.set(name.into(), value);
+ )),
+ }
+ }
- Ok(name.to_owned())
+ fn expand_macros(&mut self, text: &TokenString) -> Result<String> {
+ #[cfg(feature = "full")]
+ let mut deferred_eval_context = DeferredEvalContext::new(self);
+ let result = self.expand_macros_deferred_eval(
+ text,
+ #[cfg(feature = "full")]
+ &mut deferred_eval_context,
+ );
+ #[cfg(feature = "full")]
+ for child in deferred_eval_context {
+ self.extend(child);
+ }
+ result
}
- fn expand_macros(&self, text: &TokenString) -> Result<String> {
- self.macros
- .expand(text)
+ fn expand_macros_deferred_eval(
+ &self,
+ text: &TokenString,
+ #[cfg(feature = "full")] deferred_eval_context: &mut DeferredEvalContext<R>,
+ ) -> Result<String> {
+ self.stack
+ .with_scope(&self.macros)
+ .expand(
+ text,
+ #[cfg(feature = "full")]
+ Some(deferred_eval_context),
+ )
.wrap_err_with(|| format!("while expanding \"{}\"", text))
}
pub fn finish(self) -> FinishedMakefileReader {
FinishedMakefileReader {
inference_rules: self.inference_rules,
- macros: self.macros.data,
- #[cfg(feature = "full")]
- macro_exports: self.macros.exported,
+ macros: self.macros,
targets: self.targets.into(),
first_non_special_target: self.first_non_special_target,
failed_includes: self.failed_includes,
@@ -932,11 +1121,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
fn extend(&mut self, new: FinishedMakefileReader) {
self.inference_rules.extend(new.inference_rules);
- self.macros.extend(
- new.macros,
- #[cfg(feature = "full")]
- new.macro_exports,
- );
+ self.macros.extend(new.macros);
for (_, target) in new.targets {
self.targets.put(target);
}
@@ -948,10 +1133,8 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
}
pub struct FinishedMakefileReader {
- pub inference_rules: Vec<InferenceRule>,
- pub macros: HashMap<String, Macro>,
- #[cfg(feature = "full")]
- pub macro_exports: ExportConfig,
+ pub inference_rules: InferenceRuleSet,
+ pub macros: MacroSet,
pub targets: HashMap<String, Target>,
pub first_non_special_target: Option<String>,
pub failed_includes: Vec<String>,
@@ -959,7 +1142,10 @@ pub struct FinishedMakefileReader {
#[cfg(test)]
mod test {
+ use std::io::Cursor;
+
use super::*;
+ use crate::makefile::token::Token;
type R = Result<()>;
@@ -977,6 +1163,7 @@ a: $(x) b \\
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1003,8 +1190,9 @@ worked = perhaps
endif
";
let args = Args::empty();
- let makefile = MakefileReader::read(
+ let mut makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1029,6 +1217,7 @@ endif
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1049,8 +1238,9 @@ baz
endef
";
let args = Args::empty();
- let makefile = MakefileReader::read(
+ let mut makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1077,8 +1267,9 @@ endif
FOO = bar
";
let args = Args::empty();
- let makefile = MakefileReader::read(
+ let mut makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1127,6 +1318,7 @@ clean:
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1148,6 +1340,7 @@ info:
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1161,7 +1354,8 @@ info:
prerequisites: vec!["bar".to_owned(), "baz".to_owned()],
commands: vec![],
stem: None,
- already_updated: Cell::new(false)
+ already_updated: Cell::new(false),
+ macros: MacroSet::new(),
}
);
assert_eq!(
@@ -1171,7 +1365,8 @@ info:
prerequisites: vec!["test#post".to_owned()],
commands: vec![],
stem: None,
- already_updated: Cell::new(false)
+ already_updated: Cell::new(false),
+ macros: MacroSet::new(),
}
);
assert_eq!(
@@ -1181,7 +1376,8 @@ info:
prerequisites: vec![],
commands: vec![CommandLine::from(TokenString::text("hello # there")),],
stem: None,
- already_updated: Cell::new(false)
+ already_updated: Cell::new(false),
+ macros: MacroSet::new(),
}
);
@@ -1198,6 +1394,7 @@ cursed:
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1222,6 +1419,7 @@ cursed:
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1243,6 +1441,7 @@ test: c
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1263,6 +1462,7 @@ test: c
let args = Args::empty();
let makefile = MakefileReader::read(
&args,
+ MacroScopeStack::default(),
MacroSet::new(),
Cursor::new(file),
"",
@@ -1270,11 +1470,42 @@ test: c
)?;
let makefile = makefile.finish();
assert_eq!(
- makefile.macros.get("x").map(|x| &x.text),
+ makefile.macros.get_non_recursive("x").map(|x| &x.text),
Some(&TokenString::text("3"))
);
assert!(
- matches!(makefile.macro_exports, ExportConfig::Only(exported) if exported.contains("x"))
+ matches!(makefile.macros.exported, ExportConfig::Only(exported) if exported.contains("x"))
+ );
+ Ok(())
+ }
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn shell_comment() -> R {
+ let file = r#"
+FOO=$(shell \
+echo \
+#abc)
+ "#;
+ let args = Args::empty();
+ let makefile = MakefileReader::read(
+ &args,
+ MacroScopeStack::default(),
+ MacroSet::new(),
+ Cursor::new(file),
+ "",
+ Default::default(),
+ )?;
+ let makefile = makefile.finish();
+ assert_eq!(
+ makefile.macros.get_non_recursive("FOO").map(|x| &x.text),
+ Some(&TokenString::from(vec![
+ Token::Text(String::new()),
+ Token::FunctionCall {
+ name: TokenString::text("shell"),
+ args: vec![TokenString::text("echo #abc")],
+ },
+ ]))
);
Ok(())
}
diff --git a/src/makefile/lookup_internal.rs b/src/makefile/lookup_internal.rs
new file mode 100644
index 0000000..a497ab4
--- /dev/null
+++ b/src/makefile/lookup_internal.rs
@@ -0,0 +1,164 @@
+use eyre::{bail, eyre, Result};
+use std::cell::RefCell;
+use std::path::Path;
+use std::rc::Rc;
+
+use super::target::Target;
+
+#[derive(Clone)]
+pub enum LookupInternal<'a> {
+ Partial {
+ targets: &'a Vec<&'a str>,
+ },
+ Complete {
+ target: Option<&'a Target>,
+ get_target: &'a dyn Fn(&str) -> Result<Rc<RefCell<Target>>>,
+ },
+}
+
+impl<'a> LookupInternal<'a> {
+ pub const fn new_partial(targets: &'a Vec<&str>) -> Self {
+ Self::Partial { targets }
+ }
+
+ pub const fn new(
+ target: Option<&'a Target>,
+ get_target: &'a dyn Fn(&str) -> Result<Rc<RefCell<Target>>>,
+ ) -> Self {
+ Self::Complete { target, get_target }
+ }
+
+ pub fn lookup(&self, macro_name: &str) -> Result<String> {
+ let macro_pieces = match macro_name.chars().next() {
+ Some('@') => self.target_name()?,
+ Some('?') => self.newer_prerequisites()?,
+ Some('<') => self.inference_prerequisite()?,
+ Some('*') => self.target_stem()?,
+ #[cfg(feature = "full")]
+ Some('^') => self.all_prerequisites()?,
+ _ => bail!("unknown internal macro {}", macro_name),
+ };
+
+ let macro_pieces = if macro_name.ends_with('D') {
+ macro_pieces
+ .into_iter()
+ .map(|x| {
+ Path::new(&x)
+ .parent()
+ .ok_or_else(|| eyre!("no parent"))
+ .map(|x| x.to_string_lossy().into())
+ })
+ .collect::<Result<_, _>>()?
+ } else if macro_name.ends_with('F') {
+ macro_pieces
+ .into_iter()
+ .map(|x| {
+ Path::new(&x)
+ .file_name()
+ .ok_or_else(|| eyre!("no filename"))
+ .map(|x| x.to_string_lossy().into())
+ })
+ .collect::<Result<_, _>>()?
+ } else {
+ macro_pieces
+ };
+
+ Ok(macro_pieces.join(" "))
+ }
+
+ /// POSIX: The $@ shall evaluate to the full target name of the current target.
+ fn target_name(&self) -> Result<Vec<String>> {
+ match self {
+ Self::Partial { targets } => {
+ Ok(targets.iter().map(|target| target.to_string()).collect())
+ }
+ Self::Complete {
+ target: Some(target),
+ ..
+ } => Ok(vec![target.name.clone()]),
+ Self::Complete { target: None, .. } => {
+ bail!("tried to expand internal macro with no target")
+ }
+ }
+ }
+
+ /// POSIX: The $? macro shall evaluate to the list of prerequisites that are newer than the current target.
+ fn newer_prerequisites(&self) -> Result<Vec<String>> {
+ match self {
+ Self::Partial { .. } => bail!("can’t expand $? when target not defined"),
+ Self::Complete {
+ target: Some(target),
+ get_target,
+ } => Ok(target
+ .prerequisites
+ .iter()
+ .filter(|prereq| {
+ get_target(prereq)
+ .ok()
+ .and_then(|prereq| prereq.borrow().newer_than(target))
+ .unwrap_or(false)
+ })
+ .cloned()
+ .collect()),
+ Self::Complete { target: None, .. } => {
+ bail!("tried to expand internal macro with no target")
+ }
+ }
+ }
+
+ /// POSIX: In an inference rule, the $< macro shall evaluate to the filename whose existence allowed the inference rule to be chosen for the target. In the .DEFAULT rule, the $< macro shall evaluate to the current target name.
+ ///
+ /// GNU: The name of the first prerequisite.
+ fn inference_prerequisite(&self) -> Result<Vec<String>> {
+ match self {
+ Self::Partial { .. } => bail!("can’t expand $< when target not defined"),
+ Self::Complete {
+ target: Some(target),
+ ..
+ } => {
+ // TODO check that exists_but_inferring_anyway won’t break this
+ Ok(vec![target
+ .prerequisites
+ .first()
+ .cloned()
+ .unwrap_or_default()])
+ }
+ Self::Complete { target: None, .. } => {
+ bail!("tried to expand internal macro with no target")
+ }
+ }
+ }
+
+ /// POSIX: The $* macro shall evaluate to the current target name with its suffix deleted.
+ fn target_stem(&self) -> Result<Vec<String>> {
+ match self {
+ Self::Partial { .. } => bail!("can’t expand $* when target not defined"),
+ Self::Complete {
+ target: Some(target),
+ ..
+ } => Ok(vec![target
+ .stem
+ .as_ref()
+ .unwrap_or(&target.name)
+ .to_owned()]),
+ Self::Complete { target: None, .. } => {
+ bail!("tried to expand internal macro with no target")
+ }
+ }
+ }
+
+ /// GNU: The names of all the prerequisites.
+ #[cfg(feature = "full")]
+ fn all_prerequisites(&self) -> Result<Vec<String>> {
+ match self {
+ Self::Partial { .. } => bail!("can’t expand $^ when target not defined"),
+ Self::Complete {
+ target: Some(target),
+ ..
+ } => Ok(target.prerequisites.clone()),
+ Self::Complete { target: None, .. } => {
+ bail!("tried to expand internal macro with no target")
+ }
+ }
+ }
+}
diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs
index d77557b..d27cbfa 100644
--- a/src/makefile/macro.rs
+++ b/src/makefile/macro.rs
@@ -1,20 +1,20 @@
-use std::cell::RefCell;
use std::collections::HashMap;
+#[cfg(feature = "full")]
use std::collections::HashSet;
use std::env;
use std::fmt;
-use std::rc::Rc;
-
-use eyre::{bail, Result, WrapErr};
-#[cfg(not(feature = "full"))]
-use regex::Regex;
+#[cfg(feature = "full")]
+use std::io::BufRead;
#[cfg(feature = "full")]
-use super::functions;
-use super::token::{Token, TokenString};
-use super::ItemSource;
+use super::eval_context::DeferredEvalContext;
+#[cfg(feature = "full")]
+use super::MacroScopeStack;
+use super::{ItemSource, TokenString};
+#[cfg(feature = "full")]
+use eyre::Result;
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Macro {
pub source: ItemSource,
pub text: TokenString,
@@ -22,12 +22,8 @@ pub struct Macro {
pub eagerly_expanded: bool,
}
-pub trait LookupInternal: for<'a> Fn(&'a str) -> Result<String> {}
-
-impl<F: for<'a> Fn(&'a str) -> Result<String>> LookupInternal for F {}
-
#[cfg(feature = "full")]
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExportConfig {
Only(HashSet<String>),
AllBut(HashSet<String>),
@@ -69,13 +65,6 @@ impl ExportConfig {
}
}
- fn same_type(&self) -> Self {
- match self {
- Self::Only(_) => Self::only(),
- Self::AllBut(_) => Self::all_but(),
- }
- }
-
fn should_export(&self, x: &str) -> bool {
match self {
Self::Only(exported) => exported.contains(x),
@@ -84,29 +73,19 @@ impl ExportConfig {
}
}
-#[derive(Clone)]
-pub struct Set<'parent, 'lookup> {
- parent: Option<&'parent Set<'parent, 'lookup>>,
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Set {
pub data: HashMap<String, Macro>,
- lookup_internal: Option<&'lookup dyn LookupInternal>,
- #[cfg(feature = "full")]
- pub to_eval: Rc<RefCell<Vec<String>>>,
#[cfg(feature = "full")]
pub exported: ExportConfig,
- warnings: Rc<RefCell<HashSet<String>>>,
}
-impl<'parent, 'lookup> Set<'parent, 'lookup> {
+impl Set {
pub fn new() -> Self {
Self {
- parent: None,
data: HashMap::new(),
- lookup_internal: None,
- #[cfg(feature = "full")]
- to_eval: Rc::new(RefCell::new(Vec::new())),
#[cfg(feature = "full")]
exported: ExportConfig::only(),
- warnings: Default::default(),
}
}
@@ -140,49 +119,18 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> {
}
}
- fn lookup_internal(&self, name: &str) -> Result<String> {
- if let Some(lookup) = self.lookup_internal {
- lookup(name)
- } else if let Some(parent) = self.parent {
- parent.lookup_internal(name)
- } else {
- bail!(
- "tried to lookup {:?} but no lookup function is available",
- name
- )
- }
- }
-
- pub fn get(&self, name: &str) -> Option<&Macro> {
- self.data
- .get(name)
- .or_else(|| self.parent.and_then(|parent| parent.get(name)))
+ /// To properly process inherited macros, use [MacroScopeStack::get].
+ pub fn get_non_recursive(&self, name: &str) -> Option<&Macro> {
+ self.data.get(name)
}
pub fn set(&mut self, name: String, r#macro: Macro) {
self.data.insert(name, r#macro);
}
- #[cfg(feature = "full")]
- pub fn is_defined(&self, name: &str) -> bool {
- self.get(name).map_or(false, |x| !x.text.is_empty())
- }
-
- // `remove` is fine, but I think for "remove-and-return" `pop` is better
- pub fn pop(&mut self, name: &str) -> Option<Macro> {
- // TODO figure out a better way to handle inheritance
- self.data
- .remove(name)
- .or_else(|| self.parent.and_then(|p| p.get(name).cloned()))
- }
-
- pub fn extend(
- &mut self,
- other: HashMap<String, Macro>,
- #[cfg(feature = "full")] other_exports: ExportConfig,
- ) {
+ pub fn extend(&mut self, other: Self) {
#[cfg(feature = "full")]
- match (&mut self.exported, other_exports) {
+ match (&mut self.exported, other.exported) {
(ExportConfig::Only(se), ExportConfig::Only(oe)) => {
se.extend(oe);
}
@@ -190,187 +138,47 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> {
sne.extend(one);
}
(ExportConfig::Only(se), ExportConfig::AllBut(one)) => {
- se.extend(other.keys().cloned().filter(|name| !one.contains(name)));
+ se.extend(
+ other
+ .data
+ .keys()
+ .filter(|name| !one.contains(*name))
+ .cloned(),
+ );
}
(ExportConfig::AllBut(sne), ExportConfig::Only(oe)) => {
- sne.extend(other.keys().cloned().filter(|name| !oe.contains(name)));
- }
- }
- self.data.extend(other);
- }
-
- fn warn(&self, text: String) {
- if !self.warnings.borrow().contains(&text) {
- log::warn!("{}", &text);
- self.warnings.borrow_mut().insert(text);
- }
- }
-
- pub fn expand(&self, text: &TokenString) -> Result<String> {
- let mut result = String::new();
- for token in text.tokens() {
- match token {
- Token::Text(t) => result.push_str(t),
- Token::MacroExpansion { name, replacement } => {
- let name = self
- .expand(name)
- .wrap_err_with(|| format!("while expanding \"{}\"", name))?;
- let internal_macro_names = &['@', '?', '<', '*', '^'][..];
- let internal_macro_suffices = &['D', 'F'][..];
- let just_internal = name.len() == 1 && name.starts_with(internal_macro_names);
- let suffixed_internal = name.len() == 2
- && name.starts_with(internal_macro_names)
- && name.ends_with(internal_macro_suffices);
- let macro_value = if just_internal || suffixed_internal {
- self.lookup_internal(&name)
- .wrap_err_with(|| format!("while expanding $\"{}\"", name))?
- } else {
- self.get(&name).map_or_else(
- || {
- self.warn(format!("undefined macro {}", name));
- Ok(String::new())
- },
- |x| {
- self.expand(&x.text)
- .wrap_err_with(|| format!("while expanding \"{}\"", &x.text))
- },
- )?
- };
- let macro_value = match replacement {
- Some((subst1, subst2)) => {
- let subst1 = self.expand(subst1)?;
- #[cfg(feature = "full")]
- {
- let (subst1, subst2) = if subst1.contains('%') {
- (subst1, subst2.clone())
- } else {
- let mut real_subst2 = TokenString::text("%");
- real_subst2.extend(subst2.clone());
- (format!("%{}", subst1), real_subst2)
- };
- let args = [
- TokenString::text(subst1),
- subst2,
- TokenString::text(macro_value),
- ];
- functions::expand_call(
- "patsubst",
- &args,
- self,
- Some(Rc::clone(&self.to_eval)),
- )?
- }
- #[cfg(not(feature = "full"))]
- {
- let subst1_suffix = regex::escape(&subst1);
- let subst1_suffix =
- Regex::new(&format!(r"{}(\s|$)", subst1_suffix))
- .context("formed invalid regex somehow")?;
- let subst2 = self.expand(subst2)?;
- subst1_suffix
- .replace_all(&macro_value, |c: &regex::Captures| {
- format!("{}{}", subst2, c.get(1).unwrap().as_str())
- })
- .to_string()
- }
- }
- None => macro_value,
- };
- log::trace!(
- "expanded {} (from {:?}) into \"{}\"",
- token,
- self.get(&name).map(|x| &x.source),
- &macro_value
- );
- result.push_str(&macro_value);
- }
- #[cfg(feature = "full")]
- Token::FunctionCall { name, args } => {
- let name = self.expand(name)?;
- let fn_result =
- functions::expand_call(&name, args, self, Some(Rc::clone(&self.to_eval)))?;
- log::trace!("expanded {} into \"{}\"", token, &fn_result);
- result.push_str(&fn_result);
- }
+ sne.extend(
+ other
+ .data
+ .keys()
+ .filter(|name| !oe.contains(*name))
+ .cloned(),
+ );
}
}
- Ok(result)
+ self.data.extend(other.data);
}
#[cfg(feature = "full")]
- pub fn origin(&self, name: &str) -> &'static str {
- match self.data.get(name) {
- None => self.parent.map_or("undefined", |p| p.origin(name)),
- Some(Macro {
- source: ItemSource::Builtin,
- ..
- }) => "default",
- Some(Macro {
- source: ItemSource::Environment,
- ..
- }) => "environment",
- // TODO figure out when to return "environment override"
- Some(Macro {
- source: ItemSource::File { .. },
- ..
- }) => "file",
- Some(Macro {
- source: ItemSource::CommandLineOrMakeflags,
- ..
- }) => "command line",
- // TODO handle override
- Some(Macro {
- source: ItemSource::FunctionCall,
- ..
- }) => "automatic",
- }
- }
-
- pub fn with_lookup<'l, 's: 'l>(&'s self, lookup: &'l dyn LookupInternal) -> Set<'s, 'l> {
- Set {
- parent: Some(self),
- data: HashMap::new(),
- lookup_internal: Some(lookup),
- #[cfg(feature = "full")]
- to_eval: Rc::clone(&self.to_eval),
- #[cfg(feature = "full")]
- exported: self.exported.same_type(),
- warnings: Rc::clone(&self.warnings),
- }
- }
-
- pub fn with_overlay<'s>(&'s self) -> Set<'s, 'lookup> {
- Set {
- parent: Some(self),
- data: HashMap::new(),
- lookup_internal: None,
- #[cfg(feature = "full")]
- to_eval: Rc::clone(&self.to_eval),
- #[cfg(feature = "full")]
- exported: self.exported.same_type(),
- warnings: Rc::clone(&self.warnings),
- }
- }
-
- #[cfg(feature = "full")]
- pub fn resolve_exports(&self) -> Result<Vec<(&str, String)>> {
+ pub fn resolve_exports<R: BufRead>(
+ &self,
+ mut eval_context: Option<&mut DeferredEvalContext<R>>,
+ ) -> Result<Vec<(&str, String)>> {
let own_exports = self
.data
.iter()
.filter(|(name, _)| self.exported.should_export(name))
- .map(|(name, value)| self.expand(&value.text).map(|text| (name.as_ref(), text)))
+ .map(|(name, value)| {
+ MacroScopeStack::from_scope(self)
+ .expand(&value.text, eval_context.as_deref_mut())
+ .map(|text| (name.as_ref(), text))
+ })
.collect::<Result<Vec<_>>>()?;
- Ok(if let Some(parent) = self.parent {
- let mut parent_exports = parent.resolve_exports()?;
- parent_exports.extend(own_exports);
- parent_exports
- } else {
- own_exports
- })
+ Ok(own_exports)
}
}
-impl fmt::Display for Set<'_, '_> {
+impl fmt::Display for Set {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pieces = self
.data
@@ -381,9 +189,23 @@ impl fmt::Display for Set<'_, '_> {
}
}
+impl Default for Set {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
fn builtins() -> Vec<(&'static str, TokenString)> {
// Fuck it, might as well.
- macro_rules! handle {
+ macro_rules! handle_key {
+ ($key:ident) => {
+ stringify!($key)
+ };
+ ($key:literal) => {
+ $key
+ };
+ }
+ macro_rules! handle_value {
($value:ident) => {
stringify!($value).parse().unwrap()
};
@@ -395,8 +217,8 @@ fn builtins() -> Vec<(&'static str, TokenString)> {
};
}
macro_rules! make {
- ($($name:ident=$value:tt)+) => {vec![$(
- (stringify!($name), handle!($value))
+ ($($name:tt=$value:tt)+) => {vec![$(
+ (handle_key!($name), handle_value!($value))
),+]};
}
@@ -429,12 +251,19 @@ fn builtins() -> Vec<(&'static str, TokenString)> {
ARFLAGS="rv"
CFLAGS=""
FFLAGS=""
+
+ // yes, Linux, this is definitely GNU Make 4.0+
+ ".FEATURES"="output-sync"
]
}
#[cfg(test)]
mod test {
use super::*;
+ #[cfg(feature = "full")]
+ use crate::makefile::functions::NO_EVAL;
+ use crate::MacroScopeStack;
+ use eyre::Result;
type R = Result<()>;
@@ -450,7 +279,14 @@ mod test {
eagerly_expanded: false,
},
);
- assert_eq!(macros.expand(&"$(oof:;=?)".parse()?)?, "bruh? swag? yeet?");
+ assert_eq!(
+ MacroScopeStack::from_scope(&macros).expand(
+ &"$(oof:;=?)".parse()?,
+ #[cfg(feature = "full")]
+ NO_EVAL
+ )?,
+ "bruh? swag? yeet?"
+ );
Ok(())
}
@@ -466,7 +302,10 @@ mod test {
eagerly_expanded: false,
},
);
- assert_eq!(macros.expand(&"$(m:%=%-objs)".parse()?)?, "conf-objs");
+ assert_eq!(
+ MacroScopeStack::from_scope(&macros).expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?,
+ "conf-objs"
+ );
Ok(())
}
}
diff --git a/src/makefile/macro_scope.rs b/src/makefile/macro_scope.rs
new file mode 100644
index 0000000..c03870f
--- /dev/null
+++ b/src/makefile/macro_scope.rs
@@ -0,0 +1,229 @@
+use std::borrow::Cow;
+use std::collections::HashSet;
+#[cfg(feature = "full")]
+use std::io::BufRead;
+use std::iter;
+use std::sync::RwLock;
+
+use eyre::Context;
+use lazy_static::lazy_static;
+#[cfg(not(feature = "full"))]
+use regex::Regex;
+
+#[cfg(feature = "full")]
+use super::eval_context::DeferredEvalContext;
+#[cfg(feature = "full")]
+use super::functions;
+use super::token::Token;
+use super::{ItemSource, LookupInternal, Macro, MacroSet, TokenString};
+
+pub trait MacroScope {
+ /// Looks up the macro with the given name and returns it if it exists.
+ ///
+ /// Uses [Cow] to allow for lazy macro definitions.
+ fn get(&self, name: &str) -> Option<Cow<Macro>>;
+}
+
+impl MacroScope for MacroSet {
+ fn get(&self, name: &str) -> Option<Cow<Macro>> {
+ self.get_non_recursive(name).map(Cow::Borrowed)
+ }
+}
+
+impl<'a> MacroScope for LookupInternal<'a> {
+ fn get(&self, name: &str) -> Option<Cow<Macro>> {
+ self.lookup(name).ok().map(|value| {
+ Cow::Owned(Macro {
+ source: ItemSource::Builtin,
+ text: TokenString::text(value),
+ #[cfg(feature = "full")]
+ eagerly_expanded: false,
+ })
+ })
+ }
+}
+
+impl<T: MacroScope> MacroScope for Option<&T> {
+ fn get(&self, name: &str) -> Option<Cow<Macro>> {
+ self.as_ref().and_then(|value| value.get(name))
+ }
+}
+
+// warning on undefined macros is useful but can get repetitive fast
+lazy_static! {
+ static ref WARNINGS_EMITTED: RwLock<HashSet<String>> = Default::default();
+}
+
+fn warn(text: String) {
+ let already_warned = WARNINGS_EMITTED
+ .read()
+ .map_or(true, |warnings| warnings.contains(&text));
+ if !already_warned {
+ log::warn!("{}", &text);
+ if let Ok(mut warnings) = WARNINGS_EMITTED.write() {
+ warnings.insert(text);
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct MacroScopeStack<'a> {
+ scopes: Vec<&'a dyn MacroScope>,
+}
+
+impl<'a> MacroScopeStack<'a> {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn from_scope(scope: &'a dyn MacroScope) -> Self {
+ Self {
+ scopes: vec![scope],
+ }
+ }
+
+ pub fn with_scope(&self, new_scope: &'a dyn MacroScope) -> Self {
+ Self {
+ scopes: iter::once(new_scope).chain(self.scopes.clone()).collect(),
+ }
+ }
+
+ pub fn get(&self, name: &str) -> Option<Cow<Macro>> {
+ for scope in &self.scopes {
+ if let Some(r#macro) = scope.get(name) {
+ return Some(r#macro);
+ }
+ }
+ None
+ }
+
+ #[cfg(feature = "full")]
+ pub fn is_defined(&self, name: &str) -> bool {
+ self.get(name).map_or(false, |x| !x.text.is_empty())
+ }
+
+ pub fn expand<#[cfg(feature = "full")] R: BufRead>(
+ &self,
+ text: &TokenString,
+ #[cfg(feature = "full")] mut eval_context: Option<&mut DeferredEvalContext<R>>,
+ ) -> eyre::Result<String> {
+ let mut result = String::new();
+ for token in text.tokens() {
+ match token {
+ Token::Text(t) => result.push_str(t),
+ Token::MacroExpansion { name, replacement } => {
+ let name = self
+ .expand(
+ name,
+ #[cfg(feature = "full")]
+ eval_context.as_deref_mut(),
+ )
+ .wrap_err_with(|| format!("while expanding \"{}\"", name))?;
+ let macro_value = self.get(&name).map_or_else(
+ || {
+ warn(format!("undefined macro {}", name));
+ Ok(String::new())
+ },
+ |x| {
+ self.expand(
+ &x.text,
+ #[cfg(feature = "full")]
+ eval_context.as_deref_mut(),
+ )
+ .wrap_err_with(|| format!("while expanding \"{}\"", &x.text))
+ },
+ )?;
+ let macro_value = match replacement {
+ Some((subst1, subst2)) => {
+ let subst1 = self.expand(
+ subst1,
+ #[cfg(feature = "full")]
+ eval_context.as_deref_mut(),
+ )?;
+ #[cfg(feature = "full")]
+ {
+ let (subst1, subst2) = if subst1.contains('%') {
+ (subst1, subst2.clone())
+ } else {
+ let mut real_subst2 = TokenString::text("%");
+ real_subst2.extend(subst2.clone());
+ (format!("%{}", subst1), real_subst2)
+ };
+ let args = [
+ TokenString::text(subst1),
+ subst2,
+ TokenString::text(macro_value),
+ ];
+ functions::expand_call(
+ "patsubst",
+ &args,
+ self,
+ eval_context.as_deref_mut(),
+ )?
+ }
+ #[cfg(not(feature = "full"))]
+ {
+ let subst1_suffix = regex::escape(&subst1);
+ let subst1_suffix =
+ Regex::new(&format!(r"{}(\s|$)", subst1_suffix))
+ .context("formed invalid regex somehow")?;
+ let subst2 = self.expand(subst2)?;
+ subst1_suffix
+ .replace_all(&macro_value, |c: &regex::Captures| {
+ format!("{}{}", subst2, c.get(1).unwrap().as_str())
+ })
+ .to_string()
+ }
+ }
+ None => macro_value,
+ };
+ log::trace!(
+ "expanded {} (from {:?}) into \"{}\"",
+ token,
+ self.get(&name).map(|x| x.source.clone()),
+ &macro_value
+ );
+ result.push_str(&macro_value);
+ }
+ #[cfg(feature = "full")]
+ Token::FunctionCall { name, args } => {
+ let name = self.expand(name, eval_context.as_deref_mut())?;
+ let fn_result =
+ functions::expand_call(&name, args, self, eval_context.as_deref_mut())?;
+ log::trace!("expanded {} into \"{}\"", token, &fn_result);
+ result.push_str(&fn_result);
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ #[cfg(feature = "full")]
+ pub fn origin(&self, name: &str) -> &'static str {
+ match self.get(name).as_deref() {
+ None => "undefined",
+ Some(Macro {
+ source: ItemSource::Builtin,
+ ..
+ }) => "default",
+ Some(Macro {
+ source: ItemSource::Environment,
+ ..
+ }) => "environment",
+ // TODO figure out when to return "environment override"
+ Some(Macro {
+ source: ItemSource::File { .. },
+ ..
+ }) => "file",
+ Some(Macro {
+ source: ItemSource::CommandLineOrMakeflags,
+ ..
+ }) => "command line",
+ // TODO handle override
+ Some(Macro {
+ source: ItemSource::FunctionCall,
+ ..
+ }) => "automatic",
+ }
+ }
+}
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index 277fbd3..d746ed1 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -8,10 +8,15 @@ use std::rc::Rc;
use eyre::{bail, eyre, Result, WrapErr};
use command_line::CommandLine;
-use inference_rules::InferenceRule;
+#[cfg(feature = "full")]
+use functions::NO_EVAL;
+use inference_rules::{InferenceRule, InferenceRuleSet};
use input::FinishedMakefileReader;
pub use input::MakefileReader;
-use r#macro::{Macro, Set as MacroSet};
+use lookup_internal::LookupInternal;
+pub use macro_scope::MacroScopeStack;
+use r#macro::Macro;
+pub use r#macro::Set as MacroSet;
use target::{DynamicTargetSet, Target};
use token::TokenString;
@@ -21,27 +26,36 @@ mod command_line;
#[cfg(feature = "full")]
mod conditional;
#[cfg(feature = "full")]
+mod eval_context;
+#[cfg(feature = "full")]
mod functions;
mod inference_rules;
mod input;
+mod lookup_internal;
mod r#macro;
+mod macro_scope;
+mod parse;
mod pattern;
mod target;
mod token;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ItemSource {
- File { name: String, line: usize },
+ File {
+ name: String,
+ line: usize,
+ },
CommandLineOrMakeflags,
Environment,
Builtin,
+ #[cfg(feature = "full")]
FunctionCall,
}
pub struct Makefile<'a> {
- inference_rules: Vec<InferenceRule>,
+ inference_rules: InferenceRuleSet,
builtin_inference_rules: Vec<InferenceRule>,
- pub macros: MacroSet<'static, 'static>,
+ pub macros: MacroSet,
targets: DynamicTargetSet,
pub first_non_special_target: Option<String>,
args: &'a Args,
@@ -60,7 +74,7 @@ impl<'a> Makefile<'a> {
"MAKE".to_owned(),
Macro {
source: ItemSource::Builtin,
- text: std::env::current_exe().map_or_else(
+ text: env::current_exe().map_or_else(
|_| TokenString::text("makers"),
|x| TokenString::text(x.to_string_lossy()),
),
@@ -68,6 +82,15 @@ impl<'a> Makefile<'a> {
eagerly_expanded: false,
},
);
+ macros.set(
+ "MAKEFLAGS".to_owned(),
+ Macro {
+ source: ItemSource::Builtin,
+ text: TokenString::text(args.makeflags()),
+ #[cfg(feature = "full")]
+ eagerly_expanded: false,
+ },
+ );
if !args.no_builtin_rules {
inference_rules.extend(builtin_inference_rules());
macros.add_builtins();
@@ -119,7 +142,7 @@ impl<'a> Makefile<'a> {
}
Makefile {
- inference_rules: vec![],
+ inference_rules: InferenceRuleSet::default(),
builtin_inference_rules: inference_rules,
macros,
targets,
@@ -131,11 +154,7 @@ impl<'a> Makefile<'a> {
pub fn extend(&mut self, new: FinishedMakefileReader) -> Result<()> {
self.inference_rules.extend(new.inference_rules);
- self.macros.extend(
- new.macros,
- #[cfg(feature = "full")]
- new.macro_exports,
- );
+ self.macros.extend(new.macros);
for (_, target) in new.targets {
self.targets.put(target);
}
@@ -147,9 +166,9 @@ impl<'a> Makefile<'a> {
self.update_target(&failed_include).wrap_err_with(|| {
format!("while building missing included file {}", &failed_include)
})?;
- let macros = self.macros.with_overlay();
+ let stack = MacroScopeStack::default().with_scope(&self.macros);
let file =
- MakefileReader::read_file(self.args, macros, failed_include, Default::default())?
+ MakefileReader::read_file(self.args, stack, failed_include, Default::default())?
.finish();
self.extend(file)?;
}
@@ -157,13 +176,10 @@ impl<'a> Makefile<'a> {
}
fn special_target_has_prereq(&self, target: &str, name: &str) -> bool {
- match self.targets.get(target) {
- Some(target) => {
- let target = target.borrow();
- target.prerequisites.is_empty() || target.prerequisites.iter().any(|e| e == name)
- }
- None => false,
- }
+ self.targets.get(target).map_or(false, |target| {
+ let target = target.borrow();
+ target.prerequisites.is_empty() || target.prerequisites.iter().any(|e| e == name)
+ })
}
fn infer_target(
@@ -184,7 +200,7 @@ impl<'a> Makefile<'a> {
let follow_gnu = cfg!(feature = "full");
- let vpath_options = match self.macros.get("VPATH") {
+ let vpath_options = match self.macros.get_non_recursive("VPATH") {
Some(Macro { text, .. }) if follow_gnu => {
let vpath = self.expand_macros(text, None)?;
env::split_paths(&vpath).collect()
@@ -202,7 +218,12 @@ impl<'a> Makefile<'a> {
.inference_rules
.iter()
.chain(self.builtin_inference_rules.iter())
- .filter(|rule| !banned_rules.contains(rule))
+ .filter(|rule| {
+ !banned_rules.iter().any(|banned_rule| {
+ banned_rule.products == rule.products
+ && banned_rule.prerequisites == rule.prerequisites
+ })
+ })
.filter(|rule| rule.matches(name).unwrap_or(false));
for rule in inference_rule_candidates {
log::trace!(
@@ -219,7 +240,10 @@ impl<'a> Makefile<'a> {
// we can't build this based on itself! fuck outta here
return None;
}
- if self.targets.has(&prereq_path_name) {
+ if self.targets.has(&prereq_path_name)
+ || self.special_target_has_prereq(".PHONY", &prereq_path_name)
+ {
+ // TODO consider only checking phony after transitive inference has failed
return Some(prereq_path_name);
}
let prereq_path = PathBuf::from(&prereq_path_name);
@@ -261,6 +285,7 @@ impl<'a> Makefile<'a> {
.first_match(name)?
.and_then(|x| x.get(1).map(|x| x.as_str().to_owned())),
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
});
break;
}
@@ -278,7 +303,11 @@ impl<'a> Makefile<'a> {
let follow_gnu = cfg!(feature = "full");
#[cfg(feature = "full")]
- let name = name.strip_prefix("./").unwrap_or(name);
+ if let Some(name_without_leading_dot_slash) = name.strip_prefix("./") {
+ if let Ok(result) = self.get_target(name_without_leading_dot_slash) {
+ return Ok(result);
+ }
+ }
let exists_but_infer_anyway = if follow_gnu {
self.targets
@@ -303,6 +332,7 @@ impl<'a> Makefile<'a> {
commands,
stem: None,
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
});
} else {
// if it already exists, it counts as up-to-date
@@ -313,6 +343,7 @@ impl<'a> Makefile<'a> {
commands: vec![],
stem: None,
already_updated: Cell::new(true),
+ macros: MacroSet::new(),
});
}
}
@@ -322,10 +353,9 @@ impl<'a> Makefile<'a> {
self.targets.put(new_target);
}
- Ok(self
- .targets
+ self.targets
.get(name)
- .ok_or_else(|| eyre!("Target {:?} not found!", name))?)
+ .ok_or_else(|| eyre!("Target {:?} not found!", name))
}
pub fn update_target(&self, name: &str) -> Result<()> {
@@ -343,74 +373,15 @@ impl<'a> Makefile<'a> {
}
fn expand_macros(&self, text: &TokenString, target: Option<&Target>) -> Result<String> {
- let target = target.cloned();
- let lookup_internal = move |macro_name: &str| {
- let target = target
- .as_ref()
- .ok_or_else(|| eyre!("internal macro but no current target!"))?;
- let macro_pieces = if macro_name.starts_with('@') {
- // The $@ shall evaluate to the full target name of the
- // current target.
- vec![target.name.clone()]
- } else if macro_name.starts_with('?') {
- // The $? macro shall evaluate to the list of prerequisites
- // that are newer than the current target.
- target
- .prerequisites
- .iter()
- .filter(|prereq| {
- self.get_target(prereq)
- .ok()
- .and_then(|prereq| prereq.borrow().newer_than(target))
- .unwrap_or(false)
- })
- .cloned()
- .collect()
- } else if macro_name.starts_with('<') {
- // In an inference rule, the $< macro shall evaluate to the
- // filename whose existence allowed the inference rule to be
- // chosen for the target. In the .DEFAULT rule, the $< macro
- // shall evaluate to the current target name.
- // TODO make that actually be the case (rn exists_but_inferring_anyway might fuck that up)
- vec![target.prerequisites.get(0).cloned().unwrap_or_default()]
- } else if macro_name.starts_with('*') {
- // The $* macro shall evaluate to the current target name with
- // its suffix deleted. (GNUism: the match stem)
- vec![target.stem.as_ref().unwrap_or(&target.name).to_owned()]
- } else if macro_name.starts_with('^') {
- target.prerequisites.clone()
- } else {
- unreachable!()
- };
-
- let macro_pieces = if macro_name.ends_with('D') {
- macro_pieces
- .into_iter()
- .map(|x| {
- Path::new(&x)
- .parent()
- .ok_or_else(|| eyre!("no parent"))
- .map(|x| x.to_string_lossy().into())
- })
- .collect::<Result<_, _>>()?
- } else if macro_name.ends_with('F') {
- macro_pieces
- .into_iter()
- .map(|x| {
- Path::new(&x)
- .file_name()
- .ok_or_else(|| eyre!("no filename"))
- .map(|x| x.to_string_lossy().into())
- })
- .collect::<Result<_, _>>()?
- } else {
- macro_pieces
- };
-
- Ok(macro_pieces.join(" "))
- };
-
- self.macros.with_lookup(&lookup_internal).expand(text)
+ MacroScopeStack::default()
+ .with_scope(&self.macros)
+ .with_scope(&LookupInternal::new(target, &|name| self.get_target(name)))
+ .with_scope(&target.map(|target| &target.macros))
+ .expand(
+ text,
+ #[cfg(feature = "full")]
+ NO_EVAL,
+ )
}
}
@@ -460,6 +431,7 @@ fn builtin_inference_rules() -> Vec<InferenceRule> {
prepend_dot!($($second)?).into(),
concat!(".", stringify!($first)).into(),
vec![$(CommandLine::from($cmd.parse().unwrap())),+],
+ MacroSet::new(),
)
),+]
};
@@ -515,6 +487,7 @@ fn builtin_targets() -> Vec<Target> {
commands: vec![],
stem: None,
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
}]
}
@@ -533,9 +506,10 @@ mod test {
products: vec!["this-is-a-%-case".to_owned()],
prerequisites: vec![],
commands: vec![],
+ macros: MacroSet::new(),
};
let file = Makefile {
- inference_rules: vec![rule],
+ inference_rules: vec![rule].into(),
builtin_inference_rules: vec![],
macros: MacroSet::new(),
targets: Default::default(),
@@ -559,6 +533,7 @@ mod test {
commands: vec![],
stem: None,
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
};
let phony = Target {
name: ".PHONY".to_string(),
@@ -566,13 +541,14 @@ mod test {
commands: vec![],
stem: None,
already_updated: Cell::new(false),
+ macros: MacroSet::new(),
};
let targets = DynamicTargetSet::default();
targets.put(target);
targets.put(phony);
let file = Makefile {
- inference_rules: vec![],
+ inference_rules: InferenceRuleSet::default(),
builtin_inference_rules: vec![],
macros: MacroSet::new(),
targets,
@@ -584,4 +560,42 @@ mod test {
assert!(file.update_target("all").is_ok());
Ok(())
}
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn missing_phony_targets_allow_inference() -> R {
+ let args = Args::empty();
+ let rule = InferenceRule {
+ source: ItemSource::CommandLineOrMakeflags,
+ products: vec!["%ll".to_owned()],
+ prerequisites: vec!["missing".to_owned()],
+ commands: vec![],
+ macros: MacroSet::new(),
+ };
+ let phony = Target {
+ name: ".PHONY".to_string(),
+ prerequisites: vec!["missing".to_owned()],
+ commands: vec![],
+ stem: None,
+ already_updated: Cell::new(false),
+ macros: MacroSet::new(),
+ };
+
+ let mut inference_rules = InferenceRuleSet::default();
+ inference_rules.put(rule);
+ let targets = DynamicTargetSet::default();
+ targets.put(phony);
+ let file = Makefile {
+ inference_rules,
+ builtin_inference_rules: vec![],
+ macros: MacroSet::new(),
+ targets,
+ first_non_special_target: None,
+ args: &args,
+ already_inferred: Default::default(),
+ };
+
+ file.update_target("all")?;
+ Ok(())
+ }
}
diff --git a/src/makefile/parse.rs b/src/makefile/parse.rs
new file mode 100644
index 0000000..191b7e0
--- /dev/null
+++ b/src/makefile/parse.rs
@@ -0,0 +1,19 @@
+use super::Macro;
+use super::TokenString;
+
+#[derive(Debug)]
+pub struct MacroAssignment {
+ pub name: String,
+ pub value: TokenString,
+ #[cfg(feature = "full")]
+ pub expand_value: bool,
+ #[cfg(feature = "full")]
+ pub skip_if_defined: bool,
+ #[cfg(feature = "full")]
+ pub append: bool,
+}
+
+pub enum MacroAssignmentOutcome {
+ Set,
+ AppendedTo(Macro),
+}
diff --git a/src/makefile/target.rs b/src/makefile/target.rs
index c3431e4..9f81802 100644
--- a/src/makefile/target.rs
+++ b/src/makefile/target.rs
@@ -8,9 +8,7 @@ use std::time::SystemTime;
use eyre::{Result, WrapErr};
-use crate::makefile::command_line::CommandLine;
-
-use super::Makefile;
+use super::{CommandLine, MacroSet, Makefile};
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Target {
@@ -19,10 +17,11 @@ pub struct Target {
pub commands: Vec<CommandLine>,
pub stem: Option<String>,
pub already_updated: Cell<bool>,
+ pub macros: MacroSet,
}
impl Target {
- pub fn extend(&mut self, other: Target) {
+ pub fn extend(&mut self, other: Self) {
assert_eq!(&self.name, &other.name);
match (self.commands.is_empty(), other.commands.is_empty()) {
(false, false) => {
@@ -43,6 +42,7 @@ impl Target {
self.stem = self.stem.take().or(other.stem);
let already_updated = self.already_updated.get() || other.already_updated.get();
self.already_updated.set(already_updated);
+ self.macros.extend(other.macros);
}
}
}
@@ -152,11 +152,15 @@ impl StaticTargetSet {
self.data.insert(target.name.clone(), target);
}
}
+
+ pub fn has(&self, name: &str) -> bool {
+ self.data.contains_key(name)
+ }
}
-impl Into<HashMap<String, Target>> for StaticTargetSet {
- fn into(self) -> HashMap<String, Target> {
- self.data
+impl From<StaticTargetSet> for HashMap<String, Target> {
+ fn from(value: StaticTargetSet) -> Self {
+ value.data
}
}
@@ -168,7 +172,7 @@ pub struct DynamicTargetSet {
impl DynamicTargetSet {
pub fn get(&self, name: &str) -> Option<Rc<RefCell<Target>>> {
- self.data.borrow().get(name).map(|x| Rc::clone(x))
+ self.data.borrow().get(name).map(Rc::clone)
}
pub fn put(&self, target: Target) {
diff --git a/src/makefile/token.rs b/src/makefile/token.rs
index 2721387..8507a3a 100644
--- a/src/makefile/token.rs
+++ b/src/makefile/token.rs
@@ -17,6 +17,7 @@ use nom::{
character::complete::{space0, space1},
multi::separated_list1,
};
+use regex::Regex;
trait Err<'a>: 'a + ParseError<&'a str> + ContextError<&'a str> {}
impl<'a, T: 'a + ParseError<&'a str> + ContextError<&'a str>> Err<'a> for T {}
@@ -128,7 +129,7 @@ impl TokenString {
}
pub fn is_empty(&self) -> bool {
- match self.0.get(0) {
+ match self.0.first() {
None => true,
Some(Token::Text(t)) if t.is_empty() && self.0.len() == 1 => true,
_ => false,
@@ -144,6 +145,29 @@ impl TokenString {
}
})
}
+
+ pub fn matches_regex(&self, regex: &Regex) -> bool {
+ self.0.iter().any(|x| {
+ if let Token::Text(x) = x {
+ regex.is_match(x)
+ } else {
+ false
+ }
+ })
+ }
+
+ /// Returns (token index within string, pattern index within token).
+ pub fn find(&self, pattern: &str) -> Option<(usize, usize)> {
+ self.0
+ .iter()
+ .enumerate()
+ .find_map(|(token_index, token)| match token {
+ Token::Text(text) => text
+ .find(pattern)
+ .map(|pattern_index| (token_index, pattern_index)),
+ _ => None,
+ })
+ }
}
impl fmt::Display for TokenString {
@@ -231,6 +255,13 @@ impl Delimiter {
Self::Braces => "}",
}
}
+
+ const fn end_char(&self) -> char {
+ match self {
+ Delimiter::Parens => ')',
+ Delimiter::Braces => '}',
+ }
+ }
}
fn macro_function_name<'a, E: Err<'a>>(
@@ -341,13 +372,12 @@ fn text_but_not<'a, E: Err<'a>>(
}
fn nested_delimiters<'a, E: Err<'a>>(
- ends: Vec<char>,
context: Delimiter,
) -> impl FnMut(&'a str) -> IResult<&'a str, TokenString, E> {
map(
tuple((
tag(context.start()),
- move |x| tokens_but_not(ends.clone(), context)(x),
+ move |x| tokens_but_not(vec![context.end_char()], context)(x),
tag(context.end()),
)),
|(left, center, right)| {
@@ -368,7 +398,7 @@ fn single_token_but_not<'a, E: Err<'a>>(
alt((
text_but_not(tbn_ends),
macro_expansion,
- nested_delimiters(ends, context),
+ nested_delimiters(context),
))
}
@@ -386,7 +416,7 @@ fn empty_tokens<'a, E: Err<'a>>(input: &'a str) -> IResult<&'a str, TokenString,
fn fold_tokens<'a, E: Err<'a>>(
parser: impl FnMut(&'a str) -> IResult<&'a str, TokenString, E>,
) -> impl FnMut(&'a str) -> IResult<&'a str, TokenString, E> {
- fold_many1(parser, TokenString::empty(), |mut acc, x| {
+ fold_many1(parser, TokenString::empty, |mut acc, x| {
acc.extend(x);
acc
})
@@ -614,4 +644,28 @@ mod test {
);
Ok(())
}
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn quoted_function_call_comma() -> R {
+ let text = "$(egg $$(bug a, b/c))";
+ let tokens = tokenize(text)?;
+
+ assert_eq!(
+ tokens,
+ TokenString::just(Token::FunctionCall {
+ name: TokenString::text("egg"),
+ args: vec![TokenString::text("$(bug a, b/c)")],
+ })
+ );
+ Ok(())
+ }
+
+ #[cfg(feature = "full")]
+ #[test]
+ fn unbalanced_parentheses_rejected() -> R {
+ let text = "$(egg ()";
+ assert!(tokenize(text).is_err());
+ Ok(())
+ }
}
diff --git a/tests/conditional_assignment_inheritance.rs b/tests/conditional_assignment_inheritance.rs
new file mode 100644
index 0000000..24f0533
--- /dev/null
+++ b/tests/conditional_assignment_inheritance.rs
@@ -0,0 +1,29 @@
+#![cfg(feature = "full")]
+
+mod utils;
+
+use std::fs;
+use utils::{make, R};
+
+#[test]
+fn conditional_assignment_inheritance_test() -> R {
+ let dir = tempfile::tempdir()?;
+
+ let file_a = "
+EGG = bug
+include file_b.mk
+check:
+\t@echo $(EGG)
+";
+ fs::write(dir.path().join("Makefile"), file_a)?;
+ let file_b = "
+EGG ?= nope
+";
+ fs::write(dir.path().join("file_b.mk"), file_b)?;
+
+ let result = make(&dir)?;
+ assert!(result.status.success());
+ assert_eq!(String::from_utf8(result.stdout)?.trim(), "bug");
+
+ Ok(())
+}
diff --git a/tests/rule_specific_macros.rs b/tests/rule_specific_macros.rs
new file mode 100644
index 0000000..34e7e77
--- /dev/null
+++ b/tests/rule_specific_macros.rs
@@ -0,0 +1,85 @@
+#![cfg(feature = "full")]
+
+mod utils;
+
+use std::fs;
+use utils::{make, R};
+
+#[test]
+fn target_specific_macros() -> R {
+ let dir = tempfile::tempdir()?;
+
+ let file = "
+foo.h: EGG = bug
+foo.h:
+\techo $(EGG)
+ ";
+ fs::write(dir.path().join("Makefile"), file)?;
+
+ let result = make(&dir)?;
+ dbg!(&result);
+ assert!(result.status.success());
+ let stdout = String::from_utf8(result.stdout)?;
+ assert!(stdout.contains("echo bug"));
+
+ Ok(())
+}
+
+#[test]
+#[ignore = "not yet implemented"]
+fn target_specific_macros_inherited() -> R {
+ let dir = tempfile::tempdir()?;
+
+ // example from https://www.gnu.org/software/make/manual/html_node/Target_002dspecific.html
+ let file = "
+CC=echo cc
+prog : CFLAGS = -g
+prog : prog.o foo.o bar.o
+ ";
+ fs::write(dir.path().join("Makefile"), file)?;
+ fs::write(dir.path().join("prog.c"), "")?;
+ fs::write(dir.path().join("foo.c"), "")?;
+ fs::write(dir.path().join("bar.c"), "")?;
+
+ let result = make(&dir)?;
+ dbg!(&result);
+ assert!(result.status.success());
+ let stdout = String::from_utf8(result.stdout)?;
+ assert!(stdout.contains("echo cc -g -c foo.c"));
+ assert!(stdout.contains("echo cc -g -c bar.o"));
+ assert!(stdout.contains("echo cc -g -c prog.c"));
+
+ Ok(())
+}
+
+#[test]
+#[ignore = "not yet implemented"]
+fn inference_rule_specific_macros() -> R {
+ let dir = tempfile::tempdir()?;
+
+ // example from https://www.gnu.org/software/make/manual/html_node/Pattern_002dspecific.html
+ let file = "
+CC=echo cc
+%.o: %.c
+\t$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
+
+lib/%.o: CFLAGS := -fPIC -g
+%.o: CFLAGS := -g
+
+all: foo.o lib/bar.o
+ ";
+ fs::write(dir.path().join("Makefile"), file)?;
+ fs::write(dir.path().join("foo.c"), "")?;
+ fs::create_dir(dir.path().join("lib"))?;
+ fs::write(dir.path().join("bar.c"), "")?;
+
+ let result = make(&dir)?;
+ dbg!(&result);
+ assert!(result.status.success());
+ let stdout = String::from_utf8(result.stdout)?;
+ dbg!(&stdout);
+ assert!(stdout.contains("echo cc -g foo.c -o foo.o"));
+ assert!(stdout.contains("echo cc -fPIC -g lib/bar.c -o lib/bar.o"));
+
+ Ok(())
+}
diff --git a/tests/update_logic.rs b/tests/update_logic.rs
index 9e37b19..6eefe6b 100644
--- a/tests/update_logic.rs
+++ b/tests/update_logic.rs
@@ -1,19 +1,9 @@
use std::fs;
-use std::path::Path;
-use std::process::{Command, Output};
-
-use eyre::{Result, WrapErr};
use std::thread::sleep;
use std::time::Duration;
-type R = Result<()>;
-
-fn make(dir: impl AsRef<Path>) -> Result<Output> {
- Command::new(env!("CARGO_BIN_EXE_makers"))
- .current_dir(dir)
- .output()
- .wrap_err("")
-}
+mod utils;
+use utils::{make, R};
#[test]
fn basic_test() -> R {
diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs
new file mode 100644
index 0000000..8b834c1
--- /dev/null
+++ b/tests/utils/mod.rs
@@ -0,0 +1,13 @@
+use std::path::Path;
+use std::process::{Command, Output};
+
+use eyre::Context;
+
+pub type R = eyre::Result<()>;
+
+pub fn make(dir: impl AsRef<Path>) -> eyre::Result<Output> {
+ Command::new(env!("CARGO_BIN_EXE_makers"))
+ .current_dir(dir)
+ .output()
+ .wrap_err("")
+}