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

bobbai00 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 4be9baeaa2 chore(build): add customized LICENSE to jar META-INF and 
dist zips (#4367)
4be9baeaa2 is described below

commit 4be9baeaa29342e8c2a48d90e3491a98dc76fb7a
Author: Jiadong Bai <[email protected]>
AuthorDate: Fri Apr 17 14:04:08 2026 -0700

    chore(build): add customized LICENSE to jar META-INF and dist zips (#4367)
    
    ### What changes were proposed in this PR?
    
    This PR ensures that:
    - Each jar's `LICENSE` describes only what is in that jar
    - For `workflow-operator` jar: the LICENSE includes Apache 2.0 and 3rd
    party code `mbknor-jackson-jsonschema` MIT attribution and MIT license.
      - All other jars: the LICENSE is Apache 2.0 only
    - the zips produced by `sbt dist` include LICENSE/NOTICE/DISCLAIMER at
    the top level
    
    
    
    ### Any related issues, documentation, discussions?
    
    Closes #4394
    
    ### How was this PR tested?
    
    - Built dist zips and inspected jar META-INF/LICENSE content
    - Verified all Texera module jars are thin jars (no dependency classes
    merged in)
    - Confirmed `workflow-operator` is the only jar with non-Apache classes
    (`com/kjetland`)
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Co-authored using: Claude Code (Claude Opus 4.6)
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
    Co-authored-by: Xinyuan Lin <[email protected]>
---
 .licenserc.yaml                                    |   1 -
 bin/access-control-service.dockerfile              |   3 -
 bin/computing-unit-master.dockerfile               |   3 -
 bin/computing-unit-worker.dockerfile               |   3 -
 bin/config-service.dockerfile                      |   3 -
 bin/file-service.dockerfile                        |   3 -
 bin/texera-web-application.dockerfile              |   3 -
 bin/workflow-compiling-service.dockerfile          |   3 -
 ...flow-computing-unit-managing-service.dockerfile |   3 -
 build.sbt                                          |  31 ++---
 project/AddMetaInfLicenseFiles.scala               | 142 +++++++++++++++++++++
 11 files changed, 155 insertions(+), 43 deletions(-)

diff --git a/.licenserc.yaml b/.licenserc.yaml
index 6c07e6b008..a521c0be3b 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -11,7 +11,6 @@ header:
     - '**/*.json'
     - '**/*.jsonl'
     - 'DESCRIPTION'
-    - 'DISCLAIMER'
     - 'DISCLAIMER-WIP'
     - 'LICENSE'
     - 'NOTICE'
diff --git a/bin/access-control-service.dockerfile 
b/bin/access-control-service.dockerfile
index 0a7866a0c9..208fb4f40c 100644
--- a/bin/access-control-service.dockerfile
+++ b/bin/access-control-service.dockerfile
@@ -51,9 +51,6 @@ COPY --from=build /texera/.git /texera/.git
 COPY --from=build /texera/target/access-control-service* /texera/
 # Copy resources directories from build phase
 COPY --from=build /texera/access-control-service/src/main/resources 
/texera/access-control-service/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/access-control-service"]
 
 EXPOSE 9096
\ No newline at end of file
diff --git a/bin/computing-unit-master.dockerfile 
b/bin/computing-unit-master.dockerfile
index fa079558f4..ff0fbcff25 100644
--- a/bin/computing-unit-master.dockerfile
+++ b/bin/computing-unit-master.dockerfile
@@ -69,9 +69,6 @@ COPY --from=build /texera/common/config/src/main/resources 
/texera/amber/common/
 COPY --from=build /texera/amber/src/main/resources 
/texera/amber/src/main/resources
 # Copy code for python UDF
 COPY --from=build /texera/amber/src/main/python /texera/amber/src/main/python
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/computing-unit-master"]
 
 EXPOSE 8085
diff --git a/bin/computing-unit-worker.dockerfile 
b/bin/computing-unit-worker.dockerfile
index 9fd013f384..b0a1b9f2af 100644
--- a/bin/computing-unit-worker.dockerfile
+++ b/bin/computing-unit-worker.dockerfile
@@ -68,9 +68,6 @@ COPY --from=build /texera/amber/target/amber-* /texera/amber/
 # Copy resources directories from build phase
 COPY --from=build /texera/amber/src/main/resources 
/texera/amber/src/main/resources
 COPY --from=build /texera/common/config/src/main/resources 
/texera/amber/common/config/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/computing-unit-worker"]
 
 EXPOSE 8085
\ No newline at end of file
diff --git a/bin/config-service.dockerfile b/bin/config-service.dockerfile
index f3abbcc617..a7665eb067 100644
--- a/bin/config-service.dockerfile
+++ b/bin/config-service.dockerfile
@@ -52,9 +52,6 @@ COPY --from=build /texera/target/config-service-* /texera/
 # Copy resources directories from build phase
 COPY --from=build /texera/common/config/src/main/resources 
/texera/common/config/src/main/resources
 COPY --from=build /texera/config-service/src/main/resources 
/texera/config-service/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/config-service"]
 
 EXPOSE 9094
\ No newline at end of file
diff --git a/bin/file-service.dockerfile b/bin/file-service.dockerfile
index 9663818ccd..88bdf1c6b0 100644
--- a/bin/file-service.dockerfile
+++ b/bin/file-service.dockerfile
@@ -51,9 +51,6 @@ COPY --from=build /texera/target/file-service-* /texera/
 # Copy resources directories from build phase
 COPY --from=build /texera/common/config/src/main/resources 
/texera/common/config/src/main/resources
 COPY --from=build /texera/file-service/src/main/resources 
/texera/file-service/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/file-service"]
 
 EXPOSE 9092
\ No newline at end of file
diff --git a/bin/texera-web-application.dockerfile 
b/bin/texera-web-application.dockerfile
index a3bb2d85e3..fec179d8d2 100644
--- a/bin/texera-web-application.dockerfile
+++ b/bin/texera-web-application.dockerfile
@@ -67,9 +67,6 @@ COPY --from=build /texera/amber/target/amber-* /texera/amber/
 # Copy resources directories from build phase
 COPY --from=build /texera/amber/src/main/resources 
/texera/amber/src/main/resources
 COPY --from=build /texera/common/config/src/main/resources 
/texera/amber/common/config/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/texera-web-application"]
 
 EXPOSE 8080
\ No newline at end of file
diff --git a/bin/workflow-compiling-service.dockerfile 
b/bin/workflow-compiling-service.dockerfile
index 291d2d52e2..440dce2777 100644
--- a/bin/workflow-compiling-service.dockerfile
+++ b/bin/workflow-compiling-service.dockerfile
@@ -52,9 +52,6 @@ COPY --from=build /texera/target/workflow-compiling-service-* 
/texera/
 # Copy resources directories from build phase
 COPY --from=build /texera/common/config/src/main/resources 
/texera/common/config/src/main/resources
 COPY --from=build /texera/workflow-compiling-service/src/main/resources 
/texera/workflow-compiling-service/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/workflow-compiling-service"]
 
 EXPOSE 9090
\ No newline at end of file
diff --git a/bin/workflow-computing-unit-managing-service.dockerfile 
b/bin/workflow-computing-unit-managing-service.dockerfile
index 0f0739d60c..e11dacc046 100644
--- a/bin/workflow-computing-unit-managing-service.dockerfile
+++ b/bin/workflow-computing-unit-managing-service.dockerfile
@@ -52,9 +52,6 @@ COPY --from=build 
/texera/target/computing-unit-managing-service-* /texera/
 # Copy resources directories from build phase
 COPY --from=build /texera/common/config/src/main/resources 
/texera/common/config/src/main/resources
 COPY --from=build /texera/computing-unit-managing-service/src/main/resources 
/texera/computing-unit-managing-service/src/main/resources
-# Copy ASF licensing files
-COPY --from=build /texera/LICENSE /texera/NOTICE /texera/DISCLAIMER-WIP 
/texera/
-
 CMD ["bin/computing-unit-managing-service"]
 
 EXPOSE 8888
\ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 6673408561..8e5ffd02cc 100644
--- a/build.sbt
+++ b/build.sbt
@@ -15,23 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
-// Copy LICENSE, NOTICE, and DISCLAIMER-WIP from the repo root into META-INF 
of every JAR.
-// This ensures ASF licensing files are present in all binary artifacts.
-lazy val asfLicensingSettings = Seq(
-  Compile / resourceGenerators += Def.task {
-    val rootDir = (ThisBuild / baseDirectory).value
-    val metaInfDir = (Compile / resourceManaged).value / "META-INF"
-    val filesToCopy = Seq("LICENSE", "NOTICE", "DISCLAIMER-WIP")
-    filesToCopy.flatMap { fileName =>
-      val src = rootDir / fileName
-      if (src.exists()) {
-        val dest = metaInfDir / fileName
-        IO.copyFile(src, dest)
-        Seq(dest)
-      } else Seq.empty
-    }
-  }.taskValue
-)
+// Per-module ASF licensing: each jar's META-INF/LICENSE describes only what 
is in that jar.
+// Modules without vendored code get Apache 2.0 only; workflow-operator 
includes mbknor attribution.
+// See project/AddMetaInfLicenseFiles.scala.
+lazy val asfLicensingSettings = AddMetaInfLicenseFiles.defaultSettings
+lazy val asfLicensingSettingsWithVendored = 
AddMetaInfLicenseFiles.workflowOperatorSettings
+lazy val asfDistLicensingSettings = AddMetaInfLicenseFiles.distSettings
 
 lazy val DAO = (project in file("common/dao")).settings(asfLicensingSettings)
 lazy val Config = (project in 
file("common/config")).settings(asfLicensingSettings)
@@ -41,6 +30,7 @@ lazy val Auth = (project in file("common/auth"))
 lazy val ConfigService = (project in file("config-service"))
   .dependsOn(Auth, Config)
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .settings(
     dependencyOverrides ++= Seq(
       // override it as io.dropwizard 4 require 2.16.1 or higher
@@ -50,6 +40,7 @@ lazy val ConfigService = (project in file("config-service"))
 lazy val AccessControlService = (project in file("access-control-service"))
   .dependsOn(Auth, Config, DAO)
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .settings(
     dependencyOverrides ++= Seq(
       // override it as io.dropwizard 4 require 2.16.1 or higher
@@ -74,6 +65,7 @@ lazy val WorkflowCore = (project in 
file("common/workflow-core"))
 lazy val ComputingUnitManagingService = (project in 
file("computing-unit-managing-service"))
   .dependsOn(WorkflowCore, Auth, Config)
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .settings(
     dependencyOverrides ++= Seq(
       // override it as io.dropwizard 4 require 2.16.1 or higher
@@ -82,6 +74,7 @@ lazy val ComputingUnitManagingService = (project in 
file("computing-unit-managin
   )
 lazy val FileService = (project in file("file-service"))
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .dependsOn(WorkflowCore, Auth, Config)
   .configs(Test)
   .dependsOn(DAO % "test->test") // test scope dependency
@@ -94,10 +87,11 @@ lazy val FileService = (project in file("file-service"))
     )
   )
 
-lazy val WorkflowOperator = (project in 
file("common/workflow-operator")).settings(asfLicensingSettings).dependsOn(WorkflowCore)
+lazy val WorkflowOperator = (project in 
file("common/workflow-operator")).settings(asfLicensingSettingsWithVendored).dependsOn(WorkflowCore)
 lazy val WorkflowCompilingService = (project in 
file("workflow-compiling-service"))
   .dependsOn(WorkflowOperator, Config)
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .settings(
     dependencyOverrides ++= Seq(
       // override it as io.dropwizard 4 require 2.16.1 or higher
@@ -110,6 +104,7 @@ lazy val WorkflowCompilingService = (project in 
file("workflow-compiling-service
 lazy val WorkflowExecutionService = (project in file("amber"))
   .dependsOn(WorkflowOperator, Auth, Config)
   .settings(asfLicensingSettings)
+  .settings(asfDistLicensingSettings)
   .settings(
     dependencyOverrides ++= Seq(
       "com.fasterxml.jackson.core" % "jackson-core" % "2.15.1",
diff --git a/project/AddMetaInfLicenseFiles.scala 
b/project/AddMetaInfLicenseFiles.scala
new file mode 100644
index 0000000000..30f191a0db
--- /dev/null
+++ b/project/AddMetaInfLicenseFiles.scala
@@ -0,0 +1,142 @@
+// 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.
+
+import sbt._
+import sbt.Keys._
+import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport._
+
+/**
+ * Generates per-module LICENSE files for jar META-INF and dist zip top level.
+ *
+ * Each jar's META-INF/LICENSE describes only what is in that specific jar:
+ *  - Modules without vendored code get Apache 2.0 only.
+ *  - workflow-operator gets Apache 2.0 plus the mbknor-jackson-jsonschema
+ *    attribution and the full MIT license text.
+ *
+ * NOTICE and DISCLAIMER-WIP are copied as-is from the repo root.
+ *
+ * See https://github.com/apache/texera/issues/4131
+ */
+object AddMetaInfLicenseFiles {
+
+  private lazy val rootDir = LocalRootProject / baseDirectory
+
+  private val ThirdPartyHeader = "THIRD-PARTY DEPENDENCIES"
+
+  /** Extract the Apache 2.0 license text (before the THIRD-PARTY section) 
from root LICENSE. */
+  private def apacheLicenseText(rootDir: File): String = {
+    val lines = IO.readLines(rootDir / "LICENSE")
+    val headerIndex = lines.indexWhere(_.trim == ThirdPartyHeader)
+    val cutoffIndex =
+      if (headerIndex >= 0) {
+        // Cut at the "---" delimiter line preceding the header
+        val delimiterIndex = lines.lastIndexWhere(_.startsWith("---"), 
headerIndex - 1)
+        if (delimiterIndex >= 0) delimiterIndex else headerIndex
+      } else {
+        lines.length
+      }
+    lines.take(cutoffIndex).mkString("\n").trim + "\n"
+  }
+
+  /** The vendored code section for workflow-operator 
(mbknor-jackson-jsonschema). */
+  private def workflowOperatorVendoredSection(rootDir: File): String = {
+    val mitLicense = IO.read(rootDir / "licenses" / "LICENSE-MIT.txt")
+    s"""
+       
|--------------------------------------------------------------------------------
+       |THIRD-PARTY DEPENDENCIES
+       
|--------------------------------------------------------------------------------
+       |
+       |This jar bundles compiled code from the following third-party project.
+       |The full license text is included below.
+       |
+       |MIT License
+       |--------------------------------------
+       |
+       |This product bundles code derived from mbknor-jackson-jsonschema:
+       |  - com/kjetland/jackson/jsonSchema/
+       |  Copyright (c) 2016 Kjell Tore Eliassen (mbknor)
+       |  Source: https://github.com/mbknor/mbknor-jackson-jsonschema
+       |
+       
|--------------------------------------------------------------------------------
+       |Full text of the MIT License:
+       
|--------------------------------------------------------------------------------
+       |
+       |${mitLicense.trim}
+       |""".stripMargin
+  }
+
+  private def writeToMetaInf(managed: File, fileName: String, content: 
String): File = {
+    val dest = managed / "META-INF" / fileName
+    IO.write(dest, content)
+    dest
+  }
+
+  private def copyToMetaInf(managed: File, src: File, fileName: String): File 
= {
+    val dest = managed / "META-INF" / fileName
+    IO.copyFile(src, dest)
+    dest
+  }
+
+  private def noticeAndDisclaimer(managed: File, rootDir: File): Seq[File] = {
+    val files = Seq(copyToMetaInf(managed, rootDir / "NOTICE", "NOTICE"))
+    val disclaimer = rootDir / "DISCLAIMER-WIP"
+    if (disclaimer.exists()) files :+ copyToMetaInf(managed, disclaimer, 
"DISCLAIMER-WIP")
+    else files
+  }
+
+  /** Settings for modules WITHOUT vendored third-party code.
+   *  META-INF/LICENSE contains only the Apache 2.0 license text. */
+  lazy val defaultSettings: Seq[Setting[_]] = Seq(
+    Compile / resourceGenerators += Def.task {
+      val managed = (Compile / resourceManaged).value
+      val root = rootDir.value
+      val licenseContent = apacheLicenseText(root)
+      writeToMetaInf(managed, "LICENSE", licenseContent) +: 
noticeAndDisclaimer(managed, root)
+    }.taskValue
+  )
+
+  /** Settings for workflow-operator which contains vendored 
mbknor-jackson-jsonschema code.
+   *  META-INF/LICENSE contains Apache 2.0 plus the mbknor attribution and MIT 
license text. */
+  lazy val workflowOperatorSettings: Seq[Setting[_]] = Seq(
+    Compile / resourceGenerators += Def.task {
+      val managed = (Compile / resourceManaged).value
+      val root = rootDir.value
+      val licenseContent = apacheLicenseText(root) + "\n" + 
workflowOperatorVendoredSection(root)
+      writeToMetaInf(managed, "LICENSE", licenseContent) +: 
noticeAndDisclaimer(managed, root)
+    }.taskValue
+  )
+
+  /** Additional settings for dist-producing modules: places the same files
+   *  at the top level of the sbt-native-packager Universal zip so they
+   *  appear alongside lib/ and bin/ in the distribution. */
+  lazy val distSettings: Seq[Setting[_]] = Seq(
+    Universal / mappings := {
+      val existing = (Universal / mappings).value
+      val root = rootDir.value
+      val licenseFile = root / "LICENSE"
+      val noticeFile = root / "NOTICE"
+      val disclaimerFile = root / "DISCLAIMER-WIP"
+      val reserved = Set("LICENSE", "NOTICE", "DISCLAIMER-WIP")
+      val filtered = existing.filterNot { case (_, path) => 
reserved.contains(path) }
+      val extras = Seq(
+        licenseFile -> "LICENSE",
+        noticeFile -> "NOTICE"
+      ) ++ (if (disclaimerFile.exists()) Seq(disclaimerFile -> 
"DISCLAIMER-WIP") else Seq.empty)
+      filtered ++ extras
+    }
+  )
+}

Reply via email to