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

janhoy pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new d21dc1d  SOLR-15867 Bring in build stuff from lucene (#491)
d21dc1d is described below

commit d21dc1dffa48419d991f72c950b1fc1716301b56
Author: Jan Høydahl <[email protected]>
AuthorDate: Sat Jan 8 00:52:23 2022 +0100

    SOLR-15867 Bring in build stuff from lucene (#491)
    
    janhoy:
    * Bring in improved build parts from lucene 9
    * Add maven and docker folders to release dir
    * Add instructions for building docker file to workflow.txt
    * Package official dockerfile into release
    
    houston:
    * Add support for useGpg, slim down Docker release.
    * Add ability to build a release with a non-signed Dockerfile
    * tar -> tgz. Fixed official docker build. a bit messy still
    
    Co-authored-by: Houston Putman <[email protected]>
---
 build.gradle                                       |   1 -
 buildSrc/build.gradle                              |  15 +-
 .../{build.gradle => scriptDepVersions.gradle}     |  24 +--
 .../java/org/apache/lucene/gradle/Checksum.java    | 195 +++++++++++++++++++++
 gradle/documentation/changes-to-html.gradle        |  24 ++-
 gradle/help.gradle                                 |   2 +-
 gradle/releasing.gradle                            |  55 ------
 help/dependencies.txt                              |   4 +-
 help/gpgSigning.txt                                |  70 --------
 help/publishing.txt                                | 138 +++++++++++++++
 help/workflow.txt                                  |  15 +-
 settings.gradle                                    |   1 +
 solr/CHANGES.txt                                   |   2 +
 solr/distribution/build.gradle                     | 160 +++++++++++++++++
 solr/distribution/source-release.gradle            |  50 ++++++
 solr/docker/build.gradle                           |  24 ++-
 solr/docker/gradle-help.txt                        |   2 +-
 solr/docker/templates/Dockerfile.body.template     |   2 +-
 solr/packaging/build.gradle                        |  61 +------
 19 files changed, 632 insertions(+), 213 deletions(-)

diff --git a/build.gradle b/build.gradle
index fb8f757..badb430 100644
--- a/build.gradle
+++ b/build.gradle
@@ -127,7 +127,6 @@ apply from: file('gradle/java/jar-manifest.gradle')
 
 // Publishing and releasing
 apply from: file('gradle/maven/defaults-maven.gradle')
-apply from: file('gradle/releasing.gradle')
 
 // IDE support, settings and specials.
 apply from: file('gradle/ide/intellij-idea.gradle')
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index b1aee14..91f8874 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -15,15 +15,24 @@
  * limitations under the License.
  */
 
+repositories {
+  mavenCentral()
+}
+
 ext {
   // Minimum Java version required to compile buildSrc.
   minJavaVersion = JavaVersion.VERSION_11
 }
 
+// Make sure the build environment is consistent.
+apply from: file('../gradle/validation/check-environment.gradle')
+
+// Load common buildSrc and script deps.
+apply from: file("scriptDepVersions.gradle")
+
 dependencies {
   implementation gradleApi()
   implementation localGroovy()
-}
 
-// Make sure the build environment is consistent.
-apply from: file('../gradle/validation/check-environment.gradle')
+  implementation 
"commons-codec:commons-codec:${scriptDepVersions['commons-codec']}"
+}
diff --git a/buildSrc/build.gradle b/buildSrc/scriptDepVersions.gradle
similarity index 64%
copy from buildSrc/build.gradle
copy to buildSrc/scriptDepVersions.gradle
index b1aee14..5c27c51 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/scriptDepVersions.gradle
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-ext {
-  // Minimum Java version required to compile buildSrc.
-  minJavaVersion = JavaVersion.VERSION_11
-}
+// Declare script dependency versions outside of palantir's
+// version unification control. These are not our main dependencies
+// but are reused in buildSrc and across applied scripts.
 
-dependencies {
-  implementation gradleApi()
-  implementation localGroovy()
+ext {
+  // TODO: Check if Solr needs all of these
+  scriptDepVersions = [
+      "apache-rat": "0.11",
+      "commons-codec": "1.13",
+      "ecj": "3.27.0",
+      "flexmark": "0.61.24",
+      "javacc": "7.0.4",
+      "jflex": "1.8.2",
+      "jgit": "5.9.0.202009080501-r",
+  ]
 }
-
-// Make sure the build environment is consistent.
-apply from: file('../gradle/validation/check-environment.gradle')
diff --git a/buildSrc/src/main/java/org/apache/lucene/gradle/Checksum.java 
b/buildSrc/src/main/java/org/apache/lucene/gradle/Checksum.java
new file mode 100644
index 0000000..0dab9dc
--- /dev/null
+++ b/buildSrc/src/main/java/org/apache/lucene/gradle/Checksum.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+/*
+ * A task that generates SHA512 checksums. Cloned from:
+ * 
https://github.com/gradle/gradle-checksum/blob/03351de/src/main/java/org/gradle/crypto/checksum/Checksum.java
+ * with custom fixes to make the checksum palatable to shasum, see pending PR:
+ * https://github.com/gradle/gradle-checksum/pull/4
+ *
+ * Original license ASL:
+ * https://github.com/gradle/gradle-checksum/blob/03351de/LICENSE
+ */
+
+package org.apache.lucene.gradle;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileType;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.work.Incremental;
+import org.gradle.work.InputChanges;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Locale;
+
+public class Checksum extends DefaultTask {
+  private FileCollection files;
+  private File outputDir;
+  private Algorithm algorithm;
+
+  public enum Algorithm {
+    MD5(new DigestUtils(DigestUtils.getMd5Digest())),
+    SHA256(new DigestUtils(DigestUtils.getSha256Digest())),
+    SHA384(new DigestUtils(DigestUtils.getSha384Digest())),
+    SHA512(new DigestUtils(DigestUtils.getSha512Digest()));
+
+    private final DigestUtils hashFunction;
+
+    Algorithm(DigestUtils hashFunction) {
+      this.hashFunction = hashFunction;
+    }
+
+    public String getExtension() {
+      return name().toLowerCase(Locale.ROOT);
+    }
+  }
+
+  public Checksum() {
+    outputDir = new File(getProject().getBuildDir(), "checksums");
+    algorithm = Algorithm.SHA256;
+  }
+
+  @InputFiles
+  @Incremental
+  public FileCollection getFiles() {
+    return files;
+  }
+
+  public void setFiles(FileCollection files) {
+    this.files = files;
+  }
+
+  @Input
+  public Algorithm getAlgorithm() {
+    return algorithm;
+  }
+
+  public void setAlgorithm(Algorithm algorithm) {
+    this.algorithm = algorithm;
+  }
+
+  @OutputDirectory
+  public File getOutputDir() {
+    return outputDir;
+  }
+
+  public void setOutputDir(File outputDir) {
+    if (outputDir.exists() && !outputDir.isDirectory()) {
+      throw new IllegalArgumentException("Output directory must be a 
directory.");
+    }
+    this.outputDir = outputDir;
+  }
+
+  @TaskAction
+  public void generateChecksumFiles(InputChanges inputChanges) throws 
IOException {
+    if (!getOutputDir().exists()) {
+      if (!getOutputDir().mkdirs()) {
+        throw new IOException("Could not create directory:" + getOutputDir());
+      }
+    }
+
+    if (!inputChanges.isIncremental()) {
+      getProject().delete(allPossibleChecksumFiles());
+    }
+
+    inputChanges
+        .getFileChanges(getFiles())
+        .forEach(
+            fileChange -> {
+              if (fileChange.getFileType() == FileType.DIRECTORY) {
+                getProject()
+                    .getLogger()
+                    .warn("Checksums can't be applied to directories: " + 
fileChange.getFile());
+                return;
+              }
+
+              File input = fileChange.getFile();
+              switch (fileChange.getChangeType()) {
+                case REMOVED:
+                  if (input.isFile()) {
+                    getProject().delete(outputFileFor(input));
+                  }
+                  break;
+
+                case ADDED:
+                case MODIFIED:
+                  input = fileChange.getFile();
+                  if (input.isFile()) {
+                    File checksumFile = outputFileFor(input);
+
+                    try {
+                      String checksum = 
algorithm.hashFunction.digestAsHex(input).trim();
+
+                      /*
+                       * https://man7.org/linux/man-pages/man1/sha1sum.1.html
+                       *
+                       * The default mode is to print a line with checksum, a 
space, a character
+                       * indicating input mode ('*' for binary, ' ' for text 
or where
+                       * binary is insignificant), and name for each FILE.
+                       */
+                      boolean binaryMode = true;
+
+                      Files.writeString(
+                          checksumFile.toPath(),
+                          String.format(
+                              Locale.ROOT,
+                              "%s %s%s",
+                              checksum,
+                              binaryMode ? "*" : " ",
+                              input.getName()),
+                          StandardCharsets.UTF_8);
+                    } catch (IOException e) {
+                      throw new GradleException("Trouble creating checksum: " 
+ e.getMessage(), e);
+                    }
+                  }
+                  break;
+                default:
+                  throw new RuntimeException();
+              }
+            });
+  }
+
+  private File outputFileFor(File inputFile) {
+    return new File(getOutputDir(), inputFile.getName() + "." + 
algorithm.getExtension());
+  }
+
+  private FileCollection allPossibleChecksumFiles() {
+    FileCollection possibleFiles = null;
+    for (Algorithm algo : Algorithm.values()) {
+      if (possibleFiles == null) {
+        possibleFiles = filesFor(algo);
+      } else {
+        possibleFiles = possibleFiles.plus(filesFor(algo));
+      }
+    }
+    return possibleFiles;
+  }
+
+  private FileCollection filesFor(final Algorithm algo) {
+    return getProject()
+        .fileTree(getOutputDir(), files -> files.include("**/*." + 
algo.toString().toLowerCase()));
+  }
+}
diff --git a/gradle/documentation/changes-to-html.gradle 
b/gradle/documentation/changes-to-html.gradle
index e9692ab..3b4ca69 100644
--- a/gradle/documentation/changes-to-html.gradle
+++ b/gradle/documentation/changes-to-html.gradle
@@ -22,6 +22,18 @@ configure(project(':solr:documentation')) {
     siteDir = resources
     script = file("${resources}/changes2html.pl")
   }
+
+  // Make the rendered HTML of changes available as a separate
+  // artifact for the distribution.
+  configurations {
+    changesHtml
+  }
+
+  artifacts {
+    changesHtml changesToHtml.targetDir, {
+      builtBy changesToHtml
+    }
+  }
 }
 
 // compile changes.txt into an html file
@@ -44,7 +56,7 @@ class ChangesToHtmlTask extends DefaultTask {
 
   @OutputDirectory
   final DirectoryProperty targetDir = project.objects.directoryProperty()
-    .fileProvider(project.providers.provider { 
project.file("${project.docroot}/changes") })
+      .fileProvider(project.providers.provider { 
project.file("${project.docroot}/changes") })
 
   @Input
   def luceneDocUrl = "${-> project.luceneDocUrl }"
@@ -72,11 +84,11 @@ class ChangesToHtmlTask extends DefaultTask {
       ignoreExitValue = true
 
       args += [
-        "-CSD",
-        script,
-        "${productName}",
-        versionsFile.toString(),
-        luceneDocUrl.concat('/')   // slash required at end by perl script
+          "-CSD",
+          script,
+          "${productName}",
+          versionsFile.toString(),
+          luceneDocUrl.concat('/')   // slash required at end by perl script
       ]
     }
 
diff --git a/gradle/help.gradle b/gradle/help.gradle
index 7bbdaf5..3a354ed 100644
--- a/gradle/help.gradle
+++ b/gradle/help.gradle
@@ -30,7 +30,7 @@ configure(rootProject) {
       ["Git", "help/git.txt", "Git assistance and guides."],
       ["ValidateLogCalls", "help/validateLogCalls.txt", "How to use logging 
calls efficiently."],
       ["IDEs", "help/IDEs.txt", "IDE support."],
-      ["GpgSigning", "help/gpgSigning.txt", "Signing artifacts with GPG."],
+      ["Publishing", "help/publishing.txt", "Release publishing publishing, 
signing, etc."],
       ["Docker", "solr/docker/gradle-help.txt", "Building Solr Docker 
images."],
   ]
 
diff --git a/gradle/releasing.gradle b/gradle/releasing.gradle
deleted file mode 100644
index b05b826..0000000
--- a/gradle/releasing.gradle
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 org.apache.commons.codec.digest.DigestUtils
-
-// We're using commons-codec for computing checksums.
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-
-    dependencies {
-        classpath 
"commons-codec:commons-codec:${scriptDepVersions['commons-codec']}"
-    }
-}
-
-allprojects {
-    plugins.withType(DistributionPlugin) {
-        def checksum = {
-            outputs.files.each { File file ->
-                String sha512 = new 
DigestUtils(DigestUtils.sha512Digest).digestAsHex(file).trim()
-                new File(file.parent, file.name + ".sha512").write(sha512 + "  
" + file.name, "UTF-8")
-            }
-        }
-
-        distZip {
-            doLast checksum
-        }
-
-        distTar {
-            compression = Compression.GZIP
-            doLast checksum
-        }
-
-        installDist {
-            doLast {
-                logger.lifecycle "Distribution assembled under: 
${destinationDir}"
-            }
-        }
-    }
-}
diff --git a/help/dependencies.txt b/help/dependencies.txt
index 2990db6..8cd78b7 100644
--- a/help/dependencies.txt
+++ b/help/dependencies.txt
@@ -13,8 +13,8 @@ 
https://docs.gradle.org/current/userguide/dependency_management_for_java_project
 
https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation
 
https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management
 
-For the needs of Solr we will typically focus on three
-configurations and attach project dependencies to them:
+Solr typically uses three configurations and attach project
+dependencies to them:
 
 api  - makes a dependency available for main classes, tests and any
   other modules importing the project (exportable dependency),
diff --git a/help/gpgSigning.txt b/help/gpgSigning.txt
deleted file mode 100644
index 80e45f0..0000000
--- a/help/gpgSigning.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-GPG Signing
-===========
-
-GPG Signing of distribution files (typically by a release manager) is done 
with the 'signDist' command.
-
-The only required configuration property Gradle needs is the 
'signing.gnupg.keyName' (aka: the fingerprint) of
-the key you wish to use:
-
-./gradlew signDist -Psigning.gnupg.keyName=YOUR_KEY_FINGERPRINT
-
-By default when you run this command, Gradle will delegate to the `gpg2` 
command for managing the signing of each file, which (should)
-in turn use the `gpg-agent` to prompt you for your secret key only as needed 
using a dialog box specific to your operating system and/or
-`gpg-agent` preferences.
-
-You may wish to put the `signing.gnupg.keyName` in your 
`~/.gradle/gradle.properties` so it is set automatically any time you use gradle
-
-
-Additional Configuration
-------------------------
-
-The following additional properties -- specified either on the command line 
via `-P...` or in your `~/.gradle/gradle.properties` may be
-useful/necessary in your system:
-
-signing.gnupg.useLegacyGpg=true                    # Changes the default 
executable from `gpg2` to `gpg` and explicitly sets `--use-agent`
-signing.gnupg.executable=gpg                       # Allows explicit control 
over what command executable used (ex: `gpg2`, `gpg`, `gpg.exe`, etc...)
-signing.gnupg.homeDir=/tmp/gnupg-home              # overrides GnuPG's default 
home directory (ex: `~/.gnupg/`)
-signing.gnupg.optionsFile=/tmp/gnupg-home/my.conf  # overrides GnuPG's default 
configuration file
-signing.gnupg.passphrase=...                       # Provide your passphrase 
to gradle to hand off to gpg.  *NOT RECOMMENDED*, see below.
-
-
-Notes About Error Messages
---------------------------
-
-
-### `gpg: signing failed: Inappropriate ioctl for device`
-
-This typically happens if your `gpg-agent` is configured (either globally for 
your operating system, or personally in your
-`~/.gnupg/gpg-agent.conf`) to use a `pinentry` command which depends on using 
the same `tty` as the `gpg` command (ex: `pinentry-curses`,
-or `pinentry-tty`, etc...).
-
-`tty` based `pinentry` implementations do not work when Gradle's 
`SigningPlugin` is attempting to invoke `gpg` -- among other problems:
-Gradle is multi-threaded, and we sign multiple artifacts by default; so even 
if the `SigningPlugin` didn't automatically force `--no-tty` when
-running `gpg` you could easily run into problems where a second `pinentry` 
process wanted to read from the same `tty` in the middle of you
-typing in your passphrase to the first process.
-
-Developers are encouraged to configure a *non* `tty` based `pinentry` (ex: 
`pinentry-gnome`, `pinentry-x11`, `pinentry-qt`, `pinentry-mac`,
-`pinentry-wsl-ps1`, etc...) either globally in your operating system, or 
personally in your `~/.gnupg/gpg-agent.conf`, or in a new
-`gpg-agent.conf` file a new GnuPG configuration directory (containing a copy 
of your private keys) that you direct gradle to via
-`signing.gnupg.homeDir`
-
-If none of these options are viable for you, then as a last resort you may 
wish to consider using the `signing.gnupg.passphrase=...` property.
-This will expose your secret passphrase to the Gradle process, which will then 
pass it directly to each `gpg-agent` instance using
-`--pinentry-mode=loopback`.
-
-
-### `gpg: signing failed: No such file or directory`
-
-This may mean that there is a problem preventing `gpg` from communicating 
correctly with the `gpg-agent` (and/or invoking your `pinentry`
-program) that is independent of gradle.  Try running `pkill gpg-agent` and 
then retrying your `./gradlew` command
-
-
-### `No value has been specified for property 'signatory.keyId'.`
-
-Do not bother ever attempting to set a command line (or gradle.properties) 
property named `signatory.keyId`. This is evidently the
-name of an internal property that the gradle `SigningPlugin` expects the 
`GnupgSignatory` plugin we use to provide -- which it does
-as long as you have specified a valid value for `signing.gnupg.keyName`
-
-If you see this error, it means you did not properly set 
`signing.gnupg.keyName` _AND_ you invoked a task which is attempting to use
-the `SigningPlugin`, but does not depend on the custom 
`failUnlessGpgKeyProperty` to report the error correctly.  Please file a Jira
-noting what `./gradlew` command you attempted to run so we can fix it's 
dependencies, and try again after setting `signing.gnupg.keyName`.
diff --git a/help/publishing.txt b/help/publishing.txt
new file mode 100644
index 0000000..5b8bc5d
--- /dev/null
+++ b/help/publishing.txt
@@ -0,0 +1,138 @@
+Distribution and artifact publishing
+====================================
+
+
+See all distribution-related tasks by running:
+gradlew tasks --group distribution
+
+
+Maven
+-----
+
+To publish Solr Maven artifacts to a local ~/.m2 repository, run:
+
+gradlew mavenToLocalRepo
+
+To generate a local build/maven-local folder, run:
+
+gradlew mavenToLocalFolder
+
+To publish Solr Maven artifacts to Apache repositories
+(CI or release manager's job, typically!), run:
+
+gradlew mavenToApacheSnapshots -PasfNexusUsername= -PasfNexusPassword=
+gradlew mavenToApacheReleases  -PasfNexusUsername= -PasfNexusPassword= 
[optional signing options]
+
+See artifact signing section below if you plan to use mavenToApacheReleases.
+
+It is a good idea to avoid passing passwords on command line. CI jobs have
+these properties saved in ~/.gradle/gradle.properties - this way they
+are read automatically.
+
+Apache Releases repository will not accept snapshots.
+
+
+Release (distribution) artifacts
+--------------------------------
+
+To collect all release artifacts, and optionally sign them, run:
+
+gradlew assembleRelease [optional signing options]
+
+All distribution artifacts will be placed under:
+
+solr/distribution/build/release
+
+Artifact signing is optional (but required if you're really making a release).
+
+
+Artifact signing
+----------------
+
+Certain tasks may optionally sign artifacts or require artifacts to be signed:
+
+ assembleRelease
+ mavenToApacheReleases
+
+Signing can be enabled by adding the "-Psign" option, for example:
+
+gradlew assembleRelease mavenToApacheReleases -Psign
+
+By default gradle uses a Java-based implementation of PGP for signing, which 
requieres
+several "signing.*" properties via either ~/.gradle/gradle.properties or 
command-line options:
+
+https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials
+
+An example full command-line that assembles signed artifacts could look like 
this:
+
+gradlew assembleRelease mavenToApacheReleases -Psign -Psigning.keyId=... 
-Psigning.password=... -Psigning.secretKeyRingFile=...
+
+The keyId is the last 8 digits of your key (gpg -k will print your keys). 
Gradle documentation has more options
+of secure passing of private key information and passwords.
+
+
+Artifact signing using an external GPG with GPG Agent
+-----------------------------------------------------
+
+You can use an external GPG command to deal with signing artifacts, with out 
needing to give gradle your passphrase,
+by adding a "-PuseGpg" option, but this changes the properties you must 
specify:
+
+For gpg2:
+gradlew [tasks] -Psign -PuseGpg -Psigning.gnupg.keyName=...
+
+For gpg:
+gradlew [tasks] -Psign -PuseGpg -Psigning.gnupg.keyName=... 
-Psigning.gnupg.useLegacyGpg=true
+
+The keyName is the last 8 digits of your key (gpg -k will print your keys).
+
+There are additional (optional) "signing.gnupg.*" properties which exist that 
may be useful/necessary in your system:
+
+signing.gnupg.useLegacyGpg=true                    # Changes the default 
executable from `gpg2` to `gpg` and explicitly sets `--use-agent`
+signing.gnupg.executable=gpg                       # Allows explicit control 
over what command executable used (ex: `gpg2`, `gpg`, `gpg.exe`, etc...)
+signing.gnupg.homeDir=/tmp/gnupg-home              # overrides GnuPG's default 
home directory (ex: `~/.gnupg/`)
+signing.gnupg.optionsFile=/tmp/gnupg-home/my.conf  # overrides GnuPG's default 
configuration file
+signing.gnupg.passphrase=...                       # Provide your passphrase 
to gradle to hand off to gpg.  *NOT RECOMMENDED*, see below.
+
+If in doubt, consult gradle's signing plugin documentation:
+https://docs.gradle.org/current/userguide/signing_plugin.html#sec:using_gpg_agent
+
+"signing.gnupg.passphrase" is not recommended because there is no advantage to 
using an external GPG process if you use it.  If you
+are comfortable giving gradle your passphrase, then there is no reason to use 
an external GPG process via '-PuseGpg'.   Just use the
+"signing.*" options described previously to let gradle deal with your key 
directly.
+
+Because of how Gradle's signing plugin invokes GPG, using an external GPG 
process *only* works if your GPG configuration uses a 
+GPG agent (required by gpg2) and if the "pinentry" for your GPG agent does not 
require access to the tty to prompt you for a password.
+
+If you the following command fails with your GPG configuration, you can not 
use an external GPG process with gradle:
+
+echo foo | gpg --batch --no-tty --armor --detach-sign --use-agent --local-user 
YOUR_KEY_NAME
+
+
+Notes About GPG Error Messages
+------------------------------
+
+### `gpg: signing failed: Inappropriate ioctl for device` or `Invalid IPC 
response`
+
+This typically happens if your `gpg-agent` is configured (either globally for 
your operating system, or personally in your
+`~/.gnupg/gpg-agent.conf`) to use a `pinentry` command which depends on using 
the same `tty` as the `gpg` command (ex: `pinentry-curses`,
+or `pinentry-tty`, etc...).
+
+`tty` based `pinentry` implementations do not work when Gradle's signing 
plugin is attempting to invoke `gpg` -- among other problems:
+Gradle is multi-threaded and we sign multiple artifacts by default.  Even if 
you use "--max-workers 1" to force single-threaded execution,
+the signing plugin invokes gpg with `--batch --no-tty`, making it impossible 
for gpg (or a tty based pinentry) to prompt you for your passphrase
+in the same terminal where you run Gradle.
+
+Developers are encouraged to configure a *non* `tty` based `pinentry` (ex: 
`pinentry-gnome`, `pinentry-x11`, `pinentry-qt`, `pinentry-mac`,
+`pinentry-wsl-ps1`, etc...) either globally in your operating system, or 
personally in your `~/.gnupg/gpg-agent.conf`, or in a new
+`gpg-agent.conf` file a new GnuPG configuration directory (containing a copy 
of your private keys) that you direct gradle to via
+`signing.gnupg.homeDir`
+
+If this is not possible, then you should avoid using an external GPG process, 
and use the default (pure java) Artifact signing support
+
+
+### `gpg: signing failed: No such file or directory`
+
+This may mean that there is a problem preventing `gpg` from communicating 
correctly with the `gpg-agent` (and/or invoking your `pinentry`
+program) that is independent of gradle.  Try running `pkill gpg-agent` and 
then retrying your `./gradlew` command
+
+
diff --git a/help/workflow.txt b/help/workflow.txt
index 2b22c3c..c59d77d 100644
--- a/help/workflow.txt
+++ b/help/workflow.txt
@@ -27,11 +27,22 @@ local maven repository for inspection:
 gradlew mavenLocal
 ls -R build/maven-local/
 
-Put together Solr distribution:
+Put together a local Solr binary "distribution" folder:
 gradlew -p solr/packaging assemble
-ls solr/packaging/build/distributions/solr-* # release archives
 ls solr/packaging/build/solr-*               # expanded directory
 
+For quick local development
+gradlew -p solr/packaging dev
+ls solr/packaging/build/dev                  # expanded directory
+
+Generate the release tar and zip archives (see publishing.txt for details)
+gradlew -p solr/distribution assembleRelease
+ls solr/distribution/build/release           # release archives
+
+Build a docker image from the local repository (see docker/gradle-help.txt for 
more)
+gradlew dockerBuild dockerTag
+docker run --rm -p 8983:8983 apache/solr:9.0.0-SNAPSHOT
+
 
 Other validation and checks
 ===========================
diff --git a/settings.gradle b/settings.gradle
index a04cd7c..88eab39 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -47,6 +47,7 @@ include "solr:solr-ref-guide"
 include "solr:example"
 include "solr:documentation"
 include "solr:packaging"
+include "solr:distribution"
 include "solr:docker"
 
 // Configures development for joint Lucene/ Solr composite build.
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 66711b2..ed5630f 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -206,6 +206,8 @@ Build
 
 * SOLR-15793: Pin http_parser.rb to specific version to allow Solr Ref Guide 
on Jekyll to run (Eric Pugh)
 
+* SOLR-15867: Make the gradle build ready for 9.0 release (janhoy, Houston 
Putman, Dawid Weiss)
+
 Other Changes
 ----------------------
 * SOLR-15603: Add an option to activate Gradle build cache, build task 
cleanups (Alexis Tual, Dawid Weiss)
diff --git a/solr/distribution/build.gradle b/solr/distribution/build.gradle
new file mode 100644
index 0000000..a586b09
--- /dev/null
+++ b/solr/distribution/build.gradle
@@ -0,0 +1,160 @@
+/*
+ * 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 org.apache.lucene.gradle.Checksum
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+
+plugins {
+  id 'signing'
+}
+
+// This project puts together the Solr "distribution", assembling bits and 
pieces
+// from across the project structure into release artifacts.
+
+ext {
+  releaseDir = file("${buildDir}/release")
+  withSignedArtifacts = { ->
+    def propValue = propertyOrDefault("sign", null)
+    // Allow -Psign to work as a shorthand for -Psign=true
+    return propValue != null && (propValue.isBlank() || 
Boolean.parseBoolean(propValue))
+  }.call()
+  useGpgForSigning = { ->
+    def propValue = propertyOrDefault("useGpg", null)
+    // Allow -PuseGpg to work as a shorthand for -PuseGpg=true
+    return propValue != null && (propValue.isBlank() || 
Boolean.parseBoolean(propValue))
+  }.call()
+}
+
+if (project.ext.useGpgForSigning) {
+  signing {
+    useGpgCmd()
+  }
+}
+
+// Prepare the "source" distribution artifact.
+apply from: 
buildscript.sourceFile.toPath().resolveSibling("source-release.gradle")
+
+
+// Set up the HTML-rendered "changes" distribution artifact by linking to 
documentation's output.
+configurations {
+  changesHtml
+  docker
+}
+
+dependencies {
+  changesHtml project(path: ":solr:documentation", configuration: 
"changesHtml")
+  docker project(path: ':solr:docker', configuration: 
project.ext.withSignedArtifacts ? 'packagingOfficial' : 'packaging')
+}
+
+def distTarTask = rootProject.getTasksByName("distTar", true)[0]
+def distZipTask = rootProject.getTasksByName("distZip", true)[0]
+
+// Compute checksums for release archives.
+task computeChecksums(type: Checksum) {
+  algorithm = Checksum.Algorithm.SHA512
+
+  files = objects.fileCollection()
+  [
+      tasks.assembleSourceTgz,
+      distTarTask,
+      distZipTask,
+  ].each { dep ->
+    dependsOn dep
+    files += dep.outputs.files
+  }
+
+  outputDir = file("${buildDir}/checksums")
+}
+
+task signBinaryTgz(type: Sign) {
+  sign distTarTask
+}
+task signBinaryZip(type: Sign) {
+  sign distZipTask
+}
+task signSourceTgz(type: Sign) {
+  // The source tgz is not an archive task so be explicit about the outputs to 
sign.
+  dependsOn tasks.assembleSourceTgz
+  sign tasks.assembleSourceTgz.destination
+}
+
+task signReleaseArchives(type: Sync) {
+  from tasks.signBinaryTgz
+  from tasks.signBinaryZip
+  from tasks.signSourceTgz
+
+  into "${buildDir}/signatures"
+}
+
+task prepareGitRev() {
+  dependsOn ":gitStatus"
+
+  ext.outputFile = file("${buildDir}/.gitrev")
+
+  outputs.file(ext.outputFile)
+  inputs.property("gitrev", provider { -> rootProject.ext.gitRev })
+
+  doFirst {
+    Files.writeString(ext.outputFile.toPath(), rootProject.ext.gitRev, 
StandardCharsets.UTF_8)
+  }
+}
+
+
+// Assemble everything needed in the release folder structure.
+task assembleRelease(type: Sync) {
+  description "Assemble all Solr artifacts for a release."
+  dependsOn ":mavenToLocalFolder"
+
+  from(configurations.changesHtml, {
+    into "changes"
+  })
+
+  from(configurations.docker, {
+    include 'Dockerfile.*'
+    into "docker"
+  })
+
+  from(rootProject.mavenLocalDir, {
+    into "maven"
+  })
+
+  from tasks.prepareGitRev
+  from tasks.assembleSourceTgz
+  from distTarTask
+  from distZipTask
+
+  from tasks.computeChecksums
+
+  // Conditionally, attach signatures of all the release archives.
+  if (project.ext.withSignedArtifacts) {
+    from tasks.signReleaseArchives
+  }
+
+  into releaseDir
+}
+
+
+// Add the description and task group to some of the tasks that make
+// sense at the user-level help.
+tasks.matching {it.name in [
+    "assembleSourceTgz",
+    "assembleRelease",
+]}.all {
+  group "distribution"
+}
diff --git a/solr/distribution/source-release.gradle 
b/solr/distribution/source-release.gradle
new file mode 100644
index 0000000..9e0c11b
--- /dev/null
+++ b/solr/distribution/source-release.gradle
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+
+// Prepare the "source" distribution artifact. We use raw git export, no 
additional complexity needed.
+
+configure(project(":solr:distribution")) {
+  task assembleSourceTgz() {
+    description "Assemble source Solr artifact as a .tgz file."
+
+    // Make sure no unstaged/ dirty files are present.
+    dependsOn ":gitStatus", ":checkWorkingCopyClean"
+
+    ext {
+      destination = file("${buildDir}/packages/solr-${version}-src.tgz")
+    }
+
+    inputs.property("git-revision", { -> rootProject.ext.gitRev })
+    outputs.file destination
+
+    doFirst {
+      quietExec {
+        executable = project.externalTool("git")
+        workingDir = project.rootDir
+
+        args += [
+            "archive",
+            "--format", "tgz",
+            "--prefix", "solr-${version}/",
+            "--output", destination,
+            "HEAD"
+        ]
+      }
+    }
+  }
+}
diff --git a/solr/docker/build.gradle b/solr/docker/build.gradle
index c2ed014..92cbb26 100644
--- a/solr/docker/build.gradle
+++ b/solr/docker/build.gradle
@@ -29,7 +29,7 @@ def dockerImageTag = 
propertyOrEnvOrDefault("solr.docker.imageTag", "SOLR_DOCKER
 def dockerImageName = propertyOrEnvOrDefault("solr.docker.imageName", 
"SOLR_DOCKER_IMAGE_NAME", "${dockerImageRepo}:${dockerImageTag}")
 def baseDockerImage = propertyOrEnvOrDefault("solr.docker.baseImage", 
"SOLR_DOCKER_BASE_IMAGE", 'openjdk:11-jre-slim')
 
-def releaseGpgFingerprint = propertyOrDefault('signing.gnupg.keyName','');
+def releaseGpgFingerprint = 
propertyOrDefault('signing.gnupg.keyName',propertyOrDefault('signing.keyId',''));
 
 // Build directory locations
 def imageIdFile = "$buildDir/image-id"
@@ -41,6 +41,9 @@ configurations {
   packaging {
     canBeResolved = true
   }
+  packagingOfficial {
+    canBeResolved = true
+  }
   solrTgz {
     canBeConsumed = false
     canBeResolved = true
@@ -59,6 +62,7 @@ configurations {
 }
 
 ext {
+  dockerfilesDirPath = "${buildDir}/dockerfiles"
   packagingDir = file("${buildDir}/packaging")
 }
 
@@ -67,7 +71,11 @@ dependencies {
     builtBy 'assemblePackaging'
   }
 
-  solrTgz project(path: ":solr:packaging", configuration: 'solrTgz')
+  packagingOfficial files("${dockerfilesDirPath}/Dockerfile.official") {
+    builtBy 'createDockerfileOfficial'
+  }
+
+  solrTgz project(path: ":solr:packaging", configuration: "solrTgz")
   solrTgzSignature project(path: ":solr:packaging", configuration: 
'solrTgzSignature')
 
   dockerImage files(imageIdFile) {
@@ -98,7 +106,7 @@ task assemblePackaging(type: Sync) {
   from(projectDir, {
     include "scripts/**"
   })
-  from(buildDir, {
+  from(dockerfilesDirPath, {
     include 'Dockerfile.local'
   })
   into packagingDir
@@ -271,7 +279,7 @@ The final Dockerfiles are merely the snippet headers 
combined with the snippet b
  */
 [ dfLocalDetails, dfOfficialDetails ].each{ details ->
   def fileName = "Dockerfile.${ -> details.name.toLowerCase(Locale.ROOT) }"
-  def outFile = file("$buildDir/${fileName}")
+  def outFile = file("$dockerfilesDirPath/${fileName}")
   
   def props = [
     // Values defined here should be common (and consistent) across both 
Dockerfiles
@@ -327,7 +335,7 @@ tasks.createDockerfileOfficial.dependsOn 
configurations.solrTgz // to lazy compu
 if (''.equals(releaseGpgFingerprint)) {
   gradle.taskGraph.whenReady { graph ->
     if ( graph.hasTask(createDockerfileOfficial) ) {
-      throw new GradleException("No GPG keyName found, please see 
help/gpgSigning.txt (GPG key is neccessary to create Dockerfile.official)")
+      throw new GradleException("No GPG keyName found, please see 
help/publishing.txt (GPG key is neccessary to create Dockerfile.official)")
     }
   }
 }
@@ -338,10 +346,10 @@ task testBuildDockerfileOfficial(type: Copy) {
   dependsOn createDockerfileOfficial
   dependsOn configurations.solrTgz
   dependsOn configurations.solrTgzSignature
-  
+
   def mockHttpdHome = file("$smokeTestOfficial/mock-httpd-home");
   
-  inputs.file("$buildDir/Dockerfile.official")
+  inputs.file("$dockerfilesDirPath/Dockerfile.official")
   outputs.dir(mockHttpdHome)
   outputs.file(imageIdFileOfficial)
 
@@ -383,7 +391,7 @@ task testBuildDockerfileOfficial(type: Copy) {
         // *NOW* we can actually run our docker build command...
         logger.lifecycle('Running docker build on Dockerfile.official...');
         exec {
-          standardInput = 
file("${buildDir}/Dockerfile.official").newDataInputStream()
+          standardInput = 
file("${dockerfilesDirPath}/Dockerfile.official").newDataInputStream()
           commandLine 'docker', 'build',
             '--add-host', "mock-solr-dl-server:${mockServerIp}",
             '--no-cache', // force fresh downloads from our current network
diff --git a/solr/docker/gradle-help.txt b/solr/docker/gradle-help.txt
index 765fd44..83cc439 100644
--- a/solr/docker/gradle-help.txt
+++ b/solr/docker/gradle-help.txt
@@ -93,7 +93,7 @@ All users should build custom images using the instructions 
above.
 NOTE: All gradle commands for the Official Dockerfile below require the Solr 
artifacts to be signed with a GPG Key.
 For necessary inputs and properties, please refer to:
 
-gradlew helpGpgSigning
+gradlew helpPublishing
 
 You can use the following command to build an official Solr Dockerfile.
 The Dockerfile will be created at: solr/docker/build/Dockerfile.official
diff --git a/solr/docker/templates/Dockerfile.body.template 
b/solr/docker/templates/Dockerfile.body.template
index 4aa7d3c..110a7a9 100644
--- a/solr/docker/templates/Dockerfile.body.template
+++ b/solr/docker/templates/Dockerfile.body.template
@@ -30,7 +30,7 @@
 #  TODO; arguably these permissions should have been set correctly previously 
in the TAR
 RUN set -ex; \
   (cd /opt; ln -s solr-*/ solr); \
-  rm -Rf /opt/solr/docs /opt/solr/docker/Dockerfile* 
/opt/solr/dist/solr-solrj-*.jar /opt/solr/dist/solrj-lib 
/opt/solr/dist/solr-core-*.jar; \
+  rm -Rf /opt/solr/docs /opt/solr/dist/solr-solrj-*.jar 
/opt/solr/dist/solrj-lib /opt/solr/dist/solr-core-*.jar; \
   find /opt/solr/ -type d -print0 | xargs -0 chmod 0755; \
   find /opt/solr/ -type f -print0 | xargs -0 chmod 0644; \
   chmod -R 0755 /opt/solr/docker/scripts /opt/solr/bin 
/opt/solr/contrib/prometheus-exporter/bin/solr-exporter 
/opt/solr/server/scripts/cloud-scripts
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
index 8f98c67..e2f86d1 100644
--- a/solr/packaging/build.gradle
+++ b/solr/packaging/build.gradle
@@ -21,7 +21,6 @@
 plugins {
   id 'base'
   id 'distribution'
-  id 'signing'
 }
 
 description = 'Solr distribution packaging'
@@ -81,6 +80,10 @@ dependencies {
   docs project(path: ':solr:documentation', configuration: 'minimalSite')
 
   docker project(path: ':solr:docker', configuration: 'packaging')
+
+  solrTgzSignature files("$buildDir/distributions/solr-${version}.tgz.asc") {
+    builtBy ":solr:distribution:signBinaryTgz"
+  }
 }
 
 distributions {
@@ -112,14 +115,6 @@ distributions {
         include "README.md"
       })
 
-      // Should we include Lucene changes?
-      /*
-        from(project(":lucene").projectDir, {
-          include "CHANGES.txt"
-          rename { file -> 'LUCENE_CHANGES.txt' }
-        })
-      */
-
       from(configurations.contrib, {
         into "contrib"
       })
@@ -164,48 +159,8 @@ task dev(type: Copy) {
   into devDir
 }
 
-assemble.dependsOn installDist
-
-
-// NOTE: we don't use the convinence DSL of the 'signing' extension to define 
our 'Sign' tasks because
-// that (by default) adds our signature files to the 'archives' configuration 
-- which is what
-// assemble & installDist try to copy/sync, so they wouldn't work w/o GPG 
installed (which would be bad).
-//
-// We also want to hook in our own property check dependency since the default 
error message from Sign task
-// refers to the wrong (internal only) property name ("signatory.keyId")
-signing {
-  useGpgCmd() // so gpg-agent can be used
-}
-task failUnlessGpgKeyProperty {
-  // placeholder that can be depended on by any task needing GPG key which 
will 'fail fast' if it's not set.
-  def propName = 'signing.gnupg.keyName'
-
-  // This explicitly checks the taskGraph (instead of a simple 'doFirst') so 
it can fail the user's gradle
-  // invocation immediately before any unrelated build tasks may run in 
parallel
-  if ( ! project.hasProperty(propName) ) {
-    gradle.taskGraph.whenReady { graph ->
-      if ( graph.hasTask(failUnlessGpgKeyProperty) ) {
-        // TODO: it would be really nice if taskGraph was an actual graph and 
we could report what tasks in (transitive) depend on us
-        throw new GradleException("'$propName' property must be set for GPG 
signing, please see help/gpgSigning.txt")
-      }
-    }
-  }
-}
-task signDistTar(type: Sign) {
-  dependsOn failUnlessGpgKeyProperty
-  sign configurations.solrTgz
-}
-dependencies {
-  solrTgzSignature files(tasks.signDistTar.signatureFiles.singleFile) {
-    builtBy signDistTar
-  }
-}
-task signDistZip(type: Sign) {
-  dependsOn failUnlessGpgKeyProperty
-  sign configurations.solrZip
-}
-task signDist {
-  group = 'Distribution'
-  description = 'GPG Signs the main distributions'
-  dependsOn signDistTar, signDistZip
+distTar {
+  compression = Compression.GZIP
 }
+
+assemble.dependsOn installDist
\ No newline at end of file

Reply via email to