1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
use krates::Utf8PathBuf as PathBuf;
use spdx::Expression;
use std::collections::BTreeMap;
use toml_span::{Deserialize, Value, de_helpers as de};
const MODE: spdx::ParseMode = spdx::ParseMode {
allow_deprecated: true,
allow_slash_as_or_operator: false,
allow_imprecise_license_names: false,
allow_postfix_plus_on_gpl: false,
allow_unknown: false,
};
#[inline]
fn parse_expr<'de>(
th: &mut de::TableHelper<'de>,
key: &'static str,
) -> Result<Expression, toml_span::Error> {
let s = th.required_s::<std::borrow::Cow<'de, str>>(key)?;
Expression::parse_mode(&s.value, MODE).map_err(|err| toml_span::Error {
kind: toml_span::ErrorKind::Custom(err.reason.to_string().into()),
span: (s.span.start + err.span.start..s.span.start + err.span.end).into(),
line_info: None,
})
}
#[inline]
fn parse_path<'de>(
th: &mut de::TableHelper<'de>,
key: &'static str,
) -> Result<PathBuf, toml_span::Error> {
let s = th.required::<String>(key)?;
Ok(PathBuf::from(s))
}
pub struct Additional {
pub root: PathBuf,
pub license: Expression,
pub license_file: PathBuf,
pub license_start: Option<usize>,
pub license_end: Option<usize>,
}
impl<'de> Deserialize<'de> for Additional {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let root = parse_path(&mut tab, "root")?;
let license = parse_expr(&mut tab, "license-file")?;
let license_file = parse_path(&mut tab, "license-file")?;
let license_start = tab.optional("license-start");
let license_end = tab.optional("license-end");
tab.finalize(None)?;
Ok(Self {
root,
license,
license_file,
license_start,
license_end,
})
}
}
pub struct Ignore {
pub license: Expression,
pub license_file: PathBuf,
pub license_start: Option<usize>,
pub license_end: Option<usize>,
}
impl<'de> Deserialize<'de> for Ignore {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let license = parse_expr(&mut tab, "license")?;
let license_file = parse_path(&mut tab, "license-file")?;
let license_start = tab.optional("license-start");
let license_end = tab.optional("license-end");
tab.finalize(None)?;
Ok(Self {
license,
license_file,
license_start,
license_end,
})
}
}
pub struct ClarificationFile {
/// The crate relative path to the file
pub path: PathBuf,
/// The SHA-256 checksum of the file in hex
pub checksum: String,
/// The license applied to the file. Defaults to the license of the parent
/// clarification if not specified.
pub license: Option<Expression>,
/// The beginning of the text to checksum
pub start: Option<String>,
/// The end of the text to checksum
pub end: Option<String>,
}
impl<'de> Deserialize<'de> for ClarificationFile {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let path = parse_path(&mut tab, "path")?;
let checksum = tab.required("checksum")?;
let license = if let Some(lic) = tab.optional_s::<std::borrow::Cow<'de, str>>("license") {
Some(
Expression::parse(&lic.value).map_err(|err| toml_span::Error {
kind: toml_span::ErrorKind::Custom(err.to_string().into()),
span: lic.span,
line_info: None,
})?,
)
} else {
None
};
let start = tab.optional("start");
let end = tab.optional("end");
tab.finalize(None)?;
Ok(Self {
path,
checksum,
license,
start,
end,
})
}
}
pub struct Clarification {
/// The full clarified license expression, as if it appeared as the `license`
/// in the crate's Cargo.toml manifest
pub license: Expression,
/// Normally, if clarifying a file via git, the file in question is retrieved
/// from the same commit the package was built with, which is retrieved via
/// the `.cargo_vcs_info.json` file included in the package. However, this
/// file may not be present, notably if the crate is published with the
/// `--allow-dirty` flag due to file system modifications that aren't committed
/// to source control. In this case, the revision must be specified manually
/// and used instead. This option should absolutely only be used in such a
/// case, as otherwise it is possible for a drift between the license as it
/// was at the time of the actual publish of the crate, and the revision
/// specified here.
pub override_git_commit: Option<String>,
/// 1 or more files that are used as the source of truth for the license
/// expression
pub files: Vec<ClarificationFile>,
/// 1 or more files, retrieved from the source git repository for the same
/// version that was published, used as the source of truth for the license
/// expression
pub git: Vec<ClarificationFile>,
}
impl<'de> Deserialize<'de> for Clarification {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let license = parse_expr(&mut tab, "license")?;
let override_git_commit = tab.optional("override-git-commit");
let files = tab.optional("files").unwrap_or_default();
let git = tab.optional("git").unwrap_or_default();
tab.finalize(None)?;
Ok(Self {
license,
override_git_commit,
files,
git,
})
}
}
pub struct KrateConfig {
/// The list of additional accepted licenses for this crate, again in
/// priority order
pub accepted: Vec<spdx::Licensee>,
/// Overrides the license expression for a crate as long as 1 or more file
/// checksums match
pub clarify: Option<Clarification>,
}
impl<'de> Deserialize<'de> for KrateConfig {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let accepted = if let Some((_, mut lic)) = tab.take("accepted") {
let mut l = Vec::new();
match lic.take() {
toml_span::value::ValueInner::Array(lic) => {
for mut v in lic {
match v.take_string(None) {
Ok(lstr) => {
// We need to allow deprecated identifiers since external dependencies
// can use them even though they shouldn't
match spdx::Licensee::parse_mode(&lstr, MODE) {
Ok(licensee) => l.push(licensee),
Err(error) => {
tab.errors.push(toml_span::Error {
kind: toml_span::ErrorKind::Custom(
error.reason.to_string().into(),
),
span: (v.span.start + error.span.start
..v.span.start + error.span.end)
.into(),
line_info: None,
});
}
}
}
Err(err) => {
tab.errors.push(err);
}
}
}
}
other => {
tab.errors.push(de::expected("an array", other, lic.span));
}
}
l
} else {
Vec::new()
};
let clarify = tab.optional("clarify");
tab.finalize(None)?;
Ok(Self { accepted, clarify })
}
}
/// Configures how private crates are handled and detected
#[derive(Default)]
pub struct Private {
/// If enabled, ignores workspace crates that aren't published, or are
/// only published to private registries
pub ignore: bool,
/// One or more private registries that you might publish crates to, if
/// a crate is only published to private registries, and `ignore` is true,
/// the crate will not have its license checked
pub registries: Vec<String>,
}
impl<'de> Deserialize<'de> for Private {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let ignore = tab.optional("ignore").unwrap_or_default();
let registries = tab.optional("registries").unwrap_or_default();
tab.finalize(None)?;
Ok(Self { ignore, registries })
}
}
#[derive(Default)]
pub struct Config {
/// Only includes dependencies that match at least one of the specified
/// targets
pub targets: Vec<String>,
/// Configures how private crates are handled and detected
pub private: Private,
/// Sets the maximum depth from the root of each crate that will be scanned
/// for license files.
pub max_depth: Option<u32>,
/// Ignores any build dependencies in the graph
pub ignore_build_dependencies: bool,
/// Ignores any dev dependencies in the graph
pub ignore_dev_dependencies: bool,
/// Ignores any transitive dependencies in the graph, ie, only direct
/// dependencies of crates in the workspace will be included
pub ignore_transitive_dependencies: bool,
/// The list of licenses we will use for all crates, in priority order
pub accepted: Vec<spdx::Licensee>,
/// Some crates have extremely complicated licensing which requires tedious
/// configuration to actually correctly identify. Rather than require every
/// user of cargo-about to redo that same configuration if they happen to
/// use those problematic crates, they can apply workarounds instead.
pub workarounds: Vec<String>,
/// Crate specific configuration
pub crates: BTreeMap<String, toml_span::Spanned<KrateConfig>>,
}
impl<'de> Deserialize<'de> for Config {
fn deserialize(value: &mut Value<'de>) -> Result<Self, toml_span::DeserError> {
let mut tab = de::TableHelper::new(value)?;
let targets = tab.optional("targets").unwrap_or_default();
let private = tab.optional("private").unwrap_or_default();
if tab.take("no-clearly-defined").is_some() {
log::warn!("`no-clearly-defined` has been removed");
}
if tab.take("clearly-defined-timeout-secs").is_some() {
log::warn!("`clearly-defined-timeout-secs` has been removed");
}
if tab.take("filter-noassertion").is_some() {
log::warn!("`filter-noassertion` has been removed");
}
let max_depth = tab.optional("max-depth");
let ignore_build_dependencies = tab
.optional("ignore-build-dependencies")
.unwrap_or_default();
let ignore_dev_dependencies = tab.optional("ignore-dev-dependencies").unwrap_or_default();
let ignore_transitive_dependencies = tab
.optional("ignore-transitive-dependencies")
.unwrap_or_default();
let accepted = if let Some((_, mut lic)) = tab.take("accepted") {
let mut l = Vec::new();
match lic.take() {
toml_span::value::ValueInner::Array(lic) => {
for mut v in lic {
match v.take_string(None) {
Ok(lstr) => {
// We need to allow deprecated identifiers since external dependencies
// can use them even though they shouldn't
match spdx::Licensee::parse_mode(&lstr, MODE) {
Ok(licensee) => l.push(licensee),
Err(error) => {
tab.errors.push(toml_span::Error {
kind: toml_span::ErrorKind::Custom(
error.reason.to_string().into(),
),
span: (v.span.start + error.span.start
..v.span.start + error.span.end)
.into(),
line_info: None,
});
}
}
}
Err(err) => {
tab.errors.push(err);
}
}
}
}
other => {
tab.errors.push(de::expected("an array", other, lic.span));
}
}
l
} else {
tab.errors.push(toml_span::Error {
kind: toml_span::ErrorKind::MissingField("accepted"),
span: (0..0).into(),
line_info: None,
});
Vec::new()
};
let workarounds = tab.optional("workarounds").unwrap_or_default();
let mut crates = BTreeMap::default();
for (key, mut value) in tab.table {
match KrateConfig::deserialize(&mut value) {
Ok(kc) => {
crates.insert(
key.name.into_owned(),
toml_span::Spanned::with_span(kc, value.span),
);
}
Err(mut err) => {
tab.errors.append(&mut err.errors);
}
}
}
if !tab.errors.is_empty() {
return Err(toml_span::DeserError { errors: tab.errors });
}
Ok(Self {
targets,
private,
max_depth,
ignore_build_dependencies,
ignore_dev_dependencies,
ignore_transitive_dependencies,
accepted,
workarounds,
crates,
})
}
}