|
1 | 1 | """
|
2 | 2 | Interface to the config_machines.xml file. This class inherits from GenericXML.py
|
3 | 3 | """
|
| 4 | + |
4 | 5 | from CIME.XML.standard_module_setup import *
|
5 | 6 | from CIME.XML.generic_xml import GenericXML
|
6 | 7 | from CIME.XML.files import Files
|
7 |
| -from CIME.utils import convert_to_unknown_type, get_cime_config |
| 8 | +from CIME.utils import CIMEError, expect, convert_to_unknown_type, get_cime_config |
8 | 9 |
|
| 10 | +import re |
| 11 | +import logging |
9 | 12 | import socket
|
| 13 | +from functools import partial |
10 | 14 | from pathlib import Path
|
11 | 15 |
|
12 | 16 | logger = logging.getLogger(__name__)
|
13 | 17 |
|
14 | 18 |
|
| 19 | +def match_value_by_attribute_regex(element, attribute_name, value): |
| 20 | + """Checks element contains attribute whose pattern matches a value. |
| 21 | +
|
| 22 | + If the element does not have the attribute it's considered a match. |
| 23 | +
|
| 24 | + Args: |
| 25 | + element (CIME.XML.generic_xml._Element): XML element to check attributes. |
| 26 | + attribute_name (str): Name of attribute with regex value. |
| 27 | + value (str): Value that is matched against attributes regex value. |
| 28 | +
|
| 29 | + Returns: |
| 30 | + bool: True if attribute regex matches the target value otherwise False. |
| 31 | + """ |
| 32 | + attribute_value = element.attrib.get(attribute_name, None) |
| 33 | + |
| 34 | + return ( |
| 35 | + True |
| 36 | + if value is None |
| 37 | + or attribute_value is None |
| 38 | + or re.match(attribute_value, value) is not None |
| 39 | + else False |
| 40 | + ) |
| 41 | + |
| 42 | + |
15 | 43 | class Machines(GenericXML):
|
16 | 44 | def __init__(
|
17 | 45 | self,
|
@@ -484,30 +512,179 @@ def set_value(self, vid, value, subgroup=None, ignore_type=True):
|
484 | 512 | # A temporary cache only
|
485 | 513 | self.custom_settings[vid] = value
|
486 | 514 |
|
487 |
| - def print_values(self): |
488 |
| - # write out machines |
489 |
| - machines = self.get_children("machine") |
490 |
| - logger.info("Machines") |
491 |
| - for machine in machines: |
492 |
| - name = self.get(machine, "MACH") |
493 |
| - desc = self.get_child("DESC", root=machine) |
494 |
| - os_ = self.get_child("OS", root=machine) |
495 |
| - compilers = self.get_child("COMPILERS", root=machine) |
496 |
| - max_tasks_per_node = self.get_child("MAX_TASKS_PER_NODE", root=machine) |
497 |
| - max_mpitasks_per_node = self.get_child( |
498 |
| - "MAX_MPITASKS_PER_NODE", root=machine |
| 515 | + def print_values(self, compiler=None): |
| 516 | + """Prints machine values. |
| 517 | +
|
| 518 | + Args: |
| 519 | + compiler (str, optional): Name of the compiler to print extra details for. Defaults to None. |
| 520 | + """ |
| 521 | + current = self.probe_machine_name(False) |
| 522 | + |
| 523 | + if self.machine_node is None: |
| 524 | + for machine in self.get_children("machine"): |
| 525 | + self._print_machine_values(machine, current) |
| 526 | + else: |
| 527 | + self._print_machine_values(self.machine_node, current, compiler) |
| 528 | + |
| 529 | + def _print_machine_values(self, machine, current=None, compiler=None): |
| 530 | + """Prints a machines details. |
| 531 | +
|
| 532 | + Args: |
| 533 | + machine (CIME.XML.machines.Machine): Machine object. |
| 534 | + current (str, optional): Name of the current machine. Defaults to None. |
| 535 | + compiler (str, optional): If not None, then modules and environment variables matching compiler are printed. Defaults to None. |
| 536 | +
|
| 537 | + Raises: |
| 538 | + CIMEError: If `compiler` is not valid. |
| 539 | + """ |
| 540 | + name = self.get(machine, "MACH") |
| 541 | + if current is not None and current == name: |
| 542 | + name = f"{name} (current)" |
| 543 | + desc = self.text(self.get_child("DESC", root=machine)) |
| 544 | + os_ = self.text(self.get_child("OS", root=machine)) |
| 545 | + |
| 546 | + compilers = self.text(self.get_child("COMPILERS", root=machine)) |
| 547 | + if compiler is not None and compiler not in compilers.split(","): |
| 548 | + raise CIMEError( |
| 549 | + f"Compiler {compiler!r} is not a valid choice from ({compilers})" |
499 | 550 | )
|
500 |
| - max_gpus_per_node = self.get_child("MAX_GPUS_PER_NODE", root=machine) |
501 | 551 |
|
502 |
| - print(" {} : {} ".format(name, self.text(desc))) |
503 |
| - print(" os ", self.text(os_)) |
504 |
| - print(" compilers ", self.text(compilers)) |
505 |
| - if max_mpitasks_per_node is not None: |
506 |
| - print(" pes/node ", self.text(max_mpitasks_per_node)) |
507 |
| - if max_tasks_per_node is not None: |
508 |
| - print(" max_tasks/node ", self.text(max_tasks_per_node)) |
509 |
| - if max_gpus_per_node is not None: |
510 |
| - print(" max_gpus/node ", self.text(max_gpus_per_node)) |
| 552 | + mpilibs_nodes = self._get_children_filter_attribute_regex( |
| 553 | + "MPILIBS", "compiler", compiler, root=machine |
| 554 | + ) |
| 555 | + mpilibs = set([y for x in mpilibs_nodes for y in self.text(x).split(",")]) |
| 556 | + |
| 557 | + max_tasks_per_node = self.text( |
| 558 | + self.get_child("MAX_TASKS_PER_NODE", root=machine) |
| 559 | + ) |
| 560 | + max_mpitasks_per_node = self.text( |
| 561 | + self.get_child("MAX_MPITASKS_PER_NODE", root=machine) |
| 562 | + ) |
| 563 | + max_gpus_per_node = self.get_optional_child("MAX_GPUS_PER_NODE", root=machine) |
| 564 | + max_gpus_per_node_text = ( |
| 565 | + self.text(max_gpus_per_node) if max_gpus_per_node else 0 |
| 566 | + ) |
| 567 | + |
| 568 | + if compiler is not None: |
| 569 | + name = f"{name} ({compiler})" |
| 570 | + |
| 571 | + print(" {} : {} ".format(name, desc)) |
| 572 | + print(" os ", os_) |
| 573 | + print(" compilers ", compilers) |
| 574 | + print(" mpilibs ", ",".join(mpilibs)) |
| 575 | + print(" pes/node ", max_mpitasks_per_node) |
| 576 | + print(" max_tasks/node ", max_tasks_per_node) |
| 577 | + print(" max_gpus/node ", max_gpus_per_node_text) |
| 578 | + print("") |
| 579 | + |
| 580 | + if compiler is not None: |
| 581 | + module_system_node = self.get_child("module_system", root=machine) |
| 582 | + |
| 583 | + def command_formatter(node): |
| 584 | + if node.text is None: |
| 585 | + return f"{node.attrib['name']}" |
| 586 | + else: |
| 587 | + return f"{node.attrib['name']} {node.text}" |
| 588 | + |
| 589 | + print(" Module commands:") |
| 590 | + for requirements, commands in self._filter_children_by_compiler( |
| 591 | + "modules", "command", compiler, command_formatter, module_system_node |
| 592 | + ): |
| 593 | + indent = "" if requirements == "" else " " |
| 594 | + if requirements != "": |
| 595 | + print(f" (with {requirements})") |
| 596 | + for x in commands: |
| 597 | + print(f" {indent}{x}") |
| 598 | + print("") |
| 599 | + |
| 600 | + def env_formatter(node, machines=None): |
| 601 | + return f"{node.attrib['name']}: {machines._get_resolved_environment_variable(node.text)}" |
| 602 | + |
| 603 | + print(" Environment variables:") |
| 604 | + for requirements, variables in self._filter_children_by_compiler( |
| 605 | + "environment_variables", |
| 606 | + "env", |
| 607 | + compiler, |
| 608 | + partial(env_formatter, machines=self), |
| 609 | + machine, |
| 610 | + ): |
| 611 | + indent = "" if requirements == "" else " " |
| 612 | + if requirements != "": |
| 613 | + print(f" (with {requirements})") |
| 614 | + for x in variables: |
| 615 | + print(f" {indent}{x}") |
| 616 | + |
| 617 | + def _filter_children_by_compiler(self, parent, child, compiler, formatter, root): |
| 618 | + """Filters parent nodes and returns requirements and children of filtered nodes. |
| 619 | +
|
| 620 | + Example of a yielded values: |
| 621 | +
|
| 622 | + "mpilib=openmpi DEBUG=true", ["HOME: /home/dev", "NETCDF_C_PATH: ../netcdf"] |
| 623 | +
|
| 624 | + Args: |
| 625 | + parent (str): Name of the nodes to filter. |
| 626 | + child (str): Name of the children nodes from filtered parent nodes. |
| 627 | + compiler (str): Name of the compiler that will be matched against the regex. |
| 628 | + formatter (function): Function to format the child nodes from the parents that match. |
| 629 | + root (CIME.XML.generic_xml._Element): Root node to filter parent nodes from. |
| 630 | +
|
| 631 | + Yields: |
| 632 | + str, list: Requirements for parent node and list of formated child nodes. |
| 633 | + """ |
| 634 | + nodes = self._get_children_filter_attribute_regex( |
| 635 | + parent, "compiler", compiler, root=root |
| 636 | + ) |
| 637 | + |
| 638 | + for x in nodes: |
| 639 | + attrib = {**x.attrib} |
| 640 | + attrib.pop("compiler", None) |
| 641 | + |
| 642 | + requirements = " ".join([f"{y}={z!r}" for y, z in attrib.items()]) |
| 643 | + values = [formatter(y) for y in self.get_children(child, root=x)] |
| 644 | + |
| 645 | + yield requirements, values |
| 646 | + |
| 647 | + def _get_children_filter_attribute_regex(self, name, attribute_name, value, root): |
| 648 | + """Filter children nodes using regex. |
| 649 | +
|
| 650 | + Uses regex from attribute of children nodes to match a value. |
| 651 | +
|
| 652 | + Args: |
| 653 | + name (str): Name of the children nodes. |
| 654 | + attribute_name (str): Name of the attribute on the child nodes to build regex from. |
| 655 | + value (str): Value that is matched using regex from attribute. |
| 656 | + root (CIME.XML.generic_xml._Element): Root node to query children nodes from. |
| 657 | +
|
| 658 | + Returns: |
| 659 | + list: List of children whose regex attribute matches the value. |
| 660 | + """ |
| 661 | + return [ |
| 662 | + x |
| 663 | + for x in self.get_children(name, root=root) |
| 664 | + if match_value_by_attribute_regex(x, attribute_name, value) |
| 665 | + ] |
| 666 | + |
| 667 | + def _get_resolved_environment_variable(self, text): |
| 668 | + """Attempts to resolve machines environment variable. |
| 669 | +
|
| 670 | + Args: |
| 671 | + text (str): Environment variable value. |
| 672 | +
|
| 673 | + Returns: |
| 674 | + str: Resolved value or error message. |
| 675 | + """ |
| 676 | + if text is None: |
| 677 | + return "" |
| 678 | + |
| 679 | + try: |
| 680 | + value = self.get_resolved_value(text, allow_unresolved_envvars=True) |
| 681 | + except Exception as e: |
| 682 | + return f"Failed to resolve {text!r} with: {e!s}" |
| 683 | + |
| 684 | + if value == text and "$" in text: |
| 685 | + value = f"Failed to resolve {text!r}" |
| 686 | + |
| 687 | + return value |
511 | 688 |
|
512 | 689 | def return_values(self):
|
513 | 690 | """return a dictionary of machine info
|
|
0 commit comments