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"

Reply via email to