Files
TheRock/rocm-libraries/shared/tensile/Tensile/Utilities/Toolchain.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

287 lines
9.5 KiB
Python

################################################################################
#
# Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################
import os
import re
from pathlib import Path
from subprocess import PIPE, run
from typing import List, NamedTuple, Union
from warnings import warn
DEFAULT_ROCM_BIN_PATH_POSIX = Path("/opt/rocm/bin")
DEFAULT_ROCM_LLVM_BIN_PATH_POSIX = Path("/opt/rocm/lib/llvm/bin")
DEFAULT_ROCM_BIN_PATH_WINDOWS = Path("C:/Program Files/AMD/ROCm")
osSelect = lambda linux, windows: linux if os.name != "nt" else windows
def _windowsLatestRocmBin(path: Union[Path, str]) -> Path:
"""Get the path to the latest ROCm bin directory, on Windows.
This function assumes that ROCm versions are differentiated with the form ``X.Y``.
Args:
path: The path to the ROCm root directory, typically ``C:/Program Files/AMD/ROCm``.
Returns:
The path to the ROCm bin directory for the latest ROCm version.
Typically of the form ``C:/Program Files/AMD/ROCm/X.Y/bin``.
"""
path = Path(path)
pattern = re.compile(r"^\d+\.\d+$")
versions = [d for d in path.iterdir() if d.is_dir() and pattern.match(d.name)]
if not versions:
return None
latest = max(versions, key=lambda d: tuple(map(int, d.name.split("."))))
return latest / "bin"
def _windowsSearchPaths() -> List[Path]:
defaultPath = DEFAULT_ROCM_BIN_PATH_WINDOWS
searchPaths = []
if os.environ.get("HIP_PATH"):
hipPaths = [Path(p) / "bin" for p in os.environ["HIP_PATH"].split(os.pathsep)]
searchPaths.extend(hipPaths)
if Path(defaultPath).exists():
latestRocmBin = _windowsLatestRocmBin(defaultPath)
if latestRocmBin:
searchPaths.append(latestRocmBin)
if os.environ.get("PATH"):
envPath = [Path(p) for p in os.environ["PATH"].split(os.pathsep)]
searchPaths.extend(envPath)
return searchPaths
def _windowsWithExtensions(exe: str) -> List[str]:
if not os.name == "nt":
raise ValueError("These extensions should not be added on anything but Windows")
files = [exe]
files.extend([exe + ext.lower() for ext in os.environ["PATHEXT"].split(";")])
return files
def _posixSearchPaths() -> List[Path]:
searchPaths = []
if os.environ.get("ROCM_PATH"):
for p in os.environ["ROCM_PATH"].split(os.pathsep):
searchPaths.append(Path(p) / "bin")
searchPaths.append(Path(p) / "lib" / "llvm" / "bin")
searchPaths.extend(
[
DEFAULT_ROCM_BIN_PATH_POSIX,
DEFAULT_ROCM_LLVM_BIN_PATH_POSIX,
]
)
if os.environ.get("PATH"):
envPath = [Path(p) for p in os.environ["PATH"].split(os.pathsep)]
searchPaths.extend(envPath)
return searchPaths
class ToolchainDefaults(NamedTuple):
CXX_COMPILER = osSelect(linux="amdclang++", windows="clang++.exe")
C_COMPILER = osSelect(linux="amdclang", windows="clang.exe")
OFFLOAD_BUNDLER = osSelect(linux="clang-offload-bundler", windows="clang-offload-bundler.exe")
ASSEMBLER = osSelect(linux="amdclang++", windows="clang++.exe")
HIP_CONFIG = osSelect(linux="hipconfig", windows="hipconfig")
DEVICE_ENUMERATOR = osSelect(linux="rocm_agent_enumerator", windows="hipinfo.exe")
def _supportedComponent(component: str, targets: List[str]) -> bool:
if os.name == "nt":
targets = [tExt for t in targets for tExt in _windowsWithExtensions(t)]
isSupported = any([component == t for t in targets]) or any(
[Path(component).name == t for t in targets]
)
return isSupported
def supportedCCompiler(compiler: str) -> bool:
"""Determine if a C compiler/assembler is supported by Tensile.
Args:
compiler: The name of a compiler to test for support.
Return:
If supported True; otherwise, False.
"""
return _supportedComponent(compiler, ["amdclang", "clang", "hipcc"])
def supportedCxxCompiler(compiler: str) -> bool:
"""Determine if a C++/HIP compiler/assembler is supported by Tensile.
Args:
compiler: The name of a compiler to test for support.
Return:
If supported True; otherwise, False.
"""
return _supportedComponent(compiler, ["amdclang++", "clang++", "hipcc"])
def supportedOffloadBundler(bundler: str) -> bool:
"""Determine if an offload bundler is supported by Tensile.
Args:
bundler: The name of an offload bundler to test for support.
Return:
If supported True; otherwise, False.
"""
return _supportedComponent(bundler, ["clang-offload-bundler"])
def supportedHip(exe: str) -> bool:
"""Determine if a HIP config executable is supported by Tensile.
Args:
bundler: The name of an offload bundler to test for support.
Return:
If supported True; otherwise, False.
"""
return _supportedComponent(exe, ["hipconfig", "hipcc"])
def supportedDeviceEnumerator(enumerator: str) -> bool:
"""Determine if a device enumerator is supported by Tensile.
Args:
enumerator: The name of a device enumerator to test for support.
Return:
If supported True; otherwise, False.
"""
if os.name == "nt":
return _supportedComponent(enumerator, ["hipinfo", "hipInfo"])
return _supportedComponent(enumerator, ["rocm_agent_enumerator", "amdgpu-arch"])
def _exeExists(file: Path) -> bool:
"""Check if a file exists and is executable.
Args:
file: The file to check.
Returns:
If the file exists and is executable, True; otherwise, False
"""
if os.access(file, os.X_OK):
if "rocm" not in map(str.lower, file.parts):
warn(f"Found `{file.name}` but in a non-default ROCm location: {file}")
return True
return False
def _validateExecutable(file: str, searchPaths: List[Path]) -> str:
"""Validate that the given toolchain component is in the PATH and executable.
Args:
file: The executable to validate.
searchPaths: List of directories to search for the executable.
Returns:
The validated executable with an absolute path.
"""
if not any(
(
supportedCxxCompiler(file),
supportedCCompiler(file),
supportedOffloadBundler(file),
supportedHip(file),
supportedDeviceEnumerator(file),
)
):
raise ValueError(f"{file} is not a supported toolchain component for OS: {os.name}")
if _exeExists(Path(file)):
return file
files = _windowsWithExtensions(file) if os.name == "nt" else [file]
for path in searchPaths:
for f in files:
p = path / f
if _exeExists(p):
return str(p)
raise FileNotFoundError(
f"`{file}` either not found or not executable in any search path: "
f"{':'.join(map(str, searchPaths))}\n"
"Please refer to the troubleshooting section of the Tensile documentation at "
"https://rocm.docs.amd.com/projects/Tensile/en/latest/src/#"
)
def validateToolchain(*args: str):
"""Validate that the given toolchain components are in the PATH and executable.
Args:
args: List of executable toolchain components to validate.
Returns:
List of validated executables with absolute paths.
Raises:
ValueError: If no toolchain components are provided.
FileNotFoundError: If a toolchain component is not found in the PATH.
"""
if not args:
raise ValueError("No toolchain components to validate, at least one argument is required")
searchPaths = _windowsSearchPaths() if os.name == "nt" else _posixSearchPaths()
out = (_validateExecutable(x, searchPaths) for x in args)
return next(out) if len(args) == 1 else tuple(out)
def getVersion(
executable: str, versionFlag: str = "--version", regex: str = r"version\s+([\d.]+)"
) -> str:
"""Print the version of a toolchain component.
Args:
executable: The toolchain component to check the version of.
versionFlag: The flag to pass to the executable to get the version.
"""
args = f'"{executable}" "{versionFlag}"'
try:
output = run(args, stdout=PIPE, shell=True).stdout.decode().strip()
match = re.search(regex, output, re.IGNORECASE)
if match:
return match.group(1)
warn(f"Failed to get version for {executable}: {output}")
return "<unknown>"
except Exception as e:
raise RuntimeError(f"Failed to get version when calling {args}: {e}")