Skip to content

Commit

Permalink
Merge branch 'main' into warn-missing-file
Browse files Browse the repository at this point in the history
  • Loading branch information
jameslamb authored May 15, 2024
2 parents 95f0b19 + dccdf0d commit dfde9d3
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 276 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ In cases where more dynamic customization is sensible, suitable environment vari

Any option without a default is required.

| Option | Definition | Type | Default | Supports dynamic modification |
|-----------------------|---------------------------------------------------------------------|----------------|---------------------|-------------------------------|
| `build-backend` | The wrapped build backend (e.g. `setuptools.build_meta`) | string | | N |
| `commit-file` | The file in which to write the git commit hash | string | "" (No file) | N |
| `dependencies-file` | The path to the `dependencies.yaml` file to use | string | "dependencies.yaml" | Y |
| `matrix-entry` | A `;`-separated list of `=`-delimited key/value pairs | string | "" | Y |
| `require-cuda` | If false, builds will succeed even if nvcc is not available | bool | true | Y |
| `requires` | List of build requirements (in addition to `build-system.requires`) | list[str] | [] | N |
| Option | Definition | Type | Default | Supports dynamic modification |
|-----------------------|--------------------------------------------------------------------------------------------------|----------------|-------------------------------|-------------------------------|
| `build-backend` | The wrapped build backend (e.g. `setuptools.build_meta`) | string | | N |
| `commit-files` | List of files in which to write the git commit hash | list[str] | ["<project_name>/GIT_COMMIT"] | N |
| `dependencies-file` | The path to the `dependencies.yaml` file to use | string | "dependencies.yaml" | Y |
| `disable-cuda` | If true, CUDA version in build environment is ignored when setting package name and dependencies | bool | false | Y |
| `matrix-entry` | A `;`-separated list of `=`-delimited key/value pairs | string | "" | Y |
| `requires` | List of build requirements (in addition to `build-system.requires`) | list[str] | [] | N |


## Outstanding questions
Expand Down
3 changes: 1 addition & 2 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ dependencies:
- PyYAML
- packaging
- rapids-dependency-file-generator>=1.13.3,<2.0.dev0
- tomli
- tomli-w
- tomlkit
test:
common:
- output_types: [conda, requirements, pyproject]
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ dependencies = [
"PyYAML",
"packaging",
"rapids-dependency-file-generator>=1.13.3,<2.0.dev0",
"tomli",
"tomli-w",
"tomlkit",
] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit dependencies.yaml and run `rapids-dependency-file-generator`.
license = { text = "Apache 2.0" }
readme = { file = "README.md", content-type = "text/markdown" }
Expand Down
24 changes: 13 additions & 11 deletions rapids_build_backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
config_val_callable = Callable[[], Union[config_val_type, mutable_config_val_type]]

config_options_type = dict[
str, tuple[Union[config_val_type, config_val_callable], bool]
str, tuple[Union[config_val_type, config_val_callable], bool, bool]
]


class Config:
"""Manage the build configuration for the project."""

# Mapping from config option to default value (None indicates that option is
# required) and whether it may be overridden by an environment variable or a config
# Mapping from config option to default value, whether it's required, and
# whether it may be overridden by an environment variable or a config
# setting.
config_options: "config_options_type" = {
"build-backend": (None, False),
"commit-file": ("", False),
"dependencies-file": ("dependencies.yaml", True),
"matrix-entry": ("", True),
"require-cuda": (True, True),
"requires": (lambda: [], False),
"build-backend": (None, True, False),
"commit-files": (None, False, False),
"dependencies-file": ("dependencies.yaml", False, True),
"disable-cuda": (False, False, True),
"matrix-entry": ("", False, True),
"requires": (lambda: [], False, False),
}

def __init__(self, dirname=".", config_settings=None):
Expand All @@ -46,7 +46,9 @@ def __init__(self, dirname=".", config_settings=None):
def __getattr__(self, name):
config_name = name.replace("_", "-")
if config_name in Config.config_options:
default_value, allows_override = Config.config_options[config_name]
default_value, required, allows_override = Config.config_options[
config_name
]
if callable(default_value):
default_value = default_value()

Expand Down Expand Up @@ -74,7 +76,7 @@ def __getattr__(self, name):
try:
return self.config[config_name]
except KeyError:
if default_value is not None:
if not required:
return default_value

raise AttributeError(f"Config is missing required attribute '{name}'")
Expand Down
119 changes: 51 additions & 68 deletions rapids_build_backend/impls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from typing import Union

import rapids_dependency_file_generator
import tomli_w
import tomlkit

from . import utils
from .config import Config
Expand Down Expand Up @@ -40,62 +40,44 @@ def _get_backend(build_backend):


@lru_cache
def _get_cuda_version(require_cuda: bool):
def _get_cuda_version():
"""Get the CUDA suffix based on nvcc.
Parameters
----------
require_cuda : bool
If True, raise an exception if nvcc is not in the PATH. If False, return None.
Returns
-------
str or None
The CUDA major version (e.g., "11") or None if CUDA could not be detected.
The CUDA major version (e.g., "11")
"""
try:
nvcc_exists = (
subprocess.run(["which", "nvcc"], capture_output=True).returncode == 0
nvcc_exists = subprocess.run(["which", "nvcc"], capture_output=True).returncode == 0
if not nvcc_exists:
raise ValueError(
"Could not determine the CUDA version. Make sure nvcc is in your PATH."
)
if not nvcc_exists:
raise ValueError(
"Could not determine the CUDA version. Make sure nvcc is in your PATH."
)

try:
process_output = subprocess.run(["nvcc", "--version"], capture_output=True)
except subprocess.CalledProcessError as e:
raise ValueError("Failed to get version from nvcc.") from e
try:
process_output = subprocess.run(["nvcc", "--version"], capture_output=True)
except subprocess.CalledProcessError as e:
raise ValueError("Failed to get version from nvcc.") from e

output_lines = process_output.stdout.decode().splitlines()
output_lines = process_output.stdout.decode().splitlines()

match = re.search(r"release (\d+)\.(\d+)", output_lines[3])
if match is None:
raise ValueError("Failed to parse CUDA version from nvcc output.")
return match.groups()
except Exception:
if not require_cuda:
return None
raise
match = re.search(r"release (\d+)\.(\d+)", output_lines[3])
if match is None:
raise ValueError("Failed to parse CUDA version from nvcc output.")
return match.groups()


@lru_cache
def _get_cuda_suffix(require_cuda: bool) -> str:
def _get_cuda_suffix() -> str:
"""Get the CUDA suffix based on nvcc.
Parameters
----------
require_cuda : bool
If True, raise an exception if CUDA could not be detected. If False, return an
empty string.
Returns
-------
str
The CUDA suffix (e.g., "-cu11") or an empty string if CUDA could not be
detected.
"""
if (version := _get_cuda_version(require_cuda)) is None:
if (version := _get_cuda_version()) is None:
return ""
return f"-cu{version[0]}"

Expand All @@ -119,36 +101,27 @@ def _get_git_commit() -> Union[str, None]:


@contextmanager
def _edit_git_commit(config):
def _write_git_commits(config, project_name: str):
"""
Temporarily modify the git commit of the package being built.
Temporarily write the git commit files for the package being built. If the
`commit-files` config option is not specified, write to `<project_name>/GIT_COMMIT`.
This is useful for projects that want to embed the current git commit in the package
at build time.
"""
commit_file = config.commit_file
commit = _get_git_commit()

if commit_file != "" and commit is not None:
bkp_commit_file = os.path.join(
os.path.dirname(commit_file),
f".{os.path.basename(commit_file)}.rapids-build-backend.bak",
)
try:
try:
shutil.move(commit_file, bkp_commit_file)
except FileNotFoundError:
bkp_commit_file = None
commit_files = config.commit_files
if commit_files is None:
commit_files = [os.path.join(project_name.replace("-", "_"), "GIT_COMMIT")]
commit = _get_git_commit() if commit_files else None

if commit is not None:
for commit_file in commit_files:
with open(commit_file, "w") as f:
f.write(f"{commit}\n")

try:
yield
finally:
# Restore by moving rather than writing to avoid any formatting changes.
if bkp_commit_file:
shutil.move(bkp_commit_file, commit_file)
else:
for commit_file in commit_files:
os.unlink(commit_file)
else:
yield
Expand All @@ -166,7 +139,8 @@ def _edit_pyproject(config):
pyproject_file = "pyproject.toml"
bkp_pyproject_file = ".pyproject.toml.rapids-build-backend.bak"

cuda_version = _get_cuda_version(config.require_cuda)
if not config.disable_cuda:
cuda_version_major, cuda_version_minor = _get_cuda_version()

# "dependencies.yaml" might not exist in sdists and wouldn't need to... so don't
# raise an exception if that file can't be found when this runs
Expand Down Expand Up @@ -202,8 +176,8 @@ def _edit_pyproject(config):
):
continue
matrix = _parse_matrix(config.matrix_entry) or dict(file_config.matrix)
if cuda_version is not None:
matrix["cuda"] = [f"{cuda_version[0]}.{cuda_version[1]}"]
if not config.disable_cuda:
matrix["cuda"] = [f"{cuda_version_major}.{cuda_version_minor}"]
rapids_dependency_file_generator.make_dependency_files(
parsed_config=parsed_config,
file_keys=[file_key],
Expand All @@ -212,11 +186,12 @@ def _edit_pyproject(config):
prepend_channels=[],
to_stdout=False,
)
pyproject = utils._get_pyproject()
project_data = pyproject["project"]
project_data["name"] += _get_cuda_suffix(config.require_cuda)
with open(pyproject_file, "wb") as f:
tomli_w.dump(pyproject, f)
if not config.disable_cuda:
pyproject = utils._get_pyproject()
project_data = pyproject["project"]
project_data["name"] += _get_cuda_suffix()
with open(pyproject_file, "w") as f:
tomlkit.dump(pyproject, f)
yield
finally:
# Restore by moving rather than writing to avoid any formatting changes.
Expand All @@ -236,7 +211,9 @@ def _edit_pyproject(config):
# (the actual build backend, which conditionally imports these functions).
def get_requires_for_build_wheel(config_settings):
config = Config(config_settings=config_settings)
with _edit_pyproject(config), _edit_git_commit(config):
pyproject = utils._get_pyproject()
project_name = pyproject["project"]["name"]
with _edit_pyproject(config), _write_git_commits(config, project_name):
# Reload the config for a possibly updated tool.rapids-build-backend.requires
reloaded_config = Config(config_settings=config_settings)
requires = list(reloaded_config.requires)
Expand All @@ -252,7 +229,9 @@ def get_requires_for_build_wheel(config_settings):

def get_requires_for_build_sdist(config_settings):
config = Config(config_settings=config_settings)
with _edit_pyproject(config), _edit_git_commit(config):
pyproject = utils._get_pyproject()
project_name = pyproject["project"]["name"]
with _edit_pyproject(config), _write_git_commits(config, project_name):
# Reload the config for a possibly updated tool.rapids-build-backend.requires
reloaded_config = Config(config_settings=config_settings)
requires = list(reloaded_config.requires)
Expand Down Expand Up @@ -284,15 +263,19 @@ def get_requires_for_build_editable(config_settings):

def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
config = Config(config_settings=config_settings)
with _edit_pyproject(config), _edit_git_commit(config):
pyproject = utils._get_pyproject()
project_name = pyproject["project"]["name"]
with _edit_pyproject(config), _write_git_commits(config, project_name):
return _get_backend(config.build_backend).build_wheel(
wheel_directory, config_settings, metadata_directory
)


def build_sdist(sdist_directory, config_settings=None):
config = Config(config_settings=config_settings)
with _edit_pyproject(config), _edit_git_commit(config):
pyproject = utils._get_pyproject()
project_name = pyproject["project"]["name"]
with _edit_pyproject(config), _write_git_commits(config, project_name):
return _get_backend(config.build_backend).build_sdist(
sdist_directory, config_settings
)
Expand Down
8 changes: 4 additions & 4 deletions rapids_build_backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import os

import tomli
import tomlkit


def _get_pyproject(dirname: str = ".") -> dict:
def _get_pyproject(dirname: str = ".") -> tomlkit.toml_document.TOMLDocument:
"""Parse and return the pyproject.toml file."""
with open(os.path.join(dirname, "pyproject.toml"), "rb") as f:
return tomli.load(f)
with open(os.path.join(dirname, "pyproject.toml")) as f:
return tomlkit.load(f)
19 changes: 9 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
from pathlib import Path

import pytest
import tomli
import tomli_w
import tomlkit
from jinja2 import Environment, FileSystemLoader
from packaging.version import parse as parse_version

Expand Down Expand Up @@ -69,10 +68,10 @@ def patch_nvcc_if_needed(nvcc_version):
try:
# Only create a patch if one is required. In addition to reducing overhead, this
# also ensures that we test the real nvcc and don't mask any relevant errors.
if (
_get_cuda_version(False) is None
or _get_cuda_version(False)[0] != nvcc_version
):
try:
if _get_cuda_version()[0] != nvcc_version:
raise ValueError
except ValueError:
nvcc = _create_nvcc(nvcc_version)
os.environ["PATH"] = os.pathsep.join(
[os.path.dirname(nvcc), os.environ["PATH"]]
Expand Down Expand Up @@ -179,14 +178,14 @@ def wheelhouse(tmp_path_factory, pip_cache):
)

pyproject_file = rapids_build_backend_build_dir / "pyproject.toml"
with open(pyproject_file, "rb") as f:
pyproject = tomli.load(f)
with open(pyproject_file) as f:
pyproject = tomlkit.load(f)
project_data = pyproject["project"]

version = parse_version(project_data["version"])
project_data["version"] = f"{version.major + 1}.{version.minor}.{version.micro}"
with open(pyproject_file, "wb") as f:
tomli_w.dump(pyproject, f)
with open(pyproject_file, "w") as f:
tomlkit.dump(pyproject, f)

subprocess.run(
[
Expand Down
Loading

0 comments on commit dfde9d3

Please sign in to comment.