Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

license-scan: Add Cargo.toml support #719

Merged
merged 3 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ DOCKER_BUILDKIT = "1"

[env.development]
# Defined here to allow us to override ${BUILDSYS_ARCH} on the command line.
BUILDSYS_SDK_IMAGE = "thar/sdk-${BUILDSYS_ARCH}:v0.7"
BUILDSYS_SDK_IMAGE = "thar/sdk-${BUILDSYS_ARCH}:v0.8"
# Permit pulling directly Upstream URLs when lookaside cache results in MISSes.
BUILDSYS_ALLOW_UPSTREAM_SOURCE_URL = "true"
# Extra flags used when spawning containers.
Expand Down
13 changes: 11 additions & 2 deletions extras/sdk-container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,8 @@ FROM sdk-rust as sdk-license-scan

USER root
RUN \
mkdir -p /usr/libexec/tools /home/builder/license-scan && \
chown -R builder:builder /usr/libexec/tools /home/builder/license-scan
mkdir -p /usr/libexec/tools /home/builder/license-scan /usr/share/licenses/thar-license-scan && \
chown -R builder:builder /usr/libexec/tools /home/builder/license-scan /usr/share/licenses/thar-license-scan

ARG SPDXVER="3.7"

Expand All @@ -354,6 +354,13 @@ COPY license-scan /home/builder/license-scan
RUN cargo build --release --locked
RUN install -p -m 0755 target/release/thar-license-scan /usr/libexec/tools/
RUN cp -r license-list-data/json/details /usr/libexec/tools/spdx-data
# quine - scan the license tool itself for licenses
RUN \
iliana marked this conversation as resolved.
Show resolved Hide resolved
/usr/libexec/tools/thar-license-scan \
--clarify clarify.toml \
--spdx-data /usr/libexec/tools/spdx-data \
--out-dir /usr/share/licenses/thar-license-scan/vendor \
cargo --locked Cargo.toml

# =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

Expand Down Expand Up @@ -391,6 +398,8 @@ COPY --chown=0:0 --from=sdk-go /home/builder/go/src /usr/libexec/go/src/

# "sdk-license-scan" has our attribution generation tool.
COPY --chown=0:0 --from=sdk-license-scan /usr/libexec/tools/ /usr/libexec/tools/
# quine - include the licenses for the license scan tool itself
COPY --chown=0:0 --from=sdk-license-scan /usr/share/licenses/thar-license-scan/ /usr/share/licenses/thar-license-scan/
iliana marked this conversation as resolved.
Show resolved Hide resolved

# Add Rust programs and libraries to the path.
RUN \
Expand Down
2 changes: 1 addition & 1 deletion extras/sdk-container/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARCH ?= $(shell uname -m)

VERSION := v0.7
VERSION := v0.8
TAG := thar/sdk-$(ARCH):$(VERSION)
ARCHIVE := thar-sdk-$(ARCH)-$(VERSION).tar.gz

Expand Down
14 changes: 14 additions & 0 deletions extras/sdk-container/license-scan/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions extras/sdk-container/license-scan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ publish = false
[dependencies]
anyhow = "1"
askalono = "0.4"
cargo_metadata = "0.9"
ignore = "0.4"
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
Expand Down
62 changes: 62 additions & 0 deletions extras/sdk-container/license-scan/clarify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[clarify.askalono]
expression = "Apache-2.0"
license-files = [
{ path = "LICENSE", hash = 0x18785531 },
{ path = "NOTICE", hash = 0x96b3ea7d },
]
skip-files = [
"src/license.rs" # source code named "license"...
]

[clarify.backtrace-sys]
# backtrace-sys is MIT/Apache-2.0, libbacktrace is BSD-3-Clause
expression = "(MIT OR Apache-2.0) AND BSD-3-Clause"
license-files = [
{ path = "src/libbacktrace/LICENSE", hash = 0x0ce09262 },
]

[clarify.crossbeam-channel]
expression = "(MIT OR Apache-2.0) AND BSD-2-Clause AND CC-BY-3.0"
license-files = [
{ path = "LICENSE-APACHE", hash = 0x24b54f4b },
{ path = "LICENSE-MIT", hash = 0xbc436f08 },
{ path = "LICENSE-THIRD-PARTY", hash = 0xc6242648 },
]

[clarify.crossbeam-queue]
expression = "(MIT OR Apache-2.0) AND BSD-2-Clause-FreeBSD"
license-files = [
{ path = "LICENSE-APACHE", hash = 0x24b54f4b },
{ path = "LICENSE-MIT", hash = 0xbc436f08 },
{ path = "LICENSE-THIRD-PARTY", hash = 0x7e40bc60 },
]

[clarify.regex]
expression = "MIT OR Apache-2.0"
license-files = [
{ path = "LICENSE-APACHE", hash = 0x24b54f4b },
{ path = "LICENSE-MIT", hash = 0xb755395b },
]
skip-files = [
"src/testdata/LICENSE", # we aren't using the test data
]

[clarify.regex-syntax]
expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016"
license-files = [
{ path = "LICENSE-APACHE", hash = 0x24b54f4b },
{ path = "LICENSE-MIT", hash = 0xb755395b },
{ path = "src/unicode_tables/LICENSE-UNICODE", hash = 0xa7f28b93 },
]

[clarify.zstd-sys]
# zstd-sys is MIT OR Apache-2.0
# libzstd is GPL-2.0-only OR BSD-3-Clause (selecting BSD-3-Clause)
expression = "(MIT OR Apache-2.0) AND BSD-3-Clause"
license-files = [
{ path = "zstd/LICENSE", hash = 0x79cda15 },
]
skip-files = [
"zstd/COPYING", # copy of the GPLv2 we are not choosing from libzstd's dual license
"zstd/contrib/linux-kernel/COPYING", # kernel source and patches for adding zstd (?!), not used
]
126 changes: 118 additions & 8 deletions extras/sdk-container/license-scan/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![warn(clippy::pedantic)]
#![allow(clippy::redundant_closure_for_method_calls)]

use anyhow::{bail, ensure, Context, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use askalono::{ScanStrategy, Store, TextData};
use ignore::types::{Types, TypesBuilder};
use ignore::WalkBuilder;
Expand Down Expand Up @@ -42,6 +42,18 @@ enum Cmd {
/// Path to the vendor directory of a project.
vendor_dir: PathBuf,
},
Cargo {
/// Path to Cargo.toml for a project.
manifest_path: PathBuf,

/// Equivalent to `cargo --locked`
#[structopt(long)]
locked: bool,

/// Equivalent to `cargo --offline`
#[structopt(long)]
offline: bool,
},
}

fn main() -> Result<()> {
Expand Down Expand Up @@ -75,6 +87,53 @@ fn main() -> Result<()> {
&opt.out_dir.join(&repo),
&scanner,
&clarify,
None,
)?;
}
Ok(())
}
Cmd::Cargo {
manifest_path,
locked,
offline,
} => {
let mut builder = cargo_metadata::MetadataCommand::new();
builder.manifest_path(manifest_path);
if locked {
builder.other_options(&["--locked".to_owned()]);
}
if offline {
builder.other_options(&["--offline".to_owned()]);
}
let metadata = builder.exec()?;
for package in metadata.packages {
if package.source.is_none() {
if let Some(publish) = package.publish {
if publish.is_empty() {
// `package.source` is None if the project is a local project;
// `package.publish` is an empty Vec if `publish = false` is set
continue;
}
}
}
write_attribution(
&package.name,
package
.manifest_path
.parent()
.expect("expected a path to Cargo.toml to have a parent"),
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
&opt.out_dir
.join(format!("{}-{}", package.name, package.version)),
&scanner,
&clarify,
if let Some(license) = package.license {
Some(Expression::parse(&unslash(&license)).map_err(|err| {
// spdx errors use the lifetime of the string
anyhow!(err.to_string())
})?)
} else {
None
},
)?;
}
Ok(())
Expand Down Expand Up @@ -203,6 +262,12 @@ lazy_static::lazy_static! {
};
}

/// Replace '/' characters in a license string with 'OR'. (crates.io allows '/' instead of 'OR' for
/// compatibility.)
fn unslash(s: &str) -> String {
s.split('/').map(str::trim).collect::<Vec<_>>().join(" OR ")
}

/// Returns true if the file is expected to not be a license text (such as the Apache-2.0 NOTICE
/// file).
fn non_license(path: &Path) -> bool {
Expand All @@ -222,14 +287,20 @@ fn hash(data: &[u8]) -> u32 {
.expect("XxHash32 returned hash larger than 32 bits")
}

#[allow(clippy::too_many_lines)] // maybe someday...
fn write_attribution(
name: &str,
scan_dir: &Path,
out_dir: &Path,
scanner: &ScanStrategy<'_>,
clarifications: &Clarifications,
stated_license: Option<Expression>,
) -> Result<()> {
eprintln!("{}:", name);
if let Some(stated_license) = stated_license.as_ref() {
eprintln!("{} ({}):", name, stated_license);
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
} else {
eprintln!("{}:", name);
}
let mut files = HashMap::new();
for entry in WalkBuilder::new(scan_dir).types(TYPES.clone()).build() {
let entry = entry?;
Expand Down Expand Up @@ -266,6 +337,21 @@ fn write_attribution(
file_hash
);
} else {
if stated_license.is_some() {
// if the package states a license and we heuristically detect that this is
// a top-level "either license, at your option" file, ignore it
let trainwreck = data.split_whitespace().collect::<Vec<_>>().join(" ");
if trainwreck.contains("under the terms of either license")
|| trainwreck.contains("at your option")
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
{
eprintln!(
" + {} (hash = 0x{:x}) detected as non-license file",
file.display(),
file_hash
);
continue;
}
}
bail!(
"failed to detect any license from {} (hash = 0x{:x}), \
please add a clarification",
Expand All @@ -282,15 +368,39 @@ fn write_attribution(
result.license.name,
result.score,
);
licenses.push(result.license.name);
if let Some(stated_license) = stated_license.as_ref() {
// If the package states a license, verify that the license we detected is a
// subset of that.
if !stated_license
.requirements()
.any(|er| er.req.license.id() == spdx::license_id(result.license.name))
tjkirch marked this conversation as resolved.
Show resolved Hide resolved
{
bail!(
"detected license \"{}\" from {} is not present in the license \
field \"{}\" for {}",
result.license.name,
file.display(),
stated_license,
name
);
}
} else {
licenses.push(result.license.name);
}
}
}
licenses.sort();
licenses.dedup();
let expression = licenses.join(" AND ");
eprintln!(" = {}", expression);

copy_files(out_dir, &files, &[])?;
expression

if let Some(stated_license) = stated_license {
stated_license.to_string()
} else {
licenses.sort();
licenses.dedup();
let expression = licenses.join(" AND ");
eprintln!(" = {}", expression);
expression
}
};

fs::create_dir_all(out_dir)?;
Expand Down
1 change: 1 addition & 0 deletions packages/release/release.spec
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Requires: %{_cross_os}updog
Requires: %{_cross_os}util-linux
Requires: %{_cross_os}preinit
Requires: %{_cross_os}wicked
Requires: %{_cross_os}workspaces
tjkirch marked this conversation as resolved.
Show resolved Hide resolved

%description
%{summary}.
Expand Down
6 changes: 6 additions & 0 deletions packages/workspaces/workspaces.spec
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ install -d %{buildroot}%{_cross_tmpfilesdir}
install -p -m 0644 %{S:200} %{buildroot}%{_cross_tmpfilesdir}/migration.conf
install -p -m 0644 %{S:201} %{buildroot}%{_cross_tmpfilesdir}/host-containers.conf

%cross_scan_attribution --clarify %{_builddir}/workspaces/clarify.toml \
cargo --offline --locked %{_builddir}/workspaces/Cargo.toml

%files
%{_cross_attribution_vendor_dir}

%files -n %{_cross_os}apiserver
%{_cross_bindir}/apiserver
%{_cross_unitdir}/apiserver.service
Expand Down
Loading