forked from mesonbuild/meson
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
527 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## CUDA dependency | ||
|
||
Native support for compiling and linking against the CUDA Toolkit using | ||
the `dependency` function: | ||
|
||
```meson | ||
project('CUDA test', 'cpp', meson_version: '>= 0.53.0') | ||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda')) | ||
``` | ||
|
||
See [the CUDA dependency](Dependencies.md#cuda) for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
# Copyright 2013-2019 The Meson development team | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
|
||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import glob | ||
import re | ||
import os | ||
|
||
from .. import mlog | ||
from .. import mesonlib | ||
from ..environment import detect_cpu_family | ||
|
||
from .base import (DependencyException, ExternalDependency, HasNativeKwarg) | ||
|
||
|
||
class CudaDependency(ExternalDependency): | ||
|
||
supported_languages = ['cuda', 'cpp', 'c'] # see also _default_language | ||
|
||
def __init__(self, environment, kwargs): | ||
HasNativeKwarg.__init__(self, kwargs) # initialize self.for_machine | ||
compilers = environment.coredata.compilers[self.for_machine] | ||
language = self._detect_language(compilers) | ||
if language not in self.supported_languages: | ||
raise DependencyException('Language \'{}\' is not supported by the CUDA Toolkit. Supported languages are {}.'.format(language, self.supported_languages)) | ||
|
||
super().__init__('cuda', environment, language, kwargs) | ||
self.requested_modules = self.get_requested(kwargs) | ||
if 'cudart' not in self.requested_modules: | ||
self.requested_modules = ['cudart'] + self.requested_modules | ||
|
||
(self.cuda_path, self.version, self.is_found) = self._detect_cuda_path_and_version() | ||
if not self.is_found: | ||
return | ||
|
||
if not os.path.isabs(self.cuda_path): | ||
raise DependencyException('CUDA Toolkit path must be absolute, got \'{}\'.'.format(self.cuda_path)) | ||
|
||
# nvcc already knows where to find the CUDA Toolkit, but if we're compiling | ||
# a mixed C/C++/CUDA project, we still need to make the include dir searchable | ||
if self.language != 'cuda' or len(compilers) > 1: | ||
self.incdir = os.path.join(self.cuda_path, 'include') | ||
self.compile_args += ['-I{}'.format(self.incdir)] | ||
|
||
if self.language != 'cuda': | ||
arch_libdir = self._detect_arch_libdir() | ||
self.libdir = os.path.join(self.cuda_path, arch_libdir) | ||
mlog.debug('CUDA library directory is', mlog.bold(self.libdir)) | ||
else: | ||
self.libdir = None | ||
|
||
self.is_found = self._find_requested_libraries() | ||
|
||
@classmethod | ||
def _detect_language(cls, compilers): | ||
for lang in cls.supported_languages: | ||
if lang in compilers: | ||
return lang | ||
return list(compilers.keys())[0] | ||
|
||
def _detect_cuda_path_and_version(self): | ||
self.env_var = self._default_path_env_var() | ||
mlog.debug('Default path env var:', mlog.bold(self.env_var)) | ||
|
||
version_reqs = self.version_reqs | ||
if self.language == 'cuda': | ||
nvcc_version = self._strip_patch_version(self.get_compiler().version) | ||
mlog.debug('nvcc version:', mlog.bold(nvcc_version)) | ||
if version_reqs: | ||
# make sure nvcc version satisfies specified version requirements | ||
(found_some, not_found, found) = mesonlib.version_compare_many(nvcc_version, version_reqs) | ||
if not_found: | ||
msg = 'The current nvcc version {} does not satisfy the specified CUDA Toolkit version requirements {}.'.format(nvcc_version, version_reqs) | ||
return self._report_dependency_error(msg, (None, None, False)) | ||
|
||
# use nvcc version to find a matching CUDA Toolkit | ||
version_reqs = ['={}'.format(nvcc_version)] | ||
else: | ||
nvcc_version = None | ||
|
||
paths = [(path, self._cuda_toolkit_version(path), default) for (path, default) in self._cuda_paths()] | ||
if version_reqs: | ||
return self._find_matching_toolkit(paths, version_reqs, nvcc_version) | ||
|
||
defaults = [(path, version) for (path, version, default) in paths if default] | ||
if defaults: | ||
return (*defaults[0], True) | ||
|
||
platform_msg = 'set the CUDA_PATH environment variable' if self._is_windows() \ | ||
else 'set the CUDA_PATH environment variable/create the \'/usr/local/cuda\' symbolic link' | ||
msg = 'Please specify the desired CUDA Toolkit version (e.g. dependency(\'cuda\', version : \'>=10.1\')) or {} to point to the location of your desired version.'.format(platform_msg) | ||
return self._report_dependency_error(msg, (None, None, False)) | ||
|
||
def _find_matching_toolkit(self, paths, version_reqs, nvcc_version): | ||
# keep the default paths order intact, sort the rest in the descending order | ||
# according to the toolkit version | ||
defaults, rest = mesonlib.partition(lambda t: not t[2], paths) | ||
defaults = list(defaults) | ||
paths = defaults + sorted(rest, key=lambda t: mesonlib.Version(t[1]), reverse=True) | ||
mlog.debug('Search paths: {}'.format(paths)) | ||
|
||
if nvcc_version and defaults: | ||
default_src = "the {} environment variable".format(self.env_var) if self.env_var else "the \'/usr/local/cuda\' symbolic link" | ||
nvcc_warning = 'The default CUDA Toolkit as designated by {} ({}) doesn\'t match the current nvcc version {} and will be ignored.'.format(default_src, os.path.realpath(defaults[0][0]), nvcc_version) | ||
else: | ||
nvcc_warning = None | ||
|
||
for (path, version, default) in paths: | ||
(found_some, not_found, found) = mesonlib.version_compare_many(version, version_reqs) | ||
if not not_found: | ||
if not default and nvcc_warning: | ||
mlog.warning(nvcc_warning) | ||
return (path, version, True) | ||
|
||
if nvcc_warning: | ||
mlog.warning(nvcc_warning) | ||
return (None, None, False) | ||
|
||
def _default_path_env_var(self): | ||
env_vars = ['CUDA_PATH'] if self._is_windows() else ['CUDA_PATH', 'CUDA_HOME', 'CUDA_ROOT'] | ||
env_vars = [var for var in env_vars if var in os.environ] | ||
user_defaults = set([os.environ[var] for var in env_vars]) | ||
if len(user_defaults) > 1: | ||
mlog.warning('Environment variables {} point to conflicting toolkit locations ({}). Toolkit selection might produce unexpected results.'.format(', '.join(env_vars), ', '.join(user_defaults))) | ||
return env_vars[0] if env_vars else None | ||
|
||
def _cuda_paths(self): | ||
return ([(os.environ[self.env_var], True)] if self.env_var else []) \ | ||
+ (self._cuda_paths_win() if self._is_windows() else self._cuda_paths_nix()) | ||
|
||
def _cuda_paths_win(self): | ||
env_vars = os.environ.keys() | ||
return [(os.environ[var], False) for var in env_vars if var.startswith('CUDA_PATH_')] | ||
|
||
def _cuda_paths_nix(self): | ||
# include /usr/local/cuda default only if no env_var was found | ||
pattern = '/usr/local/cuda-*' if self.env_var else '/usr/local/cuda*' | ||
return [(path, os.path.basename(path) == 'cuda') for path in glob.iglob(pattern)] | ||
|
||
toolkit_version_regex = re.compile(r'^CUDA Version\s+(.*)$') | ||
path_version_win_regex = re.compile(r'^v(.*)$') | ||
path_version_nix_regex = re.compile(r'^cuda-(.*)$') | ||
|
||
def _cuda_toolkit_version(self, path): | ||
version = self._read_toolkit_version_txt(path) | ||
if version: | ||
return version | ||
|
||
mlog.debug('Falling back to extracting version from path') | ||
path_version_regex = self.path_version_win_regex if self._is_windows() else self.path_version_nix_regex | ||
m = path_version_regex.match(os.path.basename(path)) | ||
if m: | ||
return m[1] | ||
|
||
mlog.warning('Could not detect CUDA Toolkit version for {}'.format(path)) | ||
return '0.0' | ||
|
||
def _read_toolkit_version_txt(self, path): | ||
# Read 'version.txt' at the root of the CUDA Toolkit directory to determine the tookit version | ||
version_file_path = os.path.join(path, 'version.txt') | ||
try: | ||
with open(version_file_path) as version_file: | ||
version_str = version_file.readline() # e.g. 'CUDA Version 10.1.168' | ||
m = self.toolkit_version_regex.match(version_str) | ||
if m: | ||
return self._strip_patch_version(m[1]) | ||
except Exception as e: | ||
mlog.debug('Could not read CUDA Toolkit\'s version file {}: {}'.format(version_file_path, str(e))) | ||
|
||
return None | ||
|
||
@classmethod | ||
def _strip_patch_version(cls, version): | ||
return '.'.join(version.split('.')[:2]) | ||
|
||
def _detect_arch_libdir(self): | ||
arch = detect_cpu_family(self.env.coredata.compilers.host) | ||
machine = self.env.machines[self.for_machine] | ||
msg = '{} architecture is not supported in {} version of the CUDA Toolkit.' | ||
if machine.is_windows(): | ||
libdirs = {'x86': 'Win32', 'x86_64': 'x64'} | ||
if arch not in libdirs: | ||
raise DependencyException(msg.format(arch, 'Windows')) | ||
return os.path.join('lib', libdirs[arch]) | ||
elif machine.is_linux(): | ||
libdirs = {'x86_64': 'lib64', 'ppc64': 'lib'} | ||
if arch not in libdirs: | ||
raise DependencyException(msg.format(arch, 'Linux')) | ||
return libdirs[arch] | ||
elif machine.is_osx(): | ||
libdirs = {'x86_64': 'lib64'} | ||
if arch not in libdirs: | ||
raise DependencyException(msg.format(arch, 'macOS')) | ||
return libdirs[arch] | ||
else: | ||
raise DependencyException('CUDA Toolkit: unsupported platform.') | ||
|
||
def _find_requested_libraries(self): | ||
self.lib_modules = {} | ||
all_found = True | ||
|
||
for module in self.requested_modules: | ||
args = self.clib_compiler.find_library(module, self.env, [self.libdir] if self.libdir else []) | ||
if args is None: | ||
self._report_dependency_error('Couldn\'t find requested CUDA module \'{}\''.format(module)) | ||
all_found = False | ||
else: | ||
mlog.debug('Link args for CUDA module \'{}\' are {}'.format(module, args)) | ||
self.lib_modules[module] = args | ||
|
||
return all_found | ||
|
||
def _is_windows(self): | ||
return self.env.machines[self.for_machine].is_windows() | ||
|
||
def _report_dependency_error(self, msg, ret_val=None): | ||
if self.required: | ||
raise DependencyException(msg) | ||
|
||
mlog.debug(msg) | ||
return ret_val | ||
|
||
def log_details(self): | ||
module_str = ', '.join(self.requested_modules) | ||
return 'modules: ' + module_str | ||
|
||
def log_info(self): | ||
return self.cuda_path if self.cuda_path else '' | ||
|
||
def get_requested(self, kwargs): | ||
candidates = mesonlib.extract_as_list(kwargs, 'modules') | ||
for c in candidates: | ||
if not isinstance(c, str): | ||
raise DependencyException('CUDA module argument is not a string.') | ||
return candidates | ||
|
||
def get_link_args(self, **kwargs): | ||
args = [] | ||
if self.libdir: | ||
args += self.clib_compiler.get_linker_search_args(self.libdir) | ||
for lib in self.requested_modules: | ||
args += self.lib_modules[lib] | ||
return args |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
exe = executable('prog', 'prog.c', dependencies: dependency('cuda')) | ||
test('cudatest', exe) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#include <cuda_runtime.h> | ||
#include <stdio.h> | ||
|
||
int cuda_devices() { | ||
int result = 0; | ||
cudaGetDeviceCount(&result); | ||
return result; | ||
} | ||
|
||
int main() { | ||
int n = cuda_devices(); | ||
if (n == 0) { | ||
printf("No CUDA hardware found. Exiting.\n"); | ||
return 0; | ||
} | ||
|
||
printf("Found %i CUDA devices.\n", n); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda')) | ||
test('cudatest', exe) |
Oops, something went wrong.