Copilot commented on code in PR #3964:
URL: https://github.com/apache/solr/pull/3964#discussion_r2628839665


##########
solr/docker/build.gradle:
##########
@@ -158,32 +186,64 @@ task dockerBuild() {
 
   // Ensure that the docker image is rebuilt on build-arg changes or changes 
in the docker context
   inputs.properties([
-          baseDockerImage: baseDockerImage
+          baseDockerImage: baseDockerImage,
+          dockerImageArch: dockerImageArch
   ])
   var solrTgzConfiguration = isImageSlim() ? configurations.solrSlimTgz : 
configurations.solrFullTgz
   inputs.files(solrTgzConfiguration)
   inputs.property("isSlimImage", isImageSlim())
+  inputs.property("isMultiArch", isMultiArch())
   dependsOn(solrTgzConfiguration)
 
   doLast {
-    exec {
-      standardInput = solrTgzConfiguration.singleFile.newDataInputStream()
-      commandLine "docker", "build",
-              "-f", "solr-${ -> project.version 
}${dockerImageDistSuffix}/docker/Dockerfile",
-              "--iidfile", imageIdFile,
-              "--build-arg", "BASE_IMAGE=${ -> 
inputs.properties.baseDockerImage}",
-              "-"
+    def archValue = inputs.properties.dockerImageArch
+    def useSingleArch = !archValue.isEmpty() && !archValue.contains(',')
+
+    // Multi-arch builds are not supported by dockerBuild, only by dockerPush
+    if (!archValue.isEmpty() && archValue.contains(',')) {
+      throw new GradleException("Multi-architecture builds (SOLR_DOCKER_ARCH 
with multiple platforms) are not supported by dockerBuild.\n" +
+              "Please use 'dockerPush' instead, which will build and push the 
multi-architecture image in a single step.\n" +
+              "Example: SOLR_DOCKER_ARCH=linux/arm64,linux/amd64 gradlew 
dockerPush")

Review Comment:
   The error message uses 'gradlew' without './' prefix, which is inconsistent 
with the documentation in docker.txt that uses './gradlew'. Consider using 
'./gradlew' for consistency with the rest of the documentation.
   ```suggestion
                 "Example: SOLR_DOCKER_ARCH=linux/arm64,linux/amd64 ./gradlew 
dockerPush")
   ```



##########
solr/docker/build.gradle:
##########
@@ -234,19 +294,62 @@ task dockerPush(dependsOn: tasks.dockerTag) {
   // Ensure that the docker image is re-pushed if the image ID or tag changes
   inputs.properties([
           dockerImageName: dockerImageName,
+          dockerImageArch: dockerImageArch
   ])
   inputs.file(imageIdFile)
 
   // We don't want to push a docker image unless the tests have passed
   mustRunAfter tasks.testDocker
 
   doLast {
-    exec {
-      commandLine "docker", "push", dockerImageName
-    }
+    def archValue = inputs.properties.dockerImageArch
+    def useMultiArch = !archValue.isEmpty()
+
+    if (useMultiArch && isMultiArch()) {

Review Comment:
   The condition `useMultiArch && isMultiArch()` is redundant since 
`useMultiArch` is set to `!archValue.isEmpty()` and `isMultiArch()` checks both 
`!isEmpty()` and `contains(',')`. This means the else branch at line 346 will 
execute for single-arch builds with an explicit platform (e.g., "linux/arm64"), 
which will attempt a standard `docker push` instead of using the buildx-built 
image. This breaks the single-arch explicit platform workflow that was built in 
dockerBuild.
   ```suggestion
       if (useMultiArch) {
   ```



##########
solr/docker/build.gradle:
##########
@@ -234,19 +294,62 @@ task dockerPush(dependsOn: tasks.dockerTag) {
   // Ensure that the docker image is re-pushed if the image ID or tag changes
   inputs.properties([
           dockerImageName: dockerImageName,
+          dockerImageArch: dockerImageArch
   ])
   inputs.file(imageIdFile)
 
   // We don't want to push a docker image unless the tests have passed
   mustRunAfter tasks.testDocker
 
   doLast {
-    exec {
-      commandLine "docker", "push", dockerImageName
-    }
+    def archValue = inputs.properties.dockerImageArch
+    def useMultiArch = !archValue.isEmpty()
+
+    if (useMultiArch && isMultiArch()) {
+      // Multi-arch push requires buildx and rebuilding with --push
+
+      // Get builder info and validate it exists
+      def builderInfo = getBuildxBuilderInfo()
+
+      // Verify the builder supports the requested platforms (fail fast)
+      def requestedPlatforms = archValue.split(',').collect { it.trim() }
+      def missingPlatforms = []
+      requestedPlatforms.each { platform ->
+        // Check for exact platform match in the builder info
+        // Match pattern like "linux/amd64" or "linux/amd64," or "linux/amd64 
" to avoid partial matches
+        if (!builderInfo.matches("(?s).*\\b" + 
java.util.regex.Pattern.quote(platform) + "\\b.*")) {
+          missingPlatforms.add(platform)
+        }
+      }
 
-    // Print information on the image after it has been created
-    project.logger.lifecycle("Solr Docker Image Pushed: \t$dockerImageName")
+      if (!missingPlatforms.isEmpty()) {
+        throw new GradleException("Current builder does not support all 
requested platforms: ${missingPlatforms}\n" +
+                "Supported platforms: 
${builderInfo.findAll(/linux\/\w+(?:\/\w+)?/).join(', ')}\n" +

Review Comment:
   The regex pattern `/linux\/\w+(?:\/\w+)?/` will only match simple platform 
names and may not capture all supported platforms from the builder info. The 
`findAll()` method is called on a String, which doesn't have this method - it 
should be called on a Matcher. Consider parsing the builder info output more 
reliably to extract supported platforms.
   ```suggestion
           // Extract supported linux/* platforms from the builder info for a 
helpful error message
           def supportedPlatforms = (builderInfo =~ 
/linux\/[0-9A-Za-z_.-]+(?:\/[0-9A-Za-z_.-]+)*/).collect { it[0] }.unique()
           throw new GradleException("Current builder does not support all 
requested platforms: ${missingPlatforms}\n" +
                   "Supported platforms: ${supportedPlatforms.join(', ')}\n" +
   ```



##########
solr/docker/build.gradle:
##########
@@ -34,6 +34,8 @@ def dockerImageRepo = "${ -> 
propertyOrEnvOrDefault("solr.docker.imageRepo", "SO
 def dockerImageTag = "${ -> propertyOrEnvOrDefault("solr.docker.imageTag", 
"SOLR_DOCKER_IMAGE_TAG", project.version + dockerImageDistSuffix) }"
 def dockerImageName = "${ -> propertyOrEnvOrDefault("solr.docker.imageName", 
"SOLR_DOCKER_IMAGE_NAME", 
"${dockerImageRepo}:${dockerImageTag}${dockerImageTagSuffix}") }"
 def baseDockerImage = "${ -> propertyOrEnvOrDefault("solr.docker.baseImage", 
"SOLR_DOCKER_BASE_IMAGE", 'eclipse-temurin:' + javaVersion + '-jre-noble') }"
+def dockerImageArch = "${ -> propertyOrEnvOrDefault("solr.docker.arch", 
"SOLR_DOCKER_ARCH", '') }"
+def isMultiArch = { -> !dockerImageArch.isEmpty() && 
dockerImageArch.contains(',') }

Review Comment:
   The variable name 'dockerImageArch' is potentially misleading as it 
represents platforms (e.g., 'linux/amd64') rather than just architectures 
(e.g., 'amd64'). Consider renaming to 'dockerImagePlatform' or 
'dockerImagePlatforms' to better reflect that it contains platform identifiers 
including the OS.
   ```suggestion
   def dockerImagePlatforms = "${ -> propertyOrEnvOrDefault("solr.docker.arch", 
"SOLR_DOCKER_ARCH", '') }"
   // Backward-compatible alias: dockerImageArch historically referred to what 
is now called dockerImagePlatforms.
   def dockerImageArch = dockerImagePlatforms
   def isMultiArch = { -> !dockerImagePlatforms.isEmpty() && 
dockerImagePlatforms.contains(',') }
   ```



##########
solr/docker/build.gradle:
##########
@@ -234,19 +294,62 @@ task dockerPush(dependsOn: tasks.dockerTag) {
   // Ensure that the docker image is re-pushed if the image ID or tag changes
   inputs.properties([
           dockerImageName: dockerImageName,
+          dockerImageArch: dockerImageArch
   ])
   inputs.file(imageIdFile)
 
   // We don't want to push a docker image unless the tests have passed
   mustRunAfter tasks.testDocker
 
   doLast {
-    exec {
-      commandLine "docker", "push", dockerImageName
-    }
+    def archValue = inputs.properties.dockerImageArch
+    def useMultiArch = !archValue.isEmpty()
+
+    if (useMultiArch && isMultiArch()) {
+      // Multi-arch push requires buildx and rebuilding with --push
+
+      // Get builder info and validate it exists
+      def builderInfo = getBuildxBuilderInfo()
+
+      // Verify the builder supports the requested platforms (fail fast)
+      def requestedPlatforms = archValue.split(',').collect { it.trim() }
+      def missingPlatforms = []
+      requestedPlatforms.each { platform ->
+        // Check for exact platform match in the builder info
+        // Match pattern like "linux/amd64" or "linux/amd64," or "linux/amd64 
" to avoid partial matches
+        if (!builderInfo.matches("(?s).*\\b" + 
java.util.regex.Pattern.quote(platform) + "\\b.*")) {

Review Comment:
   The word boundary matcher `\b` may not work correctly with platform strings 
containing forward slashes. For example, `\b` doesn't match around `/` 
characters, so this pattern could miss valid platforms or incorrectly match 
partial strings. Consider using a more explicit pattern that checks for the 
platform string followed by a comma, newline, or end of string.
   ```suggestion
           // Match pattern like "linux/amd64" or "linux/amd64," or 
"linux/amd64 " to avoid partial matches,
           // using explicit separators (comma/whitespace/start/end) instead of 
word boundaries, since
           // platform strings contain '/' which does not work well with \b.
           if (!builderInfo.matches("(?s).*(^|[\\s,])" + 
java.util.regex.Pattern.quote(platform) + "($|[\\s,]).*")) {
   ```



##########
dev-docs/gradle-help/docker.txt:
##########
@@ -61,6 +75,38 @@ Docker Image Name: (Use this to explicitly set a whole image 
name. If given, the
    EnvVar: SOLR_DOCKER_IMAGE_NAME
    Gradle Property: -Psolr.docker.imageName
 
+Multi-Architecture Builds
+-------
+
+To build a multi-architecture Docker image, use the SOLR_DOCKER_ARCH 
environment variable or Gradle property.
+
+IMPORTANT: Multi-architecture builds (with multiple platforms) are ONLY 
supported by dockerPush, not dockerBuild.
+This is because Docker cannot load multi-platform images into the local daemon.
+
+Prerequisites:
+   - Docker Buildx must be installed (included with Docker Desktop and recent 
Docker Engine versions)
+   - A buildx builder must be configured. If you don't have one, create it:
+     docker buildx create --name solr-builder --use
+
+Build and push for multiple architectures:
+   SOLR_DOCKER_ARCH=linux/arm64,linux/amd64 ./gradlew dockerPush
+
+Using Gradle property:
+   ./gradlew dockerPush -Psolr.docker.arch=linux/arm64,linux/amd64
+
+The dockerPush task will build and push the multi-architecture image in a 
single step using Docker Buildx.
+The task will validate that a suitable builder exists and that it supports all 
requested platforms before building.
+
+Single-Architecture Builds with Explicit Platform
+--------------------------------------------------
+
+You can build for a specific architecture and load it into your local Docker 
daemon:
+
+Build for a specific architecture:
+   SOLR_DOCKER_ARCH=linux/arm64 gradlew dockerBuild

Review Comment:
   The command uses 'gradlew' without './' prefix, which is inconsistent with 
line 92 which uses './gradlew'. Consider using './gradlew' for consistency.



##########
solr/docker/build.gradle:
##########
@@ -158,32 +186,64 @@ task dockerBuild() {
 
   // Ensure that the docker image is rebuilt on build-arg changes or changes 
in the docker context
   inputs.properties([
-          baseDockerImage: baseDockerImage
+          baseDockerImage: baseDockerImage,
+          dockerImageArch: dockerImageArch
   ])
   var solrTgzConfiguration = isImageSlim() ? configurations.solrSlimTgz : 
configurations.solrFullTgz
   inputs.files(solrTgzConfiguration)
   inputs.property("isSlimImage", isImageSlim())
+  inputs.property("isMultiArch", isMultiArch())
   dependsOn(solrTgzConfiguration)
 
   doLast {
-    exec {
-      standardInput = solrTgzConfiguration.singleFile.newDataInputStream()
-      commandLine "docker", "build",
-              "-f", "solr-${ -> project.version 
}${dockerImageDistSuffix}/docker/Dockerfile",
-              "--iidfile", imageIdFile,
-              "--build-arg", "BASE_IMAGE=${ -> 
inputs.properties.baseDockerImage}",
-              "-"
+    def archValue = inputs.properties.dockerImageArch
+    def useSingleArch = !archValue.isEmpty() && !archValue.contains(',')
+
+    // Multi-arch builds are not supported by dockerBuild, only by dockerPush
+    if (!archValue.isEmpty() && archValue.contains(',')) {

Review Comment:
   The variable 'useSingleArch' is defined here but the same logic is 
duplicated on line 203 in the if condition. Consider reusing the 
'useSingleArch' variable or the 'isMultiArch()' function to avoid code 
duplication and potential inconsistencies.
   ```suggestion
       if (!archValue.isEmpty() && !useSingleArch) {
   ```



-- 
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