diff --git a/repos/spack_repo/builtin/packages/gasnet/package.py b/repos/spack_repo/builtin/packages/gasnet/package.py index 21d28874df4..a06ac0674bd 100644 --- a/repos/spack_repo/builtin/packages/gasnet/package.py +++ b/repos/spack_repo/builtin/packages/gasnet/package.py @@ -4,15 +4,16 @@ import os import warnings +from pathlib import Path +from spack_repo.builtin.build_systems.autotools import AutotoolsPackage from spack_repo.builtin.build_systems.cuda import CudaPackage -from spack_repo.builtin.build_systems.generic import Package from spack_repo.builtin.build_systems.rocm import ROCmPackage from spack.package import * -class Gasnet(Package, CudaPackage, ROCmPackage): +class Gasnet(AutotoolsPackage, CudaPackage, ROCmPackage): """GASNet is a language-independent, networking middleware layer that provides network-independent, high-performance communication primitives including Remote Memory Access (RMA) and Active Messages (AM). It has been @@ -22,20 +23,31 @@ class Gasnet(Package, CudaPackage, ROCmPackage): writers (as opposed to end users), and the primary goals are high performance, interface portability, and expressiveness. - ***NOTICE***: The GASNet library built by this Spack package is ONLY intended for - unit-testing purposes, and is generally UNSUITABLE FOR PRODUCTION USE. - The RECOMMENDED way to build GASNet is as an embedded library as configured - by the higher-level client runtime package (UPC++, Legion, Chapel, etc), including - system-specific configuration. + ***NOTICE***: The Spack built version of GASNet is generally considered + UNSUITABLE FOR PRODUCTION USE by its authors. The RECOMMENDED way to build + GASNet is as an embedded library as configured by the higher-level client + runtime package (UPC++, Chapel, etc), including system-specific configuration. + + Despite this recommendation, this Spack package is a best effort to allow + supporting a range of platforms. For optimal performance and more + fine-grained control on a particular target platform, HPC admins can provide an + external build. + + Note on variant defaults: + Many variants default to "auto" to respect GASNet's carefully tuned + configure-time auto-detection. The configure script's defaults were + chosen over time to balance the needs of multiple clients and target + systems. Explicit variant settings override this auto-detection. """ homepage = "https://gasnet.lbl.gov" url = "https://gasnet.lbl.gov/EX/GASNet-2024.5.0.tar.gz" git = "https://bitbucket.org/berkeleylab/gasnet.git" - maintainers("PHHargrove", "bonachea") + maintainers("PHHargrove", "bonachea", "rbberger") tags = ["e4s", "ecp"] + executables = ["^gasnet_trace$"] version("develop", branch="develop") version("main", branch="stable") @@ -72,8 +84,89 @@ class Gasnet(Package, CudaPackage, ROCmPackage): + "For detailed recommendations, consult https://gasnet.lbl.gov", ) + # ============================================================ + # Threading support variants + # ============================================================ + + variant( + "pthreads", + default="auto", + values=("auto", "on", "off"), + description="Enable use of pthreads (required for PAR/PARSYNC modes)", + ) + + variant( + "par", + default="auto", + values=("auto", "on", "off"), + description="support PAR-mode pthreaded GASNet clients", + ) + + variant( + "parsync", + default="auto", + values=("auto", "on", "off"), + description="support PARSYNC-mode pthreaded GASNet clients", + ) + + variant("seq", default=True, description="support SEQ-mode single-threaded GASNet clients") + + # ============================================================ + # Memory and system variants + # ============================================================ + + variant( + "pshm", + default="auto", + values=("auto", "on", "off"), + description="Enable/disable inter-process shared memory support", + ) + + variant( + "pmi", + default="auto", + values=("auto", "none", "x", "1", "2", "cray"), + description="PMI version (auto-detected when needed by conduit)", + ) + + # ============================================================ + # True binary choice variants + # ============================================================ + variant("debug", default=False, description="Enable library debugging mode") + variant( + "mpi_compat", default=False, description="Enable/disable MPI compatibility in all conduits" + ) + variant( + "segment", + default="off", + values=("off", "fast", "large", "everything"), + description="Build GASNet in the FAST/LARGE/EVERYTHING shared segment configuration", + ) + + with when("conduits=ofi"): + variant( + "ofi_provider", + default="auto", + values=("auto", "cxi"), + description="Statically configure ofi-conduit for the given provider", + ) + variant( + "ofi_spawner", + default="auto", + values=("auto", "ssh", "mpi", "pmi"), + description="ofi job spawner", + ) + + with when("conduits=ibv"): + variant( + "ibv_max_hcas", + default="1", + values=(str(x) for x in range(9)), + description="Maximum number of IBV HCAs to open", + ) + variant( "cuda", default=False, @@ -95,13 +188,20 @@ class Gasnet(Package, CudaPackage, ROCmPackage): when="@2023.9.0:", ) + variant("pic", default=False, description="Produce position-independent code (for shared libs)") + depends_on("c", type="build") # generated depends_on("cxx", type="build") # generated depends_on("gmake", type="build") + depends_on("mpi", when="+mpi_compat") depends_on("mpi", when="conduits=mpi") + depends_on("libfabric", when="conduits=ofi") + depends_on("pmix", when="pmi=x") + depends_on("cray-pmi", when="pmi=cray") + depends_on("autoconf@2.69", type="build", when=bootstrap_version) depends_on("automake@1.16:", type="build", when=bootstrap_version) @@ -111,8 +211,19 @@ class Gasnet(Package, CudaPackage, ROCmPackage): depends_on("oneapi-level-zero@1.8.0:", when="+level_zero") - def install(self, spec, prefix): - if spec.satisfies(self.bootstrap_version): + # ============================================================ + # Conflicts + # ============================================================ + + conflicts("par=on", when="pthreads=off", msg="PAR mode requires pthreads") + conflicts("parsync=on", when="pthreads=off", msg="PARSYNC mode requires pthreads") + conflicts( + "pshm=on", when="segment=everything", msg="PSHM not compatible with SEGMENT_EVERYTHING" + ) + + @run_before("configure") + def bootstrap(self): + if self.spec.satisfies(self.bootstrap_version): bootstrapsh = Executable("./Bootstrap") bootstrapsh() # Record git-describe when fetched from git: @@ -122,58 +233,136 @@ def install(self, spec, prefix): except ProcessError: warnings.warn("Omitting version stamp due to git error") - # The GASNet-EX library has a highly multi-dimensional configure space, - # to accomodate the varying behavioral requirements of each client runtime. - # The library's ABI/link compatibility is strongly dependent on these - # client-specific build-time settings, and that variability is deliberately NOT - # encoded in the variants of this package. The recommended way to build/deploy - # GASNet is as an EMBEDDED library within the build of the client package - # (eg. Berkeley UPC, UPC++, Legion, etc), some of which provide build-time - # selection of the GASNet library sources. This spack package provides - # the GASNet-EX sources, for use by appropriate client packages. - install_tree(".", prefix + "/src") - - # Library build is provided for unit-testing purposes only (see notice above) - if "conduits=none" not in spec: - options = ["--prefix=%s" % prefix] + def enable_disable_or_auto(self, option_name, variant_name=None): + """Helper for variants with 'auto', 'on', 'off' values. + + Returns configure flags for the given option. + 'auto' returns [], 'on' returns ['--enable-{option_name}'], + 'off' returns ['--disable-{option_name}']. + + Args: + option_name: The configure option name (e.g., 'pthreads') + variant_name: The variant name (e.g., 'pthreads'). If None, uses option_name. + """ + if variant_name is None: + variant_name = option_name + + value = self.spec.variants[variant_name].value + + if value == "on": + return [f"--enable-{option_name}"] + elif value == "off": + return [f"--disable-{option_name}"] + return [] # auto + + def configure_args(self): + spec = self.spec + options = ["--disable-auto-conduit-detect", "--enable-rpath"] + + flags = {"cflags": [], "cxxflags": [], "mpi-cflags": []} + + if self.spec.satisfies("+pic"): + flags["cflags"].append(self.compiler.cc_pic_flag) + flags["mpi-cflags"].append(self.compiler.cc_pic_flag) + flags["cxxflags"].append(self.compiler.cxx_pic_flag) + + for key, value in sorted(flags.items()): + if value: + options.append(f"--with-{key}={' '.join(value)}") + + # ============================================================ + # Boolean variants - use enable_or_disable helper + # ============================================================ + + options += self.enable_or_disable("debug") + options += self.enable_or_disable("seq") + options += self.enable_or_disable("mpi-compat", variant="mpi_compat") + + # ============================================================ + # String variants with "auto" - use enable_disable_or_auto helper + # ============================================================ + + options += self.enable_disable_or_auto("pthreads") + options += self.enable_disable_or_auto("par") + options += self.enable_disable_or_auto("parsync") + options += self.enable_disable_or_auto("pshm") + + # PMI + pmi_val = spec.variants["pmi"].value + if pmi_val == "none": + options.append("--disable-pmi") + elif pmi_val != "auto": + options.append("--enable-pmi") + options.append(f"--with-pmi-version={pmi_val}") + if pmi_val == "x": + options.append(f"--with-pmi-home={spec['pmix'].prefix}") + elif pmi_val == "cray": + options.append(f"--with-pmi-home={spec['cray-pmi'].prefix}") + # else: "auto" - let configure and conduits auto-detect PMI needs + + # ============================================================ + # Segment configuration + # ============================================================ + + if not spec.satisfies("segment=off"): + options.append(f"--enable-segment-{spec.variants['segment'].value}") + + # ============================================================ + # GPU support (CUDA, ROCm, Level Zero) + # ============================================================ + + if spec.satisfies("+cuda"): + options.append("--enable-kind-cuda-uva") + options.append("--with-cuda-home=" + spec["cuda"].prefix) + + if spec.satisfies("+rocm"): + options.append("--enable-kind-hip") + options.append("--with-hip-home=" + spec["hip"].prefix) + + if spec.satisfies("+level_zero"): + options.append("--enable-kind-ze") + options.append("--with-ze-home=" + spec["oneapi-level-zero"].prefix) + + # ============================================================ + # Conduits + # ============================================================ + + for c in spec.variants["conduits"].value: + options.append(f"--enable-{c}") + + if spec.satisfies("conduits=mpi") or spec.satisfies("+mpi_compat"): + options.append(f"--with-mpi-cc={spec['mpi'].mpicc}") + + if spec.satisfies("conduits=ibv"): + options.append(f"--with-ibv-max-hcas={spec.variants['ibv_max_hcas'].value}") + + if spec.satisfies("conduits=ofi"): + if not spec.satisfies("ofi_provider=auto"): + options.append(f"--with-ofi-provider={spec.variants['ofi_provider'].value}") + if not spec.satisfies("ofi_spawner=auto"): + options.append(f"--with-ofi-spawner={spec.variants['ofi_spawner'].value}") + + return options + + @run_after("build") + def build_tests(self): + if not self.spec.satisfies("conduits=none"): + for c in self.spec.variants["conduits"].value: + make("-C", f"{c}-conduit", "testgasnet-par") + make("-C", f"{c}-conduit", "testtools-par") - if spec.satisfies("+debug"): - options.append("--enable-debug") - - if spec.satisfies("+cuda"): - options.append("--enable-kind-cuda-uva") - options.append("--with-cuda-home=" + spec["cuda"].prefix) - - if spec.satisfies("+rocm"): - options.append("--enable-kind-hip") - options.append("--with-hip-home=" + spec["hip"].prefix) - - if spec.satisfies("+level_zero"): - options.append("--enable-kind-ze") - options.append("--with-ze-home=" + spec["oneapi-level-zero"].prefix) - - if spec.satisfies("conduits=mpi"): - options.append("--enable-mpi-compat") - else: - options.append("--disable-mpi-compat") - - options.append("--disable-auto-conduit-detect") - for c in spec.variants["conduits"].value: - options.append("--enable-" + c) - - options.append("--enable-rpath") - - configure(*options) - make() - make("install") + @run_after("install") + def install_source(self): + install_tree(self.stage.source_path, self.prefix + "/src") - for c in spec.variants["conduits"].value: + @run_after("install") + def install_tests(self): + if not self.spec.satisfies("conduits=none"): + for c in self.spec.variants["conduits"].value: testdir = join_path(self.prefix.tests, c) mkdirp(testdir) - make("-C", c + "-conduit", "testgasnet-par") - install(c + "-conduit/testgasnet", testdir) - make("-C", c + "-conduit", "testtools-par") - install(c + "-conduit/testtools", self.prefix.tests) + install(f"{c}-conduit/testgasnet", testdir) + install(f"{c}-conduit/testtools", prefix.tests) @run_after("install") @on_package_attributes(run_tests=True) @@ -235,3 +424,70 @@ def test_testgasnet(self): args = spawner[c][1:] + [test] out = exe(*args, output=str.split, error=str.split) assert expected in out + + @classmethod + def determine_version(cls, exe): + prefix = Path(exe).parent.parent + config = prefix / "include" / "gasnet_config.h" + if config.exists(): + with open(config, "r") as f: + for line in f: + if line.startswith("#define GASNETI_RELEASE_VERSION"): + return line.split()[2] + return None + + @classmethod + def determine_variants(cls, exes, version): + results = [] + for exe in exes: + variants = [] + prefix = Path(exe).parent.parent + config = prefix / "include" / "gasnet_config.h" + lib_dir = prefix / "lib" + + if config.exists(): + flags = {} + conduits = [] + + with open(config, "r") as f: + for line in f: + if line.startswith("#define GASNETI_CONDUITS"): + conduits = [x.strip().replace('"', "") for x in line.split()[2:]] + variants.append(f"conduits={','.join(conduits)}") + elif line.startswith("#define GASNET_SEGMENT_FAST 1"): + variants.append("segment=fast") + elif line.startswith("#define GASNET_SEGMENT_LARGE 1"): + variants.append("segment=large") + elif line.startswith("#define GASNET_SEGMENT_EVERYTHING 1"): + variants.append("segment=everything") + elif line.startswith("#define GASNETI_PSHM_ENABLED 1"): + flags["pshm"] = True + elif line.startswith("#define HAVE_PTHREAD_H 1"): + flags["pthreads"] = True + elif line.startswith("#define GASNET_DEBUG 1"): + flags["debug"] = True + + # Detect threading modes by checking for library files + # Check any conduit (use first one found) for mode-specific libraries + if lib_dir.exists() and conduits: + conduit = conduits.split(",") + flags["seq"] = (lib_dir / f"libgasnet-{conduit}-seq.a").exists() + flags["par"] = (lib_dir / f"libgasnet-{conduit}-par.a").exists() + flags["parsync"] = (lib_dir / f"libgasnet-{conduit}-parsync.a").exists() + + # Add auto/on/off variants + variants.append("pshm=on" if flags.get("pshm") else "pshm=off") + variants.append("pthreads=on" if flags.get("pthreads") else "pthreads=off") + + # Infer par and parsync from library existence + if flags.get("par") or flags.get("parsync"): + variants.append("par=on" if flags.get("par") else "par=off") + variants.append("parsync=on" if flags.get("parsync") else "parsync=off") + + # Add boolean variants + variants.append("+seq" if flags.get("seq") else "~seq") + variants.append("+debug" if flags.get("debug") else "~debug") + variants.append("+mpi_compat" if flags.get("mpi_compat") else "~mpi_compat") + + results.append(" ".join(variants)) + return results