This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 9ab0517168 [#13255] improvement(release): Check file references not
prefixed with ./ in LICENSE and NOTICE (#13256)
9ab0517168 is described below
commit 9ab051716821862a6468dc4478a3e98e7ec5c04c
Author: Bharath Krishna <[email protected]>
AuthorDate: Wed Sep 16 23:08:12 2026 -0700
[#13255] improvement(release): Check file references not prefixed with ./
in LICENSE and NOTICE (#13256)
### What changes were proposed in this pull request?
Check source LICENSE and NOTICE file references, including paths written
without `./`. Explicit `./` paths are always validated; unprefixed paths
are validated when their first segment is an existing source directory.
Remove URLs before extracting references, handle sentence punctuation,
and require files rather than directories. Fail for missing or
unreadable documents and report unresolved references with line numbers
and candidate files.
Add a self-contained Bash regression suite and run it in the existing
build workflow.
### Why are the changes needed?
The previous checker silently passed the broken `web/LICENSE` reference
and a missing LICENSE document. The initial implementation also skipped
explicit paths when their top-level directory was missing and treated
sentence periods or URL fragments as filename characters.
Fix: #13255. Complements #13254; release epic: #12755.
### Does this PR introduce _any_ user-facing change?
Release-tool diagnostics improve; no product API changes. Scope remains
source LICENSE/NOTICE. Binary documents describe a different layout and
are not checked. Unprefixed unknown-root paths and bare filenames are
not recognized; this is a sanity check, not a full license audit.
### How was this patch tested?
```bash
bash dev/release/test-check-license.sh
shellcheck dev/release/check-license.sh dev/release/test-check-license.sh
bash -n dev/release/check-license.sh dev/release/test-check-license.sh
```
All 13 regression cases pass, including execution under macOS Bash 3.2.
The original PR script fails the regression suite. The corrected checker
detects the existing broken Web UI reference in the uncorrected source
tree and passes with #13254's corrected source LICENSE/NOTICE.
`./gradlew spotlessApply` and `git diff --check` pass.
---
.github/workflows/build.yml | 2 +
dev/release/check-license.sh | 89 ++++++++++++++++++++--------
dev/release/test-check-license.sh | 118 ++++++++++++++++++++++++++++++++++++++
3 files changed, 185 insertions(+), 24 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dd814543b7..1ad49fbac5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,6 +24,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ - name: Test source license-reference checker
+ run: bash dev/release/test-check-license.sh
- name: Disable Git automatic maintenance
run: git config --local maintenance.auto false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d
diff --git a/dev/release/check-license.sh b/dev/release/check-license.sh
index 34838fa1f9..9c76448359 100755
--- a/dev/release/check-license.sh
+++ b/dev/release/check-license.sh
@@ -17,44 +17,85 @@
# limitations under the License.
#
-# This script checks if all the files mentioned in the LICENSE file are
present in the project.
-# Besides highlighting the missing files, it will display possible locations
for any new files.
-# It adds text like [NOT FOUND] --> "new/path/to/the/file" in the end of the
line where the file is missing.
-# With the output of this script, you can update the LICENSE file with the
correct file paths.
+# This script checks that every file referenced by the LICENSE and NOTICE
files is
+# present in the project, and reports the ones that are not. For each missing
file it
+# prints the referencing line and any candidate locations found elsewhere in
the tree,
+# so the reference can be corrected.
+#
+# Explicit ./ references are always checked. Unprefixed paths are checked when
their
+# first segment is an existing project directory. URLs are ignored, as are
unprefixed
+# external paths such as META-INF/NOTICE. This is a source-reference sanity
check, not
+# a complete license audit; use ./ for references whose top-level directory
may be missing.
+#
+# The LICENSE.bin and NOTICE.bin variants are deliberately not checked: their
paths
+# describe the layout of the binary package assembled by build.gradle.kts
rather than
+# the source tree, so resolving them here would report failures that are not
real.
FAILED=0
-PROJECT_ROOT=${1:-$(dirname $(dirname $(dirname $(readlink -f "$0"))))}
-LICENSE_FILE=$(cat "$PROJECT_ROOT"/LICENSE)
+PROJECT_ROOT=${1:-$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")")}
cd "$PROJECT_ROOT" || exit 1
-while IFS= read -r line; do
- echo -n "$line"
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+RESET='\033[0m'
- line=$(echo "$line" | xargs)
- # check if the line is a file
- if [[ "$line" == "./"* && "$line" == *"."* ]]; then
- line=$(echo "$line" | cut -c 3-)
+check_document() {
+ local document="$1"
+
+ if [ ! -f "$document" ]; then
+ echo -e "${RED}$document not found in $PROJECT_ROOT${RESET}"
+ FAILED=1
+ return
+ fi
+
+ if [ ! -r "$document" ]; then
+ echo -e "${RED}$document is not readable in $PROJECT_ROOT${RESET}"
+ FAILED=1
+ return
+ fi
+
+ local line_number=0
+ local line token candidates candidate
+ # the '|| [ -n "$line" ]' guard keeps the last line when the file has no
trailing newline
+ while IFS= read -r line || [ -n "$line" ]; do
+ line_number=$((line_number + 1))
+
+ while IFS= read -r token; do
+ # A period terminating a sentence is not part of a file reference.
+ token=${token%.}
+ if [[ "$token" != ./* ]] && [ ! -d "${token%%/*}" ]; then
+ continue
+ fi
+ token=${token#./}
+ [ -f "$token" ] && continue
- # check if the file does not exists
- if [ ! -f "$line" ]; then
FAILED=1
- echo -n -e " \033[0;31m[NOT FOUND]\033[0m --> "
+ echo -e "${RED}[NOT FOUND]${RESET} $document:$line_number --> $token"
+ echo " $line"
- file_name=$(basename "$line")
# scan for the file name in the project root excluding the '.gradle'
directory
- # and print the new file path if found in the project
- find . -type f -not -path '*/\.gradle/*' -name "$file_name" | tr -d '\n'
- fi
- fi
- echo
+ # and print any candidate locations found
+ candidates=$(find . -type f -not -path '*/\.gradle/*' -name "$(basename
"$token")")
+ if [ -n "$candidates" ]; then
+ echo " candidates:"
+ while IFS= read -r candidate; do
+ echo " $candidate"
+ done <<< "$candidates"
+ fi
+ done < <(printf '%s\n' "$line" |
+ sed -E 's@[[:alpha:]][[:alnum:]+.-]*://[^[:space:]<>]+@@g' |
+ grep -oE '[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)+')
+ done < "$document"
+}
-done <<< "$LICENSE_FILE"
+check_document LICENSE
+check_document NOTICE
# check if any file is missing
if [ $FAILED -ne 0 ]; then
- echo -e "\033[0;31mSome files listed in the LICENSE file are missing.
\033[0m"
+ echo -e "${RED}Some files referenced by the LICENSE or NOTICE files are
missing.${RESET}"
exit 1
fi
-echo -e "\033[0;32mAll files listed in the LICENSE file are present. \033[0m"
+echo -e "${GREEN}All files referenced by the LICENSE and NOTICE files are
present.${RESET}"
diff --git a/dev/release/test-check-license.sh
b/dev/release/test-check-license.sh
new file mode 100644
index 0000000000..62f173248c
--- /dev/null
+++ b/dev/release/test-check-license.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+# Run with: bash dev/release/test-check-license.sh
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
+CHECKER="$SCRIPT_DIR/check-license.sh"
+TEST_ROOT=$(mktemp -d)
+trap 'rm -rf "$TEST_ROOT"' EXIT
+PROJECT_ROOT="$TEST_ROOT/project with spaces"
+mkdir -p "$PROJECT_ROOT/web" "$PROJECT_ROOT/licenses"
"$PROJECT_ROOT/alternative"
+touch "$PROJECT_ROOT/web/LICENSE" "$PROJECT_ROOT/web/NOTICE"
+touch "$PROJECT_ROOT/licenses/example.txt"
"$PROJECT_ROOT/alternative/missing.txt"
+
+check_result() {
+ local expected_status="$1" expected_output="$2" status=0
+ bash "$CHECKER" "$PROJECT_ROOT" > "$TEST_ROOT/output" 2>&1 || status=$?
+ if [ "$status" -ne "$expected_status" ] || ! grep -Fq "$expected_output"
"$TEST_ROOT/output"; then
+ echo "FAIL: $CASE (expected exit $expected_status, got $status)"
+ cat "$TEST_ROOT/output"
+ exit 1
+ fi
+ echo "PASS: $CASE"
+}
+
+CASE='valid explicit and prose references, punctuation and multiple paths'
+cat > "$PROJECT_ROOT/LICENSE" <<'EOF'
+See web/LICENSE, (web/NOTICE); `licenses/example.txt`.
+See web/NOTICE.
+./licenses/example.txt
+EOF
+printf '%s' 'See web/NOTICE' > "$PROJECT_ROOT/NOTICE"
+check_result 0 'All files referenced'
+
+CASE='missing unprefixed file in prose'
+printf '%s\n' 'See web/missing.txt' > "$PROJECT_ROOT/LICENSE"
+check_result 1 'LICENSE:1 --> web/missing.txt'
+grep -Fq './alternative/missing.txt' "$TEST_ROOT/output"
+
+CASE='explicit reference with missing top-level directory'
+printf '%s\n' './absent/LICENSE.txt' > "$PROJECT_ROOT/LICENSE"
+check_result 1 'LICENSE:1 --> absent/LICENSE.txt'
+
+CASE='missing reference on final line without newline'
+printf '%s' './web/missing.txt' > "$PROJECT_ROOT/LICENSE"
+check_result 1 'LICENSE:1 --> web/missing.txt'
+
+CASE='missing NOTICE reference and line number'
+printf '%s\n' 'No references' > "$PROJECT_ROOT/LICENSE"
+printf '%s\n' 'First line' 'See web/missing.txt.' > "$PROJECT_ROOT/NOTICE"
+check_result 1 'NOTICE:2 --> web/missing.txt'
+
+CASE='directories are not files'
+printf '%s\n' './web' > "$PROJECT_ROOT/LICENSE"
+: > "$PROJECT_ROOT/NOTICE"
+check_result 1 'LICENSE:1 --> web'
+
+CASE='URLs and external paths are not source references'
+cat > "$PROJECT_ROOT/LICENSE" <<'EOF'
+https://web/missing.txt https://example.org/web/missing.txt
+(https://web/missing.txt) https://example.org/?path=web/missing.txt
+ftp://licenses/missing.txt file:///web/missing.txt
+META-INF/NOTICE org.example.Class Kyligence/kylinpy
+EOF
+check_result 0 'All files referenced'
+
+CASE='local reference after URL is still checked'
+printf '%s\n' 'https://web/remote.txt and web/missing.txt' >
"$PROJECT_ROOT/LICENSE"
+check_result 1 'LICENSE:1 --> web/missing.txt'
+
+CASE='binary package references remain out of scope'
+: > "$PROJECT_ROOT/LICENSE"
+printf '%s\n' './absent/binary.txt' > "$PROJECT_ROOT/LICENSE.bin"
+printf '%s\n' './absent/binary.txt' > "$PROJECT_ROOT/NOTICE.bin"
+check_result 0 'All files referenced'
+
+CASE='missing LICENSE document'
+rm "$PROJECT_ROOT/LICENSE"
+check_result 1 'LICENSE not found'
+
+CASE='missing NOTICE document'
+touch "$PROJECT_ROOT/LICENSE"
+rm "$PROJECT_ROOT/NOTICE"
+check_result 1 'NOTICE not found'
+
+CASE='unreadable source document'
+touch "$PROJECT_ROOT/NOTICE"
+chmod 000 "$PROJECT_ROOT/NOTICE"
+if [ -r "$PROJECT_ROOT/NOTICE" ]; then
+ echo "SKIP: $CASE (current user can read mode-000 files)"
+else
+ check_result 1 'NOTICE is not readable'
+fi
+chmod 600 "$PROJECT_ROOT/NOTICE"
+
+CASE='default project root resolved relative to script'
+mkdir -p "$PROJECT_ROOT/dev/release"
+cp "$CHECKER" "$PROJECT_ROOT/dev/release/check-license.sh"
+bash "$PROJECT_ROOT/dev/release/check-license.sh" > "$TEST_ROOT/output" 2>&1
+grep -Fq 'All files referenced' "$TEST_ROOT/output"
+echo "PASS: $CASE"