From 6af2853ab1484622d6b62220c28824c46cd6a45b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 23 Mar 2024 15:16:46 +0000 Subject: [PATCH] Start codegen for JSON schema --- Cargo.lock | 163 +++++++++++++- Cargo.toml | 5 + deny.schema.yml | 80 +++---- deny15.schema.json | 320 ---------------------------- xtask/Cargo.toml | 23 ++ xtask/src/cli.rs | 34 +++ xtask/src/cli/codegen.rs | 13 ++ xtask/src/cli/codegen/jsonschema.rs | 163 ++++++++++++++ xtask/src/entrypoint.rs | 28 +++ xtask/src/lib.rs | 4 + xtask/src/main.rs | 3 + 11 files changed, 472 insertions(+), 364 deletions(-) delete mode 100644 deny15.schema.json create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/cli.rs create mode 100644 xtask/src/cli/codegen.rs create mode 100644 xtask/src/cli/codegen/jsonschema.rs create mode 100644 xtask/src/entrypoint.rs create mode 100644 xtask/src/lib.rs create mode 100644 xtask/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c302e2f3..0b019a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata", + "regex-automata 0.4.6", "serde", ] @@ -254,7 +254,7 @@ dependencies = [ "insta", "krates", "log", - "nu-ansi-term", + "nu-ansi-term 0.50.0", "parking_lot", "rayon", "reqwest", @@ -1521,8 +1521,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", ] [[package]] @@ -1714,6 +1714,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1799,6 +1808,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "maybe-async" version = "0.2.10" @@ -1851,6 +1869,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.0" @@ -1915,6 +1943,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2066,8 +2100,17 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2078,9 +2121,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -2397,6 +2446,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1_smol" version = "1.0.0" @@ -2414,6 +2476,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -2646,6 +2717,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.34" @@ -2792,9 +2873,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -2802,6 +2895,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term 0.46.0", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2859,6 +2982,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -2883,6 +3012,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" @@ -3196,6 +3331,20 @@ dependencies = [ "tap", ] +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "itertools", + "serde", + "serde_json", + "serde_yaml", + "tracing", + "tracing-subscriber", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 7ae4ac6c..48800ea5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = ["xtask"] + [package] name = "cargo-deny" description = "Cargo plugin to help you manage large dependency graphs" @@ -20,10 +23,12 @@ rust-version = "1.70.0" [badges] maintenance = { status = "actively-developed" } + [[bin]] name = "cargo-deny" path = "src/cargo-deny/main.rs" + [features] default = ["reqwest/rustls-tls-webpki-roots", "tame-index/default"] # Enables the use of OS native certificate store. diff --git a/deny.schema.yml b/deny.schema.yml index 492fa06b..e84b09a8 100644 --- a/deny.schema.yml +++ b/deny.schema.yml @@ -43,8 +43,8 @@ definitions: advisories: description: | This section is considered when running `cargo deny check advisories` - More documentation for the advisories section can be found here: - https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html + More documentation for the advisories section can be found + [here](https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html) type: object properties: @@ -91,7 +91,7 @@ definitions: vulnerability: deprecated: true - oneOf: [{ $ref: '#/definitions/lint-level' }] + $ref: '#/definitions/lint-level' default: deny description: | **DEPRECATED** (see `version` field) @@ -100,7 +100,7 @@ definitions: unmaintained: deprecated: true - oneOf: [{ $ref: '#/definitions/lint-level' }] + $ref: '#/definitions/lint-level' default: warn description: | **DEPRECATED** (see `version` field) @@ -109,7 +109,7 @@ definitions: unsound: deprecated: true - oneOf: [{ $ref: '#/definitions/lint-level' }] + $ref: '#/definitions/lint-level' default: warn description: | **DEPRECATED** (see `version` field) @@ -117,7 +117,7 @@ definitions: Determines what happens when a crate with an `unsound` advisory is encountered. notice: - oneOf: [{ $ref: '#/definitions/lint-level' }] + $ref: '#/definitions/lint-level' default: warn description: | **DEPRECATED** (see `version` field) @@ -128,64 +128,70 @@ definitions: [RustSec Advisory DB](https://github.com/RustSec/advisory-db) yanked: - oneOf: [{ $ref: '#/definitions/lint-level' }] + $ref: '#/definitions/lint-level' default: warn description: | Determines what happens when a crate with a version that has been yanked from its source registry is encountered. + # x-taplo: + # docs: + # main: | + # Determines what happens when a crate with a version that has been yanked from its source + # registry is encountered. ignore: type: array items: { $ref: '#/definitions/advisories-ignore-item' } + description: | + ```toml + ignore = [ + "RUSTSEC-0000-0000", + { id = "RUSTSEC-0000-0000", reason = "this vulnerability does not affect us as we don't use the particular code path" }, + "yanked@0.1.1", + { crate = "yanked-crate@0.1.1", reason = "a semver compatible version hasn't been published yet" }, + ] + ``` + + Every advisory in the advisory database contains a unique identifier, eg. `RUSTSEC-2019-0001`. + Putting an identifier in this array will cause the advisory to be treated as a note, rather + than a warning or error. + + In addition, yanked crate versions can be ignored by specifying a [PackageSpec](https://embarkstudios.github.io/cargo-deny/checks/cfg.html#package-spec) + with an optional `reason`. advisories-ignore-item: oneOf: - type: string description: Either an advisory ID (e.g. `RUSTSEC-2019-0001`) or a package spec (e.g. `yanked@0.1.1`). - - { $ref: '#/definitions/ignore-advisory-object' } - - { $ref: '#/definitions/ignore-yanked-object' } + - { $ref: '#/definitions/advisories-ignore-advisory' } + - { $ref: '#/definitions/advisories-ignore-yanked' } - description: | - ```toml - ignore = [ - "RUSTSEC-0000-0000", - { id = "RUSTSEC-0000-0000", reason = "this vulnerability does not affect us as we don't use the particular code path" }, - "yanked@0.1.1", - { crate = "yanked-crate@0.1.1", reason = "a semver compatible version hasn't been published yet" }, - ] - ``` - - Every advisory in the advisory database contains a unique identifier, eg. `RUSTSEC-2019-0001`. - Putting an identifier in this array will cause the advisory to be treated as a note, rather - than a warning or error. - - In addition, yanked crate versions can be ignored by specifying a [PackageSpec](https://embarkstudios.github.io/cargo-deny/checks/cfg.html#package-spec) - with an optional `reason`. - - ignore-advisory-object: + advisories-ignore-advisory: type: object - examples: [RUSTSEC-2019-0001] required: [id] properties: id: type: string + examples: [RUSTSEC-2019-0001] description: The unique identifier of the advisory to ignore reason: { $ref: '#/definitions/ignore-reason' } - ignore-yanked-object: + advisories-ignore-yanked: type: object required: [crate] properties: crate: { $ref: '#/definitions/package-spec' } reason: { $ref: '#/definitions/ignore-reason' } - ignore-reason: type: string description: Free-form string that can be used to describe the reason why the advisory is ignored. lint-level: - deprecated: true + # anyOf: + # - { const: deny, } # description: Emit an error with details about the problem, and fail the check. } + # - { const: warn, } # description: Print a warning for each propblem, but don't fail the check. } + # - { const: allow, } # description: Print a note about the problem, but don't fail the check. } enum: [deny, warn, allow] x-taplo: docs: @@ -336,15 +342,15 @@ definitions: target: oneOf: - - $ref: '#/definitions/target-triple' - - $ref: '#/definitions/target-object' + - $ref: '#/definitions/target-string' + - $ref: '#/definitions/target-complex' - target-object: + target-complex: description: Advanced configurations to apply for the target triple type: object required: [triple] properties: - triple: { $ref: '#/definitions/target-triple' } + triple: { $ref: '#/definitions/target-string' } features: description: | Rust `cfg()` expressions support the [`target_feature = "feature-name"`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute) @@ -354,7 +360,7 @@ definitions: this writing, cargo-deny does not attempt to validate that the features you specify are actually valid for the target triple, but this is [planned](https://github.com/EmbarkStudios/cfg-expr/issues/1). - target-triple: + target-string: type: string description: | The [target triple](https://forge.rust-lang.org/release/platform-support.html) for the target @@ -372,6 +378,6 @@ definitions: minimum: 0 default: 1 description: | - The maximum depth that features will be displayed when inclusion graphs are included in + The maximum depth that features will be displayed when inclusion graphs are shown in diagnostics, unless specified via `--feature-depth` on the command line. Only applies to diagnostics that actually print features. diff --git a/deny15.schema.json b/deny15.schema.json deleted file mode 100644 index c4c2eacd..00000000 --- a/deny15.schema.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft-07/schema#", - "$id": "https://github.com/EmbarkStudios/cargo-deny/deny.schema.json", - "title": "cargo-deny configuration file", - "description": "Full documentation is at https://embarkstudios.github.io/cargo-deny/checks/cfg.html", - "type": "object", - "properties": - { - "advisories": - { - "$ref": "#/definitions/advisories" - }, - "graph": - { - "$ref": "#/definitions/graph" - }, - "output": - { - "$ref": "#/definitions/output" - } - }, - "definitions": - { - "advisories": - { - "description": "This section is considered when running `cargo deny check advisories`\nMore documentation for the advisories section can be found here:\nhttps://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html\n", - "type": "object", - "properties": - { - "db-urls": - { - "type": "array", - "items": - { - "type": "string", - "format": "uri" - }, - "default": - [ - "https://github.com/RustSec/advisory-db" - ], - "description": "URLs to one or more advisory databases." - }, - "db-path": - { - "type": "string", - "description": "Path to the root directory into which one or more advisory databases are cloned into.\n\nThis value supports basic shell expansion:\n\n- `~` - Expands to [`home::home_dir`](https://docs.rs/home/latest/home/fn.home_dir.html)\n- `$VARNAME` - Expands to [`std::env::var(\"VARNAME\")`](https://doc.rust-lang.org/std/env/fn.var.html)\n- `${VARNAME}` - Expands to [`std::env::var(\"VARNAME\")`](https://doc.rust-lang.org/std/env/fn.var.html)\n- `${VARNAME:-fallback}` - Expands to [`std::env::var(\"VARNAME\")`](https://doc.rust-lang.org/std/env/fn.var.html)\n or the fallback value if it doesn't exist (everything between the `:-` and `}`)\n- `$CARGO_HOME` - Expands to [`std::env::var(\"CARGO_HOME\")`](https://doc.rust-lang.org/std/env/fn.var.html)\n if it exists, otherwise expands to `$(home::home_dir())/.cargo`\n\nNote that the path must be valid utf-8, after expansion.\n\nDefault: `$CARGO_HOME/advisory-dbs`\n" - }, - "version": - { - "enum": - [ - 2 - ], - "description": "The advisories section has an upcoming breaking change, with deprecation warnings for several\nfields that will be removed. Setting `version = 2` will opt-in to the future default behavior.\n\nThe breaking change is as follows:\n\n- `vulnerability` - Removed, all vulnerability advisories now emit errors.\n- `unmaintained` - Removed, all unmaintained advisories now emit errors.\n- `unsound` - Removed, all unsound advisories now emit errors.\n- `notice` - Removed, all notice advisories now emit errors.\n- `severity-threshold` - Removed, all vulnerability advisories now emit errors.\n\nAs before, if you want to ignore a specific advisory, add it to the `ignore` field.\n" - }, - "vulnerability": - { - "deprecated": true, - "oneOf": - [ - { - "$ref": "#/definitions/lint-level" - } - ], - "default": "deny", - "description": "**DEPRECATED** (see `version` field)\n\nDetermines what happens when a crate with a security vulnerability is encountered.\n" - }, - "unmaintained": - { - "deprecated": true, - "oneOf": - [ - { - "$ref": "#/definitions/lint-level" - } - ], - "default": "warn", - "description": "**DEPRECATED** (see `version` field)\n\nDetermines what happens when a crate with an `unmaintained` advisory is encountered.\n" - }, - "unsound": - { - "deprecated": true, - "oneOf": - [ - { - "$ref": "#/definitions/lint-level" - } - ], - "default": "warn", - "description": "**DEPRECATED** (see `version` field)\n\nDetermines what happens when a crate with an `unsound` advisory is encountered.\n" - }, - "notice": - { - "oneOf": - [ - { - "$ref": "#/definitions/lint-level" - } - ], - "default": "warn", - "description": "**DEPRECATED** (see `version` field)\n\nDetermines what happens when a crate with a `notice` advisory is encountered.\n\n**NOTE**: As of 2019-12-17 there are no `notice` advisories in the\n[RustSec Advisory DB](https://github.com/RustSec/advisory-db)\n" - }, - "yanked": - { - "oneOf": - [ - { - "$ref": "#/definitions/lint-level" - } - ], - "default": "warn", - "description": "Determines what happens when a crate with a version that has been yanked from its source\nregistry is encountered.\n" - }, - "ignore": - { - "type": "array", - "items": - { - "$ref": "#/definitions/advisories-ignore-item" - } - } - } - }, - "advisories-ignore-item": - { - "oneOf": - [ - { - "type": "string", - "description": "Either an advisory ID (e.g. `RUSTSEC-2019-0001`) or a package spec (e.g. `yanked@0.1.1`)." - }, - { - "$ref": "#/definitions/ignore-advisory-object" - }, - { - "$ref": "#/definitions/ignore-yanked-object" - } - ], - "description": "```toml\nignore = [\n \"RUSTSEC-0000-0000\",\n { id = \"RUSTSEC-0000-0000\", reason = \"this vulnerability does not affect us as we don't use the particular code path\" },\n \"yanked@0.1.1\",\n { crate = \"yanked-crate@0.1.1\", reason = \"a semver compatible version hasn't been published yet\" },\n]\n```\n\nEvery advisory in the advisory database contains a unique identifier, eg. `RUSTSEC-2019-0001`.\nPutting an identifier in this array will cause the advisory to be treated as a note, rather\nthan a warning or error.\n\nIn addition, yanked crate versions can be ignored by specifying a [PackageSpec](https://embarkstudios.github.io/cargo-deny/checks/cfg.html#package-spec)\nwith an optional `reason`.\n" - }, - "ignore-advisory-object": - { - "type": "object", - "examples": - [ - "RUSTSEC-2019-0001" - ], - "required": - [ - "id" - ], - "properties": - { - "id": - { - "type": "string", - "description": "The unique identifier of the advisory to ignore" - }, - "reason": - { - "$ref": "#/definitions/ignore-reason" - } - } - }, - "ignore-yanked-object": - { - "type": "object", - "required": - [ - "crate" - ], - "properties": - { - "crate": - { - "$ref": "#/definitions/package-spec" - }, - "reason": - { - "$ref": "#/definitions/ignore-reason" - } - } - }, - "ignore-reason": - { - "type": "string", - "description": "Free-form string that can be used to describe the reason why the advisory is ignored." - }, - "lint-level": - { - "deprecated": true, - "enum": - [ - "deny", - "warn", - "allow" - ], - "x-taplo": - { - "docs": - { - "enumValues": - [ - "Emit an error with details about the problem, and fail the check.", - "Print a warning for each propblem, but don't fail the check.", - "Print a note about the problem, but don't fail the check." - ] - } - } - }, - "package-spec": - { - "type": "string", - "description": "Many configuration options require a package specifier at a minimum, which we'll describe here.\nThe options that use package specifiers will be called out in their individual documentation.\nWe'll use the [`bans.deny`](bans/cfg.md#the-deny-field-optional) option in the following examples.\n\n### String format\n\nIf the particular only requires a package spec at a minimum, then the string format can be used,\nwhich comes in three forms.\n\n#### Simple\n\n```toml\n# Will match any version of the simple crate\ndeny = [\"simple\"]\n```\n\nThe simplest string is one which is just the crate name. In this case, the version requirement\nused when checking will be `*` meaning it will match against all versions of that crate in the graph.\n\n#### With Version Requirements\n\n```toml\n# Will match only these versions of the simple crate that match the predicate(s)\ndeny = [\"simple:<=0.1,>0.2\"]\n```\n\nIf you want to apply version requirements (predicates) to the crate, simply append them following\na `:` separator.\n\n#### Exact\n\n```toml\n# Will match only this exact version of the simple crate\ndeny = [\n \"simple@0.1.0\",\n # This is semantically equivalent to the above\n \"simple:=0.1.0\",\n]\n```\n\nThe exact form is a specialization of the version requirements, where the semver after the `@`\nis transformed to be [= (Exact)](https://docs.rs/semver/latest/semver/enum.Op.html#opexact).\n\n### Table format\n\n#### Crate format\n\n```toml\ndeny = [\n { crate = \"simple@0.1.0\" }, # equivalent to \"simple@0.1.0\"\n { crate = \"simple\", wrappers = [\"example\"] },\n]\n```\n\nThe crate format is a replacement for the old `name` and/or `version` table format. It uses\nthe string format described above in a single `crate` key.\n\n#### Old format\n\n```toml\ndeny = [\n { name = \"simple\" },\n { name = \"simple\", version = \"*\" }\n { name = \"simple\", wrappers = [\"example\"] }\n]\n```\n\nThe old format uses a required `name` key and an optional `version` key. This format is deprecated\nand should not be used.\n" - }, - "graph": - { - "description": "The graph table configures how the dependency graph is constructed and thus which crates the\nchecks are performed against\n", - "type": "object", - "properties": - { - "targets": - { - "type": "array", - "items": - { - "$ref": "#/definitions/target" - }, - "description": "By default, cargo-deny will consider every single crate that is resolved by cargo, including\ntarget specific dependencies e.g.\n\n```toml\n[target.x86_64-pc-windows-msvc.dependencies]\nwinapi = \"0.3.8\"\n\n[target.'cfg(target_os = \"fuchsia\")'.dependencies]\nfuchsia-cprng = \"0.1.1\"\n```\n\nBut unless you are actually targeting `x86_64-fuchsia` or `aarch64-fuchsia`, the `fuchsia-cprng` is\nnever actually going to be compiled or linked into your project, so checking it is pointless for you.\n\nThe `targets` field allows you to specify one or more targets which you **actually** build for.\nEvery dependency link to a crate is checked against this list, and if none of the listed targets\nsatisfy the target constraint, the dependency link is ignored. If a crate has no dependency links\nto it, it is not included into the crate graph that the checks are\nexecuted against.\n" - }, - "exclude": - { - "type": "array", - "items": - { - "type": "string" - }, - "description": "Just as with the [`--exclude`](https://embarkstudios.github.io/cargo-deny/cli/common.html#--exclude-dev)\ncommand line option, this field allows you to specify one or more [Package ID specifications](https://doc.rust-lang.org/cargo/commands/cargo-pkgid.html)\nthat will cause the crate(s) in question to be excluded from the crate graph that is used\nfor the operation you are performing.\n\nNote that excluding a crate is recursive, if any of its transitive dependencies are only referenced\nvia the excluded crate, they will also be excluded from the crate graph.\n" - }, - "all-features": - { - "type": "boolean", - "description": "If set to `true`, `--all-features` will be used when collecting metadata." - }, - "no-default-features": - { - "type": "boolean", - "description": "If set to `true`, `--no-default-features` will be used when collecting metadata." - }, - "features": - { - "type": "array", - "items": - { - "type": "string" - }, - "description": "If set, and `--features` is not specified on the cmd line, these features will be used when\ncollecting metadata.\n" - }, - "exclude-dev": - { - "type": "boolean", - "description": "If set to `true`, all `dev-dependencies`, even one for workspace crates, are not included\nin the crate graph used for any of the checks. This option can also be enabled on cmd line\nwith `--exclude-dev` either [before](https://embarkstudios.github.io/cargo-deny/cli/common.html#--exclude-dev)\nor [after](https://embarkstudios.github.io/cargo-deny/cli/check.html#--exclude-dev)\nthe `check` subcommand.\n" - } - } - }, - "target": - { - "oneOf": - [ - { - "$ref": "#/definitions/target-triple" - }, - { - "$ref": "#/definitions/target-object" - } - ] - }, - "target-object": - { - "description": "Advanced configurations to apply for the target triple", - "type": "object", - "required": - [ - "triple" - ], - "properties": - { - "triple": - { - "$ref": "#/definitions/target-triple" - }, - "features": - { - "description": "Rust `cfg()` expressions support the [`target_feature = \"feature-name\"`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute)\npredicate, but at the moment, the only way to actually pass them when compiling is to use\nthe `RUSTFLAGS` environment variable. The `features` field allows you to specify 1 or more\n`target_feature`s you plan to build with, for a particular target triple. At the time of\nthis writing, cargo-deny does not attempt to validate that the features you specify are\nactually valid for the target triple, but this is [planned](https://github.com/EmbarkStudios/cfg-expr/issues/1).\n" - } - } - }, - "target-triple": - { - "type": "string", - "description": "The [target triple](https://forge.rust-lang.org/release/platform-support.html) for the target\nyou wish to filter target specific dependencies with. If the target triple specified is **not**\none of the targets builtin to `rustc`, the configuration check for that target will be limited\nto only the raw `[target..dependencies]` style of target configuration, as `cfg()`\nexpressions require us to know the details about the target.\n" - }, - "output": - { - "description": "The output table provides options for how/if diagnostics are outputted", - "type": "object", - "properties": - { - "feature-depth": - { - "type": "integer", - "minimum": 0, - "default": 1, - "description": "The maximum depth that features will be displayed when inclusion graphs are included in\ndiagnostics, unless specified via `--feature-depth` on the command line. Only applies to\ndiagnostics that actually print features.\n" - } - } - } - } -} \ No newline at end of file diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..cc423c85 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Amazing CLI arg parser +clap = { version = "4.3", features = ["derive"] } + +# The coolest serialization framework in the world +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" + +# Easy dynamic error handling +anyhow = "1.0" + +# Awesome logging tools +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# The best iterators utilities +itertools = "0.12" diff --git a/xtask/src/cli.rs b/xtask/src/cli.rs new file mode 100644 index 00000000..bcabfcac --- /dev/null +++ b/xtask/src/cli.rs @@ -0,0 +1,34 @@ +mod codegen; + +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + Codegen(codegen::CodegenCommand), +} + +pub(crate) fn run() -> anyhow::Result<()> { + let cli = Cli::parse(); + match cli.command { + Command::Codegen(cmd) => cmd.run(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + + fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert() + } +} diff --git a/xtask/src/cli/codegen.rs b/xtask/src/cli/codegen.rs new file mode 100644 index 00000000..2e6a4deb --- /dev/null +++ b/xtask/src/cli/codegen.rs @@ -0,0 +1,13 @@ +mod jsonschema; + +/// Update generated code that is checked in to source control. +#[derive(clap::Args, Debug)] +pub(crate) struct CodegenCommand {} + +impl CodegenCommand { + pub(crate) fn run(self) -> anyhow::Result<()> { + jsonschema::codegen()?; + + Ok(()) + } +} diff --git a/xtask/src/cli/codegen/jsonschema.rs b/xtask/src/cli/codegen/jsonschema.rs new file mode 100644 index 00000000..fc4bca45 --- /dev/null +++ b/xtask/src/cli/codegen/jsonschema.rs @@ -0,0 +1,163 @@ +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::BTreeMap; +use std::fs; + +type Untyped = BTreeMap; + +#[derive(Serialize, Deserialize, Debug)] +struct RootSchema { + definitions: BTreeMap, + + #[serde(flatten)] + schema: Schema, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct Schema { + // kind: Option, + #[serde(rename = "enum", skip_serializing_if = "Option::is_none")] + enum_values: Option>, + + // #[serde(rename = "x-taplo")] + // x_taplo: Option, + #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")] + reference: Option, + + #[serde(flatten)] + untyped: Untyped, +} + +// #[derive(Serialize, Deserialize, Debug, Clone)] +// #[serde(tag = "type")] +// enum SchemaKind { +// Object { +// properties: BTreeMap, +// }, +// Array { +// items: Box, +// }, +// String, +// Integer, +// Number, +// Boolean, +// Null, +// } + +// #[derive(Serialize, Deserialize, Debug, Clone)] +// struct XTaplo { +// docs: XTaploDocs, + +// #[serde(flatten)] +// untyped: Untyped, +// } + +// #[derive(Serialize, Deserialize, Debug, Clone)] +// struct XTaploDocs { +// enum_values: Vec, + +// #[serde(flatten)] +// untyped: Untyped, +// } + +// impl Schema { +// fn merge(&mut self, other: &Schema) { +// merge_serializable(self, other.clone()); +// } +// } + +// fn merge_serializable(a: &mut T, b: T) { +// let mut a_value = serde_json::to_value(&a).unwrap(); +// let b_value = serde_json::to_value(b).unwrap(); +// merge_json_values(&mut a_value, b_value); + +// *a = serde_json::from_value(a_value).unwrap(); +// } + +fn merge_json_values(a: &mut Value, b: Value) { + use serde_json::map::Entry; + + match (a, b) { + (Value::Object(a), Value::Object(b)) => { + for (key, b_value) in b { + match a.entry(key) { + Entry::Occupied(mut a_value) => merge_json_values(a_value.get_mut(), b_value), + Entry::Vacant(entry) => { + entry.insert(b_value); + } + } + } + } + (Value::Array(a), Value::Array(b)) => { + a.extend(b); + } + (a, b) => *a = b, + } +} + +fn inline_enum_refs(def: &mut Schema, enums: &BTreeMap) -> anyhow::Result<()> { + let mut def_value = serde_json::to_value(&def).unwrap(); + inline_enum_refs_imp(&mut def_value, enums)?; + *def = serde_json::from_value(def_value).unwrap(); + Ok(()) +} + +fn inline_enum_refs_imp( + def_value: &mut Value, + enums: &BTreeMap, +) -> anyhow::Result<()> { + let Some(def) = def_value.as_object_mut() else { + return Ok(()); + }; + + for property in def.values_mut() { + inline_enum_refs_imp(property, enums)?; + } + + let Some(reference) = def.get("$ref") else { + return Ok(()); + }; + + let reference = reference + .as_str() + .with_context(|| format!("Reference must be a string, but found: {reference}"))?; + + let reference = reference.strip_prefix("#/definitions/").with_context(|| { + format!("Reference not to #/definitions is not allowed, but found: {reference}") + })?; + + let Some(enum_def) = enums.get(reference) else { + return Ok(()); + }; + + def.remove("$ref"); + + let enum_def = serde_json::to_value(enum_def).unwrap(); + merge_json_values(def_value, enum_def); + + Ok(()) +} + +/// Generate the JSON schema based on the input YML schema. +pub(crate) fn codegen() -> anyhow::Result<()> { + let root_schema = fs::read_to_string("deny.schema.yml")?; + let mut root_schema: RootSchema = serde_yaml::from_str(&root_schema)?; + + let (enums, mut defs): (BTreeMap, BTreeMap<_, _>) = root_schema + .definitions + .into_iter() + .partition(|(_, val)| val.enum_values.is_some()); + + for def in defs.values_mut() { + inline_enum_refs(def, &enums)?; + } + + root_schema.definitions = itertools::concat([enums, defs]); + + let output = serde_json::to_string_pretty(&root_schema)?; + + std::fs::write("deny.schema.json", output)?; + + Ok(()) +} diff --git a/xtask/src/entrypoint.rs b/xtask/src/entrypoint.rs new file mode 100644 index 00000000..410cbe5b --- /dev/null +++ b/xtask/src/entrypoint.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use std::process::ExitCode; +use tracing_subscriber::prelude::*; + +pub fn run() -> ExitCode { + let Err(err) = try_run() else { + return ExitCode::SUCCESS; + }; + + eprintln!("Exitting with error: {err:?}"); + ExitCode::FAILURE +} + +fn try_run() -> Result<()> { + let env_filter = tracing_subscriber::EnvFilter::from_env("XTASK_LOG"); + + let fmt = tracing_subscriber::fmt::layer() + .with_target(true) + .with_ansi(std::env::var("COLORS").as_deref() != Ok("0")) + .pretty(); + + tracing_subscriber::registry() + .with(fmt) + .with(env_filter) + .init(); + + crate::cli::run() +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs new file mode 100644 index 00000000..86c4a19c --- /dev/null +++ b/xtask/src/lib.rs @@ -0,0 +1,4 @@ +mod cli; +mod entrypoint; + +pub use entrypoint::run; diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..91971740 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,3 @@ +fn main() -> std::process::ExitCode { + xtask::run() +}