Compare commits

...

3 Commits

Author SHA1 Message Date
Aleksey Gurtovoy f56ef583d3 dependency('cuda') 5 years ago
Dylan Baker 11f1adb7dd docs: Document that meson 0.52.0 allows layering cross files [skip ci] 5 years ago
sghctoma aba8792b66 Change default pkgconfig directory on FreeBSD 5 years ago
  1. 5
      docs/markdown/Cross-compilation.md
  2. 16
      docs/markdown/Dependencies.md
  3. 7
      docs/markdown/Native-environments.md
  4. 11
      docs/markdown/snippets/cuda_dependency.md
  5. 5
      mesonbuild/compilers/cuda.py
  6. 5
      mesonbuild/dependencies/__init__.py
  7. 1
      mesonbuild/dependencies/base.py
  8. 253
      mesonbuild/dependencies/cuda.py
  9. 7
      mesonbuild/mesonlib.py
  10. 5
      mesonbuild/modules/pkgconfig.py
  11. 2
      test cases/cuda/10 cuda dependency/c/meson.build
  12. 19
      test cases/cuda/10 cuda dependency/c/prog.c
  13. 2
      test cases/cuda/10 cuda dependency/cpp/meson.build
  14. 19
      test cases/cuda/10 cuda dependency/cpp/prog.cc
  15. 6
      test cases/cuda/10 cuda dependency/meson.build
  16. 2
      test cases/cuda/10 cuda dependency/modules/meson.build
  17. 33
      test cases/cuda/10 cuda dependency/modules/prog.cc
  18. 2
      test cases/cuda/10 cuda dependency/version_reqs/meson.build
  19. 28
      test cases/cuda/10 cuda dependency/version_reqs/prog.cc
  20. 4
      test cases/cuda/11 cuda dependency (nvcc)/meson.build
  21. 2
      test cases/cuda/11 cuda dependency (nvcc)/modules/meson.build
  22. 33
      test cases/cuda/11 cuda dependency (nvcc)/modules/prog.cu
  23. 2
      test cases/cuda/11 cuda dependency (nvcc)/version_reqs/meson.build
  24. 28
      test cases/cuda/11 cuda dependency (nvcc)/version_reqs/prog.cu
  25. 8
      test cases/cuda/12 cuda dependency (mixed)/kernel.cu
  26. 4
      test cases/cuda/12 cuda dependency (mixed)/meson.build
  27. 37
      test cases/cuda/12 cuda dependency (mixed)/prog.cpp

@ -206,6 +206,11 @@ bindir = 'bin'
This will be overwritten by any options passed on the command line.
Since meson 0.52.0 it is possible to layer cross files together. This
works like native file layering: the purpose is to compose cross files
together, and values from the second cross file will replace those
from the first.
## Starting a cross build

@ -285,6 +285,22 @@ environment variables.
You can set the argument `threading` to `single` to use boost
libraries that have been compiled for single-threaded use instead.
## CUDA
*(added 0.53.0)*
Enables compiling and linking against the CUDA Toolkit. The `version`
and `modules` keywords may be passed to request the use of a specific
CUDA Toolkit version and/or additional CUDA libraries, correspondingly:
```meson
dep = dependency('cuda', version : '>=10', modules : ['cublas'])
```
Note that explicitly adding this dependency is only necessary if you are
using CUDA Toolkit from a C/C++ file or project, or if you are utilizing
additional toolkit libraries that need to be explicitly linked to.
## CUPS
`method` may be `auto`, `config-tool`, `pkg-config`, `cmake` or `extraframework`.

@ -62,9 +62,10 @@ command line will override any options in the native file. For example, passing
## Loading multiple native files
Unlike cross file, native files allow layering. More than one native file can be
loaded, with values from a previous file being overridden by the next. The
intention of this is not overriding, but to allow composing native files.
Native files allow layering (cross files can be layered since meson 0.52.0).
More than one native file can be loaded, with values from a previous file being
overridden by the next. The intention of this is not overriding, but to allow
composing native files.
For example, if there is a project using C and C++, python 3.4-3.7, and LLVM
5-7, and it needs to build with clang 5, 6, and 7, and gcc 5.x, 6.x, and 7.x;

@ -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.

@ -18,7 +18,7 @@ from functools import partial
from .. import coredata
from .. import mlog
from ..mesonlib import EnvironmentException, MachineChoice, Popen_safe, OptionOverrideProxy, is_windows
from ..mesonlib import EnvironmentException, MachineChoice, Popen_safe, OptionOverrideProxy, is_windows, LibType
from .compilers import (Compiler, cuda_buildtype_args, cuda_optimization_args,
cuda_debug_args)
@ -294,6 +294,9 @@ class CudaCompiler(Compiler):
def get_std_exe_link_args(self) -> typing.List[str]:
return self._cook_link_args(self.host_compiler.get_std_exe_link_args())
def find_library(self, libname, env, extra_dirs, libtype: LibType = LibType.PREFER_SHARED):
return ['-l' + libname] # FIXME
def get_crt_compile_args(self, crt_val, buildtype):
return self._to_host_flags(self.host_compiler.get_crt_compile_args(crt_val, buildtype))

@ -13,6 +13,7 @@
# limitations under the License.
from .boost import BoostDependency
from .cuda import CudaDependency
from .base import ( # noqa: F401
Dependency, DependencyException, DependencyMethods, ExternalProgram, EmptyExternalProgram, NonExistingExternalProgram,
ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency,
@ -30,9 +31,11 @@ packages.update({
'llvm': LLVMDependency,
'valgrind': ValgrindDependency,
'boost': BoostDependency,
'cuda': CudaDependency,
# From misc:
'blocks': BlocksDependency,
'boost': BoostDependency,
'coarray': CoarrayDependency,
'mpi': MPIDependency,
'hdf5': HDF5Dependency,

@ -2134,6 +2134,7 @@ def get_dep_identifier(name, kwargs) -> Tuple:
display_name_map = {
'boost': 'Boost',
'cuda': 'CUDA',
'dub': 'DUB',
'gmock': 'GMock',
'gtest': 'GTest',

@ -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

@ -21,6 +21,7 @@ import platform, subprocess, operator, os, shlex, shutil, re
import collections
from enum import Enum
from functools import lru_cache, update_wrapper
from itertools import tee, filterfalse
import typing
import uuid
@ -1051,6 +1052,12 @@ def expand_arguments(args):
return None
return expended_args
def partition(pred, iterable):
'Use a predicate to partition entries into false entries and true entries'
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
def Popen_safe(args: typing.List[str], write: typing.Optional[str] = None,
stdout: typing.Union[typing.BinaryIO, int] = subprocess.PIPE,
stderr: typing.Union[typing.BinaryIO, int] = subprocess.PIPE,

@ -437,7 +437,10 @@ class PkgConfigModule(ExtensionModule):
pcfile = filebase + '.pc'
pkgroot = kwargs.get('install_dir', default_install_dir)
if pkgroot is None:
pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig')
if mesonlib.is_freebsd():
pkgroot = os.path.join(state.environment.coredata.get_builtin_option('prefix'), 'libdata', 'pkgconfig')
else:
pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig')
if not isinstance(pkgroot, str):
raise mesonlib.MesonException('Install_dir must be a string.')
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.c', dependencies: dependency('cuda'))
test('cudatest', exe)

@ -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;
}

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda'))
test('cudatest', exe)

@ -0,0 +1,19 @@
#include <cuda_runtime.h>
#include <iostream>
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
return 0;
}

@ -0,0 +1,6 @@
project('cuda dependency', 'c', 'cpp')
subdir('c')
subdir('cpp')
subdir('modules')
subdir('version_reqs')

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', modules: ['cublas']))
test('cudatest', exe)

@ -0,0 +1,33 @@
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
cublasHandle_t handle;
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS initialization failed. Exiting.\n";
return -1;
}
std::cout << "Initialized cuBLAS\n";
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS de-initialization failed. Exiting.\n";
return -1;
}
return 0;
}

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', version: ['>=8.5', '<10'], required: false, disabler: true))
test('cudatest', exe)

@ -0,0 +1,28 @@
#include <cuda_runtime.h>
#include <iostream>
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
std::cout << "Compiled against CUDA version: " << CUDART_VERSION << "\n";
int runtime_version = 0;
cudaError_t r = cudaRuntimeGetVersion(&runtime_version);
if (r != cudaSuccess) {
std::cout << "Couldn't obtain CUDA runtime version (error " << r << "). Exiting.\n";
return -1;
}
std::cout << "CUDA runtime version: " << runtime_version << "\n";
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
return 0;
}

@ -0,0 +1,4 @@
project('cuda dependency', 'cuda')
subdir('modules')
subdir('version_reqs')

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', modules: ['cublas']))
test('cudatest', exe)

@ -0,0 +1,33 @@
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
cublasHandle_t handle;
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS initialization failed. Exiting.\n";
return -1;
}
std::cout << "Initialized cuBLAS\n";
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS de-initialization failed. Exiting.\n";
return -1;
}
return 0;
}

@ -0,0 +1,2 @@
exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', version: ['>=10.1'], required: false, disabler: true))
test('cudatest', exe)

@ -0,0 +1,28 @@
#include <cuda_runtime.h>
#include <iostream>
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
std::cout << "Compiled against CUDA version: " << CUDART_VERSION << "\n";
int runtime_version = 0;
cudaError_t r = cudaRuntimeGetVersion(&runtime_version);
if (r != cudaSuccess) {
std::cout << "Couldn't obtain CUDA runtime version (error " << r << "). Exiting.\n";
return -1;
}
std::cout << "CUDA runtime version: " << runtime_version << "\n";
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
return 0;
}

@ -0,0 +1,8 @@
#include <cuda_runtime.h>
__global__ void kernel (void){
}
void do_cuda_stuff() {
kernel<<<1,1>>>();
}

@ -0,0 +1,4 @@
project('cuda dependency', 'cpp', 'cuda')
exe = executable('prog', 'prog.cpp', 'kernel.cu', dependencies: dependency('cuda', modules: ['cublas']))
test('cudatest', exe)

@ -0,0 +1,37 @@
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>
void do_cuda_stuff();
int cuda_devices() {
int result = 0;
cudaGetDeviceCount(&result);
return result;
}
int main() {
int n = cuda_devices();
if (n == 0) {
std::cout << "No CUDA hardware found. Exiting.\n";
return 0;
}
std::cout << "Found " << n << " CUDA devices.\n";
do_cuda_stuff();
cublasHandle_t handle;
if (cublasCreate(&handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS initialization failed. Exiting.\n";
return -1;
}
std::cout << "Initialized cuBLAS\n";
if (cublasDestroy(handle) != CUBLAS_STATUS_SUCCESS) {
std::cout << "cuBLAS de-initialization failed. Exiting.\n";
return -1;
}
return 0;
}
Loading…
Cancel
Save