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..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.") + @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.") @@ -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/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 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: