diff --git a/.cargo/config.toml b/.cargo/config.toml index 46565eea20671..f49754db7f4c0 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,10 +1,20 @@ [alias] -x = "run --package x --bin x --" -xcheck = "run --package x --bin x -- check" -xclippy = "run --package x --bin x -- clippy" -xfmt = "fmt" -xfix = "run --package x --bin x -- fix" -xtest = "run --package x --bin x -- test" -xlint = "run --package x --bin x -- lint" -xbuild = "run --package x --bin x -- build" -nextest = "run --package x --bin x -- nextest" +xclippy = [ + "clippy", + "--workspace", + "--", + "-Dwarnings", + "-Wclippy::all", + "-Aclippy::unit_arg", + "-Aclippy::eval-order-dependence", + "-Aclippy::new-without-default", + "-Aclippy::rc_buffer", + "-Aclippy::upper_case_acronyms", + "-Aclippy::len-without-is-empty", + "-Aclippy::from-iter-instead-of-collect", + "-Aclippy::while-let-on-iterator", + "-Aclippy::bool-assert-comparison", + "-Aclippy::needless-collect", + "-Aclippy::enum-variant-names", + "-Aclippy::self-named-constructors", +] diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml index ca2ea3dc9890f..962a31ab0b865 100644 --- a/.github/workflows/lint-test.yaml +++ b/.github/workflows/lint-test.yaml @@ -13,6 +13,7 @@ env: HAS_BUILDPULSE_SECRETS: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID != '' && secrets.BUILDPULSE_SECRET_ACCESS_KEY != '' }} HAS_DATADOG_SECRETS: ${{ secrets.DD_API_KEY != '' }} CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always # cancel redundant builds concurrency: @@ -120,22 +121,22 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/rust-setup - - run: cargo x lint - - run: cargo xclippy --workspace --all-targets + - uses: pre-commit/action@v3.0.0 + - run: cargo xclippy - run: cargo fmt --check - run: cargo install cargo-sort - run: cargo sort --grouped --check --workspace - rust-unit-xtest: + rust-doc-test: runs-on: high-perf-docker steps: - uses: actions/checkout@v3 with: fetch-depth: 0 # get all the history because cargo xtest --change-since origin/main requires it. - uses: ./.github/actions/rust-setup - - run: cargo xtest --doc --unit --changed-since "origin/main" --exclude aptos-node-checker + - run: cargo test --doc --workspace --exclude aptos-node-checker - rust-unit-nextest: + rust-unit-test: runs-on: high-perf-docker steps: - uses: actions/checkout@v3 @@ -143,7 +144,10 @@ jobs: fetch-depth: 0 # get all the history because cargo xtest --change-since origin/main requires it. - uses: ./.github/actions/rust-setup - run: docker run --detach -p 5432:5432 cimg/postgres:14.2 - - run: cargo nextest --nextest-profile ci --unit --exclude backup-cli --changed-since "origin/main" + - uses: taiki-e/install-action@v1.5.6 + with: + tool: nextest + - run: cargo nextest run --profile ci --workspace --exclude smoke-test --exclude backup-cli --exclude testcases env: INDEXER_DATABASE_URL: postgresql://postgres@localhost/postgres @@ -167,16 +171,18 @@ jobs: service: rust-unit-nextest files: target/nextest/ci/junit.xml - rust-e2e-test: + rust-smoke-test: runs-on: high-perf-docker steps: - uses: actions/checkout@v3 - uses: ./.github/actions/rust-setup - run: docker run --detach -p 5432:5432 cimg/postgres:14.2 + - uses: taiki-e/install-action@v1.5.6 + with: + tool: nextest # --test-threads is intentionally set to reduce resource contention in ci jobs. Increasing this, increases job failures and retries. - - run: cargo nextest --nextest-profile ci --package smoke-test --test-threads 6 --retries 3 + - run: cargo nextest run --profile ci --package smoke-test --test-threads 6 --retries 3 env: - RUST_BACKTRACE: full INDEXER_DATABASE_URL: postgresql://postgres@localhost/postgres - name: Upload test results to BuildPulse for flaky test detection diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000000..2a3e6e71ca778 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + files: \.(rs|move)$ + - id: end-of-file-fixer + files: \.(rs|move)$ + - id: check-added-large-files + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.3.0 + hooks: + - id: insert-license + files: \.rs$ + args: + - --license-filepath + - devtools/assets/license_header.txt + - --comment-style + - // diff --git a/Cargo.lock b/Cargo.lock index daec9eccbd19d..b3575a870a774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,16 +1343,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "atomicwrites" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb8f2cd6962fa53c0e2a9d3f97eaa7dbd1e3cbbeeb4745403515b42ae07b3ff6" -dependencies = [ - "tempfile", - "winapi 0.3.9", -] - [[package]] name = "atty" version = "0.2.14" @@ -1512,12 +1502,6 @@ dependencies = [ "num-traits 0.2.15", ] -[[package]] -name = "bimap" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" - [[package]] name = "bincode" version = "1.3.3" @@ -1742,37 +1726,6 @@ dependencies = [ "proptest-derive", ] -[[package]] -name = "camino" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fd178c5af4d59e83498ef15cf3f154e1a6f9d091270cb86283c65ef44e9ef0" -dependencies = [ - "serde 1.0.137", -] - -[[package]] -name = "cargo-platform" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" -dependencies = [ - "serde 1.0.137", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.9", - "serde 1.0.137", - "serde_json", -] - [[package]] name = "cassowary" version = "0.3.0" @@ -1806,16 +1759,6 @@ dependencies = [ "nom 7.1.1", ] -[[package]] -name = "cfg-expr" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "0.1.10" @@ -2015,27 +1958,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "colored-diff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410208eb08c3f3ad44b95b51c4fc0d5993cbcc9dd39cfadb4214b9115a97dcb5" -dependencies = [ - "ansi_term", - "dissimilar", - "itertools", -] - -[[package]] -name = "combine" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" -dependencies = [ - "bytes 1.1.0", - "memchr", -] - [[package]] name = "config" version = "0.11.0" @@ -2498,16 +2420,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "ctrlc" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" -dependencies = [ - "nix", - "winapi 0.3.9", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -2638,12 +2550,6 @@ dependencies = [ "structopt", ] -[[package]] -name = "debug-ignore" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51694c5f8b91baf933e6429a3df4ff3e9f1160386d150790b97bef73337d1b" - [[package]] name = "derive_more" version = "0.99.17" @@ -2657,23 +2563,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "determinator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d79c522bf6d953c41da626c46ad0c82d22859e0601d8a4f442b8d25f80e0f8" -dependencies = [ - "camino", - "globset", - "guppy", - "guppy-workspace-hack", - "once_cell", - "petgraph 0.6.0", - "rayon", - "serde 1.0.137", - "toml", -] - [[package]] name = "deunicode" version = "0.4.3" @@ -2732,24 +2621,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "diffus" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0ff24a73b51d9009c40897faf87d31b77345c90ffbf4dc3a1d2957032c5653" -dependencies = [ - "itertools", -] - -[[package]] -name = "diffy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27ec7cef89a63c063e06570bb861b7d35e406d6885551b346d77c459b34d3db" -dependencies = [ - "ansi_term", -] - [[package]] name = "digest" version = "0.8.1" @@ -2835,30 +2706,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" -[[package]] -name = "dissimilar" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" - [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" -[[package]] -name = "duct" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d" -dependencies = [ - "libc", - "once_cell", - "os_pipe", - "shared_child", -] - [[package]] name = "ed25519" version = "1.5.2" @@ -3570,54 +3423,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "guppy" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6570bfb78ea4e0c039bd212e353a3de840f5ef0fa5439356b5451f6e0df7678d" -dependencies = [ - "camino", - "cargo_metadata", - "cfg-if 1.0.0", - "debug-ignore", - "fixedbitset 0.4.1", - "guppy-summaries", - "guppy-workspace-hack", - "indexmap", - "itertools", - "nested", - "once_cell", - "pathdiff", - "petgraph 0.6.0", - "rayon", - "semver 1.0.9", - "serde 1.0.137", - "serde_json", - "smallvec", - "target-spec", - "toml", -] - -[[package]] -name = "guppy-summaries" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca5ad97ff788027e546992f7f374e277da50ca4e06dab268f33088a74897e9e" -dependencies = [ - "camino", - "cfg-if 1.0.0", - "diffus", - "semver 1.0.9", - "serde 1.0.137", - "toml", -] - -[[package]] -name = "guppy-workspace-hack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92620684d99f750bae383ecb3be3748142d6095760afd5cbcf2261e9a279d780" - [[package]] name = "h2" version = "0.3.13" @@ -3637,29 +3442,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hakari" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b338af81b25e5e8a7a02b574d94cf043b7851f27cac9159fbca612390332e6c8" -dependencies = [ - "atomicwrites", - "bimap", - "camino", - "cfg-if 1.0.0", - "debug-ignore", - "diffy", - "guppy", - "guppy-workspace-hack", - "indenter", - "itertools", - "pathdiff", - "rayon", - "target-spec", - "toml_edit", - "twox-hash", -] - [[package]] name = "half" version = "1.8.2" @@ -3812,15 +3594,6 @@ dependencies = [ "hmac 0.8.1", ] -[[package]] -name = "home" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "hostname" version = "0.3.1" @@ -3887,16 +3660,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime 2.1.0", - "serde 1.0.137", -] - [[package]] name = "hyper" version = "0.14.18" @@ -4039,18 +3802,6 @@ dependencies = [ "quote 1.0.18", ] -[[package]] -name = "indent_write" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "1.8.2" @@ -4073,12 +3824,6 @@ dependencies = [ "regex", ] -[[package]] -name = "indoc" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" - [[package]] name = "inspection-service" version = "0.1.0" @@ -4129,12 +3874,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" -[[package]] -name = "is_ci" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" - [[package]] name = "is_debug" version = "1.0.1" @@ -4234,15 +3973,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" -[[package]] -name = "kstring" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b310ccceade8121d7d77fee406160e457c2f4e7c7982d589da3499bc7ea4526" -dependencies = [ - "serde 1.0.137", -] - [[package]] name = "kube" version = "0.51.0" @@ -5466,12 +5196,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nested" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b420f638f07fe83056b55ea190bb815f609ec5a35e7017884a10f78839c9e" - [[package]] name = "netcore" version = "0.1.0" @@ -5584,61 +5308,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "nextest-metadata" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37186bfe40f2b45c40ca0a5d0fccd9d818ff0b893e3352fdd970cf366d11f79" -dependencies = [ - "camino", - "serde 1.0.137", - "serde_json", -] - -[[package]] -name = "nextest-runner" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6d422e5e4a9ec48d70cae317bf623580a3e70defcc558b0a150bbb01b4000b" -dependencies = [ - "aho-corasick", - "camino", - "cargo_metadata", - "chrono", - "config", - "crossbeam-channel", - "ctrlc", - "debug-ignore", - "duct", - "guppy", - "home", - "humantime-serde", - "indent_write", - "nextest-metadata", - "num_cpus", - "once_cell", - "owo-colors", - "quick-junit", - "rayon", - "serde 1.0.137", - "serde_json", - "strip-ansi-escapes", - "target-spec", - "toml", - "twox-hash", -] - -[[package]] -name = "nix" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "libc", -] - [[package]] name = "nom" version = "5.1.2" @@ -5945,16 +5614,6 @@ dependencies = [ "num-traits 0.2.15", ] -[[package]] -name = "os_pipe" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "os_str_bytes" version = "6.0.1" @@ -5984,12 +5643,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "owo-colors" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" - [[package]] name = "parking_lot" version = "0.10.2" @@ -6088,15 +5741,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -dependencies = [ - "camino", -] - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -6670,26 +6314,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-junit" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b67e2983c3a1c129640a674165ab8127066cfcecd3095befa182a77ffd93e00" -dependencies = [ - "chrono", - "indexmap", - "quick-xml", -] - -[[package]] -name = "quick-xml" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "0.6.13" @@ -7356,9 +6980,6 @@ name = "semver" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" -dependencies = [ - "serde 1.0.137", -] [[package]] name = "semver-parser" @@ -7626,16 +7247,6 @@ dependencies = [ "lazy_static 1.4.0", ] -[[package]] -name = "shared_child" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "shlex" version = "1.1.0" @@ -8091,15 +7702,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "strip-ansi-escapes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" -dependencies = [ - "vte", -] - [[package]] name = "strsim" version = "0.8.0" @@ -8142,16 +7744,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "supports-color" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" -dependencies = [ - "atty", - "is_ci", -] - [[package]] name = "syn" version = "0.15.44" @@ -8207,24 +7799,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" - -[[package]] -name = "target-spec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a479a83ee0f97d90b2ba593c696968c94d781835117362d9fcd42ca34faa5f1" -dependencies = [ - "cfg-expr", - "guppy-workspace-hack", - "serde 1.0.137", - "target-lexicon", -] - [[package]] name = "task-local-extensions" version = "0.1.1" @@ -8655,22 +8229,9 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "indexmap", "serde 1.0.137", ] -[[package]] -name = "toml_edit" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744e9ed5b352340aa47ce033716991b5589e23781acb97cad37d4ea70560f55b" -dependencies = [ - "combine", - "indexmap", - "itertools", - "kstring", -] - [[package]] name = "tower" version = "0.4.12" @@ -8884,16 +8445,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if 1.0.0", - "static_assertions", -] - [[package]] name = "typed-arena" version = "2.0.1" @@ -9099,12 +8650,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8parse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" - [[package]] name = "uuid" version = "1.0.0" @@ -9192,27 +8737,6 @@ dependencies = [ "vm-genesis", ] -[[package]] -name = "vte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" -dependencies = [ - "arrayvec", - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" -dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", -] - [[package]] name = "wait-timeout" version = "0.2.0" @@ -9529,60 +9053,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" -[[package]] -name = "x" -version = "0.1.0" -dependencies = [ - "anyhow", - "camino", - "chrono", - "colored-diff", - "determinator", - "env_logger 0.8.4", - "globset", - "guppy", - "indexmap", - "indoc", - "log", - "nextest-runner", - "rayon", - "regex", - "serde 1.0.137", - "serde_json", - "structopt", - "supports-color", - "toml", - "x-core", - "x-lint", -] - -[[package]] -name = "x-core" -version = "0.1.0" -dependencies = [ - "camino", - "debug-ignore", - "determinator", - "guppy", - "hex", - "indoc", - "log", - "once_cell", - "ouroboros", - "serde 1.0.137", - "toml", -] - -[[package]] -name = "x-lint" -version = "0.1.0" -dependencies = [ - "camino", - "guppy", - "hakari", - "x-core", -] - [[package]] name = "x25519-dalek" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 134f1ff30a555..4a2d9bf24f3c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,9 +65,6 @@ members = [ "crates/short-hex-str", "crates/transaction-emitter", "crates/transaction-emitter-lib", - "devtools/x", - "devtools/x-core", - "devtools/x-lint", "ecosystem/indexer", "ecosystem/node-checker", "execution/db-bootstrapper", @@ -162,7 +159,3 @@ codegen-units = 1 [profile.bench] debug = true - -# Build guppy in opt mode so that x commands are faster. -[profile.dev.package.guppy] -opt-level = 3 diff --git a/crates/aptos-logger/Cargo.toml b/crates/aptos-logger/Cargo.toml index cd6b866a3e0c0..e21ca57307e4d 100644 --- a/crates/aptos-logger/Cargo.toml +++ b/crates/aptos-logger/Cargo.toml @@ -25,4 +25,3 @@ tracing-subscriber = "0.3.11" aptos-infallible = { path = "../aptos-infallible" } aptos-log-derive = { path = "../aptos-log-derive" } - diff --git a/devtools/assets/license_header.txt b/devtools/assets/license_header.txt new file mode 100644 index 0000000000000..e7ba331b9b515 --- /dev/null +++ b/devtools/assets/license_header.txt @@ -0,0 +1,2 @@ +Copyright (c) Aptos +SPDX-License-Identifier: Apache-2.0 diff --git a/devtools/x-core/Cargo.toml b/devtools/x-core/Cargo.toml deleted file mode 100644 index ed8fb2e56acab..0000000000000 --- a/devtools/x-core/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "x-core" -version = "0.1.0" -authors = ["Aptos Labs "] -description = "Core data structures used by x" -edition = "2018" -publish = false -license = "Apache-2.0" - -[dependencies] -camino = { version = "1.0.3", features = ["serde1"] } -debug-ignore = "1.0.1" -determinator = "0.8.0" -guppy = "0.13.0" -hex = "0.4.3" -indoc = "1.0.3" -log = "0.4.17" -once_cell = "1.10.0" -ouroboros = "0.9.2" -serde = { version = "1.0.137", features = ["derive"] } -toml = "0.5.9" diff --git a/devtools/x-core/src/core_config.rs b/devtools/x-core/src/core_config.rs deleted file mode 100644 index 54c1c62c86082..0000000000000 --- a/devtools/x-core/src/core_config.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -/// Core configuration for x. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct XCoreConfig { - /// Subsets of this workspace - pub subsets: BTreeMap, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct SubsetConfig { - /// The root members in this subset - pub root_members: Vec, -} diff --git a/devtools/x-core/src/errors.rs b/devtools/x-core/src/errors.rs deleted file mode 100644 index 60d280489b8ec..0000000000000 --- a/devtools/x-core/src/errors.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use camino::{Utf8Path, Utf8PathBuf}; -use hex::FromHexError; -use serde::{de, ser}; -use std::{borrow::Cow, error, fmt, io, process::ExitStatus, result, str::Utf8Error}; - -/// Type alias for the return type for `run` methods. -pub type Result = result::Result; - -/// A system error that happened while running a lint. -#[derive(Debug)] -#[non_exhaustive] -pub enum SystemError { - CwdNotInProjectRoot { - current_dir: Utf8PathBuf, - project_root: &'static Utf8Path, - }, - Exec { - cmd: &'static str, - status: ExitStatus, - }, - GitRoot(Cow<'static, str>), - FromHex { - context: Cow<'static, str>, - err: FromHexError, - }, - Guppy { - context: Cow<'static, str>, - err: guppy::Error, - }, - Io { - context: Cow<'static, str>, - err: io::Error, - }, - NonUtf8Path { - path: Vec, - err: Utf8Error, - }, - Serde { - context: Cow<'static, str>, - err: Box, - }, -} - -impl SystemError { - pub fn io(context: impl Into>, err: io::Error) -> Self { - SystemError::Io { - context: context.into(), - err, - } - } - - pub fn guppy(context: impl Into>, err: guppy::Error) -> Self { - SystemError::Guppy { - context: context.into(), - err, - } - } - - pub fn git_root(msg: impl Into>) -> Self { - SystemError::GitRoot(msg.into()) - } - - pub fn from_hex(context: impl Into>, err: FromHexError) -> Self { - SystemError::FromHex { - context: context.into(), - err, - } - } - - pub fn de( - context: impl Into>, - err: impl de::Error + Send + Sync + 'static, - ) -> Self { - SystemError::Serde { - context: context.into(), - err: Box::new(err), - } - } - - pub fn ser( - context: impl Into>, - err: impl ser::Error + Send + Sync + 'static, - ) -> Self { - SystemError::Serde { - context: context.into(), - err: Box::new(err), - } - } -} - -impl fmt::Display for SystemError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SystemError::CwdNotInProjectRoot { - current_dir, - project_root, - } => write!( - f, - "current dir {} not in project root {}", - current_dir, project_root, - ), - SystemError::Exec { cmd, status } => match status.code() { - Some(code) => write!(f, "'{}' failed with exit code {}", cmd, code), - None => write!(f, "'{}' terminated by signal", cmd), - }, - SystemError::GitRoot(s) => write!(f, "git root error: {}", s), - SystemError::NonUtf8Path { path, .. } => { - write!(f, "non-UTF-8 path \"{}\"", String::from_utf8_lossy(path)) - } - SystemError::FromHex { context, .. } - | SystemError::Io { context, .. } - | SystemError::Serde { context, .. } - | SystemError::Guppy { context, .. } => write!(f, "while {}", context), - } - } -} - -impl error::Error for SystemError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match self { - SystemError::CwdNotInProjectRoot { .. } - | SystemError::Exec { .. } - | SystemError::GitRoot(_) => None, - SystemError::FromHex { err, .. } => Some(err), - SystemError::Io { err, .. } => Some(err), - SystemError::Guppy { err, .. } => Some(err), - SystemError::NonUtf8Path { err, .. } => Some(err), - SystemError::Serde { err, .. } => Some(err.as_ref()), - } - } -} diff --git a/devtools/x-core/src/git.rs b/devtools/x-core/src/git.rs deleted file mode 100644 index 252fcc02ba2e3..0000000000000 --- a/devtools/x-core/src/git.rs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::errors::*; -use camino::{Utf8Path, Utf8PathBuf}; -use determinator::Utf8Paths0; -use guppy::{graph::PackageGraph, MetadataCommand}; -use indoc::formatdoc; -use log::{debug, info}; -use once_cell::sync::OnceCell; -use std::{ - borrow::Cow, - ffi::{OsStr, OsString}, - fmt, - process::{Command, Stdio}, -}; - -/// Support for source control operations through running Git commands. -/// -/// This assumes that the underlying Git repository doesn't change in the middle of an operation, -/// and caches data as a result. If mutation operations are added, the caches would need to be -/// invalidated. -#[derive(Clone, Debug)] -pub struct GitCli { - root: &'static Utf8Path, - // Caches. - tracked_files: OnceCell, -} - -impl GitCli { - /// Creates a new instance of the Git CLI. - pub fn new(root: &'static Utf8Path) -> Result { - let git_cli = Self { - root, - tracked_files: OnceCell::new(), - }; - git_cli.validate()?; - Ok(git_cli) - } - - /// Returns the files tracked by Git in this working copy. - /// - /// The return value can be iterated on to get a list of paths. - pub fn tracked_files(&self) -> Result<&Utf8Paths0> { - self.tracked_files.get_or_try_init(|| { - // TODO: abstract out SCM and command-running functionality. - let output = self - .git_command() - // The -z causes files to not be quoted, and to be separated by \0. - .args(&["ls-files", "-z"]) - .output() - .map_err(|err| SystemError::io("running git ls-files", err))?; - if !output.status.success() { - return Err(SystemError::Exec { - cmd: "git ls-files", - status: output.status, - }); - } - - Utf8Paths0::from_bytes(output.stdout) - .map_err(|(path, err)| SystemError::NonUtf8Path { path, err }) - }) - } - - /// Returns the merge base of the current commit (`HEAD`) with the specified commit. - pub fn merge_base(&self, commit_ref: &str) -> Result { - let output = self - .git_command() - .args(&["merge-base", "HEAD", commit_ref]) - .output() - .map_err(|err| { - SystemError::io(format!("running git merge-base HEAD {}", commit_ref), err) - })?; - if !output.status.success() { - return Err(SystemError::Exec { - cmd: "git merge-base", - status: output.status, - }); - } - - // The output is a hex-encoded hash followed by a newline. - let stdout = &output.stdout[..(output.stdout.len() - 1)]; - GitHash::from_hex(stdout) - } - - /// Returns the files changed between the given commits, or the current directory if the new - /// commit isn't specified. - /// - /// For more about the diff filter, see `man git-diff`'s help for `--diff-filter`. - pub fn files_changed_between<'a>( - &self, - old: impl Into>, - new: impl Into>>, - // TODO: make this more well-typed/express more of the diff model in Rust - diff_filter: Option<&str>, - ) -> Result { - let mut command = self.git_command(); - command.args(&["diff", "-z", "--name-only"]); - if let Some(diff_filter) = diff_filter { - command.arg(format!("--diff-filter={}", diff_filter)); - } - command.arg(old.into()); - if let Some(new) = new.into() { - command.arg(new); - } - - let output = command - .output() - .map_err(|err| SystemError::io("running git diff", err))?; - if !output.status.success() { - return Err(SystemError::Exec { - cmd: "git diff", - status: output.status, - }); - } - - Utf8Paths0::from_bytes(output.stdout) - .map_err(|(path, err)| SystemError::NonUtf8Path { path, err }) - } - - /// Returns a package graph for the given commit, using a scratch repo if necessary. - pub fn package_graph_at(&self, commit_ref: &GitHash) -> Result { - // Create or initialize the scratch worktree. - let scratch = self.get_or_init_scratch(commit_ref)?; - - // Compute the package graph for the scratch worktree. - MetadataCommand::new() - .current_dir(scratch) - .build_graph() - .map_err(|err| SystemError::guppy("building package graph", err)) - } - - // --- - // Helper methods - // --- - - fn validate(&self) -> Result<()> { - // Check that the project root and the Git root match. - let output = self - .git_command() - .args(&["rev-parse", "--show-toplevel"]) - .stderr(Stdio::inherit()) - .output() - .map_err(|err| SystemError::io("running git rev-parse --show-toplevel", err))?; - if !output.status.success() { - let msg = formatdoc!( - "unable to find a git repo at {} - (hint: did you download an archive from GitHub? x requires a git clone)", - self.root - ); - return Err(SystemError::git_root(msg)); - } - - let mut git_root_bytes = output.stdout; - // Pop the newline off the git root bytes. - git_root_bytes.pop(); - let git_root = match String::from_utf8(git_root_bytes) { - Ok(git_root) => git_root, - Err(_) => { - return Err(SystemError::git_root( - "git rev-parse --show-toplevel returned a non-Unicode path", - )); - } - }; - if self.root != git_root { - let msg = formatdoc!( - "git root expected to be at {}, but actually found at {} - (hint: did you download an archive from GitHub? x requires a git clone)", - self.root, - git_root, - ); - return Err(SystemError::git_root(msg)); - } - Ok(()) - } - - // TODO: abstract out command running and error handling - fn git_command(&self) -> Command { - // TODO: add support for the GIT environment variable? - let mut command = Command::new("git"); - command.current_dir(self.root).stderr(Stdio::inherit()); - command - } - - /// Gets the scratch worktree if it exists, or initializes it if it doesn't. - /// - /// The scratch worktree is meant to be persistent across invocations of `x`. This is done for - /// performance reasons. - fn get_or_init_scratch(&self, hash: &GitHash) -> Result { - let mut scratch_dir = self.root.join("target"); - scratch_dir.extend(&["x-scratch", "tree"]); - - if scratch_dir.is_dir() && self.is_git_repo(&scratch_dir)? { - debug!("Using existing scratch worktree at {}", scratch_dir,); - - // Check out the given hash in the scratch worktree. - let output = self - .git_command() - .current_dir(&scratch_dir) - // TODO: also git clean? - .args(&["reset", &format!("{:x}", hash), "--hard"]) - .output() - .map_err(|err| SystemError::io("running git checkout in scratch tree", err))?; - if !output.status.success() { - return Err(SystemError::Exec { - cmd: "git checkout", - status: output.status, - }); - } - } else { - if scratch_dir.is_dir() { - std::fs::remove_dir_all(&scratch_dir) - .map_err(|err| SystemError::io("cleaning old scratch_dir", err))?; - } - - // Try creating a scratch worktree at that location. - info!("Setting up scratch worktree in {}", scratch_dir); - let output = self - .git_command() - .args(&["worktree", "add"]) - .arg(&scratch_dir) - .args(&[&format!("{:x}", hash), "--detach"]) - .output() - .map_err(|err| SystemError::io("running git worktree add", err))?; - if !output.status.success() { - return Err(SystemError::Exec { - cmd: "git worktree add", - status: output.status, - }); - } - } - - // TODO: some sort of cross-process locking may be necessary in the future. Don't worry - // about it for now. - Ok(scratch_dir) - } - - pub fn is_git_repo(&self, dir: &Utf8Path) -> Result { - let output = self - .git_command() - .current_dir(dir) - .args(&["rev-parse", "--git-dir"]) - .output() - .map_err(|err| SystemError::io("checking if a directory is a git repo", err))?; - - Ok(output.status.success()) - } -} - -/// A Git hash. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct GitHash([u8; 20]); - -impl GitHash { - /// Creates a new Git hash from a hex-encoded string. - pub fn from_hex(hex: impl AsRef<[u8]>) -> Result { - let hex = hex.as_ref(); - Ok(GitHash(hex::FromHex::from_hex(hex).map_err(|err| { - SystemError::from_hex(format!("parsing a Git hash: {:?}", hex), err) - })?)) - } -} - -impl<'a, 'b> From<&'a GitHash> for Cow<'b, OsStr> { - fn from(git_hash: &'a GitHash) -> Cow<'b, OsStr> { - OsString::from(format!("{:x}", git_hash)).into() - } -} - -impl fmt::LowerHex for GitHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} diff --git a/devtools/x-core/src/graph.rs b/devtools/x-core/src/graph.rs deleted file mode 100644 index ed2fa27d0f844..0000000000000 --- a/devtools/x-core/src/graph.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{Result, SystemError, WorkspaceSubsets, XCoreContext}; -use guppy::{graph::PackageGraph, MetadataCommand}; -use ouroboros::self_referencing; - -#[self_referencing] -pub(crate) struct PackageGraphPlus { - g: Box, - #[borrows(g)] - #[covariant] - subsets: WorkspaceSubsets<'this>, -} - -impl PackageGraphPlus { - pub(crate) fn create(ctx: &XCoreContext) -> Result { - let mut cmd = MetadataCommand::new(); - // Run cargo metadata from the root of the workspace. - let project_root = ctx.project_root(); - cmd.current_dir(project_root); - - Self::try_new( - Box::new( - cmd.build_graph() - .map_err(|err| SystemError::guppy("building package graph", err))?, - ), - move |graph: &PackageGraph| { - WorkspaceSubsets::new(graph, project_root, &ctx.config().subsets) - }, - ) - } - - pub(crate) fn package_graph(&self) -> &PackageGraph { - self.borrow_g() - } - - pub(crate) fn subsets(&self) -> &WorkspaceSubsets { - self.borrow_subsets() - } -} diff --git a/devtools/x-core/src/lib.rs b/devtools/x-core/src/lib.rs deleted file mode 100644 index 755a9582e6dbc..0000000000000 --- a/devtools/x-core/src/lib.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{core_config::XCoreConfig, git::GitCli}; -use camino::{Utf8Path, Utf8PathBuf}; -use debug_ignore::DebugIgnore; -use graph::PackageGraphPlus; -use guppy::graph::PackageGraph; -use once_cell::sync::OnceCell; - -pub mod core_config; -mod errors; -pub mod git; -mod graph; -mod workspace_subset; - -pub use errors::*; -pub use workspace_subset::*; - -/// Core context shared across all of x. -#[derive(Debug)] -pub struct XCoreContext { - project_root: &'static Utf8Path, - config: XCoreConfig, - current_dir: Utf8PathBuf, - current_rel_dir: Utf8PathBuf, - git_cli: OnceCell, - package_graph_plus: DebugIgnore>, -} - -impl XCoreContext { - /// Creates a new XCoreContext. - pub fn new( - project_root: &'static Utf8Path, - current_dir: Utf8PathBuf, - config: XCoreConfig, - ) -> Result { - let current_rel_dir = match current_dir.strip_prefix(project_root) { - Ok(rel_dir) => rel_dir.to_path_buf(), - Err(_) => { - return Err(SystemError::CwdNotInProjectRoot { - current_dir, - project_root, - }) - } - }; - // TODO: The project root should be managed by this struct, not by the global project_root - // function. - Ok(Self { - project_root, - config, - current_dir, - current_rel_dir, - git_cli: OnceCell::new(), - package_graph_plus: DebugIgnore(OnceCell::new()), - }) - } - - /// Returns the project root for this workspace. - pub fn project_root(&self) -> &'static Utf8Path { - self.project_root - } - - /// Returns the core config. - pub fn config(&self) -> &XCoreConfig { - &self.config - } - - /// Returns the current working directory for this process. - pub fn current_dir(&self) -> &Utf8Path { - &self.current_dir - } - - /// Returns the current working directory for this process, relative to the project root. - pub fn current_rel_dir(&self) -> &Utf8Path { - &self.current_rel_dir - } - - /// Returns true if x has been run from the project root. - pub fn current_dir_is_root(&self) -> bool { - self.current_rel_dir == "" - } - - /// Returns the Git CLI for this workspace. - pub fn git_cli(&self) -> Result<&GitCli> { - let root = self.project_root; - self.git_cli.get_or_try_init(|| GitCli::new(root)) - } - - /// Returns the package graph for this workspace. - pub fn package_graph(&self) -> Result<&PackageGraph> { - Ok(self.package_graph_plus()?.package_graph()) - } - - /// For a given list of workspace packages, returns a tuple of (known, unknown) packages. - /// - /// Initializes the package graph if it isn't already done so, and returns an error if the - pub fn partition_workspace_names<'a, B>( - &self, - names: impl IntoIterator, - ) -> Result<(B, B)> - where - B: Default + Extend<&'a str>, - { - let workspace = self.package_graph()?.workspace(); - let (known, unknown) = names - .into_iter() - .partition(|name| workspace.contains_name(name)); - Ok((known, unknown)) - } - - /// Returns information about the subsets for this workspace. - pub fn subsets(&self) -> Result<&WorkspaceSubsets> { - Ok(self.package_graph_plus()?.subsets()) - } - - // --- - // Helper methods - // --- - - fn package_graph_plus(&self) -> Result<&PackageGraphPlus> { - self.package_graph_plus - .get_or_try_init(|| PackageGraphPlus::create(self)) - } -} diff --git a/devtools/x-core/src/workspace_subset.rs b/devtools/x-core/src/workspace_subset.rs deleted file mode 100644 index 2367f799740d1..0000000000000 --- a/devtools/x-core/src/workspace_subset.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{core_config::SubsetConfig, Result, SystemError}; -use camino::{Utf8Path, Utf8PathBuf}; -use guppy::{ - graph::{ - cargo::{CargoOptions, CargoResolverVersion, CargoSet}, - feature::{FeatureFilter, FeatureSet, StandardFeatures}, - DependencyDirection, PackageGraph, PackageMetadata, PackageSet, - }, - PackageId, -}; -use serde::Deserialize; -use std::{collections::BTreeMap, fs}; -use toml::de; - -/// Contains information about all the subsets specified in this workspace. -#[derive(Clone, Debug)] -pub struct WorkspaceSubsets<'g> { - // TODO: default members should become a subset in x.toml - default_members: WorkspaceSubset<'g>, - subsets: BTreeMap>, -} - -impl<'g> WorkspaceSubsets<'g> { - /// Constructs a new store for workspace subsets. - /// - /// This is done with respect to a "standard build", which assumes: - /// * any platform - /// * v2 resolver - /// * no dev dependencies - pub fn new( - graph: &'g PackageGraph, - project_root: &Utf8Path, - config: &BTreeMap, - ) -> Result { - let mut cargo_opts = CargoOptions::new(); - cargo_opts - .set_resolver(CargoResolverVersion::V2) - .set_include_dev(false); - - let default_members = Self::read_default_members(project_root)?; - - // Look up default members by path. - let initial_packages = graph - .resolve_workspace_paths(&default_members) - .map_err(|err| SystemError::guppy("querying default members", err))?; - let default_members = - WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts); - - // For each of the subset configs, look up the packages by name. - let subsets = config - .iter() - .map(|(name, config)| { - let initial_packages = graph - .resolve_workspace_names(&config.root_members) - .map_err(|err| { - SystemError::guppy(format!("querying members for subset '{}'", name), err) - })?; - let subset = - WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts); - Ok((name.clone(), subset)) - }) - .collect::>()?; - - Ok(Self { - default_members, - subsets, - }) - } - - /// Returns information about default members. - pub fn default_members(&self) -> &WorkspaceSubset<'g> { - &self.default_members - } - - /// Returns information about the subset by name. - pub fn get(&self, name: impl AsRef) -> Option<&WorkspaceSubset<'g>> { - self.subsets.get(name.as_ref()) - } - - /// Iterate over all named subsets. - pub fn iter<'a>(&'a self) -> impl Iterator)> + 'a { - self.subsets - .iter() - .map(|(name, subset)| (name.as_str(), subset)) - } - - // --- - // Helper methods - // --- - - fn read_default_members(project_root: &Utf8Path) -> Result> { - #[derive(Deserialize)] - struct RootToml { - workspace: Workspace, - } - - #[derive(Deserialize)] - struct Workspace { - #[serde(rename = "default-members")] - default_members: Vec, - } - - let root_toml = project_root.join("Cargo.toml"); - let contents = - fs::read(&root_toml).map_err(|err| SystemError::io("reading root Cargo.toml", err))?; - let contents: RootToml = de::from_slice(&contents) - .map_err(|err| SystemError::de("deserializing root Cargo.toml", err))?; - Ok(contents.workspace.default_members) - } -} - -/// Information collected about a subset of members of a workspace. -/// -/// Some subsets of this workspace have special properties that are enforced through linters. -#[derive(Clone, Debug)] -pub struct WorkspaceSubset<'g> { - build_set: CargoSet<'g>, - unified_set: FeatureSet<'g>, -} - -impl<'g> WorkspaceSubset<'g> { - /// Creates a new subset by simulating a Cargo build on the specified workspace paths, with - /// the given feature filter. - pub fn new<'a>( - initial_packages: &PackageSet<'g>, - feature_filter: impl FeatureFilter<'g>, - cargo_opts: &CargoOptions<'_>, - ) -> Self { - // Use the Cargo resolver to figure out which packages will be included. - let build_set = initial_packages - .to_feature_set(feature_filter) - .into_cargo_set(cargo_opts) - .expect("into_cargo_set should always succeed"); - let unified_set = build_set.host_features().union(build_set.target_features()); - - Self { - build_set, - unified_set, - } - } - - /// Returns the initial members that this subset was constructed from. - pub fn initials(&self) -> &FeatureSet<'g> { - self.build_set.initials() - } - - /// Returns the status of the given package ID in the subset. - pub fn status_of(&self, package_id: &PackageId) -> WorkspaceStatus { - if self - .build_set - .initials() - .contains_package(package_id) - .unwrap_or(false) - { - WorkspaceStatus::RootMember - } else if self - .unified_set - .features_for(package_id) - .unwrap_or(None) - .is_some() - { - WorkspaceStatus::Dependency - } else { - WorkspaceStatus::Absent - } - } - - /// Returns a list of root packages in this subset, ignoring transitive dependencies. - pub fn root_members<'a>(&'a self) -> impl Iterator> + 'a { - self.build_set - .initials() - .packages_with_features(DependencyDirection::Forward) - .map(|f| *f.package()) - } - - /// Returns the set of packages and features that would be built from this subset. - /// - /// This contains information about transitive dependencies, both within the workspace and - /// outside it. - pub fn build_set(&self) -> &CargoSet<'g> { - &self.build_set - } -} - -/// The status of a particular package ID in a `WorkspaceSubset`. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -pub enum WorkspaceStatus { - /// This package ID is a root member of the workspace subset. - RootMember, - /// This package ID is a dependency of the workspace subset, but not a root member. - Dependency, - /// This package ID is not a dependency of the workspace subset. - Absent, -} diff --git a/devtools/x-lint/Cargo.toml b/devtools/x-lint/Cargo.toml deleted file mode 100644 index 1fd8d7a8ed333..0000000000000 --- a/devtools/x-lint/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "x-lint" -version = "0.1.0" -authors = ["Aptos Labs "] -description = "Lint engine for x" -edition = "2018" -publish = false -license = "Apache-2.0" - -[dependencies] -camino = "1.0.3" -guppy = "0.13.0" -hakari = "0.9.0" - -x-core = { path = "../x-core" } diff --git a/devtools/x-lint/src/content.rs b/devtools/x-lint/src/content.rs deleted file mode 100644 index d607a0256c190..0000000000000 --- a/devtools/x-lint/src/content.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{prelude::*, LintContext}; -use std::str; - -/// Represents a linter that checks file contents. -pub trait ContentLinter: Linter { - /// Pre-run step -- avoids loading the contents if possible. - /// - /// The default implementation returns `Ok(RunStatus::Executed)`; individual lints may configure - /// a more restricted set. - fn pre_run<'l>(&self, _file_ctx: &FilePathContext<'l>) -> Result> { - Ok(RunStatus::Executed) - } - - /// Executes the lint against the given content context. - fn run<'l>( - &self, - ctx: &ContentContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result>; -} - -#[derive(Debug)] -pub struct ContentContext<'l> { - file_ctx: FilePathContext<'l>, - content: Content, -} - -#[allow(dead_code)] -impl<'l> ContentContext<'l> { - /// The number of bytes that will be searched for null bytes in a file to figure out if it is - /// binary. - /// - /// The value is [the same as Git's](https://stackoverflow.com/a/6134127). - pub const BINARY_FILE_CUTOFF: usize = 8000; - - pub(super) fn new(file_ctx: FilePathContext<'l>, content: Vec) -> Self { - Self { - file_ctx, - content: Content::new(content), - } - } - - /// Returns the file context. - pub fn file_ctx(&self) -> &FilePathContext<'l> { - &self.file_ctx - } - - /// Returns the content, or `None` if this is a non-UTF-8 file. - pub fn content(&self) -> Option<&str> { - match &self.content { - Content::Utf8(text) => Some(text.as_ref()), - Content::NonUtf8(_) => None, - } - } - - /// Returns the raw bytes for the content. - pub fn content_bytes(&self) -> &[u8] { - match &self.content { - Content::Utf8(text) => text.as_bytes(), - Content::NonUtf8(bin) => bin.as_ref(), - } - } - - /// Returns true if this is a binary file. - pub fn is_binary(&self) -> bool { - match &self.content { - Content::Utf8(_) => { - // UTF-8 files are not binary by definition. - false - } - Content::NonUtf8(bin) => bin[..Self::BINARY_FILE_CUTOFF].contains(&0), - } - } -} - -impl<'l> LintContext<'l> for ContentContext<'l> { - fn kind(&self) -> LintKind<'l> { - LintKind::Content(self.file_ctx.file_path()) - } -} - -#[derive(Debug)] -enum Content { - Utf8(Box), - NonUtf8(Box<[u8]>), -} - -impl Content { - fn new(bytes: Vec) -> Self { - match String::from_utf8(bytes) { - Ok(s) => Content::Utf8(s.into()), - Err(err) => Content::NonUtf8(err.into_bytes().into()), - } - } - - #[allow(dead_code)] - fn len(&self) -> usize { - match self { - Content::Utf8(text) => text.len(), - Content::NonUtf8(bin) => bin.len(), - } - } -} diff --git a/devtools/x-lint/src/file_path.rs b/devtools/x-lint/src/file_path.rs deleted file mode 100644 index 9e9374a707e3e..0000000000000 --- a/devtools/x-lint/src/file_path.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{prelude::*, LintContext}; -use camino::Utf8Path; -use std::{fs, io}; - -/// Represents a linter that runs once per file path. -pub trait FilePathLinter: Linter { - /// Executes this linter against the given file path context. - fn run<'l>( - &self, - ctx: &FilePathContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result>; -} - -/// Contains information for a single file path. -#[derive(Clone, Debug)] -pub struct FilePathContext<'l> { - project_ctx: &'l ProjectContext<'l>, - file_path: &'l Utf8Path, -} - -impl<'l> FilePathContext<'l> { - /// Constructs a new context. - pub fn new(project_ctx: &'l ProjectContext<'l>, file_path: &'l Utf8Path) -> Self { - Self { - project_ctx, - file_path, - } - } - - /// Returns the project context. - pub fn project_ctx(&self) -> &'l ProjectContext<'l> { - self.project_ctx - } - - /// Returns the path of this file, relative to the root of the repository. - pub fn file_path(&self) -> &'l Utf8Path { - self.file_path - } - - /// Returns the extension of the file. Returns `None` if there's no extension. - pub fn extension(&self) -> Option<&'l str> { - self.file_path.extension() - } - - /// Loads this file and turns it into a `ContentContext`. - /// - /// Returns `None` if the file is missing. - /// - /// `pub(super)` is to dissuade individual linters from loading file contexts. - pub(super) fn load(self) -> Result>> { - let full_path = self.project_ctx.full_path(self.file_path); - let contents_opt = read_file(&full_path) - .map_err(|err| SystemError::io(format!("loading {}", full_path), err))?; - Ok(contents_opt.map(|content| ContentContext::new(self, content))) - } -} - -impl<'l> LintContext<'l> for FilePathContext<'l> { - fn kind(&self) -> LintKind<'l> { - LintKind::FilePath(self.file_path) - } -} - -fn read_file(full_path: &Utf8Path) -> io::Result>> { - match fs::read(full_path) { - Ok(bytes) => Ok(Some(bytes)), - Err(err) => { - if err.kind() == io::ErrorKind::NotFound { - // Files can be listed by source control but missing -- this is normal. - Ok(None) - } else { - Err(err) - } - } - } -} diff --git a/devtools/x-lint/src/lib.rs b/devtools/x-lint/src/lib.rs deleted file mode 100644 index 8a6188732d88c..0000000000000 --- a/devtools/x-lint/src/lib.rs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -//! Lint engine. -//! -//! The overall design is generally inspired by -//! [Arcanist](https://secure.phabricator.com/book/phabricator/article/arcanist_lint)'s lint engine. - -pub mod content; -pub mod file_path; -pub mod package; -pub mod project; -pub mod runner; - -use camino::Utf8Path; -use guppy::PackageId; -use std::{borrow::Cow, fmt}; - -/// Represents a linter. -pub trait Linter: Send + Sync + fmt::Debug { - /// Returns the name of the linter. - fn name(&self) -> &'static str; -} - -/// Represents common functionality among various `Context` instances. -trait LintContext<'l> { - /// Returns the kind of this lint context. - fn kind(&self) -> LintKind<'l>; - - /// Returns a `LintSource` for this lint context. - fn source(&self, name: &'static str) -> LintSource<'l> { - LintSource::new(name, self.kind()) - } -} - -/// A lint formatter. -/// -/// Lints write `LintMessage` instances to this. -pub struct LintFormatter<'l, 'a> { - source: LintSource<'l>, - messages: &'a mut Vec<(LintSource<'l>, LintMessage)>, -} - -impl<'l, 'a> LintFormatter<'l, 'a> { - pub fn new( - source: LintSource<'l>, - messages: &'a mut Vec<(LintSource<'l>, LintMessage)>, - ) -> Self { - Self { source, messages } - } - - /// Writes a new lint message to this formatter. - pub fn write(&mut self, level: LintLevel, message: impl Into>) { - self.messages - .push((self.source, LintMessage::new(level, message))); - } - - /// Writes a new lint message to this formatter with a custom kind. - pub fn write_kind( - &mut self, - kind: LintKind<'l>, - level: LintLevel, - message: impl Into>, - ) { - self.messages.push(( - LintSource::new(self.source.name(), kind), - LintMessage::new(level, message), - )); - } -} - -/// The run status of a lint. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RunStatus<'l> { - /// This lint run was successful, with messages possibly written into the `LintFormatter`. - Executed, - /// This lint was skipped. - Skipped(SkipReason<'l>), -} - -/// The reason for why this lint was skipped. -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum SkipReason<'l> { - /// This file's content was not valid UTF-8. - NonUtf8Content, - /// This extension was unsupported. - UnsupportedExtension(Option<&'l str>), - /// The given file was unsupported by this linter. - UnsupportedFile(&'l Utf8Path), - /// The given package was unsupported by this linter. - UnsupportedPackage(&'l PackageId), - /// The given file was excepted by a glob rule - GlobExemption(&'l str), - // TODO: Add more reasons. -} - -/// A message raised by a lint. -#[derive(Debug)] -pub struct LintMessage { - level: LintLevel, - message: Cow<'static, str>, -} - -impl LintMessage { - pub fn new(level: LintLevel, message: impl Into>) -> Self { - Self { - level, - message: message.into(), - } - } - - pub fn level(&self) -> LintLevel { - self.level - } - - pub fn message(&self) -> &str { - &self.message - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[allow(dead_code)] -#[non_exhaustive] -pub enum LintLevel { - Error, - Warning, - // TODO: add more levels? -} - -impl fmt::Display for LintLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LintLevel::Error => write!(f, "ERROR"), - LintLevel::Warning => write!(f, "WARNING"), - } - } -} - -/// Message source for lints. -#[derive(Copy, Clone, Debug)] -pub struct LintSource<'l> { - name: &'static str, - kind: LintKind<'l>, -} - -impl<'l> LintSource<'l> { - fn new(name: &'static str, kind: LintKind<'l>) -> Self { - Self { name, kind } - } - - pub fn name(&self) -> &'static str { - self.name - } - - pub fn kind(&self) -> LintKind<'l> { - self.kind - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum LintKind<'l> { - Project, - Package { - name: &'l str, - workspace_path: &'l Utf8Path, - }, - FilePath(&'l Utf8Path), - Content(&'l Utf8Path), -} - -impl<'l> fmt::Display for LintKind<'l> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LintKind::Project => write!(f, "project"), - LintKind::Package { - name, - workspace_path, - } => write!(f, "package '{}' (at {})", name, workspace_path), - LintKind::FilePath(path) => write!(f, "file path {}", path), - LintKind::Content(path) => write!(f, "content {}", path), - } - } -} - -pub mod prelude { - pub use super::{ - content::{ContentContext, ContentLinter}, - file_path::{FilePathContext, FilePathLinter}, - package::{PackageContext, PackageLinter}, - project::{ProjectContext, ProjectLinter}, - runner::{LintEngine, LintEngineConfig, LintResults}, - LintFormatter, LintKind, LintLevel, LintMessage, LintSource, Linter, RunStatus, SkipReason, - }; - pub use x_core::{Result, SystemError}; -} diff --git a/devtools/x-lint/src/package.rs b/devtools/x-lint/src/package.rs deleted file mode 100644 index 5936982af5d88..0000000000000 --- a/devtools/x-lint/src/package.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{prelude::*, LintContext}; -use camino::Utf8Path; -use guppy::graph::{PackageGraph, PackageMetadata}; -use x_core::WorkspaceStatus; - -/// Represents a linter that runs once per package. -pub trait PackageLinter: Linter { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result>; -} - -/// Lint context for an individual package. -#[derive(Copy, Clone, Debug)] -pub struct PackageContext<'l> { - project_ctx: &'l ProjectContext<'l>, - // PackageContext requires the package graph to be computed and available, though ProjectContext - // does not. - package_graph: &'l PackageGraph, - workspace_path: &'l Utf8Path, - metadata: PackageMetadata<'l>, - is_default_member: bool, -} - -impl<'l> PackageContext<'l> { - pub fn new( - project_ctx: &'l ProjectContext<'l>, - package_graph: &'l PackageGraph, - workspace_path: &'l Utf8Path, - metadata: PackageMetadata<'l>, - ) -> Result { - let default_members = project_ctx.default_members()?; - Ok(Self { - project_ctx, - package_graph, - workspace_path, - metadata, - is_default_member: default_members.status_of(metadata.id()) != WorkspaceStatus::Absent, - }) - } - - /// Returns the project context. - pub fn project_ctx(&self) -> &'l ProjectContext<'l> { - self.project_ctx - } - - /// Returns the package graph. - pub fn package_graph(&self) -> &'l PackageGraph { - self.package_graph - } - - /// Returns the relative path for this package in the workspace. - pub fn workspace_path(&self) -> &'l Utf8Path { - self.workspace_path - } - - /// Returns the metadata for this package. - pub fn metadata(&self) -> &PackageMetadata<'l> { - &self.metadata - } - - /// Returns true if this is a default member of this workspace. - pub fn is_default_member(&self) -> bool { - self.is_default_member - } -} - -impl<'l> LintContext<'l> for PackageContext<'l> { - fn kind(&self) -> LintKind<'l> { - LintKind::Package { - name: self.metadata.name(), - workspace_path: self.workspace_path, - } - } -} diff --git a/devtools/x-lint/src/project.rs b/devtools/x-lint/src/project.rs deleted file mode 100644 index 15e53445d9754..0000000000000 --- a/devtools/x-lint/src/project.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{prelude::*, LintContext}; -use camino::{Utf8Path, Utf8PathBuf}; -use guppy::graph::PackageGraph; -use x_core::{WorkspaceSubset, XCoreContext}; - -/// Represents a linter that checks some property for the overall project. -/// -/// Linters that implement `ProjectLinter` will run once for the whole project. -pub trait ProjectLinter: Linter { - // Since ProjectContext is only 1 word long, clippy complains about passing it by reference. Do - // it that way for consistency reasons. - #[allow(clippy::trivially_copy_pass_by_ref)] - /// Executes the lint against the given project context. - fn run<'l>( - &self, - ctx: &ProjectContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result>; -} - -/// Overall linter context for a project. -#[derive(Debug)] -pub struct ProjectContext<'l> { - core: &'l XCoreContext, -} - -impl<'l> ProjectContext<'l> { - pub fn new(core: &'l XCoreContext) -> Self { - Self { core } - } - - /// Returns the core context. - pub fn core(&self) -> &'l XCoreContext { - self.core - } - - /// Returns the project root. - pub fn project_root(&self) -> &'l Utf8Path { - self.core.project_root() - } - - /// Returns the package graph, computing it for the first time if necessary. - pub fn package_graph(&self) -> Result<&'l PackageGraph> { - self.core.package_graph() - } - - /// Returns the absolute path from the project root. - pub fn full_path(&self, path: impl AsRef) -> Utf8PathBuf { - self.core.project_root().join(path.as_ref()) - } - - /// Returns information about the default workspace members. - /// - /// This includes all packages included by default in the default workspace members, but not - /// those that Cargo would ignore. - pub fn default_members(&self) -> Result<&WorkspaceSubset> { - Ok(self.core.subsets()?.default_members()) - } -} - -impl<'l> LintContext<'l> for ProjectContext<'l> { - fn kind(&self) -> LintKind<'l> { - LintKind::Project - } -} diff --git a/devtools/x-lint/src/runner.rs b/devtools/x-lint/src/runner.rs deleted file mode 100644 index 5a980ed8feba5..0000000000000 --- a/devtools/x-lint/src/runner.rs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{prelude::*, LintContext}; -use camino::Utf8Path; -use x_core::XCoreContext; - -/// Configuration for the lint engine. -#[derive(Clone, Debug)] -pub struct LintEngineConfig<'cfg> { - core: &'cfg XCoreContext, - project_linters: &'cfg [&'cfg dyn ProjectLinter], - package_linters: &'cfg [&'cfg dyn PackageLinter], - file_path_linters: &'cfg [&'cfg dyn FilePathLinter], - content_linters: &'cfg [&'cfg dyn ContentLinter], - fail_fast: bool, -} - -impl<'cfg> LintEngineConfig<'cfg> { - pub fn new(core: &'cfg XCoreContext) -> Self { - Self { - core, - project_linters: &[], - package_linters: &[], - file_path_linters: &[], - content_linters: &[], - fail_fast: false, - } - } - - pub fn with_project_linters( - &mut self, - project_linters: &'cfg [&'cfg dyn ProjectLinter], - ) -> &mut Self { - self.project_linters = project_linters; - self - } - - pub fn with_package_linters( - &mut self, - package_linters: &'cfg [&'cfg dyn PackageLinter], - ) -> &mut Self { - self.package_linters = package_linters; - self - } - - pub fn with_file_path_linters( - &mut self, - file_path_linters: &'cfg [&'cfg dyn FilePathLinter], - ) -> &mut Self { - self.file_path_linters = file_path_linters; - self - } - - pub fn with_content_linters( - &mut self, - content_linters: &'cfg [&'cfg dyn ContentLinter], - ) -> &mut Self { - self.content_linters = content_linters; - self - } - - pub fn fail_fast(&mut self, fail_fast: bool) -> &mut Self { - self.fail_fast = fail_fast; - self - } - - pub fn build(&self) -> LintEngine<'cfg> { - LintEngine::new(self.clone()) - } -} - -/// Executor for linters. -#[derive(Debug)] -pub struct LintEngine<'cfg> { - config: LintEngineConfig<'cfg>, - project_ctx: ProjectContext<'cfg>, -} - -impl<'cfg> LintEngine<'cfg> { - pub fn new(config: LintEngineConfig<'cfg>) -> Self { - let project_ctx = ProjectContext::new(config.core); - Self { - config, - project_ctx, - } - } - - pub fn run(&self) -> Result { - let mut skipped = vec![]; - let mut messages = vec![]; - - // TODO: add support for file linters. - - // Run project linters. - if !self.config.project_linters.is_empty() { - for linter in self.config.project_linters { - let source = self.project_ctx.source(linter.name()); - let mut formatter = LintFormatter::new(source, &mut messages); - match linter.run(&self.project_ctx, &mut formatter)? { - RunStatus::Executed => { - // Lint ran successfully. - } - RunStatus::Skipped(reason) => { - skipped.push((source, reason)); - } - } - - if self.config.fail_fast && !messages.is_empty() { - // At least one issue was found. - return Ok(LintResults { skipped, messages }); - } - } - } - - // Run package linters. - if !self.config.package_linters.is_empty() { - let package_graph = self.project_ctx.package_graph()?; - - for (workspace_path, metadata) in package_graph.workspace().iter_by_path() { - let package_ctx = PackageContext::new( - &self.project_ctx, - package_graph, - workspace_path, - metadata, - )?; - for linter in self.config.package_linters { - let source = package_ctx.source(linter.name()); - let mut formatter = LintFormatter::new(source, &mut messages); - match linter.run(&package_ctx, &mut formatter)? { - RunStatus::Executed => { - // Lint ran successfully. - } - RunStatus::Skipped(reason) => { - skipped.push((source, reason)); - } - } - - if self.config.fail_fast && !messages.is_empty() { - // At least one issue was found. - return Ok(LintResults { skipped, messages }); - } - } - } - } - - // Run file path linters. - if !self.config.file_path_linters.is_empty() { - let file_list = self.file_list()?; - - let file_ctxs = file_list.map(|path| FilePathContext::new(&self.project_ctx, path)); - - for file_ctx in file_ctxs { - for linter in self.config.file_path_linters { - let source = file_ctx.source(linter.name()); - let mut formatter = LintFormatter::new(source, &mut messages); - match linter.run(&file_ctx, &mut formatter)? { - RunStatus::Executed => { - // Lint ran successfully. - } - RunStatus::Skipped(reason) => { - skipped.push((source, reason)); - } - } - - if self.config.fail_fast && !messages.is_empty() { - // At least one issue was found. - return Ok(LintResults { skipped, messages }); - } - } - } - } - - // Run content linters. - if !self.config.content_linters.is_empty() { - let file_list = self.file_list()?; - - // TODO: This should probably be a worker queue with a thread pool or something. - - let file_ctxs = file_list.map(|path| FilePathContext::new(&self.project_ctx, path)); - - for file_ctx in file_ctxs { - let linters_to_run = self - .config - .content_linters - .iter() - .copied() - .filter_map(|linter| match linter.pre_run(&file_ctx) { - Ok(RunStatus::Executed) => Some(Ok(linter)), - Ok(RunStatus::Skipped(reason)) => { - let source = file_ctx.source(linter.name()); - skipped.push((source, reason)); - None - } - Err(err) => Some(Err(err)), - }) - .collect::>>()?; - - if linters_to_run.is_empty() { - // No linters to run for this file -- no point loading it. - continue; - } - - // Load up the content for this file. - let content_ctx = match file_ctx.load()? { - Some(content_ctx) => content_ctx, - None => { - // This file is missing -- can't run content linters on it. - continue; - } - }; - - for linter in linters_to_run { - let source = content_ctx.source(linter.name()); - let mut formatter = LintFormatter::new(source, &mut messages); - - match linter.run(&content_ctx, &mut formatter)? { - RunStatus::Executed => { - // Yay! Lint ran successfully. - } - RunStatus::Skipped(reason) => { - skipped.push((source, reason)); - } - } - - if self.config.fail_fast && !messages.is_empty() { - // At least one issue was found. - return Ok(LintResults { skipped, messages }); - } - } - } - } - - Ok(LintResults { skipped, messages }) - } - - // --- - // Helper methods - // --- - - fn file_list(&self) -> Result + 'cfg> { - let git_cli = self.config.core.git_cli()?; - let tracked_files = git_cli.tracked_files()?; - // TODO: make global exclusions configurable - Ok(tracked_files - .iter() - .filter(|f| !f.starts_with("testsuite/aptos-fuzzer/artifacts/"))) - } -} - -#[derive(Debug)] -#[non_exhaustive] -pub struct LintResults<'l> { - pub skipped: Vec<(LintSource<'l>, SkipReason<'l>)>, - pub messages: Vec<(LintSource<'l>, LintMessage)>, -} diff --git a/devtools/x/Cargo.toml b/devtools/x/Cargo.toml deleted file mode 100644 index b2be7483dc6c9..0000000000000 --- a/devtools/x/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "x" -version = "0.1.0" -authors = ["Aptos Labs "] -description = "Aptos extended cargo tasks" -edition = "2018" -publish = false -license = "Apache-2.0" - -[dependencies] -anyhow = "1.0.57" -camino = "1.0.3" -chrono = "0.4.19" -colored-diff = "0.2.2" -determinator = "0.8.0" -env_logger = "0.8.3" -globset = "0.4.6" -guppy = { version = "0.13.0", features = ["summaries"] } -indexmap = "1.6.2" -indoc = "1.0.3" -log = "0.4.17" -nextest-runner = "0.4.0" -rayon = "1.5.2" -regex = "1.5.5" -serde = { version = "1.0.137", features = ["derive"] } -serde_json = "1.0.81" -structopt = "0.3.21" -supports-color = "1.3.0" -toml = "0.5.9" - -x-core = { path = "../x-core" } -x-lint = { path = "../x-lint" } diff --git a/devtools/x/build.rs b/devtools/x/build.rs deleted file mode 100644 index 49698dfeb69eb..0000000000000 --- a/devtools/x/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -fn main() { - // Disables telemetry for all x commands by setting the environment variable - println!("cargo:rustc-env=APTOS_DISABLE_TELEMETRY={}", 1); -} diff --git a/devtools/x/src/bench.rs b/devtools/x/src/bench.rs deleted file mode 100644 index 49e67b6db0950..0000000000000 --- a/devtools/x/src/bench.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use std::ffi::OsString; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - package_args: SelectedPackageArgs, - /// Do not run the benchmarks, but compile them - #[structopt(long)] - no_run: bool, - #[structopt(name = "BENCHNAME", parse(from_os_str))] - benchname: Option, - #[structopt(name = "ARGS", parse(from_os_str), last = true)] - args: Vec, -} - -pub fn run(mut args: Args, xctx: XContext) -> Result<()> { - args.args.extend(args.benchname.clone()); - - let mut direct_args = Vec::new(); - if args.no_run { - direct_args.push(OsString::from("--no-run")); - }; - - let cmd = CargoCommand::Bench { - direct_args: direct_args.as_slice(), - args: &args.args, - env: &[], - }; - - let packages = args.package_args.to_selected_packages(&xctx)?; - cmd.run_on_packages(&packages) -} diff --git a/devtools/x/src/build.rs b/devtools/x/src/build.rs deleted file mode 100644 index 8a1a0cc3eb3d4..0000000000000 --- a/devtools/x/src/build.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use log::info; -use std::ffi::OsString; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - package_args: SelectedPackageArgs, - #[structopt(flatten)] - build_args: BuildArgs, - #[structopt(long, parse(from_os_str))] - /// Copy final artifacts to this directory (unstable) - out_dir: Option, - #[structopt(long)] - /// Output the build plan in JSON (unstable) - build_plan: bool, -} - -pub fn convert_args(args: &Args) -> Vec { - let mut direct_args = Vec::new(); - args.build_args.add_args(&mut direct_args); - if let Some(out_dir) = &args.out_dir { - direct_args.push(OsString::from("--out-dir")); - direct_args.push(OsString::from(out_dir)); - }; - if args.build_plan { - direct_args.push(OsString::from("--build-plan")); - }; - - direct_args -} - -pub fn run(args: Box, xctx: XContext) -> Result<()> { - info!("Build plan: {}", args.build_plan); - - let direct_args = convert_args(&args); - - let cmd = CargoCommand::Build { - direct_args: direct_args.as_slice(), - args: &[], - env: &[], - }; - - let packages = args.package_args.to_selected_packages(&xctx)?; - cmd.run_on_packages(&packages) -} diff --git a/devtools/x/src/cargo.rs b/devtools/x/src/cargo.rs deleted file mode 100644 index 6cf404d5f302b..0000000000000 --- a/devtools/x/src/cargo.rs +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::selected_package::{SelectedInclude, SelectedPackages}, - utils::project_root, - Result, -}; -use anyhow::anyhow; -use indexmap::map::IndexMap; -use log::{info, warn}; -use std::{ - ffi::{OsStr, OsString}, - path::Path, - process::{Command, Output, Stdio}, - time::Instant, -}; - -pub mod build_args; -pub mod selected_package; - -const SECRET_ENVS: &[&str] = &["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]; -pub struct Cargo { - inner: Command, - pass_through_args: Vec, - env_additions: IndexMap>, - on_close: fn(), -} - -impl Drop for Cargo { - fn drop(&mut self) { - (self.on_close)(); - } -} - -impl Cargo { - pub fn new>(command: S) -> Self { - let mut inner = Command::new("cargo"); - let envs = IndexMap::new(); - - let on_drop = || (); - - inner.arg(command); - Self { - inner, - pass_through_args: Vec::new(), - env_additions: envs, - on_close: on_drop, - } - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Self { - self.inner.current_dir(dir); - self - } - - pub fn packages(&mut self, packages: &SelectedPackages<'_>) -> &mut Self { - match &packages.includes { - SelectedInclude::Workspace => { - self.inner.arg("--workspace"); - for &e in &packages.excludes { - self.inner.args(&["--exclude", e]); - } - } - SelectedInclude::Includes(includes) => { - for &p in includes { - if !packages.excludes.contains(p) { - self.inner.args(&["--package", p]); - } - } - } - } - self - } - - /// Adds a series of arguments to x's target command. - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - self.inner.args(args); - self - } - - /// Adds an argument to x's target command. - #[allow(dead_code)] - pub fn arg>(&mut self, arg: S) -> &mut Self { - self.inner.arg(arg); - self - } - - /// Adds "Pass Through" arguments to x's target command. - /// Pass through arguments appear after a double dash " -- " and may - /// not be handled/checked by x's target command itself, but an underlying executable. - pub fn pass_through(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - for a in args { - self.pass_through_args.push(a.as_ref().to_owned()); - } - self - } - - /// Passes extra environment variables to x's target command. - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator)>, - K: AsRef, - V: AsRef, - { - for (key, val) in vars { - self.env(key, val); - } - self - } - - /// Passes an extra environment variable to x's target command. - pub fn env(&mut self, key: K, val: Option) -> &mut Self - where - K: AsRef, - V: AsRef, - { - let converted_val = val.map(|s| s.as_ref().to_owned()); - - self.env_additions - .insert(key.as_ref().to_owned(), converted_val); - self - } - - pub fn run(&mut self) -> Result<()> { - self.inner.stdout(Stdio::inherit()).stderr(Stdio::inherit()); - self.do_run(true).map(|_| ()) - } - - /// Runs this command, capturing the standard output into a `Vec`. - /// Standard error is forwarded. - pub fn run_with_output(&mut self) -> Result> { - self.inner.stderr(Stdio::inherit()); - self.do_run(true).map(|o| o.stdout) - } - - /// Internal run command, where the magic happens. - /// If log is true, any environment variable overrides will be logged, the full command will be logged, - /// and after the command's output reaches stdout, the command will be printed again along with the time took - /// to process the command (wallclock) in ms. - fn do_run(&mut self, log: bool) -> Result { - // these arguments are passed through cargo/x to underlying executable (test, clippy, etc) - if !self.pass_through_args.is_empty() { - self.inner.arg("--").args(&self.pass_through_args); - } - - // once all the arguments are added to the command we can log it. - if log { - self.env_additions.iter().for_each(|(name, value_option)| { - if let Some(env_val) = value_option { - if SECRET_ENVS.contains(&name.to_str().unwrap_or_default()) { - info!("export {:?}=********", name); - } else { - info!("export {:?}={:?}", name, env_val); - } - } else { - info!("unset {:?}", name); - } - }); - info!("Executing: {:?}", &self.inner); - } - // process enviroment additions, removing Options that are none... - for (key, option_value) in &self.env_additions { - if let Some(value) = option_value { - self.inner.env(key, value); - } else { - self.inner.env_remove(key); - } - } - - let now = Instant::now(); - let output = self.inner.output()?; - // once the command has been executed we log it's success or failure. - if log { - if output.status.success() { - info!( - "Completed in {}ms: {:?}", - now.elapsed().as_millis(), - &self.inner - ); - } else { - warn!( - "Failed in {}ms: {:?}", - now.elapsed().as_millis(), - &self.inner - ); - } - } - if !output.status.success() { - return Err(anyhow!("failed to run cargo command")); - } - Ok(output) - } -} - -// TODO: this should really be a struct instead of an enum with repeated fields. - -/// Represents an invocations of cargo that will call multiple other invocations of -/// cargo based on groupings implied by the contents of /x.toml. -pub enum CargoCommand<'a> { - Bench { - direct_args: &'a [OsString], - args: &'a [OsString], - env: &'a [(&'a str, Option<&'a str>)], - }, - Check { - direct_args: &'a [OsString], - }, - Clippy { - direct_args: &'a [OsString], - args: &'a [OsString], - }, - Fix { - direct_args: &'a [OsString], - args: &'a [OsString], - }, - Test { - direct_args: &'a [OsString], - args: &'a [OsString], - env: &'a [(&'a str, Option<&'a str>)], - }, - Build { - direct_args: &'a [OsString], - args: &'a [OsString], - env: &'a [(&'a str, Option<&'a str>)], - }, -} - -impl<'a> CargoCommand<'a> { - pub fn run_on_packages(&self, packages: &SelectedPackages<'_>) -> Result<()> { - // Early return if we have no packages to run. - if !packages.should_invoke() { - info!("no packages to {}: exiting early", self.as_str()); - return Ok(()); - } - - let mut cargo = self.prepare_cargo(packages); - cargo.run() - } - - /// Runs this command on the selected packages, returning the standard output as a bytestring. - pub fn run_capture_stdout(&self, packages: &SelectedPackages<'_>) -> Result> { - // Early return if we have no packages to run. - if !packages.should_invoke() { - info!("no packages to {}: exiting early", self.as_str()); - Ok(vec![]) - } else { - let mut cargo = self.prepare_cargo(packages); - cargo.args(&["--message-format", "json-render-diagnostics"]); - Ok(cargo.run_with_output()?) - } - } - - fn prepare_cargo(&self, packages: &SelectedPackages<'_>) -> Cargo { - let mut cargo = Cargo::new(self.as_str()); - cargo - .current_dir(project_root()) - .args(self.direct_args()) - .packages(packages) - .pass_through(self.pass_through_args()) - .envs(self.get_extra_env().to_owned()); - - cargo - } - - pub fn as_str(&self) -> &'static str { - match self { - CargoCommand::Bench { .. } => "bench", - CargoCommand::Check { .. } => "check", - CargoCommand::Clippy { .. } => "clippy", - CargoCommand::Fix { .. } => "fix", - CargoCommand::Test { .. } => "test", - CargoCommand::Build { .. } => "build", - } - } - - fn pass_through_args(&self) -> &[OsString] { - match self { - CargoCommand::Bench { args, .. } => args, - CargoCommand::Check { .. } => &[], - CargoCommand::Clippy { args, .. } => args, - CargoCommand::Fix { args, .. } => args, - CargoCommand::Test { args, .. } => args, - CargoCommand::Build { args, .. } => args, - } - } - - fn direct_args(&self) -> &[OsString] { - match self { - CargoCommand::Bench { direct_args, .. } => direct_args, - CargoCommand::Check { direct_args, .. } => direct_args, - CargoCommand::Clippy { direct_args, .. } => direct_args, - CargoCommand::Fix { direct_args, .. } => direct_args, - CargoCommand::Test { direct_args, .. } => direct_args, - CargoCommand::Build { direct_args, .. } => direct_args, - } - } - - pub fn get_extra_env(&self) -> &[(&str, Option<&str>)] { - match self { - CargoCommand::Bench { env, .. } => env, - CargoCommand::Check { .. } => &[], - CargoCommand::Clippy { .. } => &[], - CargoCommand::Fix { .. } => &[], - CargoCommand::Test { env, .. } => env, - CargoCommand::Build { env, .. } => env, - } - } -} diff --git a/devtools/x/src/cargo/build_args.rs b/devtools/x/src/cargo/build_args.rs deleted file mode 100644 index 0fec99e356c8e..0000000000000 --- a/devtools/x/src/cargo/build_args.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use std::ffi::OsString; -use structopt::{clap::arg_enum, StructOpt}; -use supports_color::Stream; - -arg_enum! { - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub enum Coloring { - Auto, - Always, - Never, - } -} - -impl Coloring { - /// Returns true if the given stream should be colorized. - pub fn should_colorize(self, stream: Stream) -> bool { - match self { - Coloring::Auto => supports_color::on_cached(stream).is_some(), - Coloring::Always => true, - Coloring::Never => false, - } - } -} - -/// Arguments for controlling cargo build and other similar commands (like check). -#[derive(Debug, StructOpt)] -pub struct BuildArgs { - #[structopt(long, short)] - /// No output printed to stdout - pub(crate) quiet: bool, - #[structopt(long, short)] - /// Number of parallel build jobs, defaults to # of CPUs - pub(crate) jobs: Option, - #[structopt(long)] - /// Only this package's library - pub(crate) lib: bool, - #[structopt(long, number_of_values = 1)] - /// Only the specified binary - pub(crate) bin: Vec, - #[structopt(long)] - /// All binaries - pub(crate) bins: bool, - #[structopt(long, number_of_values = 1)] - /// Only the specified example - pub(crate) example: Vec, - #[structopt(long)] - /// All examples - pub(crate) examples: bool, - #[structopt(long, number_of_values = 1)] - /// Only the specified test target - pub(crate) test: Vec, - #[structopt(long)] - /// All tests - pub(crate) tests: bool, - #[structopt(long, number_of_values = 1)] - /// Only the specified bench target - pub(crate) bench: Vec, - #[structopt(long)] - /// All benches - pub(crate) benches: bool, - #[structopt(long)] - /// All targets - pub(crate) all_targets: bool, - #[structopt(long)] - /// Artifacts in release mode, with optimizations - pub(crate) release: bool, - #[structopt(long)] - /// Artifacts with the specified profile - pub(crate) profile: Option, - #[structopt(long, number_of_values = 1)] - /// Space-separated list of features to activate - pub(crate) features: Vec, - #[structopt(long)] - /// Activate all available features - pub(crate) all_features: bool, - #[structopt(long)] - /// Do not activate the `default` feature - pub(crate) no_default_features: bool, - #[structopt(long)] - /// TRIPLE - pub(crate) target: Option, - #[structopt(long, parse(from_os_str))] - /// Directory for all generated artifacts - pub(crate) target_dir: Option, - #[structopt(long, parse(from_os_str))] - /// Path to Cargo.toml - pub(crate) manifest_path: Option, - #[structopt(long)] - /// Error format - pub(crate) message_format: Option, - #[structopt(long, short, parse(from_occurrences))] - /// Use verbose output (-vv very verbose/build.rs output) - pub(crate) verbose: usize, - #[structopt(long, possible_values = &Coloring::variants(), default_value="Auto")] - /// Coloring: auto, always, never - pub(crate) color: Coloring, - #[structopt(long)] - /// Require Cargo.lock and cache are up to date - pub(crate) frozen: bool, - #[structopt(long)] - /// Require Cargo.lock is up to date - pub(crate) locked: bool, - #[structopt(long)] - /// Run without accessing the network - pub(crate) offline: bool, -} - -impl BuildArgs { - pub fn add_args(&self, direct_args: &mut Vec) { - if self.quiet { - direct_args.push(OsString::from("--quiet")); - } - if let Some(jobs) = self.jobs { - direct_args.push(OsString::from("--jobs")); - direct_args.push(OsString::from(jobs.to_string())); - }; - if self.lib { - direct_args.push(OsString::from("--lib")); - }; - if !self.bin.is_empty() { - direct_args.push(OsString::from("--bin")); - for bin in &self.bin { - direct_args.push(OsString::from(bin)); - } - } - if self.bins { - direct_args.push(OsString::from("--bins")); - }; - if !self.example.is_empty() { - direct_args.push(OsString::from("--example")); - for example in &self.example { - direct_args.push(OsString::from(example)); - } - } - if self.examples { - direct_args.push(OsString::from("--examples")); - }; - - if !self.test.is_empty() { - direct_args.push(OsString::from("--test")); - for test in &self.test { - direct_args.push(OsString::from(test)); - } - } - if self.tests { - direct_args.push(OsString::from("--tests")); - }; - - if !self.bench.is_empty() { - direct_args.push(OsString::from("--bench")); - for bench in &self.bench { - direct_args.push(OsString::from(bench)); - } - } - if self.benches { - direct_args.push(OsString::from("--benches")); - }; - - if self.all_targets { - direct_args.push(OsString::from("--all-targets")); - }; - if self.release { - direct_args.push(OsString::from("--release")); - }; - - if let Some(profile) = &self.profile { - direct_args.push(OsString::from("--profile")); - direct_args.push(OsString::from(profile.to_string())); - }; - - if !self.features.is_empty() { - direct_args.push(OsString::from("--features")); - for features in &self.features { - direct_args.push(OsString::from(features)); - } - } - if self.all_features { - direct_args.push(OsString::from("--all-features")); - }; - if self.no_default_features { - direct_args.push(OsString::from("--no-default-features")); - }; - - if let Some(target) = &self.target { - direct_args.push(OsString::from("--target")); - direct_args.push(OsString::from(target.to_string())); - }; - if let Some(target_dir) = &self.target_dir { - direct_args.push(OsString::from("--target-dir")); - direct_args.push(OsString::from(target_dir)); - }; - if let Some(manifest_path) = &self.manifest_path { - direct_args.push(OsString::from("--manifest-path")); - direct_args.push(manifest_path.to_owned()); - }; - if let Some(message_format) = &self.message_format { - direct_args.push(OsString::from("--message-format")); - direct_args.push(OsString::from(message_format.to_string())); - }; - if self.verbose > 0 { - direct_args.push(OsString::from(format!("-{}", "v".repeat(self.verbose)))); - }; - if self.color.to_string() != Coloring::Auto.to_string() { - direct_args.push(OsString::from("--color")); - direct_args.push(OsString::from(self.color.to_string())); - }; - if self.frozen { - direct_args.push(OsString::from("--frozen")); - }; - if self.locked { - direct_args.push(OsString::from("--locked")); - }; - if self.offline { - direct_args.push(OsString::from("--offline")); - }; - } -} diff --git a/devtools/x/src/cargo/selected_package.rs b/devtools/x/src/cargo/selected_package.rs deleted file mode 100644 index fb0486dcab293..0000000000000 --- a/devtools/x/src/cargo/selected_package.rs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{changed_since::changed_since_impl, context::XContext, Result}; -use anyhow::{anyhow, Context}; -use guppy::graph::DependencyDirection; -use log::warn; -use std::collections::BTreeSet; -use structopt::StructOpt; -use x_core::WorkspaceStatus; - -/// Arguments for the Cargo package selector. -#[derive(Debug, StructOpt)] -pub struct SelectedPackageArgs { - #[structopt(long, short, number_of_values = 1)] - /// Run on the provided packages - pub(crate) package: Vec, - #[structopt(long, short, number_of_values = 1)] - /// Run on the specified members (package subsets) - pub(crate) members: Vec, - #[structopt(long, number_of_values = 1)] - /// Exclude packages - pub(crate) exclude: Vec, - #[structopt(long, short)] - /// Run on packages changed since the merge base of this commit - changed_since: Option, - #[structopt(long)] - /// Run on all packages in the workspace - pub(crate) workspace: bool, -} - -impl SelectedPackageArgs { - pub fn to_selected_packages<'a>(&'a self, xctx: &'a XContext) -> Result> { - // Mutually exclusive options -- only one of these can be provided. - { - let mut exclusive = vec![]; - - if !self.package.is_empty() { - exclusive.push("--package"); - } else if !self.members.is_empty() { - exclusive.push("--members"); - } - - if self.workspace { - exclusive.push("--workspace"); - } - - if exclusive.len() > 1 { - let err_msg = exclusive.join(", "); - return Err(anyhow!("can only specify one of {}", err_msg)); - } - } - - let mut includes = if self.workspace { - SelectedInclude::Workspace - } else if !self.package.is_empty() || !self.members.is_empty() { - SelectedInclude::includes( - xctx, - self.package.iter().map(|s| s.as_str()), - self.members.iter().map(|s| s.as_str()), - )? - } else { - SelectedInclude::default_cwd(xctx)? - }; - - // Intersect with --changed-since if specified. - if let Some(base) = &self.changed_since { - let git_cli = xctx.core().git_cli().with_context(|| { - "May only use --changes-since if working in a local git repository." - })?; - let affected_set = changed_since_impl(git_cli, xctx, base)?; - includes = includes.intersection( - affected_set - .packages(DependencyDirection::Forward) - .map(|package| package.name()), - ); - } - - let mut ret = SelectedPackages::new(includes); - - if !self.exclude.is_empty() { - let workspace = xctx.core().package_graph()?.workspace(); - // Check that all the excluded package names are valid. - let (known, unknown): (Vec<_>, Vec<_>) = xctx - .core() - .partition_workspace_names(self.exclude.iter().map(|package| package.as_str()))?; - if !unknown.is_empty() { - warn!( - "excluded package(s) `{}` not found in workspace `{}`", - unknown.join(", "), - workspace.root() - ) - } - - ret.add_excludes(known); - } - - Ok(ret) - } -} - -/// Package selector for Cargo commands. -/// -/// This may represent any of the following: -/// * the entire workspace -/// * a single package without arguments -/// * a list of packages -/// -/// This may also exclude a set of packages. Note that currently, excludes only work in the "entire -/// workspace" and "list of packages" situations. They are ignored if a specific local package is -/// being built. (This is an extension on top of Cargo itself, which only supports --exclude -/// together with --workspace.) -/// -/// Excludes are applied after includes. This allows changed-since to support excludes, even if only -/// a subset of the workspace changes. -#[derive(Clone, Debug)] -pub struct SelectedPackages<'a> { - pub(super) includes: SelectedInclude<'a>, - pub(super) excludes: BTreeSet<&'a str>, -} - -impl<'a> SelectedPackages<'a> { - pub(super) fn new(includes: SelectedInclude<'a>) -> Self { - Self { - includes, - excludes: BTreeSet::new(), - } - } - - /// Adds excludes for this `SelectedPackages`. - pub fn add_excludes(&mut self, exclude_names: impl IntoIterator) -> &mut Self { - self.excludes.extend(exclude_names); - self - } - - // --- - // Helper methods - // --- - - pub(super) fn should_invoke(&self) -> bool { - match &self.includes { - SelectedInclude::Workspace => true, - SelectedInclude::Includes(includes) => { - // If everything in the include set is excluded, a command invocation isn't needed. - includes.iter().any(|p| !self.excludes.contains(p)) - } - } - } -} - -#[derive(Clone, Debug)] -pub(super) enum SelectedInclude<'a> { - Workspace, - Includes(BTreeSet<&'a str>), -} - -impl<'a> SelectedInclude<'a> { - /// Returns a `SelectedInclude` that selects the specified package and subset names. - pub fn includes( - xctx: &'a XContext, - package_names: impl IntoIterator, - subsets: impl IntoIterator>, - ) -> Result { - let mut names: BTreeSet<_> = package_names.into_iter().collect(); - - // Don't need to initialize the package graph if no subsets are specified. - for name in subsets { - let workspace = xctx.core().package_graph()?.workspace(); - let subsets = xctx.core().subsets()?; - - let name = name.as_ref(); - // TODO: turn this into a subset in x.toml - let subset = if name == "production" { - subsets.default_members() - } else { - subsets.get(name).ok_or_else(|| { - let known_subsets: Vec<_> = subsets.iter().map(|(name, _)| name).collect(); - let help = known_subsets.join(", "); - anyhow!( - "unknown subset '{}' (known subsets are: {}, production)", - name, - help - ) - })? - }; - let selected = workspace.iter().filter_map(|package| { - if subset.status_of(package.id()) != WorkspaceStatus::Absent { - Some(package.name()) - } else { - None - } - }); - - names.extend(selected); - } - - Ok(SelectedInclude::Includes(names)) - } - - /// Returns a `SelectedInclude` that selects the default set of packages for the current - /// working directory. This may either be the entire workspace or a set of packages inside the - /// workspace. - pub fn default_cwd(xctx: &'a XContext) -> Result { - if xctx.core().current_dir_is_root() { - Ok(SelectedInclude::Workspace) - } else { - // Select all packages that begin with the current rel dir. - let rel = xctx.core().current_rel_dir(); - let workspace = xctx.core().package_graph()?.workspace(); - let selected = workspace.iter_by_path().filter_map(|(path, package)| { - // If we're in devtools, run tests for all packages inside devtools. - // If we're in devtools/x/src, run tests for devtools/x. - if path.starts_with(rel) || rel.starts_with(path) { - Some(package.name()) - } else { - None - } - }); - Ok(SelectedInclude::Includes(selected.collect())) - } - } - - /// Intersects this `SelectedInclude` with the given names. - pub fn intersection(&self, names: impl IntoIterator) -> Self { - let names = names.into_iter().collect(); - match self { - SelectedInclude::Workspace => SelectedInclude::Includes(names), - SelectedInclude::Includes(includes) => { - SelectedInclude::Includes(includes.intersection(&names).copied().collect()) - } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_should_invoke() { - let packages = SelectedPackages::new(SelectedInclude::Workspace); - assert!(packages.should_invoke(), "workspace => invoke"); - - let mut packages = SelectedPackages::new(SelectedInclude::Includes( - vec!["foo", "bar"].into_iter().collect(), - )); - packages.add_excludes(vec!["foo"]); - assert!(packages.should_invoke(), "non-empty packages => invoke"); - - let packages = SelectedPackages::new(SelectedInclude::Includes(BTreeSet::new())); - assert!(!packages.should_invoke(), "no packages => do not invoke"); - - let mut packages = SelectedPackages::new(SelectedInclude::Includes( - vec!["foo", "bar"].into_iter().collect(), - )); - packages.add_excludes(vec!["foo", "bar"]); - assert!( - !packages.should_invoke(), - "all packages excluded => do not invoke" - ); - } -} diff --git a/devtools/x/src/changed_since.rs b/devtools/x/src/changed_since.rs deleted file mode 100644 index b9fc562156bb6..0000000000000 --- a/devtools/x/src/changed_since.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{context::XContext, Result}; -use anyhow::Context; -use determinator::Determinator; -use guppy::graph::{DependencyDirection, PackageSet}; -use log::trace; -use structopt::StructOpt; -use x_core::git::GitCli; - -#[derive(Debug, StructOpt)] -pub struct Args { - /// List packages changed since this commit - pub(crate) base: String, -} - -pub fn run(args: Args, xctx: XContext) -> Result<()> { - let git_cli = xctx.core().git_cli().with_context(|| { - "`x changed-since` must be run within a project cloned from a git repo." - })?; - let affected_set = changed_since_impl(git_cli, &xctx, &args.base)?; - for package in affected_set.packages(DependencyDirection::Forward) { - println!("{}", package.name()); - } - Ok(()) -} - -pub(crate) fn changed_since_impl<'g>( - git_cli: &GitCli, - xctx: &'g XContext, - base: &str, -) -> Result> { - let merge_base = git_cli - .merge_base(base) - .with_context(|| "failed to get merge base with HEAD")?; - let (old_graph, (new_graph, files_changed)) = rayon::join( - || { - trace!("building old graph"); - git_cli - .package_graph_at(&merge_base) - .with_context(|| "failed to build old package graph") - .map(|old_graph| { - // Initialize the feature graph since it will be required later on. - old_graph.feature_graph(); - old_graph - }) - }, - || { - rayon::join( - || { - trace!("building new graph"); - xctx.core().package_graph().map(|new_graph| { - // Initialize the feature graph since it will be required later on. - new_graph.feature_graph(); - new_graph - }) - }, - || { - // Get the list of files changed between the merge base and the current dir. - trace!("getting files changed"); - git_cli - .files_changed_between(&merge_base, None, None) - .with_context(|| "error while getting files changed from merge base") - }, - ) - }, - ); - let (old_graph, new_graph, files_changed) = (old_graph?, new_graph?, files_changed?); - - trace!("running determinator"); - let mut determinator = Determinator::new(&old_graph, new_graph); - determinator - .add_changed_paths(&files_changed) - .set_rules(xctx.config().determinator_rules()) - .with_context(|| "failed to set determinator rules")?; - - let determinator_set = determinator.compute(); - Ok(determinator_set.affected_set) -} diff --git a/devtools/x/src/check.rs b/devtools/x/src/check.rs deleted file mode 100644 index c7339f3a81701..0000000000000 --- a/devtools/x/src/check.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - pub(crate) package_args: SelectedPackageArgs, - #[structopt(flatten)] - pub(crate) build_args: BuildArgs, -} - -pub fn run(args: Args, xctx: XContext) -> Result<()> { - let mut direct_args = vec![]; - args.build_args.add_args(&mut direct_args); - - let cmd = CargoCommand::Check { - direct_args: &direct_args, - }; - let packages = args.package_args.to_selected_packages(&xctx)?; - cmd.run_on_packages(&packages) -} diff --git a/devtools/x/src/clippy.rs b/devtools/x/src/clippy.rs deleted file mode 100644 index 292d255a793ff..0000000000000 --- a/devtools/x/src/clippy.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use std::ffi::OsString; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - pub(crate) package_args: SelectedPackageArgs, - #[structopt(flatten)] - pub(crate) build_args: BuildArgs, - #[structopt(name = "ARGS", parse(from_os_str), last = true)] - args: Vec, -} - -pub fn run(args: Args, xctx: XContext) -> Result<()> { - let mut pass_through_args = vec!["-D".into(), "warnings".into()]; - for lint in xctx.config().allowed_clippy_lints() { - pass_through_args.push("-A".into()); - pass_through_args.push(lint.into()); - } - for lint in xctx.config().warn_clippy_lints() { - pass_through_args.push("-W".into()); - pass_through_args.push(lint.into()); - } - pass_through_args.extend(args.args); - - let mut direct_args = vec![]; - args.build_args.add_args(&mut direct_args); - - let cmd = CargoCommand::Clippy { - direct_args: &direct_args, - args: &pass_through_args, - }; - let packages = args.package_args.to_selected_packages(&xctx)?; - cmd.run_on_packages(&packages) -} diff --git a/devtools/x/src/config.rs b/devtools/x/src/config.rs deleted file mode 100644 index e374f72cfd582..0000000000000 --- a/devtools/x/src/config.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{utils::project_root, Result}; -use anyhow::Context; -use determinator::rules::DeterminatorRules; -use guppy::graph::summaries::CargoOptionsSummary; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - fs, - path::{Path, PathBuf}, -}; -use x_core::core_config::XCoreConfig; - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct XConfig { - /// Core configuration. - #[serde(flatten)] - pub core: XCoreConfig, - /// X configuration. - #[serde(flatten)] - pub config: Config, -} - -// TODO: probably split up lints and their configs into their own crate and section -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct Config { - /// Package exceptions which need to be run special - system_tests: HashMap, - /// Configuration for generating summaries - summaries: SummariesConfig, - /// Workspace configuration - workspace: WorkspaceConfig, - /// Clippy configureation - clippy: Clippy, - /// Fix configureation - fix: Fix, - grcov: CargoTool, - /// Determinator configuration - determinator: DeterminatorRules, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct CargoTool { - pub installer: CargoInstallation, -} - -/// -/// These can be passed to the installer.rs, which can check the installation against the version number supplied, -/// or install the cargo tool via either githash/repo if provided or with simply the version if the artifact is released -/// to crates.io. -/// -/// Unfortunately there is no gaurantee that the installation is correct if the version numbers match as the githash -/// is not stored by default in the version number. -/// -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct CargoInstallation { - /// The version string that must match the installation, otherwise a fresh installation will occure. - pub version: String, - /// Overrides the default install with a specific git repo. git-rev is required. - pub git: Option, - /// only used if the git url is set. This is the full git hash. - pub git_rev: Option, - /// features to enable in the installation. - pub features: Option>, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct Package { - /// Path to the crate from root - path: PathBuf, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct SummariesConfig { - /// Config for default members and subsets - pub default: CargoOptionsSummary, - /// Config for the full workspace - pub full: CargoOptionsSummary, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct WorkspaceConfig { - /// Allowed characters in file paths. Regex must have ^ and $ anchors. - pub allowed_paths: String, - /// Attributes to enforce on workspace crates - pub enforced_attributes: EnforcedAttributesConfig, - /// Banned dependencies - pub banned_deps: BannedDepsConfig, - /// Direct dep duplicate lint config - pub direct_dep_dups: DirectDepDupsConfig, - /// Exceptions to license linters - pub license_exceptions: Vec, - /// Overlay config in this workspace - pub overlay: OverlayConfig, - /// Test-only config in this workspace - pub test_only: TestOnlyConfig, - /// Move to Aptos dependencies - pub move_to_aptos_deps: MoveToAptosDepsConfig, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct EnforcedAttributesConfig { - /// Ensure the authors of every workspace crate are set to this. - pub authors: Option>, - /// Ensure the `license` field of every workspace crate is set to this. - pub license: Option, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct BannedDepsConfig { - /// Banned direct dependencies - pub direct: HashMap, - /// Banned dependencies in the default build set - pub default_build: HashMap, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct DirectDepDupsConfig { - pub allow: Vec, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct MoveToAptosDepsConfig { - pub aptos_crates_in_language: HashSet, - pub exclude: HashSet, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct OverlayConfig { - /// A list of overlay feature names - pub features: Vec, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct TestOnlyConfig { - /// A list of test-only workspace names - pub members: Vec, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct Clippy { - allowed: Vec, - warn: Vec, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -pub struct Fix {} - -impl XConfig { - pub fn from_file(f: impl AsRef) -> Result { - let f = f.as_ref(); - let contents = - fs::read(f).with_context(|| format!("could not read config file {}", f.display()))?; - Self::from_toml(&contents) - .with_context(|| format!("could not parse config file {}", f.display())) - } - - pub fn from_toml(bytes: &[u8]) -> Result { - toml::from_slice(bytes).map_err(Into::into) - } - - pub fn from_project_root() -> Result { - Self::from_file(project_root().join("x.toml")) - } -} - -impl Config { - pub fn system_tests(&self) -> &HashMap { - &self.system_tests - } - - pub fn summaries_config(&self) -> &SummariesConfig { - &self.summaries - } - - pub fn workspace_config(&self) -> &WorkspaceConfig { - &self.workspace - } - - pub fn allowed_clippy_lints(&self) -> &[String] { - &self.clippy.allowed - } - - pub fn warn_clippy_lints(&self) -> &[String] { - &self.clippy.warn - } - - pub fn tools(&self) -> Vec<(String, CargoInstallation)> { - let tools = vec![("grcov".to_owned(), self.grcov().installer.to_owned())]; - tools - } - - pub fn grcov(&self) -> &CargoTool { - &self.grcov - } - - pub fn determinator_rules(&self) -> &DeterminatorRules { - &self.determinator - } -} diff --git a/devtools/x/src/context.rs b/devtools/x/src/context.rs deleted file mode 100644 index bef169420798f..0000000000000 --- a/devtools/x/src/context.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - config::{Config, XConfig}, - installer::Installer, - utils::project_root, - Result, -}; -use anyhow::Context; -use camino::Utf8PathBuf; -use std::convert::TryInto; -use x_core::XCoreContext; - -/// Global context shared across x commands. -pub struct XContext { - core: XCoreContext, - config: Config, - installer: Installer, -} - -impl XContext { - /// Creates a new `GlobalContext` by reading the config in the project root. - pub fn new() -> Result { - Self::with_config(XConfig::from_project_root()?) - } - - /// Creates a new `GlobalContext` based on the given config. - pub fn with_config(x_config: XConfig) -> Result { - let current_dir: Utf8PathBuf = std::env::current_dir() - .with_context(|| "error while fetching current dir")? - .try_into() - .with_context(|| "current dir is not valid UTF-8")?; - let XConfig { core, config } = x_config; - Ok(Self { - core: XCoreContext::new(project_root(), current_dir, core)?, - installer: Installer::new(config.tools(), vec!["llvm-tools-preview".to_string()]), - config, - }) - } - - /// Returns a reference to the config. - pub fn config(&self) -> &Config { - &self.config - } - - /// Returns a reference to the core context. - pub fn core(&self) -> &XCoreContext { - &self.core - } - - /// Returns a reference to Installer, configured to install versions from config. - pub fn installer(&self) -> &Installer { - &self.installer - } -} diff --git a/devtools/x/src/diff_summary.rs b/devtools/x/src/diff_summary.rs deleted file mode 100644 index d8de79e2f542b..0000000000000 --- a/devtools/x/src/diff_summary.rs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::context::XContext; -use guppy::graph::summaries::{diff::SummaryDiff, Summary}; -use std::{fs, path::PathBuf}; -use structopt::{clap::arg_enum, StructOpt}; - -arg_enum! { - #[derive(Debug, Copy, Clone)] - pub enum OutputFormat { - Toml, - Json, - Text, - } -} - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(name = "BASE_SUMMARY")] - /// Path to the base summary - base_summary: PathBuf, - #[structopt(name = "COMPARE_SUMMARY")] - /// Path to the comparison summary - compare_summary: PathBuf, - #[structopt(name = "OUTPUT_FORMAT", default_value = "Text")] - /// optionally, output can be formated as json or toml - output_format: OutputFormat, -} - -pub fn run(args: Args, _xctx: XContext) -> crate::Result<()> { - let base_summary_text = fs::read_to_string(&args.base_summary)?; - let base_summary = Summary::parse(&base_summary_text)?; - let compare_summary_text = fs::read_to_string(&args.compare_summary)?; - let compare_summary = Summary::parse(&compare_summary_text)?; - - let summary_diff = SummaryDiff::new(&base_summary, &compare_summary); - - match args.output_format { - OutputFormat::Json => println!("{}", serde_json::to_string(&summary_diff)?), - OutputFormat::Toml => println!("{}", toml::to_string(&summary_diff)?), - OutputFormat::Text => println!("{}", summary_diff.report()), - }; - - Ok(()) -} diff --git a/devtools/x/src/fix.rs b/devtools/x/src/fix.rs deleted file mode 100644 index 08a0752fb7152..0000000000000 --- a/devtools/x/src/fix.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use std::ffi::OsString; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - pub(crate) package_args: SelectedPackageArgs, - #[structopt(flatten)] - pub(crate) build_args: BuildArgs, - #[structopt(name = "ARGS", parse(from_os_str), last = true)] - args: Vec, -} - -pub fn run(mut args: Args, xctx: XContext) -> Result<()> { - let mut pass_through_args = vec![]; - pass_through_args.extend(args.args); - - // Always run fix on all targets. - args.build_args.all_targets = true; - - let mut direct_args = vec![]; - args.build_args.add_args(&mut direct_args); - - let cmd = CargoCommand::Fix { - direct_args: &direct_args, - args: &pass_through_args, - }; - let packages = args.package_args.to_selected_packages(&xctx)?; - cmd.run_on_packages(&packages) -} diff --git a/devtools/x/src/generate_summaries.rs b/devtools/x/src/generate_summaries.rs deleted file mode 100644 index 826fade3db463..0000000000000 --- a/devtools/x/src/generate_summaries.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::context::XContext; -use anyhow::Context; -use camino::{Utf8Path, Utf8PathBuf}; -use guppy::graph::{ - cargo::CargoOptions, - feature::{FeatureSet, StandardFeatures}, -}; -use std::fs; -use structopt::{clap::arg_enum, StructOpt}; - -arg_enum! { - #[derive(Debug, Copy, Clone)] - pub enum OutputFormat { - Toml, - Json, - } -} - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(name = "OUT_DIR")] - /// Directory to output summaries to (default: target/summaries) - out_dir: Option, - #[structopt(name = "OUTPUT_FORMAT", default_value = "Toml")] - /// Output in text, toml or json - output_format: OutputFormat, -} - -impl Args { - const DEFAULT_OUT_DIR: &'static str = "target/summaries"; -} - -pub fn run(args: Args, xctx: XContext) -> crate::Result<()> { - let config = xctx.config(); - let summaries_config = config.summaries_config(); - let pkg_graph = xctx.core().package_graph()?; - let subsets = xctx.core().subsets()?; - - let default_opts = summaries_config.default.to_cargo_options(pkg_graph)?; - let full_opts = summaries_config.full.to_cargo_options(pkg_graph)?; - - let out_dir = args - .out_dir - .unwrap_or_else(|| xctx.core().project_root().join(Args::DEFAULT_OUT_DIR)); - - fs::create_dir_all(&out_dir)?; - - // TODO: figure out a way to unify this with WorkspaceSubset. - - // Create summaries for: - - let mut summary_count = 0; - - // * default members (default features) - // (note that we aren't using the build set from default_members() as it may have different - // options) - let initials = subsets.default_members().initials().clone(); - write_summary( - "default", - initials, - &default_opts, - &out_dir, - args.output_format, - )?; - - summary_count += 1; - - // * subsets (default features) - for (name, subset) in subsets.iter() { - let initials = subset.initials().clone(); - write_summary(name, initials, &default_opts, &out_dir, args.output_format)?; - - summary_count += 1; - } - - // * full workspace set (all features) - let initials = pkg_graph - .resolve_workspace() - .to_feature_set(StandardFeatures::All); - write_summary("full", initials, &full_opts, &out_dir, args.output_format)?; - - summary_count += 1; - - println!("wrote {} summaries to {}", summary_count, out_dir); - - Ok(()) -} - -fn write_summary( - name: &str, - initials: FeatureSet<'_>, - cargo_opts: &CargoOptions<'_>, - out_dir: &Utf8Path, - output_format: OutputFormat, -) -> crate::Result<()> { - let build_set = initials.into_cargo_set(cargo_opts)?; - let summary = build_set.to_summary(cargo_opts)?; - - let (out, path) = match output_format { - OutputFormat::Json => { - let out = serde_json::to_string(&summary)?; - let summary_path = out_dir.join(format!("summary-{}.json", name)); - (out, summary_path) - } - OutputFormat::Toml => { - let mut out = format!( - "# Summary for Aptos subset '{}'. @generated by x.\n\ - # To regenerate, run 'cargo x generate-summaries'.\n\n", - name - ); - summary - .write_to_string(&mut out) - .with_context(|| format!("error while generating summary for '{}'", name))?; - - let summary_path = out_dir.join(format!("summary-{}.toml", name)); - - (out, summary_path) - } - }; - - fs::write(&path, &out).with_context(|| format!("error while writing summary file {}", path)) -} diff --git a/devtools/x/src/installer.rs b/devtools/x/src/installer.rs deleted file mode 100644 index ededf1be782c5..0000000000000 --- a/devtools/x/src/installer.rs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{cargo::Cargo, config::CargoInstallation}; -use log::{error, info, warn}; -use std::process::Command; - -pub struct Installer { - cargo_installations: Vec<(String, CargoInstallation)>, - rust_installation: Vec, -} - -impl Installer { - pub fn new( - cargo_installations: Vec<(String, CargoInstallation)>, - rust_installation: Vec, - ) -> Installer { - Self { - cargo_installations, - rust_installation, - } - } - - // Given the name of a tool in the tools list, return the version. - fn cargo_installation(&self, name: &str) -> Option<&CargoInstallation> { - self.cargo_installations - .as_slice() - .iter() - .find_map(|(x, y)| if x.eq(&name) { Some(y) } else { None }) - } - - pub fn install_via_cargo_if_needed(&self, name: &str) -> bool { - match &self.cargo_installation(name) { - Some(cargo_installation) => install_cargo_component_if_needed(name, cargo_installation), - None => { - info!("No version of tool {} is specified ", name); - false - } - } - } - - pub fn install_via_rustup_if_needed(&self, name: &str) -> bool { - install_rustup_component_if_needed(name) - } - - #[allow(dead_code)] - fn check_cargo_component(&self, name: &str) -> bool { - match &self.cargo_installation(name) { - Some(cargo_installation) => { - check_installed_cargo_component(name, &cargo_installation.version) - } - None => { - info!("No version of tool {} is specified ", name); - false - } - } - } - - #[allow(dead_code)] - fn install_rustup_component(&self, name: &str) -> bool { - install_rustup_component_if_needed(name) - } - - pub fn check_all(&self) -> bool { - let iter = self - .cargo_installations - .as_slice() - .iter() - .map(|(name, installation)| (name, &installation.version)) - .collect::>(); - check_all_cargo_components(iter.as_slice()) - } - - pub fn install_all(&self) -> bool { - let mut result = install_all_cargo_components(self.cargo_installations.as_slice()); - result &= install_all_rustup_components(self.rust_installation.as_slice()); - result - } -} - -pub fn install_rustup_component_if_needed(name: &str) -> bool { - let mut cmd = Command::new("rustup"); - cmd.args(&["component", "list", "--installed"]); - let result = cmd.output(); - - let installed = if let Ok(output) = result { - let bytes = output.stdout.as_slice(); - let installed_components = String::from_utf8_lossy(bytes); - installed_components.contains(name) - } else { - false - }; - if !installed { - info!("installing rustup component: {}", name); - let mut cmd = Command::new("rustup"); - cmd.args(&["component", "add", name]); - if cmd.output().is_ok() { - info!("rustup component {} has been installed", name); - } else { - warn!("rustup component {} failed to install", name); - } - cmd.output().is_ok() - } else { - info!("rustup component {} is already installed", name); - true - } -} - -fn install_all_rustup_components(names: &[String]) -> bool { - let mut result = true; - for name in names { - result &= install_rustup_component_if_needed(name); - } - result -} - -pub fn install_cargo_component_if_needed(name: &str, installation: &CargoInstallation) -> bool { - if !check_installed_cargo_component(name, &installation.version) { - info!("Installing {} {}", name, installation.version); - let mut cmd = Cargo::new("install"); - cmd.arg("--force"); - if let Some(features) = &installation.features { - if !features.is_empty() { - cmd.arg("--features"); - cmd.args(features); - } - } - if let Some(git_url) = &installation.git { - cmd.arg("--git"); - cmd.arg(git_url); - if let Some(git_rev) = &installation.git_rev { - cmd.arg("--rev"); - cmd.arg(git_rev); - } - } else { - cmd.arg("--version").arg(&installation.version); - } - cmd.arg("--locked"); - cmd.arg(name); - - let result = cmd.run(); - if result.is_err() { - error!( - "Could not install {} {}, check x.toml to ensure tool exists and is not yanked, or provide a git-rev if your x.toml specifies a git-url.", - name, installation.version - ); - } - result.is_ok() - } else { - true - } -} - -fn check_installed_cargo_component(name: &str, version: &str) -> bool { - let result = Command::new(name).arg("--version").output(); - let found = match result { - Ok(output) => format!("{} {}", name, version) - .eq(String::from_utf8_lossy(output.stdout.as_slice()).trim()), - _ => false, - }; - info!( - "{} of version {} is{} installed", - name, - version, - if !found { " not" } else { "" } - ); - found -} - -fn install_all_cargo_components(tools: &[(String, CargoInstallation)]) -> bool { - let mut success: bool = true; - for (name, installation) in tools { - success &= install_cargo_component_if_needed(name, installation); - } - success -} - -fn check_all_cargo_components(tools: &[(&String, &String)]) -> bool { - let mut success: bool = true; - for (key, value) in tools { - success &= check_installed_cargo_component(key, value); - } - success -} diff --git a/devtools/x/src/lint/allowed_paths.rs b/devtools/x/src/lint/allowed_paths.rs deleted file mode 100644 index 8f606086babde..0000000000000 --- a/devtools/x/src/lint/allowed_paths.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::Context; -use regex::Regex; -use x_lint::prelude::*; - -/// Allow certain characters in file paths. -#[derive(Debug)] -pub struct AllowedPaths { - allowed_regex: Regex, -} - -impl AllowedPaths { - pub fn new(allowed_paths: &str) -> crate::Result { - Ok(Self { - allowed_regex: Regex::new(allowed_paths) - .with_context(|| "error while parsing allowed-paths regex")?, - }) - } -} - -impl Linter for AllowedPaths { - fn name(&self) -> &'static str { - "allowed-paths" - } -} - -impl FilePathLinter for AllowedPaths { - fn run<'l>( - &self, - ctx: &FilePathContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - if !self.allowed_regex.is_match(ctx.file_path().as_str()) { - out.write( - LintLevel::Error, - format!( - "path doesn't match allowed regex: {}", - self.allowed_regex.as_str() - ), - ); - } - - Ok(RunStatus::Executed) - } -} diff --git a/devtools/x/src/lint/determinator.rs b/devtools/x/src/lint/determinator.rs deleted file mode 100644 index c70dbf1de6d0b..0000000000000 --- a/devtools/x/src/lint/determinator.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::Context; -use determinator::{ - rules::{DeterminatorRules, PathMatch}, - Determinator, -}; -use guppy::graph::PackageGraph; -use indoc::indoc; -use x_lint::prelude::*; - -#[derive(Debug)] -pub(super) struct DeterminatorMatch<'cfg> { - determinator: Determinator<'cfg, 'cfg>, -} - -impl<'cfg> DeterminatorMatch<'cfg> { - pub fn new(graph: &'cfg PackageGraph, rules: &'cfg DeterminatorRules) -> crate::Result { - // Use the same graph for old and new since we only care about file changes here. - let mut determinator = Determinator::new(graph, graph); - determinator - .set_rules(rules) - .with_context(|| "failed to set determinator rules")?; - Ok(Self { determinator }) - } -} - -impl<'cfg> Linter for DeterminatorMatch<'cfg> { - fn name(&self) -> &'static str { - "determinator-match" - } -} - -impl<'cfg> FilePathLinter for DeterminatorMatch<'cfg> { - fn run<'l>( - &self, - ctx: &FilePathContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - // All other options for PathMatch are valid. - if self.determinator.match_path(ctx.file_path(), |_| ()) == PathMatch::NoMatches { - let msg = indoc!( - "path didn't match any determinator rules or packages: - * add path to x.toml's [[determinator.path-rule]] section - * rules can include \"build everything\" or \"ignore file\"" - ); - out.write(LintLevel::Error, msg); - } - - Ok(RunStatus::Executed) - } -} diff --git a/devtools/x/src/lint/guppy.rs b/devtools/x/src/lint/guppy.rs deleted file mode 100644 index 9c40cbd668ed7..0000000000000 --- a/devtools/x/src/lint/guppy.rs +++ /dev/null @@ -1,635 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -//! Project and package linters that run queries on guppy. - -use crate::config::{ - BannedDepsConfig, DirectDepDupsConfig, EnforcedAttributesConfig, MoveToAptosDepsConfig, - OverlayConfig, -}; -use guppy::{ - graph::{feature::FeatureFilterFn, PackagePublish}, - Version, VersionReq, -}; -use std::{ - collections::{BTreeMap, HashMap}, - iter, -}; -use x_core::WorkspaceStatus; -use x_lint::prelude::*; - -/// Ban certain crates from being used as direct dependencies or in the default build. -#[derive(Debug)] -pub struct BannedDeps<'cfg> { - config: &'cfg BannedDepsConfig, -} - -impl<'cfg> BannedDeps<'cfg> { - pub fn new(config: &'cfg BannedDepsConfig) -> Self { - Self { config } - } -} - -impl<'cfg> Linter for BannedDeps<'cfg> { - fn name(&self) -> &'static str { - "banned-deps" - } -} - -impl<'cfg> ProjectLinter for BannedDeps<'cfg> { - fn run<'l>( - &self, - ctx: &ProjectContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let package_graph = ctx.package_graph()?; - - let filter_ban = |banned: &'cfg HashMap| { - package_graph.packages().filter_map(move |package| { - banned - .get(package.name()) - .map(move |message| (package, message)) - }) - }; - - let banned_direct = &self.config.direct; - for (package, message) in filter_ban(banned_direct) { - // Look at the reverse direct dependencies of this package. - for link in package.reverse_direct_links() { - let from = link.from(); - if let Some(workspace_path) = from.source().workspace_path() { - out.write_kind( - LintKind::Package { - name: from.name(), - workspace_path, - }, - LintLevel::Error, - format!("banned direct dependency '{}': {}", package.name(), message), - ); - } - } - } - - let default_members = ctx.default_members()?; - - let banned_default_build = &self.config.default_build; - for (package, message) in filter_ban(banned_default_build) { - if default_members.status_of(package.id()) != WorkspaceStatus::Absent { - out.write( - LintLevel::Error, - format!( - "banned dependency in default build '{}': {}", - package.name(), - message - ), - ); - } - } - - Ok(RunStatus::Executed) - } -} - -/// Enforce attributes on workspace crates. -#[derive(Debug)] -pub struct EnforcedAttributes<'cfg> { - config: &'cfg EnforcedAttributesConfig, -} - -impl<'cfg> EnforcedAttributes<'cfg> { - pub fn new(config: &'cfg EnforcedAttributesConfig) -> Self { - Self { config } - } -} - -impl<'cfg> Linter for EnforcedAttributes<'cfg> { - fn name(&self) -> &'static str { - "enforced-attributes" - } -} - -impl<'cfg> PackageLinter for EnforcedAttributes<'cfg> { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - if let Some(authors) = &self.config.authors { - if metadata.authors() != authors.as_slice() { - out.write( - LintLevel::Error, - format!("invalid authors (expected {:?})", authors.join(", "),), - ); - } - } - if let Some(license) = &self.config.license { - if metadata.license() != Some(license.as_str()) { - out.write( - LintLevel::Error, - format!("invalid license (expected {})", license), - ) - } - } - - Ok(RunStatus::Executed) - } -} - -/// Check conventions in crate names and paths. -#[derive(Debug)] -pub struct CrateNamesPaths; - -impl Linter for CrateNamesPaths { - fn name(&self) -> &'static str { - "crate-names-paths" - } -} - -impl PackageLinter for CrateNamesPaths { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let name = ctx.metadata().name(); - if name.contains('_') { - out.write( - LintLevel::Error, - "crate name contains '_' (use '-' instead)", - ); - } - - let workspace_path = ctx.workspace_path(); - if workspace_path.as_str().contains('_') { - out.write( - LintLevel::Error, - "workspace path contains '_' (use '-' instead)", - ); - } - - for build_target in ctx.metadata().build_targets() { - let target_name = build_target.name(); - if target_name.contains('_') { - // If the path is implicitly specified by the name, don't warn about it. - let file_stem = build_target.path().file_stem(); - if file_stem != Some(target_name) { - out.write( - LintLevel::Error, - format!( - "build target '{}' contains '_' (use '-' instead)", - target_name - ), - ); - } - } - } - - Ok(RunStatus::Executed) - } -} - -/// Ensure that any workspace packages with build dependencies also have a build script. -#[derive(Debug)] -pub struct IrrelevantBuildDeps; - -impl Linter for IrrelevantBuildDeps { - fn name(&self) -> &'static str { - "irrelevant-build-deps" - } -} - -impl PackageLinter for IrrelevantBuildDeps { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - - let has_build_dep = metadata - .direct_links() - .any(|link| link.build().is_present()); - - if !metadata.has_build_script() && has_build_dep { - out.write(LintLevel::Error, "build dependencies but no build script"); - } - - Ok(RunStatus::Executed) - } -} - -/// Ensure that packages within the workspace only depend on one version of a third-party crate. -#[derive(Debug)] -pub struct DirectDepDups<'cfg> { - config: &'cfg DirectDepDupsConfig, -} - -impl<'cfg> DirectDepDups<'cfg> { - pub fn new(config: &'cfg DirectDepDupsConfig) -> crate::Result { - Ok(Self { config }) - } -} - -impl<'cfg> Linter for DirectDepDups<'cfg> { - fn name(&self) -> &'static str { - "direct-dep-dups" - } -} - -impl<'cfg> ProjectLinter for DirectDepDups<'cfg> { - fn run<'l>( - &self, - ctx: &ProjectContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let package_graph = ctx.package_graph()?; - - // This is a map of direct deps by name -> version -> packages that depend on it. - let mut direct_deps: BTreeMap<&str, BTreeMap<&Version, Vec<&str>>> = BTreeMap::new(); - package_graph.query_workspace().resolve_with_fn(|_, link| { - // Collect direct dependencies of workspace packages. - let (from, to) = link.endpoints(); - if from.in_workspace() && !to.in_workspace() { - direct_deps - .entry(to.name()) - .or_default() - .entry(to.version()) - .or_default() - .push(from.name()); - } - // query_workspace + preventing further traversals will mean that only direct - // dependencies are considered. - false - }); - for (direct_dep, versions) in direct_deps - .iter() - .filter(|(d, _)| !self.config.allow.contains(&d.to_string())) - { - if versions.len() > 1 { - let mut msg = format!("duplicate direct dependency '{}':\n", direct_dep); - for (version, packages) in versions { - msg.push_str(&format!(" * {} (", version)); - msg.push_str(&packages.join(", ")); - msg.push_str(")\n"); - } - out.write(LintLevel::Error, msg); - } - } - - Ok(RunStatus::Executed) - } -} - -/// Assertions for "overlay" features. -/// -/// An "overlay" feature is a feature name used throughout the codebase, whose purpose it is to -/// augment each package with extra code (e.g. proptest generators). Overlay features shouldn't be -/// enabled by anything except overlay features on workspace members, but may be enabled by default -/// by test-only or other non-default workspace members. -#[derive(Debug)] -pub struct OverlayFeatures<'cfg> { - config: &'cfg OverlayConfig, -} - -impl<'cfg> OverlayFeatures<'cfg> { - pub fn new(config: &'cfg OverlayConfig) -> Self { - Self { config } - } - - fn is_overlay(&self, feature: Option<&str>) -> bool { - match feature { - Some(feature) => self.config.features.iter().any(|f| *f == feature), - // The base feature isn't banned. - None => false, - } - } -} - -impl<'cfg> Linter for OverlayFeatures<'cfg> { - fn name(&self) -> &'static str { - "overlay-features" - } -} - -impl<'cfg> PackageLinter for OverlayFeatures<'cfg> { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let package = ctx.metadata(); - if !ctx.is_default_member() { - return Ok(RunStatus::Skipped(SkipReason::UnsupportedPackage( - package.id(), - ))); - } - - let filter = FeatureFilterFn::new(|_, feature_id| { - // Accept all features except for overlay ones. - !self.is_overlay(feature_id.feature()) - }); - - let package_graph = ctx.package_graph(); - - let feature_query = package_graph - .query_forward(iter::once(package.id())) - .expect("valid package ID") - .to_feature_query(filter); - - let mut overlays: Vec<(Option<&str>, &str, Option<&str>)> = vec![]; - - feature_query.resolve_with_fn(|_, link| { - // We now use the v2 resolver, so dev-only links can be skipped. - if !link.dev_only() { - let (from, to) = link.endpoints(); - let to_package = to.package(); - if to_package.in_workspace() && self.is_overlay(to.feature_id().feature()) { - overlays.push(( - from.feature_id().feature(), - to_package.name(), - to.feature_id().feature(), - )); - } - } - - // Don't need to traverse past direct dependencies. - false - }); - - if !overlays.is_empty() { - let mut msg = "overlay features enabled by default:\n".to_string(); - for (from_feature, to_package, to_feature) in overlays { - msg.push_str(&format!( - " * {} -> {}/{}\n", - feature_str(from_feature), - to_package, - feature_str(to_feature) - )); - } - msg.push_str("Use a line in the [features] section instead.\n"); - out.write(LintLevel::Error, msg); - } - - Ok(RunStatus::Executed) - } -} - -fn feature_str(feature: Option<&str>) -> &str { - feature.unwrap_or("[base]") -} - -/// Ensure that all unpublished packages only use path dependencies for workspace dependencies -#[derive(Debug)] -pub struct UnpublishedPackagesOnlyUsePathDependencies { - no_version_req: VersionReq, -} - -impl<'cfg> UnpublishedPackagesOnlyUsePathDependencies { - pub fn new() -> Self { - Self { - no_version_req: VersionReq::parse("*").expect("* should be a valid req"), - } - } -} - -impl<'cfg> Linter for UnpublishedPackagesOnlyUsePathDependencies { - fn name(&self) -> &'static str { - "unpublished-packages-only-use-path-dependencies" - } -} - -impl<'cfg> PackageLinter for UnpublishedPackagesOnlyUsePathDependencies { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - - // Skip all packages which aren't 'publish = false' - if !metadata.publish().is_never() { - return Ok(RunStatus::Executed); - } - - for direct_dep in metadata.direct_links().filter(|p| { - let to = p.to(); - to.in_workspace() - }) { - if direct_dep.version_req() != &self.no_version_req { - let msg = format!( - "unpublished package specifies a version of first-party dependency '{}' ({}); \ - unpublished packages should only use path dependencies for first-party packages.", - direct_dep.dep_name(), - direct_dep.version_req(), - ); - out.write(LintLevel::Error, msg); - } - } - - Ok(RunStatus::Executed) - } -} - -/// Ensure that all published packages only depend on other, published packages -#[derive(Debug)] -pub struct PublishedPackagesDontDependOnUnpublishedPackages {} - -impl<'cfg> PublishedPackagesDontDependOnUnpublishedPackages { - pub fn new() -> Self { - Self {} - } -} - -impl<'cfg> Linter for PublishedPackagesDontDependOnUnpublishedPackages { - fn name(&self) -> &'static str { - "published-packages-dont-depend-on-unpublished-packages" - } -} - -impl<'cfg> PackageLinter for PublishedPackagesDontDependOnUnpublishedPackages { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - - // Skip all packages which aren't publishable - if metadata.publish().is_never() { - return Ok(RunStatus::Executed); - } - - for direct_dep in metadata.direct_links().filter(|p| !p.dev_only()) { - // If the direct dependency isn't publishable - if direct_dep.to().publish().is_never() { - out.write( - LintLevel::Error, - format!( - "published package can't depend on unpublished package '{}'", - direct_dep.dep_name() - ), - ); - } - } - - Ok(RunStatus::Executed) - } -} - -/// Only allow crates to be published to crates.io -#[derive(Debug)] -pub struct OnlyPublishToCratesIo; - -impl Linter for OnlyPublishToCratesIo { - fn name(&self) -> &'static str { - "only-publish-to-crates-io" - } -} - -impl PackageLinter for OnlyPublishToCratesIo { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - - let is_ok = match metadata.publish() { - PackagePublish::Unrestricted => false, - PackagePublish::Registries(&[ref registry]) => registry == PackagePublish::CRATES_IO, - // Unpublished package. - PackagePublish::Registries(&[]) => true, - // Multiple registries or something else. - _ => false, - }; - - if !is_ok { - out.write( - LintLevel::Error, - "published package should only be publishable to crates.io. \ - If you intend to publish this package, ensure the 'publish' \ - field in the package's Cargo.toml is 'publish = [\"crates-io\"]. \ - Otherwise set the 'publish' field to 'publish = false'.", - ); - } - - Ok(RunStatus::Executed) - } -} - -/// Crates in the `/crates` directory have a flatten structure and their directory name is the same -/// as the crate name -#[derive(Debug)] -pub struct CratesInCratesDirectory; - -impl Linter for CratesInCratesDirectory { - fn name(&self) -> &'static str { - "only-publish-to-crates-io" - } -} - -impl PackageLinter for CratesInCratesDirectory { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let mut path_components = ctx.workspace_path().components(); - match path_components.next().map(|p| p.as_str()) { - Some("crates") => {} - _ => return Ok(RunStatus::Executed), - } - - match path_components.next().map(|p| p.as_str()) { - Some(directory) if directory == ctx.metadata().name() => {} - _ => { - out.write( - LintLevel::Error, - "crates in the `crates/` directory must be in a directory with the same name as the crate", - ); - } - } - - if path_components.next().is_some() { - out.write( - LintLevel::Error, - "crates in the `crates/` directory must be in a flat directory structure, no nesting", - ); - } - - Ok(RunStatus::Executed) - } -} - -// Ensure that Move crates do not depend on Aptos crates. -#[derive(Debug)] -pub struct MoveCratesDontDependOnAptosCrates<'cfg> { - config: &'cfg MoveToAptosDepsConfig, -} - -impl<'cfg> MoveCratesDontDependOnAptosCrates<'cfg> { - pub fn new(config: &'cfg MoveToAptosDepsConfig) -> Self { - Self { config } - } -} - -impl<'cfg> Linter for MoveCratesDontDependOnAptosCrates<'cfg> { - fn name(&self) -> &'static str { - "move-crates-dont-depend-on-aptos-crates" - } -} - -impl<'cfg> PackageLinter for MoveCratesDontDependOnAptosCrates<'cfg> { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let metadata = ctx.metadata(); - - let crate_name = metadata.name(); - let crate_path = metadata.source().to_string(); - - // Determine if a crate is considered a Move crate or Aptos crate. - // - // Current criteria: - // 1. All crates outside language are considered Aptos crates. - // 2. All crates inside language are considered Move crates, unless marked otherwise. - let is_move_crate = |crate_path: &str, crate_name: &str| { - if crate_path.starts_with("language/") { - if self.config.aptos_crates_in_language.contains(crate_name) { - return false; - } - return true; - } - false - }; - - if is_move_crate(&crate_path, crate_name) { - for direct_dep in metadata.direct_links() { - let dep = direct_dep.to(); - let dep_name = dep.name(); - - if dep.in_workspace() - && !self.config.exclude.contains(dep_name) - && !is_move_crate(&dep.source().to_string(), dep_name) - { - println!("(\"{}\", \"{}\"),", crate_name, dep_name); - out.write( - LintLevel::Error, - format!( - "depending on non-move crate `{}`\n\ - Note: all crates in language/ are considered Move crates by default. \ - If you are creating a new Aptos crate in language, you need to add its name to the \ - aptos_crates_in_language list in x.toml to make the linter recognize it.", - dep_name - ), - ); - } - } - } - - Ok(RunStatus::Executed) - } -} diff --git a/devtools/x/src/lint/license.rs b/devtools/x/src/lint/license.rs deleted file mode 100644 index ce65df186f5c6..0000000000000 --- a/devtools/x/src/lint/license.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::Context; -use globset::{Glob, GlobSet, GlobSetBuilder}; -use x_lint::prelude::*; - -static LICENSE_HEADER: &str = "Copyright (c) Aptos\n\ - SPDX-License-Identifier: Apache-2.0\n\ - "; - -#[derive(Copy, Clone, Debug)] -pub(super) struct LicenseHeader<'cfg> { - exceptions: &'cfg GlobSet, -} - -impl<'cfg> Linter for LicenseHeader<'cfg> { - fn name(&self) -> &'static str { - "license-header" - } -} - -impl<'cfg> LicenseHeader<'cfg> { - pub fn new(exceptions: &'cfg GlobSet) -> Self { - Self { exceptions } - } -} - -impl<'cfg> ContentLinter for LicenseHeader<'cfg> { - fn pre_run<'l>(&self, file_ctx: &FilePathContext<'l>) -> Result> { - // TODO: Add a way to pass around state between pre_run and run, so that this computation - // only needs to be done once. - match FileType::new(file_ctx) { - Some(_) => Ok(skip_license_checks(self.exceptions, file_ctx)), - None => Ok(RunStatus::Skipped(SkipReason::UnsupportedExtension( - file_ctx.extension(), - ))), - } - } - - fn run<'l>( - &self, - ctx: &ContentContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let content = match ctx.content() { - Some(content) => content, - None => { - // This is not a UTF-8 file -- don't analyze it. - return Ok(RunStatus::Skipped(SkipReason::NonUtf8Content)); - } - }; - - let file_type = FileType::new(ctx.file_ctx()).expect("None filtered out in pre_run"); - // Determine if the file is missing the license header - let missing_header = match file_type { - FileType::Rust | FileType::Proto => { - let maybe_license = content - .lines() - .skip_while(|line| line.is_empty()) - .take(2) - .map(|s| s.trim_start_matches("// ")); - !LICENSE_HEADER.lines().eq(maybe_license) - } - FileType::Shell => { - let maybe_license = content - .lines() - .skip_while(|line| line.starts_with("#!")) - .skip_while(|line| line.is_empty()) - .take(2) - .map(|s| s.trim_start_matches("# ")); - !LICENSE_HEADER.lines().eq(maybe_license) - } - }; - - if missing_header { - out.write(LintLevel::Error, "missing license header"); - } - - Ok(RunStatus::Executed) - } -} - -enum FileType { - Rust, - Shell, - Proto, -} - -impl FileType { - fn new(ctx: &FilePathContext<'_>) -> Option { - match ctx.extension() { - Some("rs") => Some(FileType::Rust), - Some("sh") => Some(FileType::Shell), - Some("proto") => Some(FileType::Proto), - _ => None, - } - } -} - -pub(super) fn build_exceptions(patterns: &[String]) -> crate::Result { - let mut builder = GlobSetBuilder::new(); - for pattern in patterns { - let glob = Glob::new(pattern).with_context(|| { - format!( - "error while processing license exception glob '{}'", - pattern - ) - })?; - builder.add(glob); - } - builder - .build() - .with_context(|| "error while building globset for license patterns") -} - -fn skip_license_checks<'l>(exceptions: &GlobSet, file: &FilePathContext<'l>) -> RunStatus<'l> { - if exceptions.is_match(file.file_path()) { - return RunStatus::Skipped(SkipReason::UnsupportedFile(file.file_path())); - } - - RunStatus::Executed -} diff --git a/devtools/x/src/lint/mod.rs b/devtools/x/src/lint/mod.rs deleted file mode 100644 index 332b10bcc38fd..0000000000000 --- a/devtools/x/src/lint/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::context::XContext; -use anyhow::anyhow; -use structopt::StructOpt; -use x_lint::prelude::*; - -mod allowed_paths; -mod determinator; -mod guppy; -mod license; -mod toml; -mod workspace_classify; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(long)] - fail_fast: bool, -} - -pub fn run(args: Args, xctx: XContext) -> crate::Result<()> { - let workspace_config = xctx.config().workspace_config(); - - let project_linters: &[&dyn ProjectLinter] = &[ - &guppy::BannedDeps::new(&workspace_config.banned_deps), - &guppy::DirectDepDups::new(&workspace_config.direct_dep_dups)?, - ]; - - let package_linters: &[&dyn PackageLinter] = &[ - &guppy::EnforcedAttributes::new(&workspace_config.enforced_attributes), - &guppy::CrateNamesPaths, - &guppy::IrrelevantBuildDeps, - &guppy::OverlayFeatures::new(&workspace_config.overlay), - &guppy::UnpublishedPackagesOnlyUsePathDependencies::new(), - &guppy::PublishedPackagesDontDependOnUnpublishedPackages::new(), - &guppy::OnlyPublishToCratesIo, - &guppy::CratesInCratesDirectory, - &guppy::MoveCratesDontDependOnAptosCrates::new(&workspace_config.move_to_aptos_deps), - &workspace_classify::DefaultOrTestOnly::new( - xctx.core().package_graph()?, - &workspace_config.test_only, - )?, - ]; - - let file_path_linters: &[&dyn FilePathLinter] = &[ - &allowed_paths::AllowedPaths::new(&workspace_config.allowed_paths)?, - &determinator::DeterminatorMatch::new( - xctx.core().package_graph()?, - xctx.config().determinator_rules(), - )?, - ]; - - let license_exceptions = license::build_exceptions(&workspace_config.license_exceptions)?; - let content_linters: &[&dyn ContentLinter] = &[ - &license::LicenseHeader::new(&license_exceptions), - &toml::RootToml, - ]; - - let engine = LintEngineConfig::new(xctx.core()) - .with_project_linters(project_linters) - .with_package_linters(package_linters) - .with_file_path_linters(file_path_linters) - .with_content_linters(content_linters) - .fail_fast(args.fail_fast) - .build(); - - let results = engine.run()?; - - // TODO: handle skipped results - - for (source, message) in &results.messages { - println!( - "[{}] [{}] [{}]: {}\n", - message.level(), - source.name(), - source.kind(), - message.message() - ); - } - - if !results.messages.is_empty() { - Err(anyhow!("there were lint errors")) - } else { - Ok(()) - } -} diff --git a/devtools/x/src/lint/toml.rs b/devtools/x/src/lint/toml.rs deleted file mode 100644 index b432943dfaad7..0000000000000 --- a/devtools/x/src/lint/toml.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use camino::Utf8Path; -use colored_diff::PrettyDifference; -use serde::{Deserialize, Serialize}; -use toml::{de, ser}; -use x_lint::prelude::*; - -/// Checks on the root toml. -#[derive(Debug)] -pub struct RootToml; - -impl Linter for RootToml { - fn name(&self) -> &'static str { - "root-toml" - } -} - -impl ContentLinter for RootToml { - fn pre_run<'l>(&self, file_ctx: &FilePathContext<'l>) -> Result> { - let file_path = file_ctx.file_path(); - if file_path == "Cargo.toml" { - Ok(RunStatus::Executed) - } else { - Ok(RunStatus::Skipped(SkipReason::UnsupportedFile(file_path))) - } - } - - fn run<'l>( - &self, - ctx: &ContentContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let contents: RootTomlContents<'_> = de::from_slice(ctx.content_bytes()) - .map_err(|err| SystemError::de("deserializing root Cargo.toml", err))?; - let workspace = contents.workspace; - - // Use guppy to produce a canonical list of workspace paths. - // This does two things: - // * ensure that workspace members are sorted. - // * ensure that every workspace member is listed. Cargo itself doesn't require every - // workspace member to be listed (just leaf packages), but other tools might. - let package_graph = ctx.file_ctx().project_ctx().package_graph()?; - let expected = Workspace { - members: package_graph - .workspace() - .iter_by_path() - .map(|(path, _)| path) - .collect(), - }; - - if workspace.members != expected.members { - out.write( - LintLevel::Error, - toml_mismatch_message( - &expected, - &workspace, - "workspace member list not canonical", - )?, - ); - } - - // TODO: autofix support would be really nice! - - Ok(RunStatus::Executed) - } -} - -/// Creates a lint message indicating the differences between the two TOML structs. -pub(super) fn toml_mismatch_message( - expected: &T, - actual: &T, - header: &str, -) -> Result { - let expected = to_toml_string(expected) - .map_err(|err| SystemError::ser("serializing expected workspace members", err))?; - let actual = to_toml_string(actual) - .map_err(|err| SystemError::ser("serializing actual workspace members", err))?; - // TODO: print out a context diff instead of the full diff. - Ok(format!( - "{}:\n\n{}", - header, - PrettyDifference { - expected: &expected, - actual: &actual - } - )) -} - -/// Serializes some data to toml using this project's standard code style. -fn to_toml_string(data: &T) -> Result { - let mut dst = String::with_capacity(128); - let mut serializer = ser::Serializer::new(&mut dst); - serializer - .pretty_array(true) - .pretty_array_indent(4) - .pretty_array_trailing_comma(true) - .pretty_string_literal(true) - .pretty_string(false); - data.serialize(&mut serializer)?; - Ok(dst) -} - -#[derive(Debug, Deserialize)] -struct RootTomlContents<'a> { - #[serde(borrow)] - workspace: Workspace<'a>, - // Add other fields as necessary. -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -struct Workspace<'a> { - #[serde(borrow)] - members: Vec<&'a Utf8Path>, - // Add other fields as necessary. -} diff --git a/devtools/x/src/lint/workspace_classify.rs b/devtools/x/src/lint/workspace_classify.rs deleted file mode 100644 index 207ea64accc3d..0000000000000 --- a/devtools/x/src/lint/workspace_classify.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::TestOnlyConfig; -use anyhow::Context; -use guppy::graph::{BuildTargetId, BuildTargetKind, PackageGraph, PackageSet}; -use indoc::indoc; -use x_core::WorkspaceStatus; -use x_lint::prelude::*; - -/// Ensure that every package in the workspace is classified as either a default member or test-only. -#[derive(Debug)] -pub struct DefaultOrTestOnly<'cfg> { - test_only: PackageSet<'cfg>, -} - -impl<'cfg> DefaultOrTestOnly<'cfg> { - pub fn new(package_graph: &'cfg PackageGraph, config: &TestOnlyConfig) -> crate::Result { - let test_only = package_graph - .resolve_workspace_names(&config.members) - .with_context(|| "error while initializing default-or-test-only lint")?; - Ok(Self { test_only }) - } -} - -impl<'cfg> Linter for DefaultOrTestOnly<'cfg> { - fn name(&self) -> &'static str { - "default-or-test-only" - } -} - -impl<'cfg> PackageLinter for DefaultOrTestOnly<'cfg> { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - let package = ctx.metadata(); - - let default_members = ctx.project_ctx().default_members()?; - - let binary_kind = package - .build_targets() - .filter_map(|target| { - if matches!(target.id(), BuildTargetId::Binary(_)) { - return Some("binary"); - } - // If this is the library target, then look for the first binary-equivalent-kind - if let (BuildTargetId::Library, BuildTargetKind::LibraryOrExample(crate_types)) = - (target.id(), target.kind()) - { - return crate_types - .iter() - .filter_map(|crate_type| { - // These library types are equivalent to binaries. - if crate_type == "cdylib" - || crate_type == "dylib" - || crate_type == "staticlib" - { - Some(crate_type.as_str()) - } else { - None - } - }) - .next(); - } - None - }) - .next(); - - let status = default_members.status_of(package.id()); - let test_only = self - .test_only - .contains(package.id()) - .expect("package is known"); - - match (binary_kind, status, test_only) { - (None, WorkspaceStatus::Absent, false) => { - // Library, not reachable from default members and not marked test-only. - let msg = indoc!( - "library package, not a dependency of default-members: - * if test-only, add to test-only in x.toml - * otherwise, make it a dependency of a default member (listed in root Cargo.toml)" - ); - out.write(LintLevel::Error, msg); - } - (None, WorkspaceStatus::Absent, true) => { - // Test-only library package. This is fine. - } - (None, WorkspaceStatus::Dependency, false) => { - // Library, dependency of default members. This is fine. - } - (None, WorkspaceStatus::Dependency, true) => { - // Library, dependency of default members and listed in test-only. - - // For a better error message, look at what immediately depends on the package and - // is in default members. - let mut reverse_deps = package - .reverse_direct_links() - .filter_map(|link| { - if !link.dev_only() - && default_members.status_of(link.from().id()) - != WorkspaceStatus::Absent - { - Some(link.from().name()) - } else { - None - } - }) - .collect::>(); - reverse_deps.sort_unstable(); - let reverse_str = reverse_deps.join(", "); - let msg = format!( - "{} {}", - indoc!( - "library package, dependency of default members and test-only: - * remove from test-only if production code - * otherwise, ensure it is not a dependency of default members:", - ), - reverse_str, - ); - out.write(LintLevel::Error, msg); - } - (None, WorkspaceStatus::RootMember, false) => { - if package.publish().is_never() { - // Library, listed in default members. It shouldn't be. - // unless it is a published library - let msg = indoc!( - "library package, listed in default-members: - * if test-only, add to test-only in x.toml instead - * if the library is intended to be published, add `publish = [\"crates-io\"] to the Cargo.toml of this package - * otherwise, remove it from default-members and make it a dependency of a binary" - ); - out.write(LintLevel::Error, msg); - } - } - (None, WorkspaceStatus::RootMember, true) => { - // Library, listed in default members and in test-only. It shouldn't be. - let msg = indoc!( - "library package, listed in default-members and test-only: - * if test-only, add to test-only in x.toml and remove from default-members - * otherwise, remove it from both and make it a dependency of a default-member" - ); - out.write(LintLevel::Error, msg); - } - (Some(kind), WorkspaceStatus::Absent, false) => { - // Binary, not listed in default members, not test-only and not reachable from one. - let msg = format!( - "{} {}", - kind, - indoc!( - "package, not listed in default-members: - * if test-only, add to test-only in x.toml - * otherwise, list it in root Cargo.toml's default-members" - ), - ); - out.write(LintLevel::Error, msg); - } - (Some(_), WorkspaceStatus::Absent, true) => { - // Test-only binary. This is fine. - } - (Some(kind), WorkspaceStatus::Dependency, false) => { - // Binary, not listed in default members but reachable from one. - let msg = format!( - "{} {}", - kind, - indoc!( - "package, not listed in default-members: - * list it in root Cargo.toml's default-members - (note: dependency of a default member, so assumed to be a production crate)" - ), - ); - out.write(LintLevel::Error, msg) - } - (Some(kind), WorkspaceStatus::Dependency, true) => { - // Binary, not listed in default members but a dependency of one + test-only - let msg = format!( - "{} {}", - kind, - indoc!( - "package, not listed in default-members but a dependency, and in test-only: - * remove it from test-only in x.toml, AND - * list it in root Cargo.toml's default-members - (note: dependency of a default member, so assumed to be a production crate)" - ), - ); - out.write(LintLevel::Error, msg) - } - (Some(_), WorkspaceStatus::RootMember, false) => { - // Binary, listed in default-members. This is fine. - } - (Some(kind), WorkspaceStatus::RootMember, true) => { - // Binary, listed in default-members and test-only. - let msg = format!( - "{} {}", - kind, - indoc!( - "package, listed in both default-members and test-only: - * remove it from test-only in x.toml - (note: default member, so assumed to be a production crate)" - ), - ); - out.write(LintLevel::Error, msg) - } - } - - Ok(RunStatus::Executed) - } -} diff --git a/devtools/x/src/main.rs b/devtools/x/src/main.rs deleted file mode 100644 index b05524047b19d..0000000000000 --- a/devtools/x/src/main.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -#![forbid(unsafe_code)] - -use chrono::Local; -use env_logger::{self, fmt::Color}; -use log::Level; -use std::{boxed::Box, io::Write}; -use structopt::StructOpt; - -mod bench; -mod build; -mod cargo; -mod changed_since; -mod check; -mod clippy; -mod config; -mod context; -mod diff_summary; -mod fix; -mod generate_summaries; -mod installer; -mod lint; -mod nextest; -mod playground; -mod test; -mod tools; -mod utils; - -type Result = anyhow::Result; - -#[derive(Debug, StructOpt)] -struct Args { - #[structopt(subcommand)] - cmd: Command, -} - -#[derive(Debug, StructOpt)] -enum Command { - #[structopt(name = "bench")] - /// Run `cargo bench` - Bench(bench::Args), - #[structopt(name = "build")] - /// Run `cargo build` - // the argument must be Boxed due to it's size and clippy (it's quite large by comparison to others.) - Build(Box), - #[structopt(name = "check")] - /// Run `cargo check` - Check(check::Args), - /// List packages changed since merge base with the given commit - /// - /// Note that this compares against the merge base (common ancestor) of the specified commit. - /// For example, if origin/master is specified, the current working directory will be compared - /// against the point at which it branched off of origin/master. - #[structopt(name = "changed-since")] - ChangedSince(changed_since::Args), - #[structopt(name = "clippy")] - /// Run `cargo clippy` - Clippy(clippy::Args), - #[structopt(name = "fix")] - /// Run `cargo fix` - Fix(fix::Args), - #[structopt(name = "test")] - /// Run tests - Test(test::Args), - #[structopt(name = "nextest")] - /// Run tests with new test runner - Nextest(nextest::Args), - #[structopt(name = "tools")] - /// Run tests - Tools(tools::Args), - #[structopt(name = "lint")] - /// Run lints - Lint(lint::Args), - /// Run playground code - Playground(playground::Args), - #[structopt(name = "generate-summaries")] - /// Generate build summaries for important subsets - GenerateSummaries(generate_summaries::Args), - #[structopt(name = "diff-summary")] - /// Diff build summaries for important subsets - DiffSummary(diff_summary::Args), -} - -fn main() -> Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) - .format(|buf, record| { - let color = match record.level() { - Level::Warn => Color::Yellow, - Level::Error => Color::Red, - _ => Color::Green, - }; - - let mut level_style = buf.style(); - level_style.set_color(color).set_bold(true); - - writeln!( - buf, - "{:>12} [{}] - {}", - level_style.value(record.level()), - Local::now().format("%T%.3f"), - record.args() - ) - }) - .init(); - - let args = Args::from_args(); - let xctx = context::XContext::new()?; - - match args.cmd { - Command::Tools(args) => tools::run(args, xctx), - Command::Test(args) => test::run(args, xctx), - Command::Nextest(args) => nextest::run(args, xctx), - Command::Build(args) => build::run(args, xctx), - Command::ChangedSince(args) => changed_since::run(args, xctx), - Command::Check(args) => check::run(args, xctx), - Command::Clippy(args) => clippy::run(args, xctx), - Command::Fix(args) => fix::run(args, xctx), - Command::Bench(args) => bench::run(args, xctx), - Command::Lint(args) => lint::run(args, xctx), - Command::Playground(args) => playground::run(args, xctx), - Command::GenerateSummaries(args) => generate_summaries::run(args, xctx), - Command::DiffSummary(args) => diff_summary::run(args, xctx), - } -} diff --git a/devtools/x/src/nextest.rs b/devtools/x/src/nextest.rs deleted file mode 100644 index 1c5e2735e1530..0000000000000 --- a/devtools/x/src/nextest.rs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - Result, -}; -use anyhow::{bail, Context}; -use camino::Utf8PathBuf; -use nextest_runner::{ - config::NextestConfig, - partition::PartitionerBuilder, - reporter::{StatusLevel, TestOutputDisplay, TestReporterBuilder}, - runner::TestRunnerBuilder, - signal::SignalHandler, - target_runner::TargetRunner, - test_filter::{RunIgnored, TestFilterBuilder}, - test_list::{BinaryList, RustTestArtifact, TestList}, -}; -use std::{ffi::OsString, io::Cursor}; -use structopt::StructOpt; -use supports_color::Stream; - -#[derive(Debug, StructOpt)] -pub struct Args { - /// Nextest profile to use - #[structopt(long, short = "P")] - nextest_profile: Option, - /// Config file [default: workspace-root/.config/nextest.toml] - config_file: Option, - #[structopt(flatten)] - pub(crate) package_args: SelectedPackageArgs, - #[structopt(long, short)] - /// Skip running expensive testsuite integration tests - unit: bool, - #[structopt(flatten)] - pub(crate) build_args: BuildArgs, - #[structopt(flatten)] - pub(crate) runner_opts: TestRunnerOpts, - #[structopt(flatten)] - reporter_opts: TestReporterOpts, - #[structopt(long)] - /// Do not run tests, only compile the test executables - no_run: bool, - /// Run ignored tests - #[structopt(long, possible_values = &RunIgnored::variants(), default_value, case_insensitive = true)] - run_ignored: RunIgnored, - /// Test partition, e.g. hash:1/2 or count:2/3 - #[structopt(long)] - partition: Option, - #[structopt(name = "FILTERS", last = true)] - filters: Vec, -} - -/// Test runner options. -#[derive(Debug, Default, StructOpt)] -pub struct TestRunnerOpts { - /// Number of retries for failing tests [default: from profile] - #[structopt(long)] - retries: Option, - - /// Cancel test run on the first failure - #[structopt(long)] - fail_fast: bool, - - /// Run all tests regardless of failure - #[structopt(long, overrides_with = "fail-fast")] - no_fail_fast: bool, - - /// Number of tests to run simultaneously [default: logical CPU count] - #[structopt(long)] - test_threads: Option, -} - -impl TestRunnerOpts { - fn to_builder(&self) -> TestRunnerBuilder { - let mut builder = TestRunnerBuilder::default(); - if let Some(retries) = self.retries { - builder.set_retries(retries); - } - if self.no_fail_fast { - builder.set_fail_fast(false); - } else if self.fail_fast { - builder.set_fail_fast(true); - } - if let Some(test_threads) = self.test_threads { - builder.set_test_threads(test_threads); - } - - builder - } -} - -#[derive(Debug, Default, StructOpt)] -#[structopt(rename_all = "kebab-case")] -pub struct TestReporterOpts { - /// Output stdout and stderr on failure - #[structopt(long, possible_values = TestOutputDisplay::variants(), case_insensitive = true)] - failure_output: Option, - /// Output stdout and stderr on success - #[structopt(long, possible_values = TestOutputDisplay::variants(), case_insensitive = true)] - success_output: Option, - /// Test statuses to output - #[structopt(long, possible_values = StatusLevel::variants(), case_insensitive = true)] - status_level: Option, -} - -impl TestReporterOpts { - fn to_builder(&self) -> TestReporterBuilder { - let mut builder = TestReporterBuilder::default(); - if let Some(failure_output) = self.failure_output { - builder.set_failure_output(failure_output); - } - if let Some(success_output) = self.success_output { - builder.set_success_output(success_output); - } - if let Some(status_level) = self.status_level { - builder.set_status_level(status_level); - } - builder - } -} - -pub fn run(args: Args, xctx: XContext) -> Result<()> { - let config = xctx.config(); - - let mut packages = args.package_args.to_selected_packages(&xctx)?; - if args.unit { - packages.add_excludes(config.system_tests().iter().map(|(p, _)| p.as_str())); - } - - let mut direct_args = Vec::new(); - args.build_args.add_args(&mut direct_args); - - // Always pass in --no-run as the test runner is responsible for running these tests. - direct_args.push(OsString::from("--no-run")); - - // TODO: no-fail-fast (needs support in nextest) - - // Step 1: build all the test binaries with --no-run. - let cmd = CargoCommand::Test { - direct_args: direct_args.as_slice(), - // Don't pass in the args (test name) -- they're for use by the test runner. - args: &[], - env: &[], - }; - - let stdout = cmd.run_capture_stdout(&packages)?; - - if args.no_run { - // Don't proceed further. - return Ok(()); - } - - let package_graph = xctx.core().package_graph()?; - let workspace = package_graph.workspace(); - - let config = NextestConfig::from_sources(workspace.root(), args.config_file.as_deref())?; - let profile = config.profile( - args.nextest_profile - .as_deref() - .unwrap_or(NextestConfig::DEFAULT_PROFILE), - )?; - - let test_binaries = BinaryList::from_messages(Cursor::new(stdout), package_graph)?; - - let test_filter = TestFilterBuilder::new(args.run_ignored, args.partition, &args.filters); - let test_artifacts = - RustTestArtifact::from_binary_list(package_graph, test_binaries, None, None)?; - let test_list = TestList::new(test_artifacts, &test_filter, &TargetRunner::empty())?; - - let handler = SignalHandler::new().context("failed to install nextest signal handler")?; - let runner = - args.runner_opts - .to_builder() - .build(&test_list, &profile, handler, TargetRunner::empty()); - - let mut reporter = args.reporter_opts.to_builder().build(&test_list, &profile); - if args.build_args.color.should_colorize(Stream::Stderr) { - reporter.colorize(); - } - - let stderr = std::io::stderr(); - let run_stats = runner.try_execute(|event| reporter.report_event(event, stderr.lock()))?; - if !run_stats.is_success() { - bail!("test run failed"); - } - - Ok(()) -} diff --git a/devtools/x/src/playground.rs b/devtools/x/src/playground.rs deleted file mode 100644 index fbf622b2c97c0..0000000000000 --- a/devtools/x/src/playground.rs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -//! Playground for arbitrary code. -//! -//! This lets users experiment with new lints and other throwaway code. -//! Add your code in the spots marked `// --- ADD PLAYGROUND CODE HERE ---`. -//! -//! This file should not have any production-related code checked into it. - -#![allow(unused_variables)] - -use crate::context::XContext; -use anyhow::anyhow; -use structopt::StructOpt; -use x_lint::prelude::*; - -#[derive(Copy, Clone, Debug)] -struct PlaygroundProject; - -impl Linter for PlaygroundProject { - fn name(&self) -> &'static str { - "playground-project" - } -} - -impl ProjectLinter for PlaygroundProject { - fn run<'l>( - &self, - ctx: &ProjectContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - // --- ADD PLAYGROUND CODE HERE --- - - Ok(RunStatus::Executed) - } -} - -#[derive(Copy, Clone, Debug)] -struct PlaygroundPackage; - -impl Linter for PlaygroundPackage { - fn name(&self) -> &'static str { - "playground-package" - } -} - -impl PackageLinter for PlaygroundPackage { - fn run<'l>( - &self, - ctx: &PackageContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - // --- ADD PLAYGROUND CODE HERE --- - - Ok(RunStatus::Executed) - } -} - -#[derive(Copy, Clone, Debug)] -struct PlaygroundFilePath; - -impl Linter for PlaygroundFilePath { - fn name(&self) -> &'static str { - "playground-file-path" - } -} - -impl FilePathLinter for PlaygroundFilePath { - fn run<'l>( - &self, - ctx: &FilePathContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - // --- ADD PLAYGROUND CODE HERE --- - - Ok(RunStatus::Executed) - } -} - -#[derive(Copy, Clone, Debug)] -struct PlaygroundContent; - -impl Linter for PlaygroundContent { - fn name(&self) -> &'static str { - "playground-content" - } -} - -impl ContentLinter for PlaygroundContent { - fn run<'l>( - &self, - ctx: &ContentContext<'l>, - out: &mut LintFormatter<'l, '_>, - ) -> Result> { - // --- ADD PLAYGROUND CODE HERE --- - - Ok(RunStatus::Executed) - } -} - -// --- - -#[derive(Debug, StructOpt)] -pub struct Args { - /// Dummy arg that doesn't do anything - #[allow(dead_code)] - #[structopt(long)] - dummy: bool, -} - -pub fn run(args: Args, xctx: XContext) -> crate::Result<()> { - let engine = LintEngineConfig::new(xctx.core()) - .with_project_linters(&[&PlaygroundProject]) - .with_package_linters(&[&PlaygroundPackage]) - .with_file_path_linters(&[&PlaygroundFilePath]) - .with_content_linters(&[&PlaygroundContent]) - .build(); - - let results = engine.run()?; - - for (source, message) in &results.messages { - println!( - "[{}] [{}] [{}]: {}\n", - message.level(), - source.name(), - source.kind(), - message.message() - ); - } - - if !results.messages.is_empty() { - Err(anyhow!("there were lint errors")) - } else { - Ok(()) - } -} diff --git a/devtools/x/src/test.rs b/devtools/x/src/test.rs deleted file mode 100644 index 4abccb561a08a..0000000000000 --- a/devtools/x/src/test.rs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - cargo::{build_args::BuildArgs, selected_package::SelectedPackageArgs, CargoCommand}, - context::XContext, - utils::project_root, - Result, -}; -use anyhow::{anyhow, Error}; -use log::info; -use std::{ - ffi::OsString, - fs::create_dir_all, - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(flatten)] - pub(crate) package_args: SelectedPackageArgs, - #[structopt(long, short)] - /// Skip running expensive testsuite integration tests - unit: bool, - #[structopt(long)] - /// Only run doctests - doc: bool, - #[structopt(flatten)] - pub(crate) build_args: BuildArgs, - #[structopt(long)] - /// Do not fast fail the run if tests (or test executables) fail - no_fail_fast: bool, - #[structopt(long)] - /// Do not run tests, only compile the test executables - no_run: bool, - #[structopt(long, parse(from_os_str))] - /// Directory to output HTML coverage report (using grcov) - html_cov_dir: Option, - #[structopt(long, parse(from_os_str))] - /// Directory to output lcov coverage html (using grcov -> lcov.info -> html using genhtml). - /// Only useful if you want the lcov.info file produced in the path. Requires that lcov be installed and on PATH. - html_lcov_dir: Option, - #[structopt(name = "TESTNAME", parse(from_os_str))] - testname: Option, - #[structopt(name = "ARGS", parse(from_os_str), last = true)] - args: Vec, -} - -pub fn run(mut args: Args, xctx: XContext) -> Result<()> { - let config = xctx.config(); - - let mut packages = args.package_args.to_selected_packages(&xctx)?; - if args.unit { - packages.add_excludes(config.system_tests().iter().map(|(p, _)| p.as_str())); - } - - args.args.extend(args.testname.clone()); - - let generate_coverage = args.html_cov_dir.is_some() || args.html_lcov_dir.is_some(); - - let llvm_profile_key = "LLVM_PROFILE_FILE"; - let llvm_profile_path: &str = "target/debug/xtest-%p-%m.profraw"; - let llvm_profile_path_ignored = "target/debug/ignored-%p-%m.profraw"; - - let env_vars = if generate_coverage { - if !xctx - .installer() - .install_via_rustup_if_needed("llvm-tools-preview") - { - return Err(anyhow!("Could not install llvm-tools-preview")); - } - if !xctx.installer().install_via_cargo_if_needed("grcov") { - return Err(anyhow!("Could not install grcov")); - } - - let shared_envionment = vec![ - ("RUSTC_BOOTSTRAP", Some("1")), - // Recommend flags for use with grcov, with these flags removed: -Copt-level=0, -Clink-dead-code. - // for more info see: https://github.com/mozilla/grcov#example-how-to-generate-gcda-fiels-for-a-rust-project - ("RUSTFLAGS", Some("-Zinstrument-coverage")), - ("RUST_MIN_STACK", Some("8388608")), - ]; - - let mut build_env_vars = shared_envionment.clone(); - build_env_vars.push((llvm_profile_key, Some(llvm_profile_path_ignored))); - - info!("Performing a seperate \"cargo build\" before running tests and collecting coverage"); - - let mut direct_args = Vec::new(); - args.build_args.add_args(&mut direct_args); - - let build = CargoCommand::Build { - direct_args: direct_args.as_slice(), - args: &[], - env: build_env_vars.as_slice(), - }; - let build_result = build.run_on_packages(&packages); - - if !args.no_fail_fast && build_result.is_err() { - return build_result; - } - - let mut output = shared_envionment.clone(); - output.push((llvm_profile_key, Some(llvm_profile_path))); - output - } else { - vec![] - }; - - let mut direct_args = Vec::new(); - args.build_args.add_args(&mut direct_args); - if args.no_run { - direct_args.push(OsString::from("--no-run")); - }; - if args.no_fail_fast { - direct_args.push(OsString::from("--no-fail-fast")); - }; - if args.doc { - direct_args.push(OsString::from("--doc")); - } - - let cmd = CargoCommand::Test { - direct_args: direct_args.as_slice(), - args: &args.args, - env: &env_vars, - }; - - let cmd_result = cmd.run_on_packages(&packages); - - if !args.no_fail_fast && cmd_result.is_err() { - return cmd_result; - } - - if let Some(html_cov_dir) = &args.html_cov_dir { - create_dir_all(&html_cov_dir)?; - let html_cov_path = &html_cov_dir.canonicalize()?; - info!("created {}", &html_cov_path.to_string_lossy()); - exec_grcov(html_cov_path, llvm_profile_path)?; - } - if let Some(html_lcov_dir) = &args.html_lcov_dir { - create_dir_all(&html_lcov_dir)?; - let html_lcov_path = &html_lcov_dir.canonicalize()?; - info!("created {}", &html_lcov_path.to_string_lossy()); - exec_lcov(html_lcov_path, llvm_profile_path)?; - exec_lcov_genhtml(html_lcov_path)?; - } - cmd_result -} - -fn exec_lcov_genhtml(html_lcov_path: &Path) -> Result<()> { - let mut genhtml = Command::new("genhtml"); - let mut lcov_file_path = PathBuf::new(); - lcov_file_path.push(html_lcov_path); - lcov_file_path.push("lcov.info"); - genhtml - .current_dir(project_root()) - .arg("-o") - .arg(html_lcov_path) - .arg("--show-details") - .arg("--highlight") - .arg("--ignore-errors") - .arg("source") - .arg("--legend") - //TODO: Paths seem to be a thing - .arg(lcov_file_path); - info!("Build grcov lcov.info file"); - info!("{:?}", genhtml); - genhtml.stdout(Stdio::inherit()).stderr(Stdio::inherit()); - - if let Some(err) = genhtml.output().err() { - Err(Error::new(err).context("Failed to generate html output from lcov.info")) - } else { - Ok(()) - } -} - -fn exec_lcov(html_lcov_path: &Path, llvm_profile_path: &str) -> Result<()> { - let debug_dir = project_root().join("target/debug/"); - let mut lcov_file_path = PathBuf::new(); - lcov_file_path.push(html_lcov_path); - lcov_file_path.push("lcov.info"); - let mut lcov_file = Command::new("grcov"); - lcov_file - .current_dir(project_root()) - //output file from coverage: gcda files - .arg(debug_dir.as_os_str()) - .arg("--binary-path") - .arg(debug_dir.as_os_str()) - //source code location - .arg("-s") - .arg(project_root().as_os_str()) - //html output - .arg("-t") - .arg("lcov") - .arg("--llvm") - .arg("--branch") - .arg("--ignore") - .arg("/*") - .arg("--ignore") - .arg("x/*") - .arg("--ignore") - .arg("testsuite/*") - .arg("--ignore-not-existing") - .arg("-o") - //TODO: Paths seem to be a thing - .arg(lcov_file_path); - info!("Converting lcov.info file to html"); - info!("{:?}", lcov_file); - lcov_file.env("RUSTFLAGS", "-Zinstrument-coverage"); - lcov_file.env("LLVM_PROFILE_FILE", llvm_profile_path); - lcov_file.stdout(Stdio::inherit()).stderr(Stdio::inherit()); - if let Some(err) = lcov_file.output().err() { - Err(Error::new(err).context("Failed to generate lcov.info with grcov")) - } else { - Ok(()) - } -} - -fn exec_grcov(html_cov_path: &Path, llvm_profile_path: &str) -> Result<()> { - let debug_dir = project_root().join("target/debug/"); - let mut grcov_html = Command::new("grcov"); - //grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "/*" -o $HOME/output/ - grcov_html - .current_dir(project_root()) - //output file from coverage: gcda files - .arg(project_root().as_os_str()) - .arg("--binary-path") - .arg(debug_dir.as_os_str()) - //source code location - .arg("-s") - .arg(project_root().as_os_str()) - //html output - .arg("-t") - .arg("html") - .arg("--branch") - .arg("--ignore") - .arg("/*") - .arg("--ignore") - .arg("x/*") - .arg("--ignore") - .arg("testsuite/*") - .arg("--ignore-not-existing") - .arg("-o") - .arg(html_cov_path); - info!("Build grcov Html Coverage Report"); - info!("{:?}", grcov_html); - grcov_html.env("LLVM_PROFILE_FILE", llvm_profile_path); - grcov_html.env("RUSTFLAGS", "-Zinstrument-coverage"); - grcov_html.stdout(Stdio::inherit()).stderr(Stdio::inherit()); - if let Some(err) = grcov_html.output().err() { - Err(Error::new(err).context("Failed to generate html output with grcov")) - } else { - Ok(()) - } -} diff --git a/devtools/x/src/tools.rs b/devtools/x/src/tools.rs deleted file mode 100644 index b4955b07429b2..0000000000000 --- a/devtools/x/src/tools.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use crate::{context::XContext, Result}; -use anyhow::anyhow; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Args { - #[structopt(long)] - /// Run in 'check' mode. Exits with 0 if all tools installed. Exits with 1 and if not, printing failed - check: bool, -} - -pub fn run(args: Args, xctx: XContext) -> Result<()> { - let success = match args.check { - false => xctx.installer().install_all(), - true => xctx.installer().check_all(), - }; - if success { - Ok(()) - } else { - Err(anyhow!("Failed to install tools")) - } -} diff --git a/devtools/x/src/utils.rs b/devtools/x/src/utils.rs deleted file mode 100644 index 659613a41dcea..0000000000000 --- a/devtools/x/src/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Aptos -// SPDX-License-Identifier: Apache-2.0 - -use camino::Utf8Path; - -/// The number of directories between the project root and the root of this crate. -pub const X_DEPTH: usize = 2; - -/// Returns the project root. TODO: switch uses to XCoreContext::project_root instead) -pub fn project_root() -> &'static Utf8Path { - Utf8Path::new(&env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(X_DEPTH) - .unwrap() -} diff --git a/scripts/dev_setup.sh b/scripts/dev_setup.sh index 852e12cd3e5f6..4650a260c764d 100755 --- a/scripts/dev_setup.sh +++ b/scripts/dev_setup.sh @@ -16,7 +16,6 @@ set -eo pipefail SHELLCHECK_VERSION=0.7.1 GRCOV_VERSION=0.8.2 -GUPPY_GIT='https://github.com/facebookincubator/cargo-guppy@39ec940f36b0a0df96a330243d127cbe2db9f919' KUBECTL_VERSION=1.18.6 TERRAFORM_VERSION=0.12.26 HELM_VERSION=3.2.4 @@ -371,17 +370,15 @@ function install_toolchain { fi } -function install_cargo_guppy { - if ! command -v cargo-guppy &> /dev/null; then - git_repo=$( echo "$GUPPY_GIT" | cut -d "@" -f 1 ); - git_hash=$( echo "$GUPPY_GIT" | cut -d "@" -f 2 ); - cargo install cargo-guppy --git "$git_repo" --rev "$git_hash" --locked +function install_cargo_sort { + if ! command -v cargo-sort &> /dev/null; then + cargo install cargo-sort --locked fi } -function install_cargo_sort { - if ! command -v cargo-sort &> /dev/null; then - cargo install cargo-sort +function install_cargo_nextest { + if ! command -v cargo-nextext &> /dev/null; then + cargo install cargo-nextest --locked fi } @@ -844,8 +841,8 @@ if [[ "$INSTALL_BUILD_TOOLS" == "true" ]]; then rustup component add rustfmt rustup component add clippy - install_cargo_guppy install_cargo_sort + install_cargo_nextest install_grcov install_postgres install_pkg git "$PACKAGE_MANAGER" @@ -926,6 +923,10 @@ if [[ "$INSTALL_API_BUILD_TOOLS" == "true" ]]; then "${PRE_COMMAND[@]}" python3 -m pip install schemathesis fi +install_python3 +pip3 install pre-commit +pre-commit install + if [[ "${BATCH_MODE}" == "false" ]]; then cat <"] -license = "Apache-2.0" - -[workspace.banned-deps.direct] -lazy_static = "use once_cell::sync::Lazy instead" - -[workspace.banned-deps.default-build] -criterion = "criterion is only for benchmarks" -proptest = "proptest is only for testing and fuzzing" - -[workspace.direct-dep-dups] -allow = [ -] - -[workspace.overlay] -features = ["failpoints", "fuzzing"] - -# This is a list of test-only members. These are workspace members that do not form part of the main -# Aptos production codebase, and are only used to verify correctness and/or performance. -# -# *** IMPORTANT *** -# -# Published developer tools (e.g. Move compiler) ARE part of the production Aptos codebase. -# They should be listed in the root Cargo.toml's default-members, not here! -# -# Before adding a new crate to this list, ensure that it is *actually* test-only. If not, add it -# (or a crate that depends on it) to the root Cargo.toml's default-members list! -# -# For more, see the "Conditional compilation for tests" section in documentation/coding_guidelines.md. -[workspace.test-only] -members = [ - # Please keep this list in alphabetical order! - - "testcases", - "aptos-faucet-cli", - "aptos-fuzz", - "aptos-fuzzer", - "aptos-proptest-helpers", - "aptos-retrier", - "aptos-rosetta-cli", - "aptos-transaction-benchmarks", - "aptos-writeset-generator", - "executor-benchmark", - "executor-test-helpers", - "forge", - "forge-cli", - "generate-format", - "language-e2e-tests", - "language-e2e-testsuite", - "memsocket", - "move-examples", - "peer-monitoring-service-client", # This will be removed once the peer monitoring service is plugged in - "peer-monitoring-service-server", # This will be removed once the peer monitoring service is plugged in - "peer-monitoring-service-types", # This will be removed once the peer monitoring service is plugged in - "smoke-test", - "x", - "x-core", - "x-lint", - - # Please keep this list in alphabetical order! -] - -[workspace.move-to-aptos-deps] -# By default, all crates in the language directory are considered Move crates -# and are forbidden from depending on any Aptos crates. -# -# Adding a crate's name to this list will mark it as a Aptos crate so that the -# linter will not impose said restriction on it. -# -# Note: if your crate is indeed a Move crate and you are getting the linter -# yelling at you, you should redesign your crate and properly remove that -# dependency, instead of adding the crate to this list and silencing the lint. -aptos_crates_in_language = [ -] -# A special set of aptos crates Move crates are allowed to depend on. -# -# You should not add new entries to this list unless you have a rare justifiable -# reason. (read: Just don't.) -# -# Particularly, please do not abuse this to silence the lint when it complains -# about Move crates depending on Aptos ones. Again, you should instead fix -# the design of your crate so it doesn't have to depend on Aptos. -exclude = [ - -] - -# Interesting subsets of the workspace, These are used for generating and -# checking dependency summaries. - -[subsets.lsr] -# The Aptos safety rules TCB. -root-members = [ - "safety-rules", -] - -[subsets.release] -# The Aptos release binaries -root-members = [ - "backup-cli", - "db-bootstrapper", - "aptos", - "aptos-node", - "aptos-operational-tool", - "safety-rules", -] - -# --- -# Determinator rules -# --- - -# CI-related files. TODO: maybe have separate rules for local and CI? -[[determinator.path-rule]] -globs = [".circleci/**/*", ".github/**/*", "codecov.yml"] -mark-changed = "all" - -# Core devtools files. -[[determinator.path-rule]] -globs = [".config/nextest.toml", "scripts/dev_setup.sh", "x.toml", ".node-version", ".prettierrc", ".actrc"] -mark-changed = "all" - -[[determinator.path-rule]] -# Ignore website and other ancillary files, and scripts not listed above. -globs = ["CODEOWNERS","dashboards/**", "developer-docs-site/**/*", "documentation/**/*", "docker/**/*", "language/documentation/**/*", "specifications/**/*", "scripts/**/*", "terraform/**/*", "crowdin.yml"] -mark-changed = [] - -[[determinator.path-rule]] -# Ignore forge files and scripts -globs = ["testsuite/run_forge.sh", "testsuite/forge-test-runner-template.yaml"] -mark-changed = [] - -[[determinator.path-rule]] -# Ignore ecosytem typescript -globs = ["ecosystem/typescript/**/*", "ecosystem/python/**/*", "ecosystem/web-wallet/**/*", "ecosystem/platform/**/*", "ecosystem/indexer-server/*"] -mark-changed = [] - -[[determinator.path-rule]] -# A bunch of images that should be ignored, I guess. -globs = [".assets/aptos_banner.png", ".assets/aptos.png", "storage/data.png"] -mark-changed = [] - -[[determinator.path-rule]] -# Ignore *.md documentation -globs = ["*.md"] -mark-changed = [] - -[[determinator.path-rule]] -# Required by get_stdlib_script_abis in transaction-builder-generator. -globs = ["aptos-move/framework/DPN/releases/artifacts/current/**/*"] -mark-changed = ["transaction-builder-generator"] -post-rule = "skip-rules" - -[[determinator.package-rule]] -# x controls the build process, so if it changes, build everything. -on-affected = ["x"] -mark-changed = "all"