aboutsummaryrefslogtreecommitdiffstats
path: root/.github/workflows/cross-compiles.yml
blob: e43f4c042967f366b06a48a549d694cac20d6d5d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# Copyright 2021-2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

name: Cross Compile

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    paths-ignore:
      - 'doc/**'
      - '*.md'
      - '*.pod'
      - 'README*'
      - 'funding.json'
      - 'LICENSE.txt'
      - 'VERSION.dat'
  push:
    paths-ignore:
      - 'doc/**'
      - '*.md'
      - '*.pod'
      - 'README*'
      - 'funding.json'
      - 'LICENSE.txt'
      - 'VERSION.dat'

permissions:
  contents: read

jobs:
  cross-compilation:
    name: cross-compilation (${{ matrix.platform.label || matrix.platform.arch }})
    # Run the full test suite on push, and on pull requests labelled with
    # 'extended tests'.  Other pull requests only run the EVP tests.
    env:
      EXTENDED: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'extended tests') }}
    strategy:
      fail-fast: false
      matrix:
        # The platform matrix specifies:
        #   arch: the architecture to build for, this defines the tool-chain
        #         prefix {arch}- and the Debian compiler package gcc-{arch}
        #         name.
        #   libs: the Debian package for the necessary link/runtime libraries.
        #   target: the OpenSSL configuration target to use, this is passed
        #           directly to the config command line.
        #   fips:   set to "no" to disable building FIPS, leave unset to
        #           build the FIPS provider.
        #   tests: omit this to run all the tests using QEMU, set it to "none"
        #          to never run the tests, otherwise its value is passed to
        #          the "make test" command to allow selective disabling of
        #          tests.
        #   qemucpu: optional; string that describes CPU properties.
        #            The string will be used to set the QEMU_CPU variable.
        #   opensslcapsname: optional; string that describes the postfix of the
        #                    OpenSSL environment variable that defines CPU
        #                    capabilities. E.g. "foo" will result in an
        #                    environment variable with the name OPENSSL_foo.
        #   opensslcaps: optional; if opensslcapsname (see above) is set, then
        #                this string will be used as content for the OpenSSL
        #                capabilities variable.
        #   ppa:   Launchpad PPA repository to download packages from.
        #   compiler: optional; set to "clang" to build with Clang instead of
        #             GCC.  The cross-compile prefix is still used for the
        #             binutils (assembler, linker, nm …).
        #   nm_check: optional; when set, a post-build step verifies that the
        #             six ML-KEM VX vector-entry-point symbols are present as
        #             text symbols in the ml_kem_vec128.o object file.
        #   label: optional; used as the artifact name suffix in place of
        #          arch, to disambiguate matrix jobs that share the same arch.
        platform: [
          {
            arch: i386-pc-msdosdjgpp,
            label: djgpp-i386-build,
            libs: libc-djgpp-dev libwatt-djgpp-dev djgpp-utils,
            target: no-threads 386 DJGPP,
            tests: none,
            ppa: jwt27/djgpp-toolchain
          }, {
            arch: aarch64-linux-gnu,
            label: aarch64,
            libs: libc6-dev-arm64-cross,
            target: linux-aarch64,
            fips: no
          }, {
            arch: alpha-linux-gnu,
            label: alpha,
            libs: libc6.1-dev-alpha-cross,
            target: linux-alpha-gcc,
            fips: no
          }, {
            arch: arm-linux-gnueabi,
            label: armel,
            libs: libc6-dev-armel-cross,
            target: linux-armv4,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            arch: arm-linux-gnueabihf,
            label: armhf,
            libs: libc6-dev-armhf-cross,
            target: linux-armv4,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            # gcc hppa seems to have some potential compiler issues
            # with -O2 on this platform, reduce optimization to -01
            arch: hppa-linux-gnu,
            label: hppa-static,
            libs: libc6-dev-hppa-cross,
            target: -static -O1 -mlong-calls linux-generic32,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            arch: m68k-linux-gnu,
            label: m68k-static,
            libs: libc6-dev-m68k-cross,
            target: -static -m68040 linux-latomic -Wno-stringop-overflow,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            arch: mips-linux-gnu,
            label: mips-static,
            libs: libc6-dev-mips-cross,
            target: -static linux-mips32,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            arch: mips64-linux-gnuabi64,
            label: mips64-static,
            libs: libc6-dev-mips64-cross,
            target: -static linux64-mips64,
            fips: no
          }, {
            arch: mipsel-linux-gnu,
            label: mipsel,
            libs: libc6-dev-mipsel-cross,
            target: linux-mips32,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          }, {
            arch: powerpc64le-linux-gnu,
            label: ppc64le,
            libs: libc6-dev-ppc64el-cross,
            target: linux-ppc64le,
            fips: no
          }, {
            arch: riscv64-linux-gnu,
            label: riscv64,
            # Pin the vector spec version, otherwise qemu-user emits a
            # "vector version is not specified" warning on stderr at every
            # process start, which upsets tests that parse or check stderr.
            qemucpu: "rv64,v=true,vext_spec=v1.0",
            libs: libc6-dev-riscv64-cross,
            target: linux64-riscv64,
            fips: no
          }, {
            # Baseline s390x job.  No qemucpu override — QEMU's default TCG
            # model avoids the "CPU model features not available" admission
            # failure that named models (z13, z13-base, …) trigger on x86
            # GitHub Actions runners.  Named z/Architecture models include
            # mandatory CPACF sub-features (kimd, km, klmd, …) that require
            # real s390x hardware or KVM; the TCG emulator cannot satisfy them
            # and QEMU 8.x exits fatally before OpenSSL even starts.
            # z900 zeros every CPACF vector and all stfle facility bits (no VX),
            # so OpenSSL uses the pure-software scalar fallback paths.
            arch: s390x-linux-gnu,
            label: s390x-z900,
            libs: libc6-dev-s390x-cross,
            target: linux64-s390x,
            fips: no,
            opensslcapsname: s390xcap,
            opensslcaps: z900
          }, {
            # Build-only: verify no-asm does not produce undefined references to
            # OPENSSL_s390xcap_P (which is only compiled when asm is enabled).
            arch: s390x-linux-gnu,
            label: s390x-noasm-build,
            libs: libc6-dev-s390x-cross,
            target: no-shared no-asm linux64-s390x,
            fips: no,
            tests: none
          }, {
            # GCC / VX enabled (FIPS).
            # The base linux64-s390x target carries no -march flag, so the
            # whole library is safe to execute on any s390x CPU.
            # ml_kem_vec128.c and ml_dsa_ntt_vec128.c use "#pragma GCC target"
            # to scope VX codegen to those TUs only.
            #
            # Why qemucpu: max instead of z13:
            #   QEMU 8.x named z/Architecture CPU models (z13, z13-base, …)
            #   include mandatory CPACF sub-features (kimd-sha-1, km-aes-*, …)
            #   that the TCG softmmu cannot provide on a non-s390x host.  QEMU
            #   prints a fatal "Some features … not available" warning and exits
            #   with a non-zero status *before* loading the OpenSSL binary.  The
            #   Perl test harness sees the non-zero exit code and marks every
            #   test as failed — even though the OpenSSL code is correct.
            #   "max" enables all features the TCG emulator actually supports
            #   (which includes VX vector-register instructions) without
            #   requiring hardware CPACF, so QEMU starts cleanly.
            #   The OPENSSL_s390xcap override below controls what capability
            #   bits OpenSSL reads at runtime, so VX detection is correct
            #   regardless of which CPU model QEMU uses.
            #
            # The nm_check steps assert VX symbols are present and that the
            # dispatcher (ml_dsa_ntt.o) has unresolved references to them.
            arch: s390x-linux-gnu,
            libs: libc6-dev-s390x-cross,
            target: linux64-s390x,
            qemucpu: max,
            opensslcapsname: s390xcap,
            # Enable the VX facility bit (stfle[2] bit 129 = 0x4000000000000000)
            # while zeroing every CPACF instruction vector (kimd/klmd/km/…).
            # QEMU emulates the VX vector-register instructions but cannot
            # execute CPACF instructions; this lets the VX code paths run
            # under QEMU without triggering illegal-instruction traps.
            opensslcaps: "stfle:0:0:4000000000000000;kimd:0:0;klmd:0:0;km:0:0;kmc:0:0;kmac:0:0;kmctr:0:0;kmo:0:0;kmf:0:0;prno:0:0;kma:0:0;pcc:0:0;kdsa:0:0",
            tests: test_internal_ml_kem test_evp_extra_ml_kem test_ml_kem_codecs test_internal_ml_dsa test_ml_dsa test_ml_dsa_codecs test_evp*,
            nm_check: "yes",
            label: s390x-gcc-z13-vx-on-fips
          }, {
            # GCC / VX masked off (no FIPS).
            # Same base build (linux64-s390x, no -march) as the VX-on job
            # above, but OPENSSL_s390xcap=z900 zeros all stfle facility bits
            # (including VX) so S390X_VX_CAPABLE is false and the generic
            # scalar path is exercised on the same binary.
            # qemucpu: max — see the VX-on job comment for why z13 is avoided.
            arch: s390x-linux-gnu,
            libs: libc6-dev-s390x-cross,
            target: linux64-s390x,
            fips: no,
            qemucpu: max,
            opensslcapsname: s390xcap,
            # z900 zeros all CPACF vectors and all stfle facility bits including
            # VX, exercising the scalar fallback path without QEMU CPACF traps.
            opensslcaps: z900,
            tests: test_internal_ml_kem test_evp_extra_ml_kem test_ml_kem_codecs test_internal_ml_dsa test_ml_dsa test_ml_dsa_codecs test_evp*,
            label: s390x-gcc-z13-vx-off
          }, {
            # Clang / z13 / VX enabled (FIPS).
            # Clang does not honour #pragma GCC target for preprocessor-level
            # macros, so -march=z13 must be passed at compile time to make
            # Clang define __VX__ and enable the VX code paths.  -march=z13
            # is safe to run on any z13+ CPU and does not imply z14 instructions.
            # Configure also adds -fzvector when Clang needs it to access
            # <vecintrin.h>; that flag only unlocks the header, not codegen.
            # Verifies that Clang emits all six ML-KEM and three ML-DSA VX
            # entry points and that the ML-DSA dispatcher references them.
            # qemucpu: max — see the GCC VX-on job comment for why z13 is avoided.
            arch: s390x-linux-gnu,
            libs: libc6-dev-s390x-cross,
            target: -march=z13 linux64-s390x,
            compiler: clang,
            qemucpu: max,
            opensslcapsname: s390xcap,
            opensslcaps: "stfle:0:0:4000000000000000;kimd:0:0;klmd:0:0;km:0:0;kmc:0:0;kmac:0:0;kmctr:0:0;kmo:0:0;kmf:0:0;prno:0:0;kma:0:0;pcc:0:0;kdsa:0:0",
            tests: test_internal_ml_kem test_evp_extra_ml_kem test_ml_kem_codecs test_internal_ml_dsa test_ml_dsa test_ml_dsa_codecs test_evp*,
            nm_check: "yes",
            label: s390x-clang-z13-vx-on-fips
          }, {
            # Clang / z13 / VX masked off (no FIPS).
            # Same -march=z13 build as the Clang VX-on job but
            # OPENSSL_s390xcap=z900 zeros all stfle bits (no VX, no CPACF) so
            # the generic scalar path is exercised on the same binary.
            # qemucpu: max — see the GCC VX-on job comment for why z13 is avoided.
            arch: s390x-linux-gnu,
            libs: libc6-dev-s390x-cross,
            target: -march=z13 linux64-s390x,
            fips: no,
            compiler: clang,
            qemucpu: max,
            opensslcapsname: s390xcap,
            opensslcaps: z900,
            tests: test_internal_ml_kem test_evp_extra_ml_kem test_ml_kem_codecs test_internal_ml_dsa test_ml_dsa test_ml_dsa_codecs test_evp*,
            label: s390x-clang-z13-vx-off
          }, {
            # Clang / z10 baseline / VX enabled (FIPS).
            # Tests the intended per-TU isolation design: the whole library is
            # compiled with -march=z10 (no VX), but Configure detects the s390x
            # target via $target{asm_arch} and runs the feature probe with the
            # effective compiler flags plus -march=z13 -mvx.  Only the two vec128
            # TUs (ml_kem_vec128.c, ml_dsa_ntt_vec128.c) receive -march=z13 -mvx
            # (and -fzvector if Clang needs it); the rest of the library is compiled
            # to the z10 baseline.  This verifies that the scalar fallback objects
            # are correct z10 code while the two vector TUs are valid z13+VX code.
            # qemucpu: max — see the GCC VX-on job comment for why z13 is avoided.
            arch: s390x-linux-gnu,
            libs: libc6-dev-s390x-cross,
            target: -march=z10 linux64-s390x,
            compiler: clang,
            qemucpu: max,
            opensslcapsname: s390xcap,
            opensslcaps: "stfle:0:0:4000000000000000;kimd:0:0;klmd:0:0;km:0:0;kmc:0:0;kmac:0:0;kmctr:0:0;kmo:0:0;kmf:0:0;prno:0:0;kma:0:0;pcc:0:0;kdsa:0:0",
            tests: test_internal_ml_kem test_evp_extra_ml_kem test_ml_kem_codecs test_internal_ml_dsa test_ml_dsa test_ml_dsa_codecs test_evp*,
            nm_check: "yes",
            label: s390x-clang-z10-vx-on-fips
          }, {
            arch: sh4-linux-gnu,
            label: sh4,
            libs: libc6-dev-sh4-cross,
            target: no-async linux-latomic,
            fips: no,
            tests: -test_includes -test_store -test_x509_store
          },

          # These build with shared libraries but they crash when run
          # They mirror static builds above in order to cover more of the
          # code base.
          {
            arch: hppa-linux-gnu,
            label: hppa-build,
            libs: libc6-dev-hppa-cross,
            target: linux-generic32,
            tests: none
          }, {
            arch: m68k-linux-gnu,
            label: m68k-cfv4e-build,
            libs: libc6-dev-m68k-cross,
            target: -mcfv4e -mxgot linux-latomic -Wno-stringop-overflow no-quic,
            tests: none
          }, {
            arch: mips-linux-gnu,
            label: mips-build,
            libs: libc6-dev-mips-cross,
            target: linux-mips32,
            tests: none
          }, {
            arch: mips64-linux-gnuabi64,
            label: mips64-build,
            libs: libc6-dev-mips64-cross,
            target: linux64-mips64,
            tests: none
          },

          # This build doesn't execute either with or without shared libraries.
          {
            arch: sparc64-linux-gnu,
            label: sparc64-build,
            libs: libc6-dev-sparc64-cross,
            target: linux64-sparcv9,
            tests: none
          }
        ]
    runs-on: ubuntu-latest
    steps:
    - name: install package repository
      if: matrix.platform.ppa != ''
      run: |
        sudo add-apt-repository ppa:${{ matrix.platform.ppa }}
    - name: install packages (GCC)
      if: matrix.platform.compiler != 'clang'
      run: |
        sudo apt-get update
        sudo apt-get -yq --allow-unauthenticated --allow-downgrades --allow-remove-essential --allow-change-held-packages install \
            gcc-${{ matrix.platform.arch }} \
            ${{ matrix.platform.libs }}
    - name: install packages (Clang)
      if: matrix.platform.compiler == 'clang'
      run: |
        sudo apt-get update
        sudo apt-get -yq --allow-unauthenticated --allow-downgrades --allow-remove-essential --allow-change-held-packages install \
            clang \
            binutils-${{ matrix.platform.arch }} \
            gcc-${{ matrix.platform.arch }} \
            ${{ matrix.platform.libs }}
        # Detect sysroot: Ubuntu <=22.04 uses .../sys-root, 24.04+ installs directly.
        SYSROOT=/usr/${{ matrix.platform.arch }}/sys-root
        [ -d "$SYSROOT" ] || SYSROOT=/usr/${{ matrix.platform.arch }}
        echo "CROSS_SYSROOT=$SYSROOT" >> $GITHUB_ENV
    - uses: actions/checkout@v6
      with:
        persist-credentials: false
    - name: checkout fuzz/corpora submodule
      run: git submodule update --init --depth 1 fuzz/corpora

    - name: config with FIPS (GCC)
      if: matrix.platform.fips != 'no' && matrix.platform.compiler != 'clang'
      run: |
        ./config --banner=Configured --strict-warnings enable-fips enable-lms \
                 --cross-compile-prefix=${{ matrix.platform.arch }}- \
                 ${{ matrix.platform.target }}
    - name: config without FIPS (GCC)
      if: matrix.platform.fips == 'no' && matrix.platform.compiler != 'clang'
      run: |
        ./config --banner=Configured --strict-warnings enable-lms \
                 --cross-compile-prefix=${{ matrix.platform.arch }}- \
                 ${{ matrix.platform.target }}
    - name: config with FIPS (Clang)
      if: matrix.platform.fips != 'no' && matrix.platform.compiler == 'clang'
      env:
        CC: clang --target=${{ matrix.platform.arch }} --sysroot=${{ env.CROSS_SYSROOT }}
        AR: ${{ matrix.platform.arch }}-ar
        RANLIB: ${{ matrix.platform.arch }}-ranlib
        AS: ${{ matrix.platform.arch }}-as
        # Reset the linker sysroot to / so that absolute-path linker scripts
        # shipped by libc6-dev-*-cross (e.g. libc.so referencing
        # /usr/<arch>/lib/libc.so.6) are resolved against the real root
        # rather than being doubled-prefixed under CROSS_SYSROOT.
        # LDFLAGS is picked up by OpenSSL's Configure and forwarded to the
        # linker only, leaving compiler-detection probes unaffected.
        LDFLAGS: -Wl,--sysroot=/
      run: |
        ./config --banner=Configured --strict-warnings enable-fips enable-lms \
                 ${{ matrix.platform.target }}
    - name: config without FIPS (Clang)
      if: matrix.platform.fips == 'no' && matrix.platform.compiler == 'clang'
      env:
        CC: clang --target=${{ matrix.platform.arch }} --sysroot=${{ env.CROSS_SYSROOT }}
        AR: ${{ matrix.platform.arch }}-ar
        RANLIB: ${{ matrix.platform.arch }}-ranlib
        AS: ${{ matrix.platform.arch }}-as
        LDFLAGS: -Wl,--sysroot=/
      run: |
        ./config --banner=Configured --strict-warnings enable-lms \
                 ${{ matrix.platform.target }}
    - name: config dump
      run: ./configdata.pm --dump

    - name: make
      run: make -s -j4

    - name: check ML-KEM VX vector symbols
      if: matrix.platform.nm_check != ''
      run: |
        ${{ matrix.platform.arch }}-nm crypto/ml_kem/libcrypto-lib-ml_kem_vec128.o \
          | awk '$2 == "T"' \
          | grep -cE ' (ossl_ml_kem_scalar_ntt_vec128|ossl_ml_kem_scalar_inverse_ntt_vec128|ossl_ml_kem_scalar_inverse_ntt_demontgomerize_vec128|ossl_ml_kem_scalar_mult_add_vec128|ossl_ml_kem_inner_product_montgomery_vec128|ossl_ml_kem_matrix_mult_intt_vec128)$' \
          | grep -q '^6$'

    - name: check ML-KEM dispatcher references VX symbols
      # Verify that ml_kem.o (the dispatcher) has unresolved external
      # references (U-type) to all six vec128 functions and also references
      # OPENSSL_s390xcap_P.  This proves that the dispatch block in
      # ml_kem_ntt_init() was compiled in — the standalone vec128 object
      # check above only verifies that the symbols exist in the vec128 file,
      # not that the dispatcher actually calls them at runtime.
      # Note: 'nm' omits the address column for undefined symbols, so
      # undefined entries look like "                 U symbol_name" and
      # $1 == "U" is the correct awk field (not $2).
      if: matrix.platform.nm_check != ''
      run: |
        ${{ matrix.platform.arch }}-nm crypto/ml_kem/libcrypto-lib-ml_kem.o \
          | awk '$1 == "U"' \
          | grep -cE ' (ossl_ml_kem_scalar_ntt_vec128|ossl_ml_kem_scalar_inverse_ntt_vec128|ossl_ml_kem_scalar_inverse_ntt_demontgomerize_vec128|ossl_ml_kem_scalar_mult_add_vec128|ossl_ml_kem_inner_product_montgomery_vec128|ossl_ml_kem_matrix_mult_intt_vec128)$' \
          | grep -q '^6$'
        ${{ matrix.platform.arch }}-nm crypto/ml_kem/libcrypto-lib-ml_kem.o \
          | awk '$1 == "U"' \
          | grep -q ' OPENSSL_s390xcap_P$'

    - name: check ML-DSA VX vector symbols
      if: matrix.platform.nm_check != ''
      run: |
        ${{ matrix.platform.arch }}-nm crypto/ml_dsa/libcrypto-lib-ml_dsa_ntt_vec128.o \
          | awk '$2 == "T"' \
          | grep -cE ' (ossl_ml_dsa_poly_ntt_vec128|ossl_ml_dsa_poly_ntt_inverse_vec128|ossl_poly_ntt_mult_scalar_vec128)$' \
          | grep -q '^3$'

    - name: check ML-DSA dispatcher references VX symbols
      # Verify that ml_dsa_ntt.o (the dispatcher) has unresolved external
      # references (U-type) to all three vec128 functions.  This proves the
      # dispatch block was compiled in — the standalone object check above
      # only verifies that the symbols exist in the vec128 file, not that
      # the dispatcher actually calls them.
      if: matrix.platform.nm_check != ''
      run: |
        ${{ matrix.platform.arch }}-nm crypto/ml_dsa/libcrypto-lib-ml_dsa_ntt.o \
          | awk '$1 == "U"' \
          | grep -cE ' (ossl_ml_dsa_poly_ntt_vec128|ossl_ml_dsa_poly_ntt_inverse_vec128|ossl_poly_ntt_mult_scalar_vec128)$' \
          | grep -q '^3$'

    - name: install qemu
      if: matrix.platform.tests != 'none'
      run: sudo apt-get -yq --allow-unauthenticated --allow-downgrades --allow-remove-essential --allow-change-held-packages install qemu-user

    - name: Set QEMU environment
      if: matrix.platform.qemucpu != ''
      run: echo "QEMU_CPU=${{ matrix.platform.qemucpu }}" >> $GITHUB_ENV

    - name: Set OpenSSL caps environment
      if: matrix.platform.opensslcapsname != ''
      run: |
        echo "OPENSSL_${{ matrix.platform.opensslcapsname }}=${{ matrix.platform.opensslcaps }}" >> "$GITHUB_ENV"

    - name: get cpu info
      if: matrix.platform.tests != 'none'
      run: |
         cat /proc/cpuinfo
         QEMU_LD_PREFIX=/usr/${{ matrix.platform.arch }} ./util/opensslwrap.sh version -c
    - name: make all tests
      if: env.EXTENDED == 'true' && matrix.platform.tests == ''
      run: |
        .github/workflows/make-test \
                  TESTS="-test_afalg" \
                  QEMU_LD_PREFIX=/usr/${{ matrix.platform.arch }}
    - name: make some tests
      if: >-
        matrix.platform.tests != 'none' &&
        matrix.platform.tests != '' &&
        (env.EXTENDED == 'true' || matrix.platform.arch == 's390x-linux-gnu')
      run: |
        .github/workflows/make-test \
                  TESTS="${{ matrix.platform.tests }} -test_afalg" \
                  QEMU_LD_PREFIX=/usr/${{ matrix.platform.arch }}
    - name: make evp tests
      if: >-
        env.EXTENDED != 'true' &&
        matrix.platform.tests != 'none' &&
        (matrix.platform.tests == '' || matrix.platform.arch != 's390x-linux-gnu')
      run: |
        .github/workflows/make-test \
                  TESTS="test_evp*" \
                  QEMU_LD_PREFIX=/usr/${{ matrix.platform.arch }}
    - name: save artifacts
      if: success() || failure()
      uses: actions/upload-artifact@v5
      with:
        name: "cross-compiles@${{ matrix.platform.label || matrix.platform.arch }}"
        path: artifacts.tar.gz
        if-no-files-found: ignore