@@ -18,90 +18,117 @@ package nextflow.processor
1818
1919import groovy.transform.CompileStatic
2020import groovy.transform.EqualsAndHashCode
21- import groovy.transform.ToString
21+ import groovy.transform.Memoized
2222
2323/**
2424 * Implements the {@code arch } process directive, to hold information on the
2525 * CPU (micro)architecture required by the process.
2626 *
27+ * <p >Supports multiple comma-separated architectures (e.g. {@code arch 'linux/amd64,linux/arm64' }).
28+ * Multi-arch is fully supported by selected executors (e.g. Seqera) via {@link #platforms()} and
29+ * {@link #containerPlatform()}. Other executors use {@link #getDockerArch()} and {@link #getSpackArch()},
30+ * which return only the first (primary) architecture.
31+ *
2732 * @author Marco De La Pierre <marco.delapierre@gmail.com>
33+ * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
2834 */
29- @ToString
3035@EqualsAndHashCode
3136@CompileStatic
3237class Architecture {
3338
34- /*
35- * example of notation in process: arch 'linux/x86_64', target: 'haswell'
36- * example of notation in config: arch = [name: 'linux/x86_64', target: 'haswell']
37- *
38- * where dockerArch = 'linux/x86_64'
39- * spackArch = target ?: arch // plus some validation for Spack syntax
40- *
41- * platform = 'linux'
42- * arch = 'x86_64'
43- * target = 'haswell'
44- *
45- * [alternate example: 'arch linux/arm/v8', where platform = 'linux' and arch = 'arm/v8']
46- */
47- // used in Nextflow
48- final String dockerArch
49- final String spackArch
50-
51- // defined, but currently not used
52- final String platform
53- final String arch
54- final String target
55-
56- static protected String getPlatform ( String value ) {
57- // return value.minus(~'/.*') // keeping for reference
58- final chunks = value. tokenize(' /' )
59- if ( chunks. size() > 1 )
60- return chunks[0 ]
61- else
62- return null
39+ @EqualsAndHashCode
40+ @CompileStatic
41+ static class ArchEntry {
42+ final String value
43+
44+ static protected String normalize ( String name ) {
45+ def chunks = name. tokenize(' /' )
46+ if ( chunks. size() == 3 )
47+ return chunks[1 ] + ' /' + chunks[2 ]
48+ else if ( chunks. size() == 2 )
49+ return chunks[1 ]
50+ else
51+ return chunks[0 ]
52+ }
53+
54+ static private void validate ( String value , String name ) {
55+ if ( value == ' x86_64' || value == ' amd64' )
56+ return
57+ if ( value == ' aarch64' || value == ' arm64' || value == ' arm64/v8' )
58+ return
59+ if ( value == ' arm64/v7' )
60+ return
61+ throw new IllegalArgumentException (" Not a valid `arch` value: ${ name} " )
62+ }
63+
64+ static ArchEntry parse (String name ) {
65+ final value = normalize(name)
66+ validate(value, name)
67+ return new ArchEntry (value)
68+ }
69+
70+ private ArchEntry (String value ) {
71+ this . value = value
72+ }
73+
74+ @Override
75+ String toString () {
76+ return value
77+ }
6378 }
6479
65- static protected String getArch ( String value ) {
66- // return value.minus(~'.*/') // keeping for reference
67- def chunks = value . tokenize( ' / ' )
68- if ( chunks . size() == 3 )
69- return chunks[ 1 ] + ' / ' + chunks[ 2 ]
70- else if ( chunks . size() == 2 )
71- return chunks[ 1 ]
72- else
73- return chunks[ 0 ]
80+ private final List< ArchEntry > entries
81+
82+ private final String target
83+
84+ /**
85+ * @return all architectures as Docker platform strings (e.g. { @code ['linux/amd64', 'linux/arm64'] } )
86+ */
87+ List< String > platforms () {
88+ entries . collect(it -> toDockerArch(it))
7489 }
7590
76- static private String validateArchToDockerArch ( Map res ) {
77- def value = getArch(res. name as String )
78- def name = res. name as String
91+ /**
92+ * @return all architectures as a comma-separated Docker platform string
93+ * (e.g. {@code 'linux/amd64,linux/arm64' })
94+ */
95+ String containerPlatform () {
96+ platforms(). join(' ,' )
97+ }
98+
99+ static private String toDockerArch (ArchEntry arch ) {
100+ final value = arch. value
79101 if ( value == ' x86_64' || value == ' amd64' )
80102 return ' linux/amd64'
81103 if ( value == ' aarch64' || value == ' arm64' || value == ' arm64/v8' )
82104 return ' linux/arm64'
83105 if ( value == ' arm64/v7' )
84106 return ' linux/arm64/v7'
85- throw new IllegalArgumentException (" Not a valid `arch` value: ${ name} " )
107+ return null
108+ }
109+
110+ /**
111+ * @return the Docker platform string for the first (primary) architecture
112+ */
113+ @Memoized
114+ String getDockerArch () {
115+ return toDockerArch(entries[0 ])
86116 }
87117
88- static private String validateArchToSpackArch ( String value , String inputArch ) {
118+ /**
119+ * @return the Spack-compatible architecture name for the first (primary) architecture,
120+ * or the explicit {@code target } microarchitecture if specified
121+ */
122+ @Memoized
123+ String getSpackArch () {
124+ if ( target != null )
125+ return target
126+ final value = entries[0 ]. value
89127 if ( value == ' x86_64' || value == ' amd64' )
90128 return ' x86_64'
91129 if ( value == ' aarch64' || value == ' arm64' || value == ' arm64/v8' )
92130 return ' aarch64'
93- if ( value == ' arm64/v7' )
94- return null
95- throw new IllegalArgumentException (" Not a valid `arch` value: ${ inputArch} " )
96- }
97-
98- static protected String getSpackArch ( Map res ) {
99- if ( res. target != null )
100- return res. target as String
101- else if ( res. name != null )
102- return validateArchToSpackArch(getArch(res. name as String ), res. name as String )
103- else
104- return null
131+ return null
105132 }
106133
107134 Architecture ( String value ) {
@@ -112,14 +139,15 @@ class Architecture {
112139 if ( ! res. name )
113140 throw new IllegalArgumentException (" Missing architecture `name` attribute" )
114141
115- this . dockerArch = validateArchToDockerArch(res)
116- this . platform = getPlatform(res. name as String )
117- this . arch = getArch(res. name as String )
142+ this . target = res. target as String
143+ this . entries = (res. name as String )
144+ .tokenize(' ,' )
145+ .collect(it -> ArchEntry . parse(it. trim()) )
146+ }
118147
119- if ( res. target != null )
120- this . target = res. target as String
121- if ( res. name!= null || res. target!= null )
122- this . spackArch = getSpackArch(res)
148+ @Override
149+ String toString () {
150+ return platforms(). join(' ,' )
123151 }
124152
125153}
0 commit comments