Skip to content

Commit

Permalink
openblas: Improve recipe plus conan v2
Browse files Browse the repository at this point in the history
    - Allow disabling AVX512 via env variable, workaround for #12705
    - Only make gfortran a dependency if actually used, related to #5910, #1867, #5338
    - Add latest OpenBLAS release version 0.3.23
    - Move replace_in_file to patches instead
    - Fix intel fortran compiler identification in OpenBLAS cmake
    - Allow build_lapack without fortran using C_LAPACK since 0.3.23
  • Loading branch information
joakimono committed Apr 23, 2023
1 parent 3da660b commit fc62160
Show file tree
Hide file tree
Showing 15 changed files with 754 additions and 99 deletions.
11 changes: 0 additions & 11 deletions recipes/openblas/all/CMakeLists.txt

This file was deleted.

40 changes: 40 additions & 0 deletions recipes/openblas/all/conandata.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
sources:
"0.3.23":
url: "https://github.com/xianyi/OpenBLAS/archive/v0.3.23.tar.gz"
sha256: "5d9491d07168a5d00116cdc068a40022c3455bf9293c7cb86a65b1054d7e5114"
"0.3.20":
url: "https://github.com/xianyi/OpenBLAS/archive/v0.3.20.tar.gz"
sha256: "8495c9affc536253648e942908e88e097f2ec7753ede55aca52e5dead3029e3c"
Expand All @@ -17,3 +20,40 @@ sources:
"0.3.10":
url: "https://github.com/xianyi/OpenBLAS/archive/v0.3.10.tar.gz"
sha256: "0484d275f87e9b8641ff2eecaa9df2830cbe276ac79ad80494822721de6e1693"

patches:
"0.3.10":
-
patch_file: "patches/0.3.10-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.12":
-
patch_file: "patches/0.3.12-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.13":
-
patch_file: "patches/0.3.13-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.15":
-
patch_file: "patches/0.3.15-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.17":
-
patch_file: "patches/0.3.17-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.20":
-
patch_file: "patches/0.3.20-0001-Fix-libm-linking-and-fortran-detection.patch"
patch_description: "Fix libm linking, fortran detection and intel fortran"
patch_type: "conan"
"0.3.23":
-
patch_file: "patches/0.3.23-0001-Fix-Intel-Fortran-detection-and-flag-change-to-fopen.patch"
patch_description: "Fix intel fortran detection and flag change to -fopenmp"
patch_type: "conan"
247 changes: 169 additions & 78 deletions recipes/openblas/all/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from conans import ConanFile, CMake, tools
from conans.errors import ConanInvalidConfiguration
import os
import functools
from os import path, environ
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.build import cross_building
from conan.tools.files import copy, get, load, rmdir, collect_libs
from conan.tools.files import apply_conandata_patches, export_conandata_patches
from conan.tools.scm import Version
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout

required_conan_version = ">=1.43.0"
required_conan_version = ">=1.55.0"


class OpenblasConan(ConanFile):
Expand All @@ -19,134 +23,221 @@ class OpenblasConan(ConanFile):
"fPIC": [True, False],
"build_lapack": [True, False],
"use_thread": [True, False],
"use_openmp": [True, False],
"dynamic_arch": [True, False],
}
default_options = {
"shared": False,
"fPIC": True,
"build_lapack": False,
"use_thread": True,
"use_openmp": False,
"dynamic_arch": False,
}
generators = "cmake"
package_type = "library"
short_paths = True

def _fortran_runtime(self, fortran_id):
if self.settings.os in ["Linux", "FreeBSD"]:
if fortran_id == "GNU":
if self.settings.compiler == "gcc":
if Version(self.settings.compiler.version).major in ["5", "6"]:
return "gfortran" # Runtime version gfortran3
if Version(self.settings.compiler.version).major == "7":
return "gfortran" # Runtime version gfortran4
if Version(self.settings.compiler.version).major > "7":
return "gfortran" # Runtime version gfortran5
if self.settings.compiler == "clang":
if Version(self.settings.compiler.version).major > "8":
return "gfortran" # Runtime version gfortran5

self.output.warning(
f"Unable to select runtime for Fortran {fortran_id} "
f"and C++ {self.settings.compiler} {self.settings.compiler.version}")
return None

@property
def _source_subfolder(self):
return "source_subfolder"
def _openmp_runtime(self):
if self.settings.os in ["Linux", "FreeBSD"]:
if self.settings.compiler == "gcc":
if Version(self.settings.compiler.version).major > "7":
return "gomp"
if self.settings.compiler == "clang":
if Version(self.settings.compiler.version).major > "8":
return "omp"
return None

@property
def _build_subfolder(self):
return "build_subfolder"
def _fortran_compiler(self):
comp_exe = self.conf.get("tools.build:compiler_executables")
if comp_exe and 'fortran' in comp_exe:
return comp_exe["fortran"]
return environ.get('FC')

def export_sources(self):
self.copy("CMakeLists.txt")
export_conandata_patches(self)

def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC

def configure(self):
if self.options.shared:
del self.options.fPIC
self.options.rm_safe("fPIC")

def layout(self):
cmake_layout(self, src_folder="src")

def package_id(self):
if self.info.options.build_lapack:
# Capture fortran compiler in package id (if found)
f_compiler = self._fortran_compiler
if not f_compiler:
self.output.warning("None or unknown fortran compiler was used.")
f_compiler = True
self.info.options.build_lapack = f_compiler

def validate(self):
if hasattr(self, "settings_build") and tools.cross_building(self, skip_x64_x86=True):
if cross_building(self, skip_x64_x86=True):
raise ConanInvalidConfiguration("Cross-building not implemented")

if self.options.use_thread and self.options.use_openmp:
raise ConanInvalidConfiguration("Both 'use_thread=True' and 'use_openmp=True' are not allowed")

if self.settings.os == "Windows" and self.options.build_lapack and self.options.use_openmp:
# In OpenBLAS cmake/system.cmake: Disable -fopenmp for LAPACK Fortran codes on Windows
self.output.warning("OpenMP is disabled on LAPACK targets on Windows")

def build_requirements(self):
if self.options.build_lapack and self.settings.os == "Windows":
self.tool_requires("ninja/1.11.1")

def system_requirements(self):
# TODO: should openmp runtime be system_requirements if 'use_openmp'?
# TODO: should gfortran runtime be system_requirement if 'build_lapack' with gfortran compiler
# GNU: gomp, LLVM/clang: omp, other: unknown
# Debian/Ubuntu: libgomp1, libomp5, Fedora/Rocky/Alpine: libgomp, libomp
# Debian/Ubuntu: libgfortran{3,4,5}, Fedora/Rocky/Alpine: libgfortran
pass

def source(self):
tools.get(
**self.conan_data["sources"][self.version],
strip_root=True,
destination=self._source_subfolder
)
get(self, **self.conan_data["sources"][self.version], strip_root=True)

@functools.lru_cache(1)
def _configure_cmake(self):
cmake = CMake(self)
def generate(self):

ninja_generator = "Ninja" if self.settings.os == "Windows" and \
self.options.build_lapack else None
tc = CMakeToolchain(self, generator=ninja_generator)

tc.variables["NOFORTRAN"] = not self.options.build_lapack
if self.options.build_lapack:
self.output.warn("Building with lapack support requires a Fortran compiler.")
cmake.definitions["NOFORTRAN"] = not self.options.build_lapack
cmake.definitions["BUILD_WITHOUT_LAPACK"] = not self.options.build_lapack
cmake.definitions["DYNAMIC_ARCH"] = self.options.dynamic_arch
cmake.definitions["USE_THREAD"] = self.options.use_thread
# This checks explicit user-specified fortran compiler
if not self._fortran_compiler:
if Version(self.version) < "0.3.21":
self.output.warning(
"Building with LAPACK support requires a Fortran compiler.")
else:
tc.variables["C_LAPACK"] = True
tc.variables["NOFORTRAN"] = True
self.output.info(
"Building LAPACK without Fortran compiler")

tc.variables["BUILD_WITHOUT_LAPACK"] = not self.options.build_lapack
tc.variables["DYNAMIC_ARCH"] = self.options.dynamic_arch
tc.variables["USE_THREAD"] = self.options.use_thread
tc.variables["USE_OPENMP"] = self.options.use_openmp

# Required for safe concurrent calls to OpenBLAS routines
cmake.definitions["USE_LOCKING"] = not self.options.use_thread
tc.variables["USE_LOCKING"] = not self.options.use_thread

cmake.definitions[
"MSVC_STATIC_CRT"
] = False # don't, may lie to consumer, /MD or /MT is managed by conan
# don't, may lie to consumer, /MD or /MT is managed by conan
tc.variables["MSVC_STATIC_CRT"] = False

# This is a workaround to add the libm dependency on linux,
# which is required to successfully compile on older gcc versions.
cmake.definitions["ANDROID"] = self.settings.os in ["Linux", "Android"]
# Env variable escape hatch for disabling AVX512
no_avx512 = environ.get("NO_AVX512")
if no_avx512 is None:
no_avx512 = False
tc.variables["NO_AVX512"] = no_avx512

cmake.configure(build_folder=self._build_subfolder)
return cmake
tc.generate()

def build(self):
if tools.Version(self.version) >= "0.3.12":
search = """message(STATUS "No Fortran compiler found, can build only BLAS but not LAPACK")"""
replace = (
"""message(FATAL_ERROR "No Fortran compiler found. Cannot build with LAPACK.")"""
)
else:
search = "enable_language(Fortran)"
replace = """include(CheckLanguage)
check_language(Fortran)
if(CMAKE_Fortran_COMPILER)
enable_language(Fortran)
else()
message(FATAL_ERROR "No Fortran compiler found. Cannot build with LAPACK.")
set (NOFORTRAN 1)
set (NO_LAPACK 1)
endif()"""

tools.replace_in_file(
os.path.join(self._source_subfolder, "cmake", "f_check.cmake"),
search,
replace,
)
cmake = self._configure_cmake()
apply_conandata_patches(self)
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
self.copy(pattern="LICENSE", dst="licenses", src=self._source_subfolder)
cmake = self._configure_cmake()
copy(self, pattern="LICENSE",
dst=path.join(self.package_folder, "licenses"),
src=self.source_folder)
cmake = CMake(self)
cmake.install()
tools.rmdir(os.path.join(self.package_folder, "lib", "pkgconfig"))
tools.rmdir(os.path.join(self.package_folder, "share"))
rmdir(self, path.join(self.package_folder, "lib", "pkgconfig"))
rmdir(self, path.join(self.package_folder, "share"))

if self.options.build_lapack:
copy(self, pattern="FORTRAN_COMPILER",
src=self.build_folder,
dst=path.join(self.package_folder, "res"))

def package_info(self):
# CMake config file:
# - OpenBLAS always has one and only one of these components: openmp, pthread or serial.
# - Whatever if this component is requested or not, official CMake imported target is always OpenBLAS::OpenBLAS
# - TODO: add openmp component when implemented in this recipe
# - Whether this component is requested or not, official CMake imported target is always OpenBLAS::OpenBLAS
self.cpp_info.set_property("cmake_file_name", "OpenBLAS")
self.cpp_info.set_property("cmake_target_name", "OpenBLAS::OpenBLAS")
self.cpp_info.set_property("pkg_config_name", "openblas")
cmake_component_name = "pthread" if self.options.use_thread else "serial" # TODO: ow to model this in CMakeDeps?
self.cpp_info.components["openblas_component"].set_property("pkg_config_name", "openblas")
self.cpp_info.components["openblas_component"].includedirs.append(
os.path.join("include", "openblas")

component_name = "serial"
if self.options.use_thread:
component_name = "pthread"
elif self.options.use_openmp:
component_name = "openmp"

self.cpp_info.components[component_name].set_property(
"pkg_config_name", "openblas")

# Target cannot be named pthread -> causes failed linking
self.cpp_info.components[component_name].set_property(
"cmake_target_name", "OpenBLAS::" + component_name)
self.cpp_info.components[component_name].includedirs.append(
path.join("include", "openblas")
)
self.cpp_info.components["openblas_component"].libs = tools.collect_libs(self)
self.cpp_info.components[component_name].libs = collect_libs(self)
if self.settings.os in ["Linux", "FreeBSD"]:
self.cpp_info.components["openblas_component"].system_libs.append("m")
self.cpp_info.components[component_name].system_libs.append("m")
if self.options.use_thread:
self.cpp_info.components["openblas_component"].system_libs.append("pthread")
if self.options.build_lapack:
self.cpp_info.components["openblas_component"].system_libs.append("gfortran")
self.cpp_info.components[component_name].system_libs.append("pthread")
if self.options.use_openmp:
openmp_rt = self._openmp_runtime
if openmp_rt:
self.cpp_info.components[component_name].system_libs.append(openmp_rt)

self.output.info(
"Setting OpenBLAS_HOME environment variable: {}".format(self.package_folder)
)
if self.options.build_lapack:
fortran_file = path.join(self.package_folder, "res", "FORTRAN_COMPILER")
# >=v0.3.21: compiling w/o fortran is possible
if path.isfile(fortran_file):
fortran_id = load(self, fortran_file)
if fortran_id == "GNU":
fortran_rt = self._fortran_runtime(fortran_id)
if fortran_rt:
self.cpp_info.components[component_name].system_libs.append("dl")
self.cpp_info.components[component_name].system_libs.append(fortran_rt)
elif fortran_id == "0":
pass
else:
self.output.warning(f"Runtime libraries for {fortran_id} are not specified")

self.cpp_info.requires.append(component_name)

# TODO: Remove env_info in conan v2
self.output.info(f"Setting OpenBLAS_HOME environment variable: {self.package_folder}")
self.env_info.OpenBLAS_HOME = self.package_folder
self.runenv_info.define_path("OpenBLAS_HOME", self.package_folder)

# TODO: to remove in conan v2 once cmake_find_package_* generators removed
self.cpp_info.names["cmake_find_package"] = "OpenBLAS"
self.cpp_info.names["cmake_find_package_multi"] = "OpenBLAS"
self.cpp_info.components["openblas_component"].names["cmake_find_package"] = cmake_component_name
self.cpp_info.components["openblas_component"].names["cmake_find_package_multi"] = cmake_component_name
self.cpp_info.components[component_name].names["cmake_find_package"] = component_name
self.cpp_info.components[component_name].names["cmake_find_package_multi"] = component_name
Loading

0 comments on commit fc62160

Please sign in to comment.