This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-3.5 by this push:
     new a187555a8b9e [SPARK-58167][SPARK-57393][3.5][BUILD] Build: PySpark and 
SparkR source distributions are missing LICENSE and NOTICE files
a187555a8b9e is described below

commit a187555a8b9eb53538cdf99c7eddf18b6398e309
Author: Holden Karau <[email protected]>
AuthorDate: Wed Aug 12 11:47:51 2026 -0700

    [SPARK-58167][SPARK-57393][3.5][BUILD] Build: PySpark and SparkR source 
distributions are missing LICENSE and NOTICE files
    
    Changes proposed:
    
    Backport of 
[[SPARK-57393](https://issues.apache.org/jira/browse/SPARK-57393)] Build: 
PySpark and SparkR source distributions are missing LICENSE and NOTICE files
    Make the PySpark and SparkR source distributions include top-level LICENSE 
and NOTICE files:
    
    - Add include LICENSE / include NOTICE to python/MANIFEST.in.
    - In dev/make-distribution.sh, copy LICENSE/NOTICE into python/ and R/pkg/ 
before building the sdists (with an EXIT trap to clean them up).
    - The classic pyspark sdist bundles the assembly jars, so it ships the 
binary license variants (LICENSE-binary/NOTICE-binary plus the full 
licenses-binary set), mirroring the binary distribution. The pyspark_connect, 
pyspark_client, and SparkR artifacts bundle no jars, so they ship the plain 
source LICENSE/NOTICE. packaging/classic/setup.py falls back to licenses/ when 
licenses-binary/ is absent (RELEASE-mode builds).
    - During the SparkR build, + file LICENSE is added to DESCRIPTION 
temporarily (restored after the build) so R CMD check --as-cran does not warn 
that the bundled LICENSE is not mentioned. The committed DESCRIPTION is 
unchanged, so SparkR CI is unaffected. The "Non-standard file/directory found 
at top level: 'NOTICE'" NOTE cannot be silenced this way and is expected in 
release-build logs.
    - Add tar-listing regression guards after the sdist and SparkR builds that 
fail the release build if LICENSE or NOTICE ever goes missing again, instead of 
relying on an RC vote to catch it.
    
    Why are they needed?
    
    ASF release policy requires every distributed artifact to include LICENSE 
and NOTICE. The pyspark, pyspark_connect, pyspark_client, and SparkR source 
tarballs currently don't, which was raised as a -1 during the Spark 3.5.9 RC1
    
    No
    
    Built the source distributions locally and confirmed each contains LICENSE 
and NOTICE at the package root
    
    Closes #57427 from holdenk/SPARK-58167-add-license-files-r2.
    
    Lead-authored-by: Holden Karau <[email protected]>
    Co-authored-by: Claude <Holden's Claude Robot [email protected]>
    Co-authored-by: Holden Karau <[email protected]>
    Co-authored-by: Huaxin Gao <[email protected]>
    Signed-off-by: Holden Karau <[email protected]>
---
 R/.gitignore             |  4 +++
 dev/make-distribution.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++++
 python/.gitignore        |  3 +++
 python/MANIFEST.in       |  8 +++++-
 python/setup.py          | 11 +++++++--
 5 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/R/.gitignore b/R/.gitignore
index c98504ab0778..693b2b7c6917 100644
--- a/R/.gitignore
+++ b/R/.gitignore
@@ -6,3 +6,7 @@ pkg/man
 pkg/html
 SparkR.Rcheck/
 SparkR_*.tar.gz
+# Transient copies made by dev/make-distribution.sh while building the package.
+pkg/LICENSE
+pkg/NOTICE
+DESCRIPTION.orig
diff --git a/dev/make-distribution.sh b/dev/make-distribution.sh
index ef7c010e930a..1b6210a6570c 100755
--- a/dev/make-distribution.sh
+++ b/dev/make-distribution.sh
@@ -32,6 +32,21 @@ set -x
 SPARK_HOME="$(cd "`dirname "$0"`/.."; pwd)"
 DISTDIR="$SPARK_HOME/dist"
 
+# The Apache LICENSE and NOTICE are copied into the Python and R package
+# directories below so they are bundled into the source distributions. Remove
+# them on exit so a failed build does not leave stray files behind.
+function cleanup_dist_license_files {
+  rm -f "$SPARK_HOME/python/LICENSE" "$SPARK_HOME/python/NOTICE" \
+        "$SPARK_HOME/R/pkg/LICENSE" "$SPARK_HOME/R/pkg/NOTICE"
+  # Restore the SparkR DESCRIPTION if a release build patched it in place (see
+  # the R packaging section). Guards against an interrupted build leaving the
+  # tracked DESCRIPTION modified.
+  if [ -f "$SPARK_HOME/R/DESCRIPTION.orig" ]; then
+    mv -f "$SPARK_HOME/R/DESCRIPTION.orig" "$SPARK_HOME/R/pkg/DESCRIPTION"
+  fi
+}
+trap cleanup_dist_license_files EXIT
+
 MAKE_TGZ=false
 MAKE_PIP=false
 MAKE_R=false
@@ -240,7 +255,31 @@ if [ "$MAKE_PIP" == "true" ]; then
   pushd "$SPARK_HOME/python" > /dev/null
   # Delete the egg info file if it exists, this can cache older setup files.
   rm -rf pyspark.egg-info || echo "No existing egg info file, skipping 
deletion"
+  # Ship the Apache LICENSE and NOTICE inside the PySpark source distribution
+  # (see MANIFEST.in). These are removed again after the sdist is built.
+  #
+  # The pyspark sdist bundles the assembly jars, so it ships the binary
+  # LICENSE/NOTICE that enumerate the bundled third-party jars' licenses,
+  # mirroring the binary distribution above.
+  if [ -e "$SPARK_HOME/LICENSE-binary" ]; then
+    cp "$SPARK_HOME/LICENSE-binary" LICENSE
+    cp "$SPARK_HOME/NOTICE-binary" NOTICE
+  else
+    cp "$SPARK_HOME/LICENSE" LICENSE
+    cp "$SPARK_HOME/NOTICE" NOTICE
+  fi
   python3 setup.py sdist
+  # Guard against regressions: every PySpark sdist must contain LICENSE and 
NOTICE
+  # at the package root. The missing files were only caught by a Spark 4.2.0 
RC1
+  # vote -1 (SPARK-57393); fail the release build here instead of at vote time.
+  for f in dist/pyspark*.tar.gz; do
+    listing=$(tar tzf "$f")
+    for required in LICENSE NOTICE; do
+      grep -qE "^[^/]+/$required\$" <<< "$listing" || \
+        { echo "ERROR: $f is missing $required at the package root"; exit 1; }
+    done
+  done
+  rm -f LICENSE NOTICE
   popd > /dev/null
 else
   echo "Skipping building python distribution package"
@@ -251,9 +290,33 @@ if [ "$MAKE_R" == "true" ]; then
   echo "Building R source package"
   R_PACKAGE_VERSION=`grep Version "$SPARK_HOME/R/pkg/DESCRIPTION" | awk 
'{print $NF}'`
   pushd "$SPARK_HOME/R" > /dev/null
+  # Ship the Apache LICENSE and NOTICE inside the SparkR source package. These
+  # are removed again after the package is built.
+  cp "$SPARK_HOME/LICENSE" pkg/LICENSE
+  cp "$SPARK_HOME/NOTICE" pkg/NOTICE
+  # Reference the bundled LICENSE from DESCRIPTION so `R CMD check --as-cran` 
does
+  # not emit "File LICENSE is not mentioned in the DESCRIPTION file". The 
committed
+  # DESCRIPTION is left untouched because SparkR CI runs check-cran.sh without 
the
+  # LICENSE file present; this edit is transient and restored after the build 
(and
+  # by the EXIT trap on failure). The backup lives outside pkg/ so R CMD check 
does
+  # not flag it as a non-standard file. NOTE: the "Non-standard file 'NOTICE'" 
note
+  # cannot be silenced this way and is expected.
+  cp pkg/DESCRIPTION "$SPARK_HOME/R/DESCRIPTION.orig"
+  sed 's/^License: Apache License (== 2.0)$/License: Apache License (== 2.0) + 
file LICENSE/' \
+    "$SPARK_HOME/R/DESCRIPTION.orig" > pkg/DESCRIPTION
   # Build source package and run full checks
   # Do not source the check-cran.sh - it should be run from where it is for it 
to set SPARK_HOME
   NO_TESTS=1 "$SPARK_HOME/R/check-cran.sh"
+  mv -f "$SPARK_HOME/R/DESCRIPTION.orig" pkg/DESCRIPTION
+  rm -f pkg/LICENSE pkg/NOTICE
+
+  # Guard against regressions: the SparkR source package must contain LICENSE 
and
+  # NOTICE at the package root (SPARK-57393).
+  listing=$(tar tzf "SparkR_$R_PACKAGE_VERSION.tar.gz")
+  for required in LICENSE NOTICE; do
+    grep -qE "^[^/]+/$required\$" <<< "$listing" || \
+      { echo "ERROR: SparkR source package is missing $required"; exit 1; }
+  done
 
   # Move R source package to match the Spark release version if the versions 
are not the same.
   # NOTE(shivaram): `mv` throws an error on Linux if source and destination 
are same file
diff --git a/python/.gitignore b/python/.gitignore
index 52128cf844a7..8ebf8df2b14d 100644
--- a/python/.gitignore
+++ b/python/.gitignore
@@ -3,3 +3,6 @@ docs/_build/
 pyspark.egg-info
 build/
 dist/
+# Transient copies made by dev/make-distribution.sh while building the sdists.
+/LICENSE
+/NOTICE
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
index 862d62b1d3b2..5faccc246cf0 100644
--- a/python/MANIFEST.in
+++ b/python/MANIFEST.in
@@ -19,8 +19,14 @@ recursive-include deps/jars *.jar
 graft deps/bin
 recursive-include deps/sbin spark-config.sh spark-daemon.sh 
start-history-server.sh stop-history-server.sh
 recursive-include deps/data *.data *.txt
-recursive-include deps/licenses *.txt
+graft deps/licenses
 recursive-include deps/examples *.py
 recursive-include lib *.zip
 recursive-include pyspark *.pyi py.typed
 include README.md
+include LICENSE
+include NOTICE
+
+# Note that these commands are processed in the order they appear, so keep
+# this exclude at the end.
+global-exclude *.py[cod] __pycache__ .DS_Store
diff --git a/python/setup.py b/python/setup.py
index 16ae358caec6..4b7023b855db 100755
--- a/python/setup.py
+++ b/python/setup.py
@@ -85,7 +85,14 @@ EXAMPLES_PATH = os.path.join(SPARK_HOME, 
"examples/src/main/python")
 SCRIPTS_PATH = os.path.join(SPARK_HOME, "bin")
 USER_SCRIPTS_PATH = os.path.join(SPARK_HOME, "sbin")
 DATA_PATH = os.path.join(SPARK_HOME, "data")
-LICENSES_PATH = os.path.join(SPARK_HOME, "licenses")
+# The classic PySpark package bundles the assembly jars, so it ships the binary
+# license texts (licenses-binary), which enumerate those jars' licenses, 
mirroring
+# the binary distribution. The connect/client packages bundle no jars.
+LICENSES_PATH = os.path.join(SPARK_HOME, "licenses-binary")
+if not os.path.isdir(LICENSES_PATH):
+    # In a binary release dist (the RELEASE mode below), the binary license 
texts
+    # were already copied to licenses/ (see dev/make-distribution.sh).
+    LICENSES_PATH = os.path.join(SPARK_HOME, "licenses")
 
 SCRIPTS_TARGET = os.path.join(TEMP_PATH, "bin")
 USER_SCRIPTS_TARGET = os.path.join(TEMP_PATH, "sbin")
@@ -298,7 +305,7 @@ try:
             ],
             "pyspark.python.lib": ["*.zip"],
             "pyspark.data": ["*.txt", "*.data"],
-            "pyspark.licenses": ["*.txt"],
+            "pyspark.licenses": ["*"],
             "pyspark.examples.src.main.python": ["*.py", "*/*.py"],
         },
         scripts=scripts,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to