diff --git a/.github/workflows/scripts/test_overview_available_software.py b/.github/workflows/scripts/test_overview_available_software.py new file mode 100644 index 000000000..ece9e3f00 --- /dev/null +++ b/.github/workflows/scripts/test_overview_available_software.py @@ -0,0 +1,64 @@ +import os +import json +from bs4 import BeautifulSoup + +path = os.path.dirname(os.path.realpath(__file__)) +path_overview = "/../../../docs/available_software/overview.md" +path_data = "/../../../docs/available_software/data/json_data.json" +if os.path.exists(path + path_overview) and os.path.exists(path + path_data): + with open(path + path_data) as json_data: + data = json.load(json_data) + with open(path + path_overview) as f: + soup = BeautifulSoup(f, "html.parser") +else: + os.write(1, b'Error: Could not find overview.md and/or data/json_data.json') + +# parse the numbers for the different targets +targets = data["targets"] +ARM_targets = [] +x86_targets = [] +amd_targets = [] +intel_targets = [] +nvidia_targets = [] + +for target in targets: + t = target.split('/') + if t[7] == 'aarch64': + ARM_targets.append(target) + else: + x86_targets.append(target) + if t[8] == "amd": + amd_targets.append(target) + elif t[8] == "intel": + intel_targets.append(target) + elif t[8] == 'nvidia': + nvidia_targets.append(target) + +# parse the overview.md page to check the number of colums in rows +table = soup.find("table", {"class": "table"}) +for row in table.find_all("tr"): + for column in row.find_all('th'): + if column.text == "x86_64": + print(f'the value for x86_64 is {column.get("colspan")} in the overview page and there are {len(x86_targets)} targets in json_data.json.') + if int(column.get("colspan")) != len(x86_targets): + os.write(2, b'Error: Please make sure the values for x86_64 in json_data.json and overview.md are the same.') + elif column.text == "aarch64": + print(f'the value for aarch64 is {column.get("colspan")} in the overview page and there are {len(ARM_targets)} targets in json_data.json.') + if int(column.get("colspan")) != len(ARM_targets): + os.write(2, b'Error: Please make sure the values for aarch64 in json_data.json and overview.md are the same.') + elif column.text == "amd": + print(f'the value for amd is {column.get("colspan")} in the overview page and there are {len(amd_targets)} targets in json_data.json.') + if int(column.get("colspan")) != len(amd_targets): + os.write(2, b'Error: Please make sure the values for amd in json_data.json and overview.md are the same.') + elif column.text == "intel": + print(f'the value for intel is {column.get("colspan")} in the overview page and there are {len(intel_targets)} targets in json_data.json.') + if int(column.get("colspan")) != len(intel_targets): + os.write(2, b'Error: Please make sure the values for intel in json_data.json and overview.md are the same.') + elif column.text == "nvidia": + print(f'the value for nvidia is {column.get("colspan")} in the overview page and there are {len(nvidia_targets)} targets in json_data.json.') + if int(column.get("colspan")) != len(nvidia_targets): + os.write(2, b'Error: Please make sure the values for nvidia in json_data.json and overview.md are the same.') +last_row = table.find_all("tr")[-1] +print(f'there are {len(last_row.find_all("th"))} columns in the overview page and {len(targets)} targets in json_data.json.') +if len(last_row.find_all("th")) != len(targets): + os.write(2, b'Error: Please make sure there are correct number of elements in the last element in overview.md for JavaScript to generate the table.') diff --git a/.github/workflows/test_overview_available_software.yml b/.github/workflows/test_overview_available_software.yml new file mode 100644 index 000000000..19fec5b07 --- /dev/null +++ b/.github/workflows/test_overview_available_software.yml @@ -0,0 +1,28 @@ +name: Test overview of available software in EESSI +on: + push: + paths: + - ".github/**" + - "docs/available_software/data/**" +jobs: + check_targets: + name: check targets in overview.md and json_data.json + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + + - name: set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + with: + python-version: '3.10' + architecture: x64 + + - name: test overview available software + id: test_overview_available_software + run: | + # install required Python packages in virtual environment + python -m venv venv + . venv/bin/activate + pip install -r mkdocs-ldjson-plugin/requirements.txt + + python .github/workflows/scripts/test_overview_available_software.py diff --git a/.gitignore b/.gitignore index b5a6cebc8..594179dac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ site/ venv* +scripts/available_software/__pycache__ +scripts/available_software/tests/__pycache__ diff --git a/docs/available_software/javascripts/populate_overview.js b/docs/available_software/javascripts/populate_overview.js index 822342deb..3f2bd69ae 100644 --- a/docs/available_software/javascripts/populate_overview.js +++ b/docs/available_software/javascripts/populate_overview.js @@ -43,7 +43,8 @@ function populate_overview(json_data) { targets: "_all", className: 'dt-body-center' } - ] + ], + scrollX: true, }); console.log(table) diff --git a/docs/available_software/overview.md b/docs/available_software/overview.md index 6cda3e1de..92dea7dab 100644 --- a/docs/available_software/overview.md +++ b/docs/available_software/overview.md @@ -10,23 +10,23 @@ This table gives an overview of all the available software in EESSI per specific aarch64 x86_64 - + amd intel - generic - neoverse_n1 - neoverse_v1 - generic - zen2 - zen3 - zen4 - haswell - skylake_avx512 - sapphirerapids + + + + + + + + + + diff --git a/scripts/available_software/available_software.py b/scripts/available_software/available_software.py index e19e7ecef..d583f1da1 100644 --- a/scripts/available_software/available_software.py +++ b/scripts/available_software/available_software.py @@ -24,6 +24,7 @@ import numpy as np from mdutils.mdutils import MdUtils from natsort import natsorted +from functools import cmp_to_key EESSI_TOPDIR = "/cvmfs/software.eessi.io/versions/2023.06" @@ -220,6 +221,37 @@ def targets_eessi() -> np.ndarray: return targets +def eessi_target_compare(a, b): + """ + A comparison function to compare the EESSI targets and order them. + First the main architecture is ordered alphabetically, then within them + the CPU targets are again ordered alphabetically, except for the + generic target, which always comes first. Targets that include an extra + vendor subdir always after those without a vendor subdir. + @return: 0, 1, -1 + """ + if a == b: + return 0 + + a_split = a.rsplit('/') + b_split = b.rsplit('/') + + # We first compare the main architecture (aarch64, x86_64, ...), which is the 7th field + if a_split[7] == b_split[7]: + # Check if one item is for generic builds (last field), These should always be listed first + if a_split[-1] == 'generic': + return -1 + if b_split[-1] == 'generic': + return 1 + # If the number of fields are not equal, one has an extra vendor subdirectory (e.g. amd, intel, nvidia). + # These should always come after the ones without this extra level. + if len(a_split) != len(b_split): + return 1 if len(a_split) > len(b_split) else -1 + + # In all other cases we just do an alphabetical sort of the strings. + return 1 if a > b else -1 + + def modules_eessi() -> dict: """ Returns names of all software module that are installed on EESSI. @@ -233,7 +265,14 @@ def modules_eessi() -> dict: if modulepath: module_unuse(modulepath) - targets = [t for t in targets_eessi() if not any(t.endswith(x) for x in EXCLUDE_CPU_TARGETS)] + targets = targets_eessi() + + # Order targets + eessi_target_compare_key = cmp_to_key(eessi_target_compare) + ordered_targets = sorted(targets, key=eessi_target_compare_key) + + targets = [t for t in ordered_targets if not any(t.endswith(x) for x in EXCLUDE_CPU_TARGETS)] + for target in targets: print(f"\t Collecting available modules for {target}... ", end="", flush=True) module_use(target + "/modules/all/") diff --git a/scripts/available_software/tests/test_json.py b/scripts/available_software/tests/test_json.py index b7fec5778..e12d0666b 100644 --- a/scripts/available_software/tests/test_json.py +++ b/scripts/available_software/tests/test_json.py @@ -6,7 +6,7 @@ import os import json -GENERIC = "/cvmfs/software.eessi.io/versions/2023.06/software/linux/aarch64/generic" +GENERIC_ARM = "/cvmfs/software.eessi.io/versions/2023.06/software/linux/aarch64/generic" ZEN2 = "/cvmfs/software.eessi.io/versions/2023.06/software/linux/x86_64/amd/zen2" @@ -39,7 +39,7 @@ def test_json_generate_simple(self): modules = modules_eessi() json_data = generate_json_overview_data(modules) assert len(json_data.keys()) == 3 - assert list(json_data["targets"]) == [GENERIC, ZEN2] + assert list(json_data["targets"]) == [GENERIC_ARM, ZEN2] assert json_data["modules"] == { "Markov": [1, 0], "cfd": [1, 1],