From a7a308b03cbebd0e919e85c7aceba5c4641f0e2d Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 24 Mar 2026 20:53:50 +0100 Subject: [PATCH 1/3] Add multi-platform build support Allow specifying multiple platforms (e.g., linux/amd64,linux/arm64) via the --platform option. Adds validation to reject multi-platform with Singularity format, mirror mode, and image-only builds. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/src/main/java/io/seqera/wave/cli/App.java | 23 ++++++-- .../groovy/io/seqera/wave/cli/AppTest.groovy | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/seqera/wave/cli/App.java b/app/src/main/java/io/seqera/wave/cli/App.java index c7976f9..ed9eb32 100644 --- a/app/src/main/java/io/seqera/wave/cli/App.java +++ b/app/src/main/java/io/seqera/wave/cli/App.java @@ -126,7 +126,7 @@ public class App implements Runnable { @Option(names = {"--freeze", "-F"}, paramLabel = "false", description = "Request a container freeze.") private boolean freeze; - @Option(names = {"--platform"}, paramLabel = "''", description = "Platform to be used for the container build. One of: linux/amd64, linux/arm64.") + @Option(names = {"--platform"}, paramLabel = "''", description = "Platform to be used for the container build. One of: linux/amd64, linux/arm64, linux/amd64,linux/arm64.") private String platform; @Option(names = {"--await"}, paramLabel = "false", arity = "0..1", description = "Await the container build to be available. you can provide a timeout like --await 10m or 2s, by default its 15 minutes.") @@ -420,9 +420,26 @@ protected void validateArgs() { if( dryRun && await != null ) throw new IllegalCliArgumentException("Options --dry-run and --await conflicts each other"); - if( !isEmpty(platform) && !VALID_PLATFORMS.contains(platform) ) - throw new IllegalCliArgumentException(String.format("Unsupported container platform: '%s'", platform)); + if( !isEmpty(platform) ) { + for( String p : platform.split(",") ) { + if( !VALID_PLATFORMS.contains(p.trim()) ) + throw new IllegalCliArgumentException(String.format("Unsupported container platform: '%s'", platform)); + } + } + + if( isMultiPlatform() && singularity ) + throw new IllegalCliArgumentException("Multi-platform builds are not supported for Singularity format"); + + if( isMultiPlatform() && mirror ) + throw new IllegalCliArgumentException("Multi-platform builds and --mirror conflict each other"); + + if( isMultiPlatform() && !isEmpty(image) && isEmpty(containerFile) && condaPackages==null && isEmpty(condaFile) && cranPackages==null ) + throw new IllegalCliArgumentException("Multi-platform builds require a container file or packages to build"); + + } + private boolean isMultiPlatform() { + return !isEmpty(platform) && platform.contains(","); } protected Client client() { diff --git a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy index 30f3e93..c3c41ee 100644 --- a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy +++ b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy @@ -631,6 +631,60 @@ class AppTest extends Specification { e.getMessage() == "Option --mirror and requires the use of a build repository" } + def 'should set multi-platform via --platform' () { + given: + def app = new App() + String[] args = ["--platform", "linux/amd64,linux/arm64", "-f", "Dockerfile"] + + when: + new CommandLine(app).parseArgs(args) + + then: + app.@platform == "linux/amd64,linux/arm64" + } + + def 'should fail when specifying multi-platform and singularity' () { + given: + def app = new App() + String[] args = ["--platform", "linux/amd64,linux/arm64", "--singularity", "--freeze", "-f", "Dockerfile"] + + when: + new CommandLine(app).parseArgs(args) + app.validateArgs() + + then: + def e = thrown(IllegalCliArgumentException) + e.getMessage() == "Multi-platform builds are not supported for Singularity format" + } + + def 'should fail when specifying multi-platform and mirror' () { + given: + def app = new App() + String[] args = ["--platform", "linux/amd64,linux/arm64", "--mirror", "--image", "foo", "--build-repo", "bar", "--tower-token", "tok"] + + when: + new CommandLine(app).parseArgs(args) + app.validateArgs() + + then: + def e = thrown(IllegalCliArgumentException) + e.getMessage() == "Multi-platform builds and --mirror conflict each other" + } + + def 'should fail when specifying multi-platform with only image' () { + given: + def app = new App() + String[] args = ["--platform", "linux/amd64,linux/arm64", "--image", "ubuntu:latest"] + + when: + new CommandLine(app).parseArgs(args) + app.validateArgs() + + then: + def e = thrown(IllegalCliArgumentException) + e.getMessage() == "Multi-platform builds require a container file or packages to build" + } + @Unroll def 'should check service version'() { given: From 102b325a01be09c5cdd1581f63b55f8e4bd567ff Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 24 Mar 2026 20:57:35 +0100 Subject: [PATCH 2/3] Update examples Signed-off-by: Paolo Di Tommaso --- app/src/main/resources/io/seqera/wave/cli/usage-examples.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/resources/io/seqera/wave/cli/usage-examples.txt b/app/src/main/resources/io/seqera/wave/cli/usage-examples.txt index b02baf6..91a1679 100644 --- a/app/src/main/resources/io/seqera/wave/cli/usage-examples.txt +++ b/app/src/main/resources/io/seqera/wave/cli/usage-examples.txt @@ -9,9 +9,12 @@ Examples: # Build a container based on Conda packages wave --conda-package bamtools=2.5.2 --conda-package samtools=1.17 - # Build a container based on Conda packages using arm64 architecture + # Build a container based on Conda packages using arm64 CPU architecture wave --conda-package fastp --platform linux/arm64 + # Build a container based on Conda packages using both amd64 and arm64 CPU architectures + wave --conda-package fastp --platform linux/amd64,linux/arm64 + # Build a container based on Conda lock file served via prefix.dev service wave --conda-package https://prefix.dev/envs/pditommaso/wave/conda-lock.yml From dd3ecd55270f684cbbf47ddf83006dd883ced81b Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 24 Mar 2026 21:21:27 +0100 Subject: [PATCH 3/3] Clarify --platform option description for multi-platform builds Co-Authored-By: Claude Opus 4.6 (1M context) --- app/src/main/java/io/seqera/wave/cli/App.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/seqera/wave/cli/App.java b/app/src/main/java/io/seqera/wave/cli/App.java index ed9eb32..6957c7e 100644 --- a/app/src/main/java/io/seqera/wave/cli/App.java +++ b/app/src/main/java/io/seqera/wave/cli/App.java @@ -126,7 +126,7 @@ public class App implements Runnable { @Option(names = {"--freeze", "-F"}, paramLabel = "false", description = "Request a container freeze.") private boolean freeze; - @Option(names = {"--platform"}, paramLabel = "''", description = "Platform to be used for the container build. One of: linux/amd64, linux/arm64, linux/amd64,linux/arm64.") + @Option(names = {"--platform"}, paramLabel = "''", description = "Platform to be used for the container build: linux/amd64, linux/arm64, or both separated by comma for multi-platform builds.") private String platform; @Option(names = {"--await"}, paramLabel = "false", arity = "0..1", description = "Await the container build to be available. you can provide a timeout like --await 10m or 2s, by default its 15 minutes.")