Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openmp/system: new recipe #22360

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
127 changes: 127 additions & 0 deletions recipes/openmp/all/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.microsoft import is_msvc

required_conan_version = ">=1.52.0"


class PackageConan(ConanFile):
name = "openmp"
description = "Conan meta-package for OpenMP (Open Multi-Processing)"
license = "MIT"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://www.openmp.org/"
topics = ("parallelism", "multiprocessing")

# package_type = "meta-package"
settings = "os", "arch", "compiler", "build_type"
options = {
"provider": ["auto", "native", "llvm"],
}
default_options = {
"provider": "auto",
}

def config_options(self):
if self.settings.compiler == "clang" and self.settings.os == "Linux":
# The Clang toolchain on Linux distros typically ships without libomp.
# FreeBSD includes it, though.
self.options.provider = "llvm"
elif self.settings.compiler == "apple-clang":
self.options.provider = "llvm"
else:
self.options.provider = "native"

def requirements(self):
if self.options.provider == "llvm":
# Note: MSVC ships with an optional LLVM OpenMP implementation, but it would require reliably setting
# `OpenMP_RUNTIME_MSVC=llvm` in CMake for all consumers of this recipe, which is not possible in a meta-package.
# Always use the latest llvm-openmp version, since the library is ABI-compatible across versions.
self.requires("llvm-openmp/[*]", transitive_headers=True, transitive_libs=True)

def package_id(self):
self.info.clear()

def validate(self):
if self.options.provider == "native" and self._openmp_flags is None:
raise ConanInvalidConfiguration(
f"{self.settings.compiler} is not supported by this recipe. Contributions are welcome!"
)

if self.options.provider == "llvm":
if self.settings.compiler not in ["clang", "apple-clang"] and not is_msvc(self):
# More info: https://cpufun.substack.com/p/is-mixing-openmp-runtimes-safe
self.output.warning(
"Warning: Using a non-native OpenMP implementation can be bug-prone. "
"Make sure you avoid accidental linking against the native implementation through external libraries."
)

@property
def _openmp_flags(self):
# Based on https://github.com/Kitware/CMake/blob/v3.30.0/Modules/FindOpenMP.cmake#L119-L154
if self.settings.compiler == "gcc":
return ["-fopenmp"]
elif self.settings.compiler == "clang":
return ["-fopenmp=libomp"]
elif self.settings.compiler == "apple-clang":
return ["-Xclang", "-fopenmp"]
elif is_msvc(self):
# Use `-o provider=llvm` for `-openmp=llvm` in MSVC.
# TODO: add support for `-openmp=experimental`?
return ["-openmp"]
elif self.settings.compiler == "intel-cc":
if self.settings.os == "Windows":
return ["-Qopenmp"]
else:
return ["-qopenmp"]
elif self.settings.compiler == "sun-cc":
return ["-xopenmp"]

# The following compilers are not currently covered by settings.yml,
# but are included for completeness.
elif self.settings.compiler == "hp":
return ["+Oopenmp"]
elif self.settings.compiler == "intel-llvm":
if self.settings.get_safe("compiler.frontend") == "msvc":
return ["-Qiopenmp"]
else:
return ["-fiopenmp"]
elif self.settings.compiler == "pathscale":
return ["-openmp"]
elif self.settings.compiler == "nag":
return ["-openmp"]
elif self.settings.compiler == "absoft":
return ["-openmp"]
elif self.settings.compiler == "nvhpc":
return ["-mp"]
elif self.settings.compiler == "pgi":
return ["-mp"]
elif self.settings.compiler == "xl":
return ["-qsmp=omp"]
elif self.settings.compiler == "cray":
return ["-h", "omp"]
elif self.settings.compiler == "fujitsu":
return ["-Kopenmp"]
elif self.settings.compiler == "fujitsu-clang":
return ["-fopenmp"]

return None

def package_info(self):
# Can't use cmake_find_mode=none because Conan tries to find_package() it internally,
# when used transitively.
self.cpp_info.set_property("cmake_file_name", "_openmp_")

self.cpp_info.frameworkdirs = []
self.cpp_info.libdirs = []
self.cpp_info.resdirs = []
self.cpp_info.includedirs = []

if self.options.provider == "native":
# Export appropriate flags for packages linking against this one transitively.
# Direct dependencies of this package will rely on CMake's FindOpenMP.cmake instead.
openmp_flags = self._openmp_flags
self.cpp_info.sharedlinkflags = openmp_flags
self.cpp_info.exelinkflags = openmp_flags
self.cpp_info.cflags = openmp_flags
self.cpp_info.cxxflags = openmp_flags
33 changes: 33 additions & 0 deletions recipes/openmp/all/test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.15)
project(PackageTest CXX C)

find_package(OpenMP REQUIRED)

message("OpenMP_FOUND: ${OpenMP_CXX_FOUND}")
message("OpenMP_VERSION: ${OpenMP_VERSION}")
message("OpenMP_C_FOUND: ${OpenMP_CXX_FOUND}")
message("OpenMP_CXX_FOUND: ${OpenMP_CXX_FOUND}")
message("OpenMP_CXX_VERSION: ${OpenMP_CXX_VERSION}")
message("OpenMP_CXX_SPEC_DATE: ${OpenMP_CXX_SPEC_DATE}")
message("OpenMP_CXX_INCLUDE_DIRS: ${OpenMP_CXX_INCLUDE_DIRS}")
message("OpenMP_CXX_LIB_NAMES: ${OpenMP_CXX_LIB_NAMES}")
message("OpenMP_CXX_LIBRARIES: ${OpenMP_CXX_LIBRARIES}")
message("OpenMP_CXX_FLAGS: ${OpenMP_CXX_FLAGS}")
message("OpenMP_omp_LIBRARY: ${OpenMP_omp_LIBRARY}")

if(NOT DEFINED OpenMP_CXX_SPEC_DATE)
message(FATAL_ERROR "FindOpenMP.cmake did not set all variables correctly.")
endif()

add_executable(test_package_cxx test_package.cpp)
target_link_libraries(test_package_cxx OpenMP::OpenMP_CXX)

add_executable(test_package_c test_package.c)
target_link_libraries(test_package_c OpenMP::OpenMP_C)

# Using PkgConfigDeps output to test the configuration exported by package_info().
# This is not a recommended or conventional way to use OpenMP.
find_package(PkgConfig REQUIRED)
pkg_check_modules(openmp REQUIRED IMPORTED_TARGET openmp)
add_executable(test_package_pkgconfig test_package.c)
target_link_libraries(test_package_pkgconfig PkgConfig::openmp)
42 changes: 42 additions & 0 deletions recipes/openmp/all/test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from conan import ConanFile
from conan.tools.build import can_run
from conan.tools.cmake import cmake_layout, CMake
from conan.tools.env import Environment
import os


class TestPackageConan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeDeps", "CMakeToolchain", "PkgConfigDeps", "VirtualBuildEnv", "VirtualRunEnv"
test_type = "explicit"

def requirements(self):
self.requires(self.tested_reference_str)

def build_requirements(self):
if not self.conf.get("tools.gnu:pkg_config", check_type=str):
self.tool_requires("pkgconf/2.2.0")

def layout(self):
cmake_layout(self)

def generate(self):
env = Environment()
# Trigger printing of runtime version info on startup.
# Should state "LLVM OMP" as the runtime library ID if everything is configured correctly.
env.define("KMP_VERSION", "TRUE")
# Display general OpenMP parameters in a standardized format.
# https://www.openmp.org/spec-html/5.0/openmpse60.html
env.define("OMP_DISPLAY_ENV", "TRUE")
env.vars(self, scope="run").save_script("conan_openmp_version")

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def test(self):
if can_run(self):
for executable in ["test_package_cxx", "test_package_c", "test_package_pkgconfig"]:
bin_path = os.path.join(self.cpp.build.bindir, executable)
self.run(bin_path, env="conanrun")
26 changes: 26 additions & 0 deletions recipes/openmp/all/test_package/test_package.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <omp.h>

#include <stdio.h>

int main()
{
int num_threads = omp_get_num_procs();
if (num_threads < 5)
num_threads = 5;
omp_set_num_threads(num_threads);
int actual_number;
#pragma omp parallel
{
#pragma omp single
{
actual_number = omp_get_num_threads();
}
}
if(actual_number != num_threads){
printf("Something went wrong. Expecting %d threads but found %d.\n", num_threads, actual_number);
printf("There are probably missing compiler flags.\n");
return 1;
}
printf("OpenMP API version supported by the compiler: %d\n", _OPENMP);
return 0;
}
24 changes: 24 additions & 0 deletions recipes/openmp/all/test_package/test_package.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <omp.h>

#include <iostream>

int main()
{
int num_threads = std::max(5, omp_get_num_procs());
omp_set_num_threads(num_threads);
int actual_number;
#pragma omp parallel
{
#pragma omp single
{
actual_number = omp_get_num_threads();
}
}
if(actual_number != num_threads){
std::cout << "Something went wrong. Expecting " << num_threads << " threads but found " << actual_number << ".\n";
std::cout << "There are probably missing compiler flags.\n";
return 1;
}
std::cout << "OpenMP API version supported by the compiler: " << _OPENMP << "\n";
return 0;
}
3 changes: 3 additions & 0 deletions recipes/openmp/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
versions:
"system":
folder: all
Loading