pjfanning commented on code in PR #865:
URL: https://github.com/apache/pekko-grpc/pull/865#discussion_r3893879483


##########
project/AssemblyLicenseCheck.scala:
##########
@@ -0,0 +1,116 @@
+/*
+ * 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 java.util.zip.ZipFile
+
+import sbt._
+import sbt.Keys._
+import sbtassembly.AssemblyKeys._
+import org.mdedetrich.apache.sonatype.ApacheSonatypePlugin.autoImport.{
+  apacheSonatypeLicenseFile,
+  apacheSonatypeNoticeFile
+}
+
+/**
+ * Guards the LICENSE and NOTICE files that ship in the assembly jars.
+ *
+ * The assembly jars bundle classes from 3rd party projects, so their 
`META-INF/LICENSE` and
+ * `META-INF/NOTICE` have to account for those projects. Those files are 
curated by hand in
+ * `legal/`, because most of the bundled jars carry no NOTICE of their own and 
the text has to be
+ * taken from the upstream projects. These tasks stop the curated files from 
drifting away from
+ * what the jars actually bundle.
+ */
+object AssemblyLicenseCheck {
+
+  val assemblyBundledModules =
+    taskKey[Seq[String]]("The `organization:name` ids of the 3rd party modules 
bundled into the assembly jar")
+  val assemblyLicenseCheck =
+    taskKey[Unit]("Check that every module bundled into the assembly jar is 
named in the assembly LICENSE file")
+  val assemblyMetaInfArchives =
+    taskKey[Seq[File]]("The published archives that should carry the assembly 
LICENSE and NOTICE files")
+  val assemblyMetaInfCheck =
+    taskKey[Unit]("Check that the published archives ship the assembly LICENSE 
and NOTICE files in META-INF")
+
+  /** A module id as listed in the assembly LICENSE file: `organization:name`, 
optionally `:version`. */
+  private val ModuleId = 
"""^([a-zA-Z0-9][\w.\-]*):([a-zA-Z0-9][\w.\-]*)(?::.*)?$""".r
+
+  lazy val settings: Seq[Setting[?]] = Seq(
+    assemblyBundledModules := {
+      val suffix = "_" + CrossVersion.binaryScalaVersion(scalaVersion.value)
+      val modules = (assembly / 
fullClasspath).value.flatMap(_.get(moduleID.key))
+      modules
+        .filterNot(_.organization == "org.apache.pekko") // our own modules, 
covered by the ASF header
+        .map { module =>
+          val name = if (module.name.endsWith(suffix)) 
module.name.dropRight(suffix.length) else module.name
+          s"${module.organization}:$name"
+        }
+        .distinct
+        .sorted
+    },
+    assemblyLicenseCheck := {
+      val log = streams.value.log
+      val licenseFile = apacheSonatypeLicenseFile.value
+      val listed = IO.readLines(licenseFile).map(_.trim).collect { case 
ModuleId(org, module) => s"$org:$module" }.toSet
+      val bundled = assemblyBundledModules.value
+
+      val extra = (listed -- bundled).toSeq.sorted
+      if (extra.nonEmpty)
+        // the assembly jars are cross built, so a module can be listed for a 
Scala version other than this one
+        log.warn(
+          s"${licenseFile.getName} lists modules that ${name.value} does not 
bundle for Scala ${scalaVersion.value}, " +
+          s"check whether they are still needed: ${extra.mkString(", ")}")
+
+      val missing = bundled.filterNot(listed)
+      if (missing.nonEmpty)
+        sys.error(
+          s"${name.value} bundles modules that ${licenseFile.getName} does not 
account for: ${missing.mkString(", ")}")
+
+      log.info(s"${licenseFile.getName} accounts for all ${bundled.size} 
modules bundled by ${name.value}")
+    },
+    assemblyMetaInfArchives := Seq(assembly.value),
+    assemblyMetaInfCheck := {
+      val log = streams.value.log
+      val expected =
+        Seq("META-INF/LICENSE" -> apacheSonatypeLicenseFile.value, 
"META-INF/NOTICE" -> apacheSonatypeNoticeFile.value)
+
+      assemblyMetaInfArchives.value.foreach { archive =>
+        val zip = new ZipFile(archive)
+        val problems =
+          try expected.flatMap { case (path, file) =>
+              Option(zip.getEntry(path)) match {
+                case None =>
+                  Some(s"$path is missing, expected a copy of $file")
+                case Some(entry) =>
+                  val actual = {
+                    val in = zip.getInputStream(entry)
+                    try in.readAllBytes()
+                    finally in.close()
+                  }
+                  if (actual.sameElements(IO.readBytes(file))) None
+                  else Some(s"$path does not match $file")
+              }
+            }
+          finally zip.close()

Review Comment:
   The files are `legal/AssemblyLicense.txt` and `legal/AssemblyNotice.txt`, a 
few KB each, and `assemblyMetaInfArchives` is two archives per project (the 
assembly jar and the bat archive), so this is 4 small reads per task run — in a 
task that has just built an assembly jar. Leaving it as is: hoisting them would 
not measurably change the runtime and the comparison reads more clearly next to 
the file it names in the error message.



##########
project/AssemblyLicenseCheck.scala:
##########
@@ -0,0 +1,116 @@
+/*
+ * 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 java.util.zip.ZipFile
+
+import sbt._
+import sbt.Keys._
+import sbtassembly.AssemblyKeys._
+import org.mdedetrich.apache.sonatype.ApacheSonatypePlugin.autoImport.{
+  apacheSonatypeLicenseFile,
+  apacheSonatypeNoticeFile
+}
+
+/**
+ * Guards the LICENSE and NOTICE files that ship in the assembly jars.
+ *
+ * The assembly jars bundle classes from 3rd party projects, so their 
`META-INF/LICENSE` and
+ * `META-INF/NOTICE` have to account for those projects. Those files are 
curated by hand in
+ * `legal/`, because most of the bundled jars carry no NOTICE of their own and 
the text has to be
+ * taken from the upstream projects. These tasks stop the curated files from 
drifting away from
+ * what the jars actually bundle.
+ */
+object AssemblyLicenseCheck {
+
+  val assemblyBundledModules =
+    taskKey[Seq[String]]("The `organization:name` ids of the 3rd party modules 
bundled into the assembly jar")
+  val assemblyLicenseCheck =
+    taskKey[Unit]("Check that every module bundled into the assembly jar is 
named in the assembly LICENSE file")
+  val assemblyMetaInfArchives =
+    taskKey[Seq[File]]("The published archives that should carry the assembly 
LICENSE and NOTICE files")
+  val assemblyMetaInfCheck =
+    taskKey[Unit]("Check that the published archives ship the assembly LICENSE 
and NOTICE files in META-INF")
+
+  /** A module id as listed in the assembly LICENSE file: `organization:name`, 
optionally `:version`. */
+  private val ModuleId = 
"""^([a-zA-Z0-9][\w.\-]*):([a-zA-Z0-9][\w.\-]*)(?::.*)?$""".r

Review Comment:
   Keeping `ModuleId`. A capitalised `val` is the convention for a `Regex` used 
as an extractor, which is exactly how it is used here (`case ModuleId(org, 
module)`); a lowercase name would compile but read as a plain value at the call 
site. The scaladoc line directly above states the shape it matches.



##########
project/Dependencies.scala:
##########
@@ -110,6 +110,10 @@ object Dependencies {
     Compile.grpcProtobuf,
     Test.scalaTest)
 
+  lazy val scalapbProtocPlugin = l ++= Seq(
+    Compile.scalapbCompilerPlugin,
+    Protobuf.protobufJava) // or else scalapb pulls older version in 
transitively

Review Comment:
   That wording is not new here — `// or else scalapb pulls older version in 
transitively` already sits on the `codegen` and `runtime` entries in this file, 
and the new `scalapbProtocPlugin` entry copies it deliberately so the three 
read the same. Rewording only the new one would leave the file inconsistent, 
and rewording all three is unrelated churn for this PR.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to