diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e60a953470a43dd8c84b083e2a00ca108cec567e..ec9020ef6131a190dd522d0ac691162104d77a22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,12 @@ repos: entry: ./.pre-commit-hooks/kotlin-block-quotes.py language: python files: ^.*\.kt$ + - id: license-header-check + name: License Header Check + entry: ./.pre-commit-hooks/license-header.sh + language: system + files: ^.*$ + pass_filenames: false - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks rev: v1.6.1 hooks: diff --git a/.pre-commit-hooks/kotlin-block-quotes.py b/.pre-commit-hooks/kotlin-block-quotes.py index 8267c6f38ac457e46db7678dec194964cc33ede2..6e19553052958de3948291b723accc05a98bb5e3 100755 --- a/.pre-commit-hooks/kotlin-block-quotes.py +++ b/.pre-commit-hooks/kotlin-block-quotes.py @@ -1,4 +1,8 @@ #!/usr/bin/env python + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + # # Script for pre-commit that fixes Kotlin block quote indentation # for Smithy codegen, where the actual whitespace in the block quotes diff --git a/.pre-commit-hooks/license-header.sh b/.pre-commit-hooks/license-header.sh new file mode 100755 index 0000000000000000000000000000000000000000..324275d066d48160e27d0e624385f66381813c26 --- /dev/null +++ b/.pre-commit-hooks/license-header.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. +# + +cd "$(git rev-parse --show-toplevel)/tools/sdk-lints" && cargo run -- check --license diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/ServiceConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/ServiceConfigDecorator.kt index ec3d54097ef1c59c64fb3dd9e437bb0ab84b3b3c..68435ce3cb5418f8d8ca804ee75611d5b88f2df1 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/ServiceConfigDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/ServiceConfigDecorator.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + package software.amazon.smithy.rustsdk import software.amazon.smithy.rust.codegen.rustlang.Writable diff --git a/aws/sdk/test-services.py b/aws/sdk/test-services.py index 0ce6d22909c90343c95db340eaa2b7cdb07a78b1..4a08be58b2e8362a04510c278ac09252d3f40ff4 100644 --- a/aws/sdk/test-services.py +++ b/aws/sdk/test-services.py @@ -1,3 +1,6 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + """ Generate a list of services which have non-trivial unit tests @@ -8,8 +11,6 @@ This script generates output like `-p aws-sdk-s3 -p aws-sdk-dynamodb`. It is int cargo test $(python test-services.py) ``` """ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0. import os from pathlib import Path diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/lang/RustWriterTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/lang/RustWriterTest.kt index b5e87733d684d91f186fd4b5d5bee19584e6977a..758204c9a5668d95bb1d9ecf073daa340e567869 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/lang/RustWriterTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/lang/RustWriterTest.kt @@ -24,20 +24,10 @@ import software.amazon.smithy.rust.codegen.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.testutil.compileAndRun import software.amazon.smithy.rust.codegen.testutil.compileAndTest import software.amazon.smithy.rust.codegen.testutil.shouldCompile -import software.amazon.smithy.rust.codegen.testutil.shouldParseAsRust import software.amazon.smithy.rust.codegen.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.util.lookup -import software.amazon.smithy.rust.testutil.shouldMatchResource class RustWriterTest { - @Test - fun `empty file`() { - val sut = RustWriter.forModule("empty") - sut.toString().shouldParseAsRust() - sut.toString().shouldCompile() - sut.toString().shouldMatchResource(javaClass, "empty.rs") - } - @Test fun `inner modules correctly handle dependencies`() { val sut = RustWriter.forModule("parent") diff --git a/codegen/src/test/resources/software/amazon/smithy/rust/lang/empty.rs b/codegen/src/test/resources/software/amazon/smithy/rust/lang/empty.rs deleted file mode 100644 index ddd9e04a924d321e3a3e7241785c438d5ed268e4..0000000000000000000000000000000000000000 --- a/codegen/src/test/resources/software/amazon/smithy/rust/lang/empty.rs +++ /dev/null @@ -1 +0,0 @@ -// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. diff --git a/rust-runtime/aws-smithy-http-server/src/routing/request_spec.rs b/rust-runtime/aws-smithy-http-server/src/routing/request_spec.rs index 8e49d40986ed07abc79d64566ffe08bdffd02bb7..e2820f92bae2b86ed28383cef538341be5c57b7e 100644 --- a/rust-runtime/aws-smithy-http-server/src/routing/request_spec.rs +++ b/rust-runtime/aws-smithy-http-server/src/routing/request_spec.rs @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + use http::Request; use regex::Regex; diff --git a/rust-runtime/aws-smithy-http-server/src/test_helpers.rs b/rust-runtime/aws-smithy-http-server/src/test_helpers.rs index ad5b81a1d1ec02717542ec086f638082482d5240..2175a0e3478c706014c3098bf955f20f4248095b 100644 --- a/rust-runtime/aws-smithy-http-server/src/test_helpers.rs +++ b/rust-runtime/aws-smithy-http-server/src/test_helpers.rs @@ -1,2 +1,7 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + pub(crate) fn assert_send() {} pub(crate) fn assert_sync() {} diff --git a/tools/sdk-lints/src/copyright.rs b/tools/sdk-lints/src/copyright.rs new file mode 100644 index 0000000000000000000000000000000000000000..c146ef830ecf8d796f998ebb3aaf9a68374bb122 --- /dev/null +++ b/tools/sdk-lints/src/copyright.rs @@ -0,0 +1,87 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +use std::ffi::OsStr; +use std::fs; +use std::path::Path; + +use anyhow::{bail, Result}; + +const EXPECTED_CONTENTS: &[&str] = &[ + "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.", + "SPDX-License-Identifier: Apache-2.0.", +]; + +const NEEDS_HEADER: [&str; 5] = ["sh", "py", "rs", "kt", "ts"]; + +pub(crate) fn check_copyright_header(path: impl AsRef) -> Result<()> { + if !needs_copyright_header(path.as_ref()) { + return Ok(()); + } + let contents = match fs::read_to_string(path.as_ref()) { + Ok(contents) => contents, + Err(err) if format!("{}", err).contains("No such file or directory") => { + eprintln!("Note: {} does not exist", path.as_ref().display()); + return Ok(()); + } + Err(e) => return Err(e)?, + }; + if !has_copyright_header(&contents) { + bail!("{:?} is missing copyright header", path.as_ref()) + } + Ok(()) +} + +fn needs_copyright_header(path: &Path) -> bool { + let mut need_extensions = NEEDS_HEADER.iter().map(|s| OsStr::new(s)); + need_extensions.any(|extension| path.extension().unwrap_or_default() == extension) +} + +fn has_copyright_header(contents: &str) -> bool { + let mut expected = EXPECTED_CONTENTS.iter().peekable(); + // copyright header must be present in the first 10 lines + for line in contents.lines().take(10) { + match expected.peek() { + Some(next) => { + if line.contains(*next) { + let _ = expected.next(); + } + } + None => return true, + } + } + expected.peek().is_none() +} + +#[cfg(test)] +mod test { + use crate::copyright::has_copyright_header; + + #[test] + fn has_license_header() { + let valid = [ + "// something else\n# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0.", + "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: Apache-2.0.", + "/*\n* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n* SPDX-License-Identifier: Apache-2.0.\n */", + ]; + + let invalid = ["", "no license", "// something else\n# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: Apache-3.0."]; + + for license in valid { + assert!( + has_copyright_header(license), + "should be true: `{}`", + license + ); + } + for license in invalid { + assert!( + !has_copyright_header(license), + "should not be true: `{}`", + license + ); + } + } +} diff --git a/tools/sdk-lints/src/main.rs b/tools/sdk-lints/src/main.rs index ffa684ba53fd9b8b45c3d9c64c8db5c15080a028..e5e2de8c7a3d0878e173471226c6340f6955e78b 100644 --- a/tools/sdk-lints/src/main.rs +++ b/tools/sdk-lints/src/main.rs @@ -7,11 +7,13 @@ use crate::lint_cargo_toml::{check_crate_author, check_crate_license, check_docs use anyhow::{bail, Context, Result}; use clap::{App, Arg, SubCommand}; use lazy_static::lazy_static; +use std::env::set_current_dir; use std::path::{Path, PathBuf}; use std::process::Command; use std::{fs, io}; mod anchor; +mod copyright; mod lint_cargo_toml; fn load_repo_root() -> Result { @@ -23,8 +25,30 @@ fn load_repo_root() -> Result { Ok(PathBuf::from(String::from_utf8(output.stdout)?.trim())) } +fn load_vcs_files() -> Result> { + let tracked_files = Command::new("git") + .arg("ls-tree") + .arg("-r") + .arg("HEAD") + .arg("--name-only") + .current_dir(load_repo_root()?) + .output() + .context("couldn't load VCS tracked files")?; + let mut output = String::from_utf8(tracked_files.stdout)?; + let changed_files = Command::new("git") + .arg("diff") + .arg("--name-only") + .output()?; + output.push_str(std::str::from_utf8(changed_files.stdout.as_slice())?); + let files = output + .lines() + .map(|line| PathBuf::from(line.trim().to_string())); + Ok(files.collect()) +} + lazy_static! { static ref REPO_ROOT: PathBuf = load_repo_root().unwrap(); + static ref VCS_FILES: Vec = load_vcs_files().unwrap(); } fn repo_root() -> &'static Path { @@ -32,6 +56,7 @@ fn repo_root() -> &'static Path { } fn main() -> Result<()> { + set_current_dir(repo_root())?; let matches = clap_app().get_matches(); if let Some(subcommand) = matches.subcommand_matches("check") { let all = subcommand.is_present("all"); @@ -45,6 +70,9 @@ fn main() -> Result<()> { if subcommand.is_present("docsrs-metadata") || all { check_docsrs_metadata()?; } + if subcommand.is_present("license") || all { + check_license_header()?; + } } else if let Some(subcommand) = matches.subcommand_matches("fix") { let all = subcommand.is_present("all"); if subcommand.is_present("readme") || all { @@ -81,6 +109,12 @@ fn clap_app() -> App<'static, 'static> { .required(false) .long("docsrs-metadata"), ) + .arg( + Arg::with_name("license") + .takes_value(false) + .required(false) + .long("license"), + ) .arg( Arg::with_name("all") .takes_value(false) @@ -149,7 +183,7 @@ fn check_authors() -> Result<()> { .with_context(|| format!("Error in {:?}", local_path)); if let Err(e) = result { failed += 1; - eprintln!("{}", e); + eprintln!("{:?}", e); } } if failed > 0 { @@ -160,6 +194,22 @@ fn check_authors() -> Result<()> { } } +fn check_license_header() -> Result<()> { + let mut failed = 0; + for license in VCS_FILES.iter() { + let result = copyright::check_copyright_header(license); + if let Err(e) = result { + failed += 1; + eprintln!("{:?}", e); + } + } + if failed > 0 { + bail!("{} files missing license headers", failed) + } + eprintln!("All files had correct license headers"); + Ok(()) +} + /// Check that all crates have correct licensing fn check_license() -> Result<()> { let mut failed = 0; @@ -263,9 +313,3 @@ fn fix_readme(path: impl AsRef) -> Result { fs::write(path.as_ref(), contents)?; Ok(updated) } - -// TODO: -// fn check_authors() -// fn check_docs_all_features() -// fn check_doc_targets() -// fn check_license()