Skip to content

Validation

Validation #646

Workflow file for this run

name: Validation
on:
workflow_dispatch:
inputs:
num_events:
description: 'number of events (keep it small, since 1000 will take around 6 hours)'
required: false
type: string
workflow_call:
inputs:
num_events:
description: 'override default number of events'
required: false
type: string
matrix_evgen:
description: 'override default event generation types to run, the env.matrix_evgen JSON list'
required: false
type: string
matrix_config:
description: 'override default list of config files to run (defined in clas12-config)'
required: false
type: string
config_file_versions:
description: 'override default versions of config files, the full env.config_file_versions JSON object'
required: false
type: string
git_upstream:
description: 'override default forks and refs, env.git_upstream JSON object elements (you do not have to specify all of them, just the ones you want to override)'
required: false
type: string
pull_request:
push:
branches: [ main ]
tags: [ '*' ]
# apply cancel-in-progress to:
# - `pull_request` triggers from the same `head_ref` (PR feature branch) and caller repo (set by `repository_id`)
# - all other triggers: allow only the most recent of its trigger type (set by `event_name`, e.g., `push`) and caller repo
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.event_name }}-${{ github.repository_id }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
# default number of events
num_events: 10
# default event generation types to run; see below for all available types and their options
matrix_evgen: >-
[
"e_K",
"e_KC",
"e_n",
"e_g",
"e_nC",
"eFT_K",
"e_gFT"
]
# default forks and branches; if `ref` is an empty string, the highest semver tag will be used
git_upstream: >-
{
"coatjava": { "fork": "JeffersonLab/coatjava", "ref": "development" },
"clas12Tags": { "fork": "gemc/clas12Tags", "ref": "" },
"clas12-config": { "fork": "JeffersonLab/clas12-config", "ref": "main" },
"clas12-validation": { "fork": "JeffersonLab/clas12-validation", "ref": "main" }
}
# default versions of config files
config_file_versions: >-
{
"coatjava": "latest",
"gemc": "latest"
}
# available event generation types, together with their options
evgen_opts: >-
{
"e_p": "-pid 2212",
"e_K": "-pid 321",
"e_pi": "-pid 211",
"e_g": "-pid 22",
"e_n": "-pid 2112",
"e_gFT": "-pid 22 -ft",
"e_pC": "-pid 2212 -cd",
"e_KC": "-pid 321 -cd",
"e_piC": "-pid 211 -cd",
"e_nC": "-pid 2112 -cd",
"e_dC": "-pid 45 -cd",
"eFT_pi": "-pid -211 -ft",
"eFT_p": "-pid -2212 -ft",
"eFT_K": "-pid -321 -ft"
}
# additional settings
java_version: 17
java_distribution: zulu
groovy_version: 4.x
jobs:
# context dump
#############################################################################
context:
name: GitHub context
runs-on: ubuntu-latest
steps:
- name: dump context
run: |
echo $GITHUB_CONTEXT > context.json
jq . context.json
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
# dependency and source info
#############################################################################
dependency_info:
name: Dependency info
runs-on: ubuntu-latest
outputs:
fork_coatjava: ${{ steps.info.outputs.fork_coatjava }}
ref_coatjava: ${{ steps.info.outputs.ref_coatjava }}
fork_clas12tags: ${{ steps.info.outputs.fork_clas12tags }}
ref_clas12tags: ${{ steps.info.outputs.ref_clas12tags }}
fork_clas12config: ${{ steps.info.outputs.fork_clas12config }}
ref_clas12config: ${{ steps.info.outputs.ref_clas12config }}
fork_clas12validation: ${{ steps.info.outputs.fork_clas12validation }}
ref_clas12validation: ${{ steps.info.outputs.ref_clas12validation }}
gemc_module_tag: ${{ steps.gemc_module_tag.outputs.gemc_module_tag }}
gemc_executable: ${{ steps.gemc_executable.outputs.gemc_executable }}
caller_repo: ${{ steps.gemc_executable.outputs.caller_repo }}
steps:
- name: checkout clas12-validation
uses: actions/checkout@v4
with:
repository: JeffersonLab/clas12-validation
# ref: feature-branch # NOTE: if making a clas12-validation change that impacts this job, temporarily set the corresponding ref here
- name: get dependency info
id: info
run: |
echo '${{ env.git_upstream }}' > upstream.json
echo '${{ inputs.git_upstream || env.git_upstream }}' > caller_overrides.json
echo '{ "${{ github.event.repository.name }}": { "fork": "${{ github.event.pull_request.head.repo.full_name || github.repository }}", "ref": "${{ github.head_ref || github.ref_name }}" }}' > source.json
jq -sc add upstream.json caller_overrides.json source.json > deps.json
### get fork names and refs
fork_coatjava=$(jq -r '."coatjava".fork' deps.json)
fork_clas12tags=$(jq -r '."clas12Tags".fork' deps.json)
fork_clas12config=$(jq -r '."clas12-config".fork' deps.json)
fork_clas12validation=$(jq -r '."clas12-validation".fork' deps.json)
ref_coatjava=$(jq -r '."coatjava".ref' deps.json)
ref_clas12tags=$(jq -r '."clas12Tags".ref' deps.json)
ref_clas12config=$(jq -r '."clas12-config".ref' deps.json)
ref_clas12validation=$(jq -r '."clas12-validation".ref' deps.json)
### if any ref names are empty strings, use the highest tag instead
[ "$ref_coatjava" = "" ] && ref_coatjava=$(bin/get_highest_tag.rb JeffersonLab/coatjava) # NOTE: using the main forks here, since they have the semver tags
[ "$ref_clas12tags" = "" ] && ref_clas12tags=$(bin/get_highest_tag.rb gemc/clas12Tags)
[ "$ref_clas12config" = "" ] && ref_clas12config=$(bin/get_highest_tag.rb JeffersonLab/clas12-config)
[ "$ref_clas12validation" = "" ] && ref_clas12validation=$(bin/get_highest_tag.rb JeffersonLab/clas12-validation)
### set output vars
echo fork_coatjava=$fork_coatjava >> $GITHUB_OUTPUT
echo fork_clas12tags=$fork_clas12tags >> $GITHUB_OUTPUT
echo fork_clas12config=$fork_clas12config >> $GITHUB_OUTPUT
echo fork_clas12validation=$fork_clas12validation >> $GITHUB_OUTPUT
echo ref_coatjava=$ref_coatjava >> $GITHUB_OUTPUT
echo ref_clas12tags=$ref_clas12tags >> $GITHUB_OUTPUT
echo ref_clas12config=$ref_clas12config >> $GITHUB_OUTPUT
echo ref_clas12validation=$ref_clas12validation >> $GITHUB_OUTPUT
- name: set GEMC module tag
id: gemc_module_tag
run: |
### if `ref_clas12tags` is a tag, use that; otherwise use the highest semver tag
### FIXME probably won't work for triggers from backports to old `clas12Tags` tags
gemc_module_tag=${{ steps.info.outputs.ref_clas12tags }}
repo=gemc/clas12Tags
bin/is_a_tag.rb $gemc_module_tag $repo || gemc_module_tag=$(bin/get_highest_tag.rb $repo)
echo gemc_module_tag=$gemc_module_tag >> $GITHUB_OUTPUT
- name: set GEMC executable
id: gemc_executable
run: |
### if the caller repo is `clas12Tags`, we want to locally build GEMC; if not, use the container version
caller_repo=$(echo "${{ github.repository }}" | sed 's;^.*/;;g')
[ "$caller_repo" = "clas12Tags" ] && gemc_executable=./clas12Tags/source/gemc || gemc_executable=gemc
echo gemc_executable=$gemc_executable >> $GITHUB_OUTPUT
echo caller_repo=$caller_repo >> $GITHUB_OUTPUT
- name: dispatch summary
run: |
msg=$(echo '${{ github.event.pull_request.title || github.event.head_commit.message }}' | head -n1)
echo '| | | |' >> $GITHUB_STEP_SUMMARY
echo '| --- | --- | --- |' >> $GITHUB_STEP_SUMMARY
echo '| **Triggered By `${{ github.event_name }}`:** | <${{ github.event.pull_request.html_url || github.event.head_commit.url }}> | ' $msg ' |' >> $GITHUB_STEP_SUMMARY
echo '| **`coatjava` Fork and Ref:** | `${{ steps.info.outputs.fork_coatjava }}` | [`${{ steps.info.outputs.ref_coatjava }}`](https://github.com/${{ steps.info.outputs.fork_coatjava }}/tree/${{ steps.info.outputs.ref_coatjava }}) |' >> $GITHUB_STEP_SUMMARY
echo '| **`clas12Tags` Fork and Ref:** | `${{ steps.info.outputs.fork_clas12tags }}` | [`${{ steps.info.outputs.ref_clas12tags }}`](https://github.com/${{ steps.info.outputs.fork_clas12tags }}/tree/${{ steps.info.outputs.ref_clas12tags }}) |' >> $GITHUB_STEP_SUMMARY
echo '| **`clas12-config` Fork and Ref:** | `${{ steps.info.outputs.fork_clas12config }}` | [`${{ steps.info.outputs.ref_clas12config }}`](https://github.com/${{ steps.info.outputs.fork_clas12config }}/tree/${{ steps.info.outputs.ref_clas12config }}) |' >> $GITHUB_STEP_SUMMARY
echo '| **`clas12-validation` Fork and Ref:** | `${{ steps.info.outputs.fork_clas12validation }}` | [`${{ steps.info.outputs.ref_clas12validation }}`](https://github.com/${{ steps.info.outputs.fork_clas12validation }}/tree/${{ steps.info.outputs.ref_clas12validation }}) |' >> $GITHUB_STEP_SUMMARY
echo '| | | |' >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '- Caller repository: `${{ steps.gemc_executable.outputs.caller_repo }}`' >> $GITHUB_STEP_SUMMARY
echo '- GEMC module: `gemc/${{ steps.gemc_module_tag.outputs.gemc_module_tag }}`' >> $GITHUB_STEP_SUMMARY
echo '- GEMC executable: `${{ steps.gemc_executable.outputs.gemc_executable }}`' >> $GITHUB_STEP_SUMMARY
# build
#############################################################################
build_coatjava:
name: Build coatjava
needs:
- dependency_info
runs-on: ubuntu-latest
steps:
- name: setup java
uses: actions/setup-java@v4
with:
java-version: ${{ env.java_version }}
distribution: ${{ env.java_distribution }}
- name: checkout coatjava
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_coatjava }}
ref: ${{ needs.dependency_info.outputs.ref_coatjava }}
path: coatjava
clean: false
fetch-tags: true
fetch-depth: 0
- name: build
uses: Wandalen/wretry.action@a163f62ae554a8f3cbe27b23db15b60c0ae2e93c # using wretry.action, to mitigate sporadic maven repo connection failures
with:
attempt_limit: 3
attempt_delay: 10000
command: 'cd coatjava ; ./build-coatjava.sh -T4'
- name: tree
run: tree
- name: tar
run: tar cavf coatjava{.tar.zst,}
- uses: actions/upload-artifact@v4
with:
name: build.coatjava
retention-days: 1
path: ./*.tar.zst
build_gemc:
name: Build GEMC
if: ${{ needs.dependency_info.outputs.caller_repo == 'clas12Tags' }} # only build GEMC if triggered by GEMC/clas12Tags
needs:
- dependency_info
runs-on: ubuntu-latest
steps:
- name: checkout clas12-validation
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12validation }}
ref: ${{ needs.dependency_info.outputs.ref_clas12validation }}
clean: false
fetch-tags: true
fetch-depth: 0
- name: checkout clas12Tags
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12tags }}
ref: ${{ needs.dependency_info.outputs.ref_clas12tags }}
path: clas12Tags
clean: false
fetch-tags: true
fetch-depth: 0
- name: build
uses: docker://jeffersonlab/gemc:dev-fedora36
with:
entrypoint: bin/ci_gemc_build.sh
args: clas12Tags/source
- name: tar
run: tar cavf clas12Tags{.tar.zst,}
- uses: actions/upload-artifact@v4
with:
name: build.gemc
retention-days: 1
path: ./*.tar.zst
# clas12-config configuration
#############################################################################
config_files:
name: Configuration files
needs:
- dependency_info
runs-on: ubuntu-latest
outputs:
num_events: ${{ steps.num_events.outputs.num_events }}
tag_config_coatjava: ${{ steps.version.outputs.tag_config_coatjava }}
tag_config_gemc: ${{ steps.version.outputs.tag_config_gemc }}
matrix_evgen: ${{ steps.read_matrices.outputs.matrix_evgen }}
matrix_config: ${{ steps.read_matrices.outputs.matrix_config }}
matrix_full: ${{ steps.matrix_full.outputs.matrix_full }}
steps:
- name: checkout clas12-validation
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12validation }}
ref: ${{ needs.dependency_info.outputs.ref_clas12validation }}
clean: false
fetch-tags: true
fetch-depth: 0
- name: checkout clas12-config
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12config }}
ref: ${{ needs.dependency_info.outputs.ref_clas12config }}
path: clas12-config
clean: false
fetch-tags: true
fetch-depth: 0
- name: set number of events
id: num_events
run: |
num_events=${{ inputs.num_events || env.num_events }}
echo num_events=$num_events >> $GITHUB_OUTPUT
echo "### Number of Events:" >> $GITHUB_STEP_SUMMARY
echo "$num_events" >> $GITHUB_STEP_SUMMARY
- name: get version info
id: version
working-directory: clas12-config
run: |
tag_config_coatjava=$(echo '${{ inputs.config_file_versions || env.config_file_versions }}' | jq -r '.coatjava')
tag_config_gemc=$(echo '${{ inputs.config_file_versions || env.config_file_versions }}' | jq -r '.gemc')
# job summary
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Versions:" >> $GITHUB_STEP_SUMMARY
echo "| Configuration File Set | Version |" >> $GITHUB_STEP_SUMMARY
echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
echo "| \`coatjava\` | ${tag_config_coatjava} |" >> $GITHUB_STEP_SUMMARY
echo "| \`gemc\` | ${tag_config_gemc} |" >> $GITHUB_STEP_SUMMARY
# output vars
echo tag_config_coatjava=$tag_config_coatjava >> $GITHUB_OUTPUT
echo tag_config_gemc=$tag_config_gemc >> $GITHUB_OUTPUT
- name: read matrices
id: read_matrices
run: |
echo matrix_evgen=$(jq -c . <(echo '{ "evgen": ${{ inputs.matrix_evgen || env.matrix_evgen }} }')) >> $GITHUB_OUTPUT
if [ -z "${{ inputs.matrix_config }}" ] ; then
config_list=$(jq -c . clas12-config/.github/ci_config_files.json)
else
config_list=$(jq -c . <(echo '${{ inputs.matrix_config }}'))
fi
echo matrix_config=$(jq -c . <(echo '{ "config":' $config_list '}')) >> $GITHUB_OUTPUT
- name: make full job matrix
id: matrix_full
run: echo matrix_full=$(jq -sc add <(echo '${{ steps.read_matrices.outputs.matrix_evgen }}') <(echo '${{ steps.read_matrices.outputs.matrix_config }}')) >> $GITHUB_OUTPUT
- name: summary
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Configuration Job Matrix:" >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
echo '${{ steps.matrix_full.outputs.matrix_full }}' | jq | xargs -0 -I{} echo {} >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: find config files
working-directory: clas12-config
run: |
for config_file in $(jq -r '.config[]' <(echo '${{ steps.read_matrices.outputs.matrix_config }}')) ; do
echo "find config files for config='$config_file'"
if [ "${{ steps.version.outputs.tag_config_coatjava }}" = "latest" ] ; then
util/latest.rb coatjava $config_file | tee -a coatjava.ver
else
ls coatjava/${{ steps.version.outputs.tag_config_coatjava }}/${config_file}.yaml | tee -a coatjava.ver
fi
if [ "${{ steps.version.outputs.tag_config_gemc }}" = "latest" ] ; then
util/latest.rb gemc $config_file | tee -a gemc.ver
else
ls gemc/${{ steps.version.outputs.tag_config_gemc }}/${config_file}.gcard | tee -a gemc.ver
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Configuration Files:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
paste {coatjava,gemc}.ver | column -t | xargs -0 -I{} echo {} >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: tar
run: tar cavf clas12-config{.tar.zst,}
- uses: actions/upload-artifact@v4
with:
name: build.config
retention-days: 1
path: ./*.tar.zst
# event generation
#############################################################################
event_generation:
name: Event generation
needs:
- build_coatjava
- config_files
- dependency_info
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.config_files.outputs.matrix_evgen) }}
steps:
- name: checkout clas12-validation
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12validation }}
ref: ${{ needs.dependency_info.outputs.ref_clas12validation }}
clean: false
fetch-tags: true
fetch-depth: 0
- name: setup java
uses: actions/setup-java@v4
with:
java-version: ${{ env.java_version }}
distribution: ${{ env.java_distribution }}
- name: setup groovy
uses: wtfjoke/setup-groovy@v2
with:
groovy-version: ${{ env.groovy_version }}
- name: download build
uses: actions/download-artifact@v4
with:
pattern: build.*
merge-multiple: true
- name: untar build
run: ls *.tar.zst | xargs -I{} tar xavf {}
- name: check if this configuration is known
run: jq -re '.${{ matrix.evgen }}' <(echo '${{ env.evgen_opts }}')
- name: event generation
run: |
coatjava/coatjava/bin/run-groovy \
coatjava/validation/advanced-tests/src/eb/scripts/gen.groovy \
$(jq -r '.${{ matrix.evgen }}' <(echo '${{ env.evgen_opts }}')) \
-n ${{ needs.config_files.outputs.num_events }}
- name: rename artifact
run: |
ls -t *.txt
mv -v $(ls -t *.txt | head -n1) evgen.${{ matrix.evgen }}.dat
- uses: actions/upload-artifact@v4
with:
name: evgen.${{ matrix.evgen }}
retention-days: 1
path: evgen.${{ matrix.evgen }}.dat
# simulation, reconstruction, and analysis: combined to one step to avoid large artifact I/O overhead
#############################################################################
fulltest:
name: Run
if: ${{ ! failure() && ! cancelled() }} # needed since `build_gemc` may be skipped
needs:
- event_generation
- build_gemc
- config_files
- dependency_info
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix: ${{ fromJson(needs.config_files.outputs.matrix_full) }}
steps:
- name: checkout clas12-validation
uses: actions/checkout@v4
with:
repository: ${{ needs.dependency_info.outputs.fork_clas12validation }}
ref: ${{ needs.dependency_info.outputs.ref_clas12validation }}
clean: false
fetch-tags: true
fetch-depth: 0
- name: setup java
uses: actions/setup-java@v4
with:
java-version: ${{ env.java_version }}
distribution: ${{ env.java_distribution }}
- name: setup groovy
uses: wtfjoke/setup-groovy@v2
with:
groovy-version: ${{ env.groovy_version }}
- name: download evgen
uses: actions/download-artifact@v4
with:
name: evgen.${{ matrix.evgen }}
- name: download build
uses: actions/download-artifact@v4
with:
pattern: build.*
merge-multiple: true
- name: tree
run: ls -lhR
- name: untar build
run: ls *.tar.zst | xargs -I{} tar xavf {}
- name: file names
id: files
run: |
echo evgenFile=evgen.${{ matrix.evgen }}.dat >> $GITHUB_OUTPUT
echo simFile=sim.${{ matrix.evgen }}.${{ matrix.config }}.hipo >> $GITHUB_OUTPUT
echo recFile=rec.${{ matrix.evgen }}.${{ matrix.config }}.hipo >> $GITHUB_OUTPUT
echo anaFile=ana.${{ matrix.evgen }}.${{ matrix.config }}.txt >> $GITHUB_OUTPUT
echo coatjavaConfigFile=clas12-config/$(grep -w ${{ matrix.config }} clas12-config/coatjava.ver) >> $GITHUB_OUTPUT
echo gemcConfigFile=clas12-config/$(grep -w ${{ matrix.config }} clas12-config/gemc.ver) >> $GITHUB_OUTPUT
- name: config files exist
run: |
ls ${{ steps.files.outputs.gemcConfigFile }}
ls ${{ steps.files.outputs.coatjavaConfigFile }}
- name: simulation
uses: docker://jeffersonlab/gemc:dev-fedora36
with:
entrypoint: bin/ci_gemc_run.sh
args: ${{ needs.dependency_info.outputs.gemc_executable }} ${{ needs.dependency_info.outputs.gemc_module_tag }} ${{ steps.files.outputs.gemcConfigFile }} ${{ steps.files.outputs.evgenFile }} ${{ steps.files.outputs.simFile }}
- name: check if output exists
run: ls ${{ steps.files.outputs.simFile }}
- name: reconstruction
run: |
coatjava/coatjava/bin/recon-util \
-y ${{ steps.files.outputs.coatjavaConfigFile }} \
-i ${{ steps.files.outputs.simFile }} \
-o ${{ steps.files.outputs.recFile }}
- name: check if output exists
run: ls ${{ steps.files.outputs.recFile }}
- name: analysis
run: |
bankName="REC::Particle"
[[ "${{ matrix.evgen }}" =~ FT ]] && bankName="RECFT::Particle"
echo "bankName = $bankName"
coatjava/coatjava/bin/run-groovy bin/multiplicity.groovy ${{ steps.files.outputs.recFile }} ${{ steps.files.outputs.anaFile }} $bankName
- uses: actions/upload-artifact@v4
with:
name: sim.${{ matrix.evgen }}.${{ matrix.config }}
retention-days: 3
path: sim.${{ matrix.evgen }}.${{ matrix.config }}.hipo
- uses: actions/upload-artifact@v4
with:
name: rec.${{ matrix.evgen }}.${{ matrix.config }}
retention-days: 3
path: rec.${{ matrix.evgen }}.${{ matrix.config }}.hipo
- uses: actions/upload-artifact@v4
with:
name: ana.${{ matrix.evgen }}.${{ matrix.config }}
retention-days: 3
path: ana.${{ matrix.evgen }}.${{ matrix.config }}.txt
# finalize
#############################################################################
final:
name: Final
if: ${{ ! failure() && ! cancelled() }} # needed since `build_gemc` may be skipped
needs:
- fulltest
- config_files
runs-on: ubuntu-latest
steps:
- name: download ana
uses: actions/download-artifact@v4
with:
pattern: ana.*
merge-multiple: true
- name: read multiplicity
run: |
echo "# Multiplicity Report" >> $GITHUB_STEP_SUMMARY
echo '```yaml' >> $GITHUB_STEP_SUMMARY
for evgen in $(echo '${{ needs.config_files.outputs.matrix_full }}' | jq -r '.evgen[]') ; do
echo "evgen: $evgen" >> $GITHUB_STEP_SUMMARY
first=true
for config in $(echo '${{ needs.config_files.outputs.matrix_full }}' | jq -r '.config[]') ; do
if $first ; then
grep -E '^bank: ' ana.${evgen}.${config}.txt >> $GITHUB_STEP_SUMMARY
echo "multiplicity: |" >> $GITHUB_STEP_SUMMARY
printf " %25s: PID (multiplicity) ... # sorted multiplicity for each PID\n" "config" >> $GITHUB_STEP_SUMMARY
first=false
fi
printf " %25s: %s" $config "$(tail -n1 ana.${evgen}.${config}.txt)" | xargs -0 -I{} echo {} >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
done
echo '```' >> $GITHUB_STEP_SUMMARY