dynamo/.github/workflows/copyright-check.ps1

364 lines
11 KiB
PowerShell

# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set-strictmode -version latest
. "$(& git rev-parse --show-toplevel)/.github/workflows/common.ps1"
# == begin common.ps1 extensions ==
$date_key = '%%DATE%%'
$date_regex = '(?>(?>\d{4})-)?(?<year>\d{4})'
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$global:copyright_matchers = @(
@{
files = @('.containerfile', '.dockerignore', '.pbtxt', '.ps1', '.py', '.sh', '.toml', '.tpl', '.txt', '.yaml', '.yml', 'Dockerfile')
found_missing = $false
matches = @(
'# SPDX-FileCopyrightText: Copyright (c) ' + $date_key + ' NVIDIA CORPORATION & AFFILIATES. All rights reserved.'
'# SPDX-License-Identifier: Apache-2.0'
)
name = 'basic'
regex = $null
vertical_spacer = '#'
}
@{
files = @('.json')
found_missing = $false
matches = @(
'"copyright": ['
' "SPDX-FileCopyrightText: Copyright (c) ' + $date_key + ' NVIDIA CORPORATION & AFFILIATES. All rights reserved.",'
' "SPDX-License-Identifier: Apache-2.0",'
)
name = 'json'
regex = $null
vertical_spacer = $null
}
@{
files = @('.md')
found_missing = $false
matches = @(
'<!--'
'SPDX-FileCopyrightText: Copyright (c) ' + $date_key + ' NVIDIA CORPORATION & AFFILIATES. All rights reserved.'
'SPDX-License-Identifier: Apache-2.0'
)
name = 'markdown'
regex = $null
vertical_spacer = ''
}
@{
files = @('.proto', '.rs')
found_missing = $false
matches = @(
'// SPDX-FileCopyrightText: Copyright (c) ' + $date_key + ' NVIDIA CORPORATION & AFFILIATES. All rights reserved.'
'// SPDX-License-Identifier: Apache-2.0'
)
name = 'c-like'
regex = $null
vertical_spacer = '//'
}
)
$global:copyright_results = @{
failed_date = @()
failed_header = @()
passed = @()
skipped = @()
unsupported = @()
}
# === end common.ps1 extensions ===
$ignored_files = @('.clang-format', '.gitattributes', '.gitignore', '.gitkeep', '.patch', 'Cargo.lock', 'LICENSE', 'uv.lock', 'rust-toolchain.toml', 'codespell.txt', 'exclusions.txt')
write-debug "<copyright-check> ignored_files = ['$($ignored_files -join "','")']."
$ignored_paths = @('.github', '.mypy_cache', '.pytest_cache', 'lib/llm/tests/data/sample-models', 'lib/llm/tests/data/deepseek-v3.2')
write-debug "<copyright-check> ignored_paths = ['$($ignored_paths -join "','")']."
$ignored_types = @('.bat', '.gif', '.ico', '.ipynb', '.jpg', '.jpeg', '.patch', '.png', '.pyc', '.pyi', '.rst', '.zip', '.md', '.json')
write-debug "<copyright-check> ignored_types = ['$($ignored_types -join "', '")']."
$ignored_folders = @('.git', '__pycache__')
function is_ignored([string] $path) {
# write-debug "<copyright-check/is_ignored> path: \"${path}\"."
if (($null -eq $path) -or ($path.length -eq 0) -or ($path.endswith('/'))) {
write-debug "<copyright-check/is_ignored> ignored: true."
return $true
}
foreach ($ignored_path in $ignored_paths) {
if ($path.startswith($ignored_path)) {
write-debug "<copyright-check/is_ignored> ignored: true."
return $true
}
}
foreach ($ignored_extension in $ignored_types) {
if ($file.endswith($ignored_extension)) {
write-debug "<copyright-check/is_ignored> ignore = true."
return $true
}
}
foreach ($ignored_file in $ignored_files) {
if ($file.endswith($ignored_file)) {
write-debug "<copyright-check/is_ignored> ignore = true."
return $true
}
}
$normalized_path = normalize_path $file
foreach ($ignored_folder in $ignored_folders) {
if ($normalized_path -contains "/${ignored_folder}/") {
write-debug "<copyright-check/is_ignored> ignore = true."
return $true
}
}
if (-not(test-path "${normalized_path}" -pathtype 'Leaf')) {
write-debug "<copyright-check/is_ignored> ignore = true."
return $true
}
write-debug "<copyright-check/is_ignored> ignore = false."
return $false
}
function build_regex([object] $matcher) {
write-debug "<copyright-check/build_regex> matcher.name: $($matcher.name)."
$regex = ''
foreach ($match in $matcher.matches) {
$match = $match -replace '([\(\)\[\]\.\+\*\\])', '\$1'
$match = $match -replace '\s+', '\s+'
# Given the amount of inconsistency between using http and https, we'll just regex it away.
$match = $match -replace 'https?://', 'https?://'
# Replace the date matcher placeholder w/ the actual regex we'll need.
$match = $match -replace $date_key, $date_regex
$regex = "${regex}${match}[\n\r\s]+"
if ($null -ne $matcher.vertical_spacer) {
$regex = "${regex}(?>$($matcher.vertical_spacer)[\n\r\s]+)*"
}
}
write-debug "<copyright-check/build_regex> -> '${regex}'."
return $regex
}
function check_header([string] $path, [object] $matcher) {
write-debug "<copyright-check/check_header> path: ""${path}""."
write-debug "<copyright-check/check_header> matcher: ""$($matcher.name)""."
$command = "git log -1 --pretty=""%cs"" -- ${file}"
$output = invoke-expression $command | out-string
$output = $output.trim()
$last_modified = $output.substring(0, 4) -as [int]
write-debug "<copyright-check/check_header> last_modified: ${last_modified}."
if ($null -eq $matcher.regex) {
$matcher.regex = $(build_regex $matcher)
}
$regex = $matcher.regex
write-debug "<copyright-check/check_header> regex: ""${regex}""."
$contents = read_content $path
if (($null -eq $contents) -or ($contents.length -le 0)) {
$global:copyright_results.skipped += $file
write-detailed " [SKIP] ${file}" 'DarkGray'
return
}
if ($contents -match $regex) {
$capture_date = $Matches.year -as [int]
if ($capture_date -lt $last_modified) {
$global:copyright_results.failed_date += $file
write-error " [FAIL] Incorrect Date in Header: ${path} (${capture_date})"
}
else {
$global:copyright_results.passed += $file
write-normal " [PASS] ${file}"
}
}
else {
$global:copyright_results.failed_header += $file
write-error " [FAIL] Invalid/Missing Header: ${file}"
$matcher.found_missing = $true
}
}
function check_file([string] $file) {
write-debug "<copyright-check/check_file> file: ""${file}""."
$path = normalize_path $file
if (test-path $path -pathtype 'Leaf') {
write-debug "<copyright-check/check_file> path: ""${path}""."
$is_checked = $false
foreach ($matcher in $global:copyright_matchers)
{
foreach ($ext in $matcher.files) {
if ($path.endswith($ext)) {
check_header $path $matcher
$is_checked = $true
break
}
}
if ($is_checked) {
return
}
}
write-warning " [WARN] Unsupported: ${file}"
$global:copyright_results.unsupported += $file
}
}
$current_year = "$(get-date -format 'yyyy')" -as [int]
write-debug "<copyright-check> current_year = ${current_year}."
foreach ($file in $(git ls-tree -r --name-only HEAD)) {
$file = $file.trim()
write-debug "<copyright-check> file: ""${file}""."
if (is_ignored $file) {
write-detailed " [SKIP] ${file}" 'DarkGray'
$global:copyright_results.skipped += $file
continue
}
check_file $file
}
function generate_report() {
$reports_path = $env:NVBUILD_REPORTS_PATH
if ($null -eq $reports_path) {
return
}
if (-not (test-path $reports_path -pathtype 'Container')) {
if (test-path $reports_path -pathtype 'Leaf') {
return
}
new-item $reports_path -itemtype 'Directory' | out-null
}
write-debug "<copyright-check/generate_report> Generating check report."
$check_results = "<copyright-verification>`n"
if ($global:copyright_results.failed_header.count -gt 0) {
$check_results += " <failed reason=""Invalid or Missing Header"">`n"
foreach ($file in $global:copyright_results.failed_header) {
$check_results += " <file>${file}</file>`n"
}
$check_results += " </failed>`n"
}
if ($global:copyright_results.failed_date.count -gt 0) {
$check_results += " <failed reason=""Incorrect Date"">`n"
foreach ($file in $global:copyright_results.failed_date) {
$check_results += " <file>${file}</file>`n"
}
$check_results += " </failed>`n"
}
if ($global:copyright_results.unsupported.count -gt 0) {
$check_results += " <skipped reason=""Unsupported File Type"">`n"
foreach ($file in $global:copyright_results.unsupported) {
$check_results += " <file>${file}</file>`n"
}
$check_results += " </skipped>`n"
}
if ($global:copyright_results.passed.count -gt 0) {
$check_results += " <passed>`n"
foreach ($file in $global:copyright_results.passed) {
$check_results += " <file>${file}</file>`n"
}
$check_results += " </passed>`n"
}
if ($global:copyright_results.skipped.count -gt 0) {
$check_results += " <skipped reason=""Ignored by Path"">`n"
foreach ($file in $global:copyright_results.skipped) {
$check_results += " <file>${file}</file>`n"
}
$check_results += " </skipped>`n"
}
$check_results += "</copyright-verification>`n"
$output_path = "${reports_path}/copyright-check.xml"
write_content $check_results $output_path -overwrite
write-minimal ''
write-minimal "Copyright check report -> ${output_path}"
}
write-normal ''
$timer.Stop()
write-high "Pass: $($global:copyright_results.passed.count), Fail: $($global:copyright_results.failed_date.count + $global:copyright_results.failed_header.count)" -no_newline
if ($global:copyright_results.skipped.count -gt 0) {
write-high ", Skipped: $($global:copyright_results.skipped.count)" -no_newline
}
if ($global:copyright_results.unsupported.count -gt 0) {
write-high ", Unsupported: $($global:copyright_results.unsupported.count)" -no_newline
}
write-minimal " ($($timer.Elapsed.TotalSeconds.ToString("0.000")) seconds)" $global:colors.low -no_newline
write-minimal ''
if ($global:copyright_results.failed_header.count -gt 0) {
write-low ''
write-low 'Copyright checkers detected missing or invalid copyright headers:'
write-low ''
foreach ($matcher in $global:copyright_matchers) {
if ($matcher.found_missing) {
write-low " name: $($matcher.name)"
write-low " files: $($matcher.files -join ", ")"
write-low " pattern:`n $($matcher.regex)"
write-low ''
}
}
}
if (($global:copyright_results.failed_date.count -gt 0) -or ($global:copyright_results.failed_header.count -gt 0)) {
write-high 'Files out of compliance:'
# Final, end of output list of errors.
foreach ($path in $global:copyright_results.failed_header) {
write-error " [FAIL] invalid/missing header: ${path}"
}
foreach ($path in $global:copyright_results.failed_date) {
write-error " [FAIL] incorrect date: ${path}"
}
exit(-1)
}