diff --git a/BUILD b/BUILD index 69959673d50..49a02647e1b 100644 --- a/BUILD +++ b/BUILD @@ -1,6 +1,21 @@ load("@onedal//dev/bazel:release.bzl", "release", "release_include", + "release_extra_file", +) +load("@onedal//dev/bazel:scripts.bzl", + "generate_vars_sh", + "generate_pkgconfig", +) + +generate_vars_sh( + name = "release_vars_sh", + out = "env/vars.sh", +) + +generate_pkgconfig( + name = "release_pkgconfig", + out = "lib/pkgconfig/onedal.pc", ) release( @@ -37,4 +52,8 @@ release( ], "//conditions:default": [], }), + extra_files = [ + release_extra_file(":release_vars_sh", "env/vars.sh"), + release_extra_file(":release_pkgconfig", "lib/pkgconfig/onedal.pc"), + ], ) diff --git a/deploy/local/BUILD b/deploy/local/BUILD new file mode 100644 index 00000000000..1bb7f770736 --- /dev/null +++ b/deploy/local/BUILD @@ -0,0 +1,23 @@ +#=============================================================================== +# Copyright contributors to the oneDAL project +# +# 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. +#=============================================================================== + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "vars_lnx.sh", + "vars_mac.sh", + "vars_win.bat", +]) diff --git a/deploy/pkg-config/BUILD b/deploy/pkg-config/BUILD new file mode 100644 index 00000000000..1f0cf7f2beb --- /dev/null +++ b/deploy/pkg-config/BUILD @@ -0,0 +1,21 @@ +#=============================================================================== +# Copyright contributors to the oneDAL project +# +# 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. +#=============================================================================== + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "pkg-config.cpp", +]) diff --git a/dev/bazel/cc.bzl b/dev/bazel/cc.bzl index 772a7d68729..bde5a5498c1 100644 --- a/dev/bazel/cc.bzl +++ b/dev/bazel/cc.bzl @@ -23,6 +23,7 @@ load("@onedal//dev/bazel:utils.bzl", load("@rules_cc//cc:defs.bzl", "cc_library") load("@onedal//dev/bazel/config:config.bzl", "CpuInfo", + "VersionInfo", ) load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") @@ -186,6 +187,22 @@ def _cc_dynamic_lib_impl(ctx): compilation_context = onedal_cc_common.collect_and_merge_compilation_contexts(ctx.attr.deps) linking_contexts = onedal_cc_common.collect_and_filter_linking_contexts( ctx.attr.deps, ctx.attr.lib_tags) + + # SONAME linker flags: resolved via select() in macro (_make_soname_linkopts), + # stored as a string list in soname_linkopts attr, and passed to the linker here. + # If not set by macro, falls back to empty list (no SONAME). + soname_linkopts = ctx.attr.soname_linkopts + # Fallback: if macro didn't set soname_linkopts (binary_major=None), auto-derive + # from @config//:version — but only on Linux (SONAME is Linux-specific). + if ctx.attr.lib_name and not soname_linkopts and ctx.attr._version_info: + is_linux = ctx.target_platform_has_constraint( + ctx.attr._linux_constraint[platform_common.ConstraintValueInfo]) + if is_linux: + vi = ctx.attr._version_info[VersionInfo] + if vi.binary_major: + soname = "lib{}.so.{}".format(ctx.attr.lib_name, vi.binary_major) + soname_linkopts = ["-Wl,-soname,{}".format(soname)] + linking_context, dynamic_lib = onedal_cc_link.dynamic( owner = ctx.label, name = ctx.attr.lib_name, @@ -194,6 +211,7 @@ def _cc_dynamic_lib_impl(ctx): feature_configuration = feature_config, linking_contexts = linking_contexts, def_file = ctx.file.def_file, + user_link_flags = soname_linkopts, ) default_info = DefaultInfo( files = depset([ dynamic_lib ]), @@ -204,18 +222,74 @@ def _cc_dynamic_lib_impl(ctx): ) return [default_info, cc_info] -cc_dynamic_lib = rule( +def _make_soname_linkopts(lib_name, binary_major): + """Return platform-aware SONAME linker flags via select(). + + Must be called from a macro (not a rule impl) because select() is not + allowed inside rule implementation functions. + + On Linux, embeds -Wl,-soname,lib.so. into the link. + On Windows and macOS, returns an empty list (handled by other mechanisms). + """ + if not lib_name or not binary_major: + return [] + soname = "lib{}.so.{}".format(lib_name, binary_major) + return select({ + "@platforms//os:linux": ["-Wl,-soname,{}".format(soname)], + # macOS: -install_name is set by the toolchain via install_name_tool. + # Windows: DLL versioning uses PE resources, not SONAME. + "//conditions:default": [], + }) + +_cc_dynamic_lib = rule( implementation = _cc_dynamic_lib_impl, attrs = { "lib_name": attr.string(), "lib_tags": attr.string_list(), "deps": attr.label_list(mandatory=True), "def_file": attr.label(allow_single_file=True), + # Platform-resolved SONAME linker flags, set by the cc_dynamic_lib macro. + "soname_linkopts": attr.string_list( + default = [], + doc = "Linker flags for SONAME embedding. Use cc_dynamic_lib macro which sets this automatically.", + ), + "_version_info": attr.label( + default = "@config//:version", + providers = [VersionInfo], + ), + "_linux_constraint": attr.label( + default = "@platforms//os:linux", + providers = [platform_common.ConstraintValueInfo], + ), }, toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], fragments = ["cpp"], ) +def cc_dynamic_lib(name, lib_name = "", lib_tags = [], deps = [], def_file = None, + binary_major = None, **kwargs): + """Build a oneDAL shared library with proper SONAME embedding. + + Args: + name: Bazel target name. + lib_name: Output library name (e.g. "onedal_core" → libonedal_core.so). + lib_tags: Module tags to collect from deps. + deps: Dependencies. + def_file: Optional .def file for Windows symbol export. + binary_major: Binary ABI major version for SONAME (e.g. "2"). + If not set, falls back to no SONAME flag. + """ + soname_linkopts = _make_soname_linkopts(lib_name, binary_major) + _cc_dynamic_lib( + name = name, + lib_name = lib_name, + lib_tags = lib_tags, + deps = deps, + def_file = def_file, + soname_linkopts = soname_linkopts, + **kwargs + ) + def _cc_exec_impl(ctx): if not ctx.attr.deps: diff --git a/dev/bazel/cc/link.bzl b/dev/bazel/cc/link.bzl index 63ceb3b2b81..660ef346ec0 100644 --- a/dev/bazel/cc/link.bzl +++ b/dev/bazel/cc/link.bzl @@ -181,11 +181,12 @@ def _link(owner, name, actions, cc_toolchain, def _dynamic(owner, name, actions, cc_toolchain, feature_configuration, linking_contexts, - def_file=None): + def_file=None, user_link_flags=[]): unpacked_linking_context, linking_outputs = _link( owner, name, actions, cc_toolchain, feature_configuration, linking_contexts, def_file, + user_link_flags = user_link_flags, ) library_to_link = linking_outputs.library_to_link if not (library_to_link and library_to_link.resolved_symlink_dynamic_library): diff --git a/dev/bazel/config/config.bzl b/dev/bazel/config/config.bzl index deb264c73c4..0d6f044ec2a 100644 --- a/dev/bazel/config/config.bzl +++ b/dev/bazel/config/config.bzl @@ -120,18 +120,25 @@ VersionInfo = provider( "build", "buildrev", "status", + # Binary ABI version (distinct from product version). + # Used for SONAME and shared library symlinks. + # Matches Make's MAJORBINARY / MINORBINARY variables. + "binary_major", + "binary_minor", ], ) def _version_info_impl(ctx): return [ VersionInfo( - major = ctx.attr.major, - minor = ctx.attr.minor, - update = ctx.attr.update, - build = ctx.attr.build, - buildrev = ctx.attr.buildrev, - status = ctx.attr.status, + major = ctx.attr.major, + minor = ctx.attr.minor, + update = ctx.attr.update, + build = ctx.attr.build, + buildrev = ctx.attr.buildrev, + status = ctx.attr.status, + binary_major = ctx.attr.binary_major, + binary_minor = ctx.attr.binary_minor, ) ] @@ -144,6 +151,10 @@ version_info = rule( "build": attr.string(mandatory=True), "buildrev": attr.string(mandatory=True), "status": attr.string(mandatory=True), + # ABI binary version — used for SONAME and symlinks. + # Must match MAJORBINARY/MINORBINARY in makefile. + "binary_major": attr.string(mandatory=True), + "binary_minor": attr.string(mandatory=True), }, ) @@ -209,12 +220,15 @@ def _declare_onedal_config_impl(repo_ctx): Label("@onedal//dev/bazel/config:config.tpl.BUILD"), substitutions = { "%{auto_cpu}": auto_cpu, - "%{version_major}": "2026", - "%{version_minor}": "0", - "%{version_update}": "0", - "%{version_build}": utils.datestamp(repo_ctx), - "%{version_buildrev}": "work", - "%{version_status}": "P", + "%{version_major}": "2026", + "%{version_minor}": "0", + "%{version_update}": "0", + "%{version_build}": utils.datestamp(repo_ctx), + "%{version_buildrev}": "work", + "%{version_status}": "P", + # Binary ABI version — must match MAJORBINARY/MINORBINARY in makefile.ver + "%{version_binary_major}": "3", + "%{version_binary_minor}": "0", }, ) diff --git a/dev/bazel/config/config.tpl.BUILD b/dev/bazel/config/config.tpl.BUILD index 83cd06bf9c2..c44d180c0e7 100644 --- a/dev/bazel/config/config.tpl.BUILD +++ b/dev/bazel/config/config.tpl.BUILD @@ -21,6 +21,10 @@ version_info( build = "%{version_build}", buildrev = "%{version_buildrev}", status = "%{version_status}", + # Binary ABI version for SONAME and shared library symlinks. + # Must match MAJORBINARY/MINORBINARY in makefile. + binary_major = "%{version_binary_major}", + binary_minor = "%{version_binary_minor}", ) config_flag( diff --git a/dev/bazel/daal.bzl b/dev/bazel/daal.bzl index 78899d186a4..2210caec9e9 100644 --- a/dev/bazel/daal.bzl +++ b/dev/bazel/daal.bzl @@ -77,6 +77,7 @@ def daal_static_lib(name, lib_tags=["daal"], **kwargs): ) def daal_dynamic_lib(name, lib_tags=["daal"], **kwargs): + """Build a oneDAL shared library with SONAME automatically set from @config//:version.""" cc_dynamic_lib( name = name, lib_tags = lib_tags, diff --git a/dev/bazel/dal.bzl b/dev/bazel/dal.bzl index 5152c74a4db..56340849975 100644 --- a/dev/bazel/dal.bzl +++ b/dev/bazel/dal.bzl @@ -159,7 +159,8 @@ def dal_test(name, hdrs=[], srcs=[], dal_deps=[], dal_test_deps=[], extra_deps=[], host_hdrs=[], host_srcs=[], host_deps=[], dpc_hdrs=[], dpc_srcs=[], dpc_deps=[], compile_as=[ "c++", "dpc++" ], framework="catch2", data=[], tags=[], private=False, - mpi=False, ccl=False, mpi_ranks=0, args=[], **kwargs): + mpi=False, ccl=False, mpi_ranks=0, args=[], + use_onedal_release_libs=True, **kwargs): # TODO: Check `compile_as` parameter # TODO: Refactor this rule once decision on the tests structure is made if not framework in ["catch2", "none"]: @@ -179,7 +180,7 @@ def dal_test(name, hdrs=[], srcs=[], dal_deps=[], dal_test_deps=[], compile_as = compile_as, dal_deps = ( dal_test_deps + - _test_link_mode_deps(dal_deps) + _test_link_mode_deps(dal_deps, use_onedal_release_libs) ) + ([ "@onedal//cpp/oneapi/dal/test/engine:common", "@onedal//cpp/oneapi/dal/test/engine:catch2_main", @@ -289,24 +290,26 @@ def dal_collect_parameters(name, root, modules=[], target="parameters", dal_deps **kwargs, ) -def dal_example(name, dal_deps=[], **kwargs): +def dal_example(name, dal_deps=[], use_onedal_release_libs=True, is_daal=False, **kwargs): dal_test( name = name, dal_deps = [ "@onedal//cpp/oneapi/dal:core", "@onedal//cpp/oneapi/dal/io", - ] + dal_deps, + ] + dal_deps if not is_daal else dal_deps, + use_onedal_release_libs = use_onedal_release_libs, framework = "none", **kwargs, ) -def dal_example_suite(name, srcs, **kwargs): +def dal_example_suite(name, srcs, is_daal=False, **kwargs): suite_deps = [] for src in srcs: _, alg_name, src_file = src.rsplit('/', 2) example_name, _ = paths.split_extension(src_file) dal_example( name = example_name, + is_daal = is_daal, srcs = [ src ], **kwargs, ) @@ -316,10 +319,11 @@ def dal_example_suite(name, srcs, **kwargs): tests = suite_deps, ) -def dal_algo_example_suite(algos, dal_deps=[], **kwargs): +def dal_algo_example_suite(algos, dal_deps=[], is_daal=False, **kwargs): for algo in algos: dal_example_suite( name = algo, + is_daal = is_daal, srcs = native.glob(["source/{}/*.cpp".format(algo)]), dal_deps = dal_deps + [ "@onedal//cpp/oneapi/dal/algo:{}".format(algo), @@ -327,15 +331,15 @@ def dal_algo_example_suite(algos, dal_deps=[], **kwargs): **kwargs, ) -def _test_link_mode_deps(dal_deps): +def _test_link_mode_deps(dal_deps, use_onedal_release_libs=True): return _select({ "@config//:test_link_mode_dev": dal_deps, "@config//:test_link_mode_release_static": [ "@onedal_release//:onedal_static", - ], + ] if use_onedal_release_libs else [], "@config//:test_link_mode_release_dynamic": [ "@onedal_release//:onedal_dynamic", - ], + ] if use_onedal_release_libs else [], }) def _test_deps_on_daal(): @@ -602,3 +606,21 @@ def _expand_select(deps): else: expanded += [dep] return expanded + +def daal_example_suite(name, srcs, **kwargs): + dal_example_suite( + name = name, + srcs = srcs, + use_onedal_release_libs = False, + is_daal = True, + **kwargs, + ) + +def daal_algo_example_suite(algos, dal_deps=[], **kwargs): + dal_algo_example_suite( + algos = algos, + dal_deps = dal_deps, + use_onedal_release_libs = False, + is_daal = True, + **kwargs, + ) diff --git a/dev/bazel/deps/onedal.bzl b/dev/bazel/deps/onedal.bzl index 807ee94b5d5..ff75399c74a 100644 --- a/dev/bazel/deps/onedal.bzl +++ b/dev/bazel/deps/onedal.bzl @@ -31,11 +31,23 @@ onedal_repo = repos.prebuilt_libs_repo_rule( # Dynamic "lib/intel64/libonedal_core.so", + "lib/intel64/libonedal_core.so.3", + "lib/intel64/libonedal_core.so.3.0", "lib/intel64/libonedal_thread.so", + "lib/intel64/libonedal_thread.so.3", + "lib/intel64/libonedal_thread.so.3.0", "lib/intel64/libonedal.so", + "lib/intel64/libonedal.so.3", + "lib/intel64/libonedal.so.3.0", "lib/intel64/libonedal_dpc.so", + "lib/intel64/libonedal_dpc.so.3", + "lib/intel64/libonedal_dpc.so.3.0", "lib/intel64/libonedal_parameters.so", + "lib/intel64/libonedal_parameters.so.3", + "lib/intel64/libonedal_parameters.so.3.0", "lib/intel64/libonedal_parameters_dpc.so", + "lib/intel64/libonedal_parameters_dpc.so.3", + "lib/intel64/libonedal_parameters_dpc.so.3.0", ], build_template = "@onedal//dev/bazel/deps:onedal.tpl.BUILD", ) diff --git a/dev/bazel/deps/onedal.tpl.BUILD b/dev/bazel/deps/onedal.tpl.BUILD index 12ebfa094bf..d190d0cd20a 100644 --- a/dev/bazel/deps/onedal.tpl.BUILD +++ b/dev/bazel/deps/onedal.tpl.BUILD @@ -59,9 +59,9 @@ cc_library( cc_library( name = "core_dynamic", - srcs = [ - "lib/intel64/libonedal_core.so", - ], + srcs = glob([ + "lib/intel64/libonedal_core.so*", + ]), deps = [ ":headers", # TODO: Currently vml_ipp lib depends on TBB, but it shouldn't @@ -72,9 +72,9 @@ cc_library( cc_library( name = "thread_dynamic", - srcs = [ - "lib/intel64/libonedal_thread.so", - ], + srcs = glob([ + "lib/intel64/libonedal_thread.so*", + ]), deps = [ ":headers", "@tbb//:tbb_binary", @@ -84,10 +84,10 @@ cc_library( cc_library( name = "onedal_dynamic", - srcs = [ - "lib/intel64/libonedal.so", - "lib/intel64/libonedal_parameters.so", - ], + srcs = glob([ + "lib/intel64/libonedal.so*", + "lib/intel64/libonedal_parameters.so*", + ]), deps = [ ":headers", ], @@ -95,10 +95,10 @@ cc_library( cc_library( name = "onedal_dynamic_dpc", - srcs = [ - "lib/intel64/libonedal_dpc.so", - "lib/intel64/libonedal_parameters_dpc.so", - ], + srcs = glob([ + "lib/intel64/libonedal_dpc.so*", + "lib/intel64/libonedal_parameters_dpc.so*", + ]), deps = [ ":headers", "@mkl//:mkl_dpc", diff --git a/dev/bazel/release.bzl b/dev/bazel/release.bzl index c11a19d0b8b..f214b73397a 100644 --- a/dev/bazel/release.bzl +++ b/dev/bazel/release.bzl @@ -16,6 +16,7 @@ load("@onedal//dev/bazel:utils.bzl", "utils", "paths") load("@onedal//dev/bazel:cc.bzl", "ModuleInfo") +load("@onedal//dev/bazel/config:config.bzl", "VersionInfo") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") def _match_file_name(file, entries): @@ -73,31 +74,145 @@ def _copy_include(ctx, prefix): dst_files.append(dst_file) return dst_files -def _copy_lib(ctx, prefix): +def _symlink(ctx, link_name, target_name, prefix): + """Create a relative symlink in the release directory. + + Args: + ctx: Rule context. + link_name: Basename of the symlink to create (e.g. "libonedal_core.so"). + target_name: Basename of the symlink target (e.g. "libonedal_core.so.2"). + prefix: Directory prefix inside the rule's output tree. + + Returns: + The declared symlink File object. + """ + link_file = ctx.actions.declare_symlink(paths.join(prefix, link_name)) + ctx.actions.symlink( + output = link_file, + # Relative symlink — target lives in the same directory. + target_path = target_name, + ) + return link_file + +def _copy_lib(ctx, prefix, version_info): + """Copy libraries to release directory with versioning and symlinks for .so files. + + For each shared library (.so) on Linux, this creates: + libonedal_core.so.{binary_major}.{binary_minor} (real file) + libonedal_core.so.{binary_major} -> .so.{binary_major}.{binary_minor} (symlink) + libonedal_core.so -> .so.{binary_major} (symlink) + + Static libraries (.a) and Windows DLLs (.dll) are copied as-is without versioning. + + macOS .dylib versioning is not yet implemented (tracked separately). + """ lib_prefix = paths.join(prefix, "lib", "intel64") libs = _collect_default_files(ctx.attr.lib) dst_files = [] + for lib in libs: - dst_path = paths.join(lib_prefix, lib.basename) - dst_file = _copy(ctx, lib, dst_path) - dst_files.append(dst_file) + # Determine if this is a shared library that needs versioning. + # On Linux: .so extension; on macOS: .dylib (handled separately). + is_shared_lib = lib.extension == "so" + + if is_shared_lib and not version_info: + fail("Shared library '{}' requires VersionInfo for SONAME versioning, " + + "but no version_info was provided to _copy_lib.".format(lib.basename)) + + if is_shared_lib and version_info: + binary_major = version_info.binary_major + binary_minor = version_info.binary_minor + + # 1. Real versioned file: libonedal_core.so.2.9 + versioned_name = "{}.{}.{}".format(lib.basename, binary_major, binary_minor) + versioned_file = _copy(ctx, lib, paths.join(lib_prefix, versioned_name)) + dst_files.append(versioned_file) + + # 2. Major symlink: libonedal_core.so.2 -> libonedal_core.so.2.9 + major_link_name = "{}.{}".format(lib.basename, binary_major) + dst_files.append(_symlink(ctx, major_link_name, versioned_name, lib_prefix)) + + # 3. Unversioned symlink: libonedal_core.so -> libonedal_core.so.2 + dst_files.append(_symlink(ctx, lib.basename, major_link_name, lib_prefix)) + else: + # Static libs (.a), DLLs (.dll), import libs (.lib) — copy as-is. + dst_path = paths.join(lib_prefix, lib.basename) + dst_files.append(_copy(ctx, lib, dst_path)) + + return dst_files + +def _copy_extra_files(ctx, prefix): + """Copy extra generated files (vars.sh, pkg-config, etc.) into the release tree. + + Each entry in extra_files is a (label, dst_subpath) pair encoded as two + parallel lists: extra_files and extra_files_dst. + + dst_subpath is the desired path *relative to the release root* + (e.g. "env/vars.sh", "lib/pkgconfig/onedal.pc"). + """ + if len(ctx.attr.extra_files) != len(ctx.attr.extra_files_dst): + fail("extra_files and extra_files_dst must have the same length: got {} vs {}".format( + len(ctx.attr.extra_files), len(ctx.attr.extra_files_dst))) + + dst_files = [] + for dep, dst_subpath in zip(ctx.attr.extra_files, ctx.attr.extra_files_dst): + srcs = dep[DefaultInfo].files.to_list() + if len(srcs) != 1: + fail("extra_files entry '{}' must produce exactly one file, got {}".format( + dep.label, len(srcs))) + src = srcs[0] + dst_path = paths.join(prefix, dst_subpath) + dst_files.append(_copy(ctx, src, dst_path)) return dst_files def _copy_to_release_impl(ctx): extra_toolchain = ctx.toolchains["@onedal//dev/bazel/toolchains:extra"] prefix = ctx.attr.name + "/daal/latest" + version_info = ctx.attr._version_info[VersionInfo] if ctx.attr._version_info else None files = [] files += _copy_include(ctx, prefix) - files += _copy_lib(ctx, prefix) + files += _copy_lib(ctx, prefix, version_info) + files += _copy_extra_files(ctx, prefix) return [DefaultInfo(files=depset(files))] +def _release_cpu_all_transition_impl(settings, attr): + """Transition to --cpu=all if the current CPU is the default ('auto'). + This ensures that when a user runs `bazel build //:release` without + specifying a target CPU, all required ISA variants are built and + bundled. If the user specifies `--cpu=avx2`, we respect that and + do not transition. + """ + if settings["//command_line_option:cpu"] == "auto": + return {"//command_line_option:cpu": "all"} + return {"//command_line_option:cpu": settings["//command_line_option:cpu"]} + +_release_cpu_all_transition = transition( + implementation = _release_cpu_all_transition_impl, + inputs = ["//command_line_option:cpu"], + outputs = ["//command_line_option:cpu"], +) + _release = rule( implementation = _copy_to_release_impl, attrs = { - "include": attr.label_list(allow_files=True), + "include": attr.label_list(allow_files=True, cfg=_release_cpu_all_transition), "include_prefix": attr.string_list(), "include_skip_prefix": attr.string_list(), - "lib": attr.label_list(allow_files=True), + "lib": attr.label_list(allow_files=True, cfg=_release_cpu_all_transition), + "extra_files": attr.label_list( + allow_files = True, + doc = "Additional generated files to include in release. Must be paired 1:1 with extra_files_dst.", + ), + "extra_files_dst": attr.string_list( + doc = "Destination paths for extra_files, relative to the release root.", + ), + "_version_info": attr.label( + default = "@config//:version", + providers = [VersionInfo], + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), }, toolchains = [ "@onedal//dev/bazel/toolchains:extra" @@ -131,7 +246,21 @@ headers_filter = rule( def release_include(hdrs, skip_prefix="", add_prefix=""): return (hdrs, add_prefix, skip_prefix) -def release(name, include, lib): +def release(name, include, lib, extra_files = []): + """Assemble the oneDAL release directory tree. + + Args: + name: Target name (also used as the output directory prefix). + include: List of release_include() tuples for headers. + lib: List of library targets (static, shared). + extra_files: List of (label, dst_path) tuples for additional files. + Use release_extra_file() helper to construct entries. + Example: + extra_files = [ + release_extra_file(":release_vars_sh", "env/vars.sh"), + release_extra_file(":release_pkgconfig", "lib/pkgconfig/onedal.pc"), + ] + """ rule_include = [] rule_include_prefix = [] rule_include_skip_prefix = [] @@ -140,10 +269,31 @@ def release(name, include, lib): rule_include.append(dep) rule_include_prefix.append(prefix) rule_include_skip_prefix.append(skip_prefix) + + rule_extra_files = [] + rule_extra_files_dst = [] + for label, dst in extra_files: + rule_extra_files.append(label) + rule_extra_files_dst.append(dst) + _release( name = name, include = rule_include, include_prefix = rule_include_prefix, include_skip_prefix = rule_include_skip_prefix, lib = lib, + extra_files = rule_extra_files, + extra_files_dst = rule_extra_files_dst, ) + +def release_extra_file(label, dst_path): + """Helper to declare an extra file for release(). + + Args: + label: Bazel label of the target producing the file. + dst_path: Destination path relative to the release root (e.g. "env/vars.sh"). + + Returns: + A tuple (label, dst_path) for use in release(extra_files=...). + """ + return (label, dst_path) diff --git a/dev/bazel/scripts.bzl b/dev/bazel/scripts.bzl new file mode 100644 index 00000000000..dea89bde132 --- /dev/null +++ b/dev/bazel/scripts.bzl @@ -0,0 +1,161 @@ +#=============================================================================== +# Copyright contributors to the oneDAL project +# +# 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. +#=============================================================================== + +"""Rules for generating release scripts and configuration files. + +Provides: + - generate_vars_sh: generates env/vars.sh (Linux/macOS) from a template + - generate_pkgconfig: generates lib/pkgconfig/onedal.pc via cpp preprocessor +""" + +load("@onedal//dev/bazel/config:config.bzl", "VersionInfo") + +# --------------------------------------------------------------------------- +# vars.sh +# --------------------------------------------------------------------------- + +def _generate_vars_sh_impl(ctx): + """Expand vars.sh template substituting binary version placeholders.""" + vi = ctx.attr._version_info[VersionInfo] + out = ctx.actions.declare_file(ctx.attr.out) + ctx.actions.expand_template( + template = ctx.file.template, + output = out, + substitutions = { + "__DAL_MAJOR_BINARY__": vi.binary_major, + "__DAL_MINOR_BINARY__": vi.binary_minor, + }, + ) + return [DefaultInfo(files = depset([out]))] + +_generate_vars_sh = rule( + implementation = _generate_vars_sh_impl, + attrs = { + "template": attr.label( + allow_single_file = True, + mandatory = True, + doc = "Source vars.sh template file (e.g. deploy/local/vars_lnx.sh).", + ), + "out": attr.string( + mandatory = True, + doc = "Output path relative to the package (e.g. 'env/vars.sh').", + ), + "_version_info": attr.label( + default = "@config//:version", + providers = [VersionInfo], + ), + }, +) + +def generate_vars_sh(name, out = "env/vars.sh", **kwargs): + """Generate release environment script from template. + + Substitutes __DAL_MAJOR_BINARY__ and __DAL_MINOR_BINARY__ with the + binary ABI version numbers defined in config.bzl. + + Args: + name: Target name. + out: Output path (default: 'env/vars.sh'). + """ + _generate_vars_sh( + name = name, + template = select({ + # TODO: add Windows condition when Windows toolchain is ready. + # "@platforms//os:windows": "@onedal//deploy/local:vars_win.bat", + "@platforms//os:osx": "@onedal//deploy/local:vars_mac.sh", + "//conditions:default": "@onedal//deploy/local:vars_lnx.sh", + }), + out = out, + **kwargs + ) + +# --------------------------------------------------------------------------- +# pkg-config +# --------------------------------------------------------------------------- + +def _generate_pkgconfig_impl(ctx): + """Generate onedal.pc via the C preprocessor from pkg-config.cpp template. + + Note: deploy/pkg-config/pkg-config.cpp currently hardcodes Version: 2026.0 + and does not use DAL_MAJOR/MINOR defines. The -D flags below are passed for + future compatibility if the template is updated to use them. + """ + vi = ctx.attr._version_info[VersionInfo] + out = ctx.actions.declare_file(ctx.attr.out) + + # pkg-config.cpp uses #if/#define blocks and the cpp preprocessor + # to emit different content for static vs dynamic variants. + # expand_template is not used here because the template relies on + # cpp's conditional compilation, which cannot be replicated with + # simple string substitution. + # Use gcc -E -P (C preprocessor) instead of cpp directly. + # 'cpp' may not find cc1plus in some CI environments; 'gcc -E -P' is more portable. + ctx.actions.run_shell( + inputs = [ctx.file.template], + outputs = [out], + command = ( + "${CC:-gcc} -E -P -x c " + + "-DDAL_MAJOR_BINARY={binary_major} " + + "-DDAL_MINOR_BINARY={binary_minor} " + + "-DDAL_MAJOR={major} " + + "-DDAL_MINOR={minor} " + + "{template} -o {out}" + ).format( + binary_major = vi.binary_major, + binary_minor = vi.binary_minor, + major = vi.major, + minor = vi.minor, + template = ctx.file.template.path, + out = out.path, + ), + mnemonic = "GenPkgConfig", + progress_message = "Generating pkg-config file {}".format(out.short_path), + use_default_shell_env = True, + ) + return [DefaultInfo(files = depset([out]))] + +_generate_pkgconfig = rule( + implementation = _generate_pkgconfig_impl, + attrs = { + "template": attr.label( + allow_single_file = True, + mandatory = True, + doc = "pkg-config.cpp template file.", + ), + "out": attr.string( + mandatory = True, + doc = "Output path relative to the package (e.g. 'lib/pkgconfig/onedal.pc').", + ), + "_version_info": attr.label( + default = "@config//:version", + providers = [VersionInfo], + ), + }, +) + +def generate_pkgconfig(name, out = "lib/pkgconfig/onedal.pc", **kwargs): + """Generate pkg-config .pc file from deploy/pkg-config/pkg-config.cpp. + + Args: + name: Target name. + out: Output path (default: 'lib/pkgconfig/onedal.pc'). + """ + _generate_pkgconfig( + name = name, + template = "@onedal//deploy/pkg-config:pkg-config.cpp", + out = out, + **kwargs + ) diff --git a/dev/bazel/tests/release_structure_test.sh b/dev/bazel/tests/release_structure_test.sh new file mode 100755 index 00000000000..c98d5f30b91 --- /dev/null +++ b/dev/bazel/tests/release_structure_test.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +#=============================================================================== +# Copyright contributors to the oneDAL project +# +# 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. +#=============================================================================== +# Tests for Bazel release structure: versioning, symlinks, vars.sh, pkg-config. +# Run after: bazel build //:release (which also builds env/vars.sh and pkg-config files) +# +# Usage: +# ./dev/bazel/tests/release_structure_test.sh +# +# Example: +# ./dev/bazel/tests/release_structure_test.sh bazel-bin/release/daal/latest + +set -euo pipefail + +RELEASE_DIR="${1:-bazel-bin/release/daal/latest}" +LIB_DIR="${RELEASE_DIR}/lib/intel64" +PASS=0 +FAIL=0 + +_pass() { echo " ✅ PASS: $1"; PASS=$((PASS + 1)); } +_fail() { echo " ❌ FAIL: $1"; FAIL=$((FAIL + 1)); } + +# --------------------------------------------------------------------------- +# 1. Library versioning and symlinks +# --------------------------------------------------------------------------- +echo "" +echo "=== Library versioning & symlinks ===" + +for lib_base in libonedal_core libonedal libonedal_thread; do + so="${LIB_DIR}/${lib_base}.so" + # Find versioned file using bash globbing (more robust than ls+grep) + shopt -s nullglob + versioned_candidates=( "${LIB_DIR}/${lib_base}.so."[0-9]*.[0-9]* ) + shopt -u nullglob + + if [ "${#versioned_candidates[@]}" -eq 0 ]; then + _fail "${lib_base}.so.X.Y not found in ${LIB_DIR}" + continue + elif [ "${#versioned_candidates[@]}" -gt 1 ]; then + _fail "${lib_base}: multiple versioned files found: ${versioned_candidates[*]}" + continue + fi + + versioned="${versioned_candidates[0]}" + + # Check versioned file is a real file + if [ -f "$versioned" ] && [ ! -L "$versioned" ]; then + _pass "${lib_base}.so.X.Y is a real file" + else + _fail "${lib_base}.so.X.Y should be a regular file, not a symlink" + fi + + # Extract major.minor from filename + version_suffix="${versioned##*.so.}" # e.g. "3.0" + major_ver="${version_suffix%%.*}" # e.g. "3" + + # Check major symlink exists and points correctly + major_link="${LIB_DIR}/${lib_base}.so.${major_ver}" + if [ -L "$major_link" ]; then + target=$(readlink "$major_link") + expected_target="$(basename "$versioned")" + if [ "$target" = "$expected_target" ]; then + _pass "${lib_base}.so.${major_ver} → ${expected_target}" + else + _fail "${lib_base}.so.${major_ver} points to $target, expected $expected_target" + fi + else + _fail "${lib_base}.so.${major_ver} symlink missing" + fi + + # Check unversioned symlink points to major link + if [ -L "$so" ]; then + target=$(readlink "$so") + expected_target="${lib_base}.so.${major_ver}" + if [ "$target" = "$expected_target" ]; then + _pass "${lib_base}.so → ${expected_target}" + else + _fail "${lib_base}.so points to $target, expected $expected_target" + fi + else + _fail "${lib_base}.so unversioned symlink missing" + fi +done + +# --------------------------------------------------------------------------- +# 2. SONAME check +# --------------------------------------------------------------------------- +echo "" +echo "=== SONAME check ===" + +for lib in "${LIB_DIR}"/libonedal_core.so.*.* "${LIB_DIR}"/libonedal.so.*.* "${LIB_DIR}"/libonedal_thread.so.*.* ; do + [ -f "$lib" ] || continue + lib_base=$(basename "$lib" | sed 's/\.so\..*//') + # Extract expected SONAME from filename: libonedal_core.so.3.0 → libonedal_core.so.3 + version_suffix="${lib##*.so.}" + major_ver="${version_suffix%%.*}" + expected_soname="${lib_base}.so.${major_ver}" + + soname=$(readelf -d "$lib" 2>/dev/null | grep SONAME | grep -oE '\[[^]]+\]' | tr -d '[]' || true) + if [ -n "$soname" ]; then + if [ "$soname" = "$expected_soname" ]; then + _pass "${lib_base}: SONAME = ${soname} (correct)" + else + _fail "${lib_base}: SONAME = ${soname}, expected ${expected_soname}" + fi + else + _fail "${lib_base}: SONAME not found in ${lib}" + fi +done + +# --------------------------------------------------------------------------- +# 3. vars.sh +# --------------------------------------------------------------------------- +echo "" +echo "=== vars.sh ===" + +VARS_SH="${RELEASE_DIR}/env/vars.sh" +if [ -f "$VARS_SH" ]; then + _pass "env/vars.sh exists" + if grep -q "DAL_MAJOR_BINARY" "$VARS_SH"; then + _pass "vars.sh contains DAL_MAJOR_BINARY" + else + _fail "vars.sh missing DAL_MAJOR_BINARY" + fi + # Check no unresolved placeholders + if grep -q "__DAL_MAJOR_BINARY__\|__DAL_MINOR_BINARY__" "$VARS_SH"; then + _fail "vars.sh still has unresolved placeholders" + else + _pass "vars.sh has no unresolved placeholders" + fi +else + _fail "env/vars.sh not found at ${VARS_SH}" +fi + +# --------------------------------------------------------------------------- +# 4. pkg-config +# --------------------------------------------------------------------------- +echo "" +echo "=== pkg-config ===" + +PC_FILE="${RELEASE_DIR}/lib/pkgconfig/onedal.pc" +if [ -f "$PC_FILE" ]; then + _pass "lib/pkgconfig/onedal.pc exists" + if grep -q "^Name:" "$PC_FILE"; then + _pass "onedal.pc has Name field" + else + _fail "onedal.pc missing Name field" + fi + if grep -q "^Libs:" "$PC_FILE"; then + _pass "onedal.pc has Libs field" + else + _fail "onedal.pc missing Libs field" + fi +else + _fail "lib/pkgconfig/onedal.pc not found" +fi + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +echo "" +echo "=== Results: ${PASS} passed, ${FAIL} failed ===" + +[ "$FAIL" -eq 0 ] || exit 1 diff --git a/examples/daal/cpp/BUILD b/examples/daal/cpp/BUILD index a34583fc9e4..0f494c8c1ea 100644 --- a/examples/daal/cpp/BUILD +++ b/examples/daal/cpp/BUILD @@ -1,7 +1,7 @@ load("@onedal//dev/bazel:dal.bzl", "dal_module", - "dal_example_suite", - "dal_algo_example_suite", + "daal_example_suite", + "daal_algo_example_suite", ) dal_module( @@ -18,7 +18,7 @@ _DATA_DEPS = [ "@onedal//examples/daal:data", ] -dal_example_suite( +daal_example_suite( name = "association_rules", compile_as = [ "c++" ], srcs = glob(["source/association_rules/*.cpp"]), @@ -29,7 +29,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "boosting", compile_as = [ "c++" ], srcs = glob(["source/boosting/*.cpp"]), @@ -42,7 +42,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "cholesky", compile_as = [ "c++" ], srcs = glob(["source/cholesky/*.cpp"]), @@ -53,7 +53,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "datasource", compile_as = [ "c++" ], srcs = glob(["source/datasource/*.cpp"]), @@ -65,7 +65,7 @@ dal_example_suite( ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "decision_forest", compile_as = [ "c++" ], srcs = glob(["source/decision_forest/*.cpp"], @@ -82,7 +82,7 @@ dal_example_suite( ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "decision_tree", compile_as = [ "c++" ], srcs = glob(["source/decision_tree/*.cpp"], @@ -99,7 +99,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "distance", compile_as = [ "c++" ], srcs = glob(["source/distance/*.cpp"]), @@ -111,7 +111,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "distributions", compile_as = [ "c++" ], srcs = glob(["source/distributions/*.cpp"]), @@ -122,7 +122,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "elastic_net", compile_as = [ "c++" ], srcs = glob(["source/elastic_net/*.cpp"]), @@ -135,7 +135,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "em", compile_as = [ "c++" ], srcs = glob(["source/em/*.cpp"]), @@ -147,7 +147,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "enable_thread_pinning_example", compile_as = [ "c++" ], srcs = glob(["source/enable_thread_pinning/*.cpp"]), @@ -158,7 +158,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "gradient_boosted_trees", compile_as = [ "c++" ], srcs = glob(["source/gradient_boosted_trees/*.cpp"]), @@ -171,7 +171,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "implicit_als", compile_as = [ "c++" ], srcs = glob(["source/implicit_als/*.cpp"]), @@ -182,7 +182,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "k_nearest_neighbors", compile_as = [ "c++" ], srcs = glob(["source/k_nearest_neighbors/*.cpp"]), @@ -193,7 +193,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "kernel_function", compile_as = [ "c++" ], srcs = glob(["source/kernel_function/*.cpp"]), @@ -204,7 +204,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "lasso_regression", compile_as = [ "c++" ], srcs = glob(["source/lasso_regression/*.cpp"]), @@ -217,7 +217,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "logistic_regression", compile_as = [ "c++" ], srcs = glob(["source/logistic_regression/*.cpp"]), @@ -229,7 +229,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "moments", compile_as = [ "c++" ], srcs = glob(["source/moments/*.cpp"]), @@ -241,7 +241,7 @@ dal_example_suite( ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "naive_bayes", compile_as = [ "c++" ], srcs = glob(["source/naive_bayes/*.cpp"], @@ -255,7 +255,7 @@ dal_example_suite( # TODO: investigate the issue with excluded example # Both examples are failing, its the reason of disabling this test -# dal_example_suite( +# daal_example_suite( # name = "normalization", # compile_as = [ "c++" ], # srcs = glob(["source/normalization/*.cpp"], @@ -271,7 +271,7 @@ dal_example_suite( # ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "optimization_solvers", compile_as = [ "c++" ], hdrs = glob(["source/optimization_solvers/*.h"]), @@ -293,7 +293,7 @@ dal_example_suite( ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "outlier_detection", compile_as = [ "c++" ], srcs = glob(["source/outlier_detection/*.cpp"]), @@ -306,7 +306,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "pca_transform", compile_as = [ "c++" ], srcs = glob(["source/pca_transform/*.cpp"]), @@ -318,7 +318,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "pivoted_qr", compile_as = [ "c++" ], srcs = glob(["source/pivoted_qr/*.cpp"]), @@ -329,7 +329,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "qr", compile_as = [ "c++" ], srcs = glob(["source/qr/*.cpp"]), @@ -340,7 +340,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "quantiles", compile_as = [ "c++" ], srcs = glob(["source/quantiles/*.cpp"]), @@ -351,7 +351,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "ridge_regression", compile_as = [ "c++" ], srcs = glob(["source/ridge_regression/*.cpp"]), @@ -362,7 +362,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "serialization_example", compile_as = [ "c++" ], srcs = glob(["source/serialization/*.cpp"]), @@ -370,7 +370,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "services_examples", compile_as = [ "c++" ], srcs = glob(["source/services/*.cpp"]), @@ -378,7 +378,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "set_number_of_threads_example", compile_as = [ "c++" ], srcs = glob(["source/set_number_of_threads/*.cpp"]), @@ -389,7 +389,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "sorting", compile_as = [ "c++" ], srcs = glob(["source/sorting/*.cpp"]), @@ -400,7 +400,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "stump", compile_as = [ "c++" ], srcs = glob(["source/stump/*.cpp"]), @@ -411,7 +411,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_example_suite( +daal_example_suite( name = "svd", compile_as = [ "c++" ], srcs = glob(["source/svd/*.cpp"]), @@ -423,7 +423,7 @@ dal_example_suite( ) # TODO: investigate the issue with excluded example -dal_example_suite( +daal_example_suite( name = "svm", compile_as = [ "c++" ], srcs = glob(["source/svm/*.cpp"], @@ -437,7 +437,7 @@ dal_example_suite( extra_deps = _TEST_DEPS ) -dal_algo_example_suite( +daal_algo_example_suite( algos = [ "covariance", "dbscan",