From 9a80f94779b6b271d1ee80b8ea93f4c439ccd473 Mon Sep 17 00:00:00 2001 From: Nader <107228500+Nader-Rahhal@users.noreply.github.com> Date: Wed, 20 May 2026 15:16:18 -0500 Subject: [PATCH 01/12] Partitioning for lin alg ops (#84) --- lib/legate_jl_wrapper/include/wrapper.inl | 15 ++++++ lib/legate_jl_wrapper/src/module.cpp | 57 ++++++++++++++++------- src/api/data.jl | 10 ++++ src/api/tasks.jl | 5 +- src/api/types.jl | 10 ++++ 5 files changed, 79 insertions(+), 18 deletions(-) diff --git a/lib/legate_jl_wrapper/include/wrapper.inl b/lib/legate_jl_wrapper/include/wrapper.inl index f1c9610d..9570310c 100644 --- a/lib/legate_jl_wrapper/include/wrapper.inl +++ b/lib/legate_jl_wrapper/include/wrapper.inl @@ -63,6 +63,11 @@ inline bool has_started() { return legate::has_started(); } * @brief Check whether the Legate runtime has finished. */ inline bool has_finished() { return legate::has_finished(); } + +inline int32_t num_procs() { + return legate::Runtime::get_runtime()->get_machine().count(); +} + } // namespace runtime namespace tasking { @@ -310,6 +315,15 @@ inline void* get_ptr(legate::PhysicalStore* store) { return legate::double_dispatch(dim, code, GetPtrFunctor{}, store); } +inline std::shared_ptr partition_by_tiling(LogicalStore& store, std::vector tile_shape) { + return std::make_shared(store.partition_by_tiling(tile_shape)); +} + +inline std::shared_ptr partition_by_tiling(LogicalStore& store, std::vector tile_shape, + std::vector color_shape) { + return std::make_shared(store.partition_by_tiling(tile_shape, color_shape)); +} + } // namespace data namespace time { @@ -330,4 +344,5 @@ inline uint64_t time_nanoseconds() { return legate::timing::measure_nanoseconds().value(); } } // namespace time + } // namespace legate_wrapper diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 2e216c5f..8c030a04 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -150,14 +150,34 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("get_obj_ptr", [](PhysicalStore& s) { return static_cast(&s); }); - mod.add_type("LogicalStoreImpl") - .method("dim", &LogicalStore::dim) - .method("type", &LogicalStore::type) - .method("reinterpret_as", &LogicalStore::reinterpret_as) - .method("promote", &LogicalStore::promote) - .method("slice", &LogicalStore::slice) - .method("get_physical_store", &LogicalStore::get_physical_store) - .method("equal_storage", &LogicalStore::equal_storage); + mod.add_type("LogicalStoreImpl"); + mod.add_type("LogicalStorePartitionImpl"); + + mod.method("dim", [](LogicalStore& s) { return s.dim(); }); + mod.method("type", [](LogicalStore& s) { return s.type(); }); + mod.method("reinterpret_as", [](LogicalStore& s, legate::Type t) { return s.reinterpret_as(t); }); + mod.method("promote", [](LogicalStore& s, int32_t extra_dim, size_t dim_size) { return s.promote(extra_dim, dim_size); }); + mod.method("slice", [](LogicalStore& s, int32_t dim, legate::Slice sl) { return s.slice(dim, sl); }); + mod.method("get_physical_store", [](LogicalStore& s) { return s.get_physical_store(); }); + mod.method("equal_storage", [](LogicalStore& s, LogicalStore& other) { return s.equal_storage(other); }); + mod.method("partition_by_tiling", + [](LogicalStore& store, std::vector tile_shape) { + return legate_wrapper::data::partition_by_tiling(store, tile_shape); + }); + + mod.method("partition_by_tiling", + [](LogicalStore& store, std::vector tile_shape, + std::vector color_shape) { + return legate_wrapper::data::partition_by_tiling(store, tile_shape, color_shape); + }); + mod.method("color_shape", [](std::shared_ptr p) { + auto s = p->color_shape(); + std::vector result(s.begin(), s.end()); + return result; + }); + mod.method("store", [](std::shared_ptr p) { + return p->store(); + }); mod.add_type("PhysicalArray") .method("dim", &PhysicalArray::dim) @@ -188,14 +208,16 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { [](AutoTask& t) { return static_cast(&t); }); mod.add_type("ManualTask") - .method("add_input", static_cast( - &ManualTask::add_input)) - .method("add_output", static_cast( - &ManualTask::add_output)) - .method("add_scalar", static_cast( - &ManualTask::add_scalar_arg)) - .method("get_obj_ptr", - [](ManualTask& t) { return static_cast(&t); }); + .method("add_input", static_cast(&ManualTask::add_input)) + .method("add_output", static_cast(&ManualTask::add_output)) + .method("add_input", [](ManualTask& t, std::shared_ptr p) { + t.add_input(*p); + }) + .method("add_output", [](ManualTask& t, std::shared_ptr p) { + t.add_output(*p); + }) + .method("add_scalar", static_cast(&ManualTask::add_scalar_arg)) + .method("get_obj_ptr", [](ManualTask& t) { return static_cast(&t); }); /* runtime */ mod.add_type("Runtime").method( @@ -215,6 +237,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("submit_auto_task", &legate_wrapper::tasking::submit_auto_task); mod.method("submit_manual_task", &legate_wrapper::tasking::submit_manual_task); + /* array management */ mod.method("create_unbound_array", &legate_wrapper::data::create_unbound_array); @@ -234,5 +257,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("time_microseconds", &legate_wrapper::time::time_microseconds); mod.method("time_nanoseconds", &legate_wrapper::time::time_nanoseconds); + mod.method("num_procs", &legate_wrapper::runtime::num_procs); + wrap_ufi(mod); } diff --git a/src/api/data.jl b/src/api/data.jl index 092b002c..7a2383b1 100644 --- a/src/api/data.jl +++ b/src/api/data.jl @@ -284,3 +284,13 @@ function get_ptr(arr::PhysicalStore) # PhysicalStore -> Ptr return _get_ptr(CxxWrap.CxxPtr(arr)) # cxxwrap call end + +function partition_by_tiling(store::LogicalStore{T,N}, tile_shape) where {T,N} + impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape)) # cxxwrap call + return LogicalStorePartition{T,N}(impl) +end + +function partition_by_tiling(store::LogicalStore{T,N}, tile_shape, color_shape) where {T,N} + impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape), to_cxx_vector(color_shape)) # cxxwrap call + return LogicalStorePartition{T,N}(impl) +end \ No newline at end of file diff --git a/src/api/tasks.jl b/src/api/tasks.jl index 12425642..e7537539 100644 --- a/src/api/tasks.jl +++ b/src/api/tasks.jl @@ -85,7 +85,7 @@ Add a logical array/store as an input to the task. """ function add_input( task::Union{AutoTask,ManualTask}, - item::Union{LogicalArray,LogicalStore}, + item::Union{LogicalArray,LogicalStore,LogicalStorePartition}, ) add_input(task, item.handle) end @@ -98,11 +98,12 @@ Add a logical array/store as an output of the task. """ function add_output( task::Union{AutoTask,ManualTask}, - item::Union{LogicalArray,LogicalStore}, + item::Union{LogicalArray,LogicalStore,LogicalStorePartition}, ) add_output(task, item.handle) end + """ add_scalar(AutoTask, scalar::Scalar) add_scalar(ManualTask, scalar::Scalar) diff --git a/src/api/types.jl b/src/api/types.jl index 2bdc65e5..b5f70493 100644 --- a/src/api/types.jl +++ b/src/api/types.jl @@ -123,3 +123,13 @@ Base.size(a::LogicalArray, i::Integer) = size(a)[i] Datatype of object within Legate. See `Legate.supported_types()` to see supported types. """ LegateType + + +""" + LogicalStorePartition{T,N} +Represents a tiled partition of a `LogicalStore`. Created via `partition_by_tiling`. +Wraps the underlying C++ `LogicalStorePartitionImpl`. +""" +struct LogicalStorePartition{T,N} + handle::CxxWrap.StdLib.SharedPtr{LogicalStorePartitionImpl} +end \ No newline at end of file From f98aaae89ff8f8287f6397a6b19f5957ef10d6b6 Mon Sep 17 00:00:00 2001 From: Nader Rahhal <107228500+Nader-Rahhal@users.noreply.github.com> Date: Sun, 24 May 2026 17:03:00 -0500 Subject: [PATCH 02/12] module --- lib/legate_jl_wrapper/src/module.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 8c030a04..15355520 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -204,6 +204,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("add_constraint", static_cast( &AutoTask::add_constraint)) + .method("add_communicator", static_cast(&AutoTask::add_communicator)) .method("get_obj_ptr", [](AutoTask& t) { return static_cast(&t); }); @@ -217,6 +218,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { t.add_output(*p); }) .method("add_scalar", static_cast(&ManualTask::add_scalar_arg)) + .method("add_communicator", static_cast(&ManualTask::add_communicator)) .method("get_obj_ptr", [](ManualTask& t) { return static_cast(&t); }); /* runtime */ From 22467468688f5c1bd998429465b896924d03ec22 Mon Sep 17 00:00:00 2001 From: Nader Rahhal <107228500+Nader-Rahhal@users.noreply.github.com> Date: Mon, 25 May 2026 11:18:19 -0500 Subject: [PATCH 03/12] more partition patching --- lib/legate_jl_wrapper/src/module.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 15355520..1f135727 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -205,8 +205,9 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { static_cast( &AutoTask::add_constraint)) .method("add_communicator", static_cast(&AutoTask::add_communicator)) - .method("get_obj_ptr", - [](AutoTask& t) { return static_cast(&t); }); + .method("get_obj_ptr", [](AutoTask& t) { return static_cast(&t); }) + .method("find_or_declare_partition", static_cast(&AutoTask::find_or_declare_partition)) + .method("declare_partition", static_cast(&AutoTask::declare_partition)); mod.add_type("ManualTask") .method("add_input", static_cast(&ManualTask::add_input)) From 785bbcddd2b16a9308972129b19ab95c264eda81 Mon Sep 17 00:00:00 2001 From: Nader Rahhal <107228500+Nader-Rahhal@users.noreply.github.com> Date: Tue, 26 May 2026 13:11:29 -0500 Subject: [PATCH 04/12] fix bug --- lib/legate_jl_wrapper/src/module.cpp | 18 +++++++++++++++--- src/api/tasks.jl | 8 ++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 1f135727..9c4f2fc0 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -204,10 +204,20 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("add_constraint", static_cast( &AutoTask::add_constraint)) - .method("add_communicator", static_cast(&AutoTask::add_communicator)) + .method("add_communicator", [](AutoTask& t, const std::string& name) { + t.add_communicator(std::string_view{name}); + }) .method("get_obj_ptr", [](AutoTask& t) { return static_cast(&t); }) .method("find_or_declare_partition", static_cast(&AutoTask::find_or_declare_partition)) - .method("declare_partition", static_cast(&AutoTask::declare_partition)); + .method("declare_partition", static_cast(&AutoTask::declare_partition)) + .method("add_broadcast", [](AutoTask& t, LogicalArray arr) { + auto part = t.find_or_declare_partition(arr); + t.add_constraint(legate::broadcast(part)); + }) + .method("add_broadcast", [](AutoTask& t, LogicalArray arr, std::vector axes) { + auto part = t.find_or_declare_partition(arr); + t.add_constraint(legate::broadcast(part, legate::Span{axes.data(), axes.size()})); + }); mod.add_type("ManualTask") .method("add_input", static_cast(&ManualTask::add_input)) @@ -219,7 +229,9 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { t.add_output(*p); }) .method("add_scalar", static_cast(&ManualTask::add_scalar_arg)) - .method("add_communicator", static_cast(&ManualTask::add_communicator)) + .method("add_communicator", [](ManualTask& t, const std::string& name) { + t.add_communicator(std::string_view{name}); + }) .method("get_obj_ptr", [](ManualTask& t) { return static_cast(&t); }); /* runtime */ diff --git a/src/api/tasks.jl b/src/api/tasks.jl index e7537539..522c7906 100644 --- a/src/api/tasks.jl +++ b/src/api/tasks.jl @@ -111,3 +111,11 @@ end Add a scalar argument to the task. """ add_scalar + +function add_broadcast(task::AutoTask, item::Union{LogicalArray,LogicalStore}) + add_broadcast(task, item.handle) +end + +function add_broadcast(task::AutoTask, item::Union{LogicalArray,LogicalStore}, axes) + add_broadcast(task, item.handle, axes) +end From 648d94c1e2d08bdb56868a6a2450a1a03b0b2b33 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Tue, 26 May 2026 15:09:02 -0500 Subject: [PATCH 05/12] Fix Legate developer build --- scripts/build_cpp_wrapper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_cpp_wrapper.sh b/scripts/build_cpp_wrapper.sh index c8e57500..8044c729 100755 --- a/scripts/build_cpp_wrapper.sh +++ b/scripts/build_cpp_wrapper.sh @@ -41,6 +41,6 @@ cmake -S $LEGATE_WRAPPER_SOURCE -B $BUILD_DIR \ -D CMAKE_INSTALL_PREFIX=$INSTALL_DIR \ -D BINARYBUILDER=OFF \ -D CMAKE_PREFIX_PATH="$LEGATE_CMAKE_DIR;$LEGION_CMAKE_DIR;$REALM_CMAKE_DIR" \ - -D CMAKE_BUILD_TYPE=Debug + -D CMAKE_BUILD_TYPE=Release cmake --build $BUILD_DIR --parallel $NTHREADS --verbose From 0426b6bd1acd4221e140ad96352d54b87b42c1be Mon Sep 17 00:00:00 2001 From: Nader Rahhal <107228500+Nader-Rahhal@users.noreply.github.com> Date: Thu, 28 May 2026 10:13:08 -0500 Subject: [PATCH 06/12] fix namespace issues --- lib/legate_jl_wrapper/src/module.cpp | 10 ++++------ src/api/tasks.jl | 8 +++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 9c4f2fc0..517fddd8 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -210,13 +210,11 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("get_obj_ptr", [](AutoTask& t) { return static_cast(&t); }) .method("find_or_declare_partition", static_cast(&AutoTask::find_or_declare_partition)) .method("declare_partition", static_cast(&AutoTask::declare_partition)) - .method("add_broadcast", [](AutoTask& t, LogicalArray arr) { - auto part = t.find_or_declare_partition(arr); - t.add_constraint(legate::broadcast(part)); + .method("broadcast", [](Variable& v) { + return legate::broadcast(v); }) - .method("add_broadcast", [](AutoTask& t, LogicalArray arr, std::vector axes) { - auto part = t.find_or_declare_partition(arr); - t.add_constraint(legate::broadcast(part, legate::Span{axes.data(), axes.size()})); + .method("broadcast", [](Variable& v, std::vector axes) { + return legate::broadcast(v, legate::Span{axes.data(), axes.size()}); }); mod.add_type("ManualTask") diff --git a/src/api/tasks.jl b/src/api/tasks.jl index 522c7906..86135baa 100644 --- a/src/api/tasks.jl +++ b/src/api/tasks.jl @@ -113,9 +113,11 @@ Add a scalar argument to the task. add_scalar function add_broadcast(task::AutoTask, item::Union{LogicalArray,LogicalStore}) - add_broadcast(task, item.handle) + part = find_or_declare_partition(task, item.handle) + add_constraint(task, broadcast(part)) end function add_broadcast(task::AutoTask, item::Union{LogicalArray,LogicalStore}, axes) - add_broadcast(task, item.handle, axes) -end + part = find_or_declare_partition(task, item.handle) + add_constraint(task, broadcast(part, axes)) +end \ No newline at end of file From 662694750facb6e5605ac5659ba62237e42002c8 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Fri, 29 May 2026 13:11:22 -0500 Subject: [PATCH 07/12] Update package search paths. (#89) --- src/utilities/preference.jl | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/utilities/preference.jl b/src/utilities/preference.jl index 1e05d654..2d39e7f3 100644 --- a/src/utilities/preference.jl +++ b/src/utilities/preference.jl @@ -138,24 +138,23 @@ const DEPS_MAP = Dict( "CUDA_DRIVER" => "libcuda", "CUDA_RUNTIME" => "libcudart", ) -function find_dependency_paths(::Type{LegatePreferences.JLL}) - results = Dict{String,String}() - - paths_to_search = copy(legate_jll.LIBPATH_list) - # If we have CUDA support try to find some other paths - if isdefined(legate_jll, :NCCL_jll) - append!(paths_to_search, legate_jll.NCCL_jll.CUDA_Runtime_jll.LIBPATH_list) - push!( - paths_to_search, - joinpath(legate_jll.NCCL_jll.CUDA_Runtime_jll.CUDA_Driver_jll.artifact_dir, "lib"), - ) - end +function _legate_lib_search_paths(base::Vector{String}) + paths = copy(base) + isdefined(legate_jll, :NCCL_jll) || return paths + nccl = legate_jll.NCCL_jll + append!(paths, nccl.CUDA_Runtime_jll.LIBPATH_list) + push!(paths, joinpath(nccl.CUDA_Runtime_jll.CUDA_Driver_jll.artifact_dir, "lib")) + return paths +end - for (name, lib) in DEPS_MAP - results[name] = dirname(Libdl.find_library(lib, paths_to_search)) - end - return results +function find_dependency_paths(::Type{LegatePreferences.JLL}) + paths = _legate_lib_search_paths(legate_jll.LIBPATH_list) + return Dict(name => dirname(Libdl.find_library(lib, paths)) for (name, lib) in DEPS_MAP) end -find_dependency_paths(::Type{LegatePreferences.Developer}) = Dict{String,String}() +function find_dependency_paths(::Type{LegatePreferences.Developer}) + isdefined(@__MODULE__, :legate_jll) || return Dict{String,String}() + paths = _legate_lib_search_paths(legate_jll.LIBPATH_list) + return Dict(name => dirname(Libdl.find_library(lib, paths)) for (name, lib) in DEPS_MAP) +end find_dependency_paths(::Type{LegatePreferences.Conda}) = Dict{String,String}() From 682ebb9606d72732f6410f03d372e05ca94171b6 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Fri, 29 May 2026 15:37:53 -0500 Subject: [PATCH 08/12] Version check (same system as cuNumeric.jl) (#90) --- .github/scripts/check_versions.py | 226 ++++++++++++++++++++++++++++ .github/workflows/ci.yml | 4 + .github/workflows/developer.yml | 6 +- .github/workflows/pkg_resolve.yml | 22 +++ .github/workflows/version_check.yml | 25 +++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/check_versions.py create mode 100644 .github/workflows/pkg_resolve.yml create mode 100644 .github/workflows/version_check.yml diff --git a/.github/scripts/check_versions.py b/.github/scripts/check_versions.py new file mode 100644 index 00000000..89d6688b --- /dev/null +++ b/.github/scripts/check_versions.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +"""Version consistency check for pull requests targeting main.""" + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, check=True, +).stdout.strip()) + +# To reuse this script for another package (e.g. cuNumeric.jl), change only this block. + +PACKAGE_NAME = "Legate" + +WRAPPER_VERSION_FILE = "lib/legate_jl_wrapper/VERSION" +WRAPPER_JLL_COMPAT_KEY = "legate_jl_wrapper_jll" +WRAPPER_SRC_PREFIXES = ( + "lib/legate_jl_wrapper/src/", + "lib/legate_jl_wrapper/include/", + "lib/legate_jl_wrapper/CMakeLists.txt", +) + +SUBPKG_TOML_PATH = "lib/LegatePreferences/Project.toml" +SUBPKG_COMPAT_KEY = "LegatePreferences" +SUBPKG_SRC_PREFIXES = ( + "lib/LegatePreferences/src/", +) + + +def parse_version(v: str) -> tuple: + return tuple(int(x) for x in v.strip().split(".")) + + +def version_gt(a: str, b: str) -> bool: + return parse_version(a) > parse_version(b) + + +def git_show(ref: str, path: str) -> str: + result = subprocess.run( + ["git", "show", f"{ref}:{path}"], + capture_output=True, text=True, check=True, + cwd=REPO_ROOT, + ) + return result.stdout + + +def changed_files(base_ref: str) -> list: + result = subprocess.run( + ["git", "diff", "--name-only", f"{base_ref}...HEAD"], + capture_output=True, text=True, check=True, + cwd=REPO_ROOT, + ) + return result.stdout.splitlines() + + +def parse_top_level_version(toml_text: str) -> str: + for line in toml_text.splitlines(): + stripped = line.strip() + if re.match(r"^\[", stripped): + break + m = re.match(r'^version\s*=\s*"([^"]+)"', stripped) + if m: + return m.group(1) + raise ValueError("Could not find top-level version in TOML") + + +def parse_compat_section(toml_text: str) -> dict: + in_compat = False + result = {} + for line in toml_text.splitlines(): + stripped = line.strip() + if re.match(r"^\[", stripped): + in_compat = stripped == "[compat]" + continue + if in_compat and stripped and not stripped.startswith("#"): + m = re.match(r'^([\w_-]+)\s*=\s*"([^"]+)"', stripped) + if m: + result[m.group(1)] = m.group(2) + return result + + +def check_package_version(base_ref: str, pr_toml: str, errors: list): + main_toml = git_show(base_ref, "Project.toml") + main_ver = parse_top_level_version(main_toml) + pr_ver = parse_top_level_version(pr_toml) + + print(f"\t[{PACKAGE_NAME}]\tmain={main_ver}\tpr={pr_ver}") + if not version_gt(pr_ver, main_ver): + errors.append( + f"{PACKAGE_NAME} version must be greater than main.\n" + f"\tmain: {main_ver} → pr: {pr_ver}\n" + f"\tBump the version in Project.toml (patch, minor, or major)." + ) + else: + print(f"\t\tOK ({main_ver} → {pr_ver})") + + +def check_wrapper_version(base_ref: str, changed: list, errors: list): + src_changed = [f for f in changed if any(f.startswith(p) for p in WRAPPER_SRC_PREFIXES)] + if not src_changed: + print("\t[wrapper]\tno source changes — skipping") + return + + print(f"\t[wrapper]\t{len(src_changed)} source file(s) changed:") + for f in src_changed: + print(f"\t\t{f}") + + pr_ver = (REPO_ROOT / WRAPPER_VERSION_FILE).read_text().strip() + try: + main_ver = git_show(base_ref, WRAPPER_VERSION_FILE).strip() + except subprocess.CalledProcessError: + main_ver = "0.0.0" + + print(f"\t\tVERSION\tmain={main_ver}\tpr={pr_ver}") + if not version_gt(pr_ver, main_ver): + errors.append( + f"{WRAPPER_VERSION_FILE} must be incremented when wrapper source changes.\n" + f"\tmain: {main_ver} → pr: {pr_ver}\n" + f"\tBump {WRAPPER_VERSION_FILE}." + ) + else: + print(f"\t\tOK VERSION ({main_ver} → {pr_ver})") + + +def check_wrapper_compat_sync(pr_toml: str, errors: list): + wrapper_ver = (REPO_ROOT / WRAPPER_VERSION_FILE).read_text().strip() + compat_ver = parse_compat_section(pr_toml).get(WRAPPER_JLL_COMPAT_KEY) + + lhs = WRAPPER_VERSION_FILE + rhs = f"Project.toml [compat] {WRAPPER_JLL_COMPAT_KEY}" + w = max(len(lhs), len(rhs)) + print(f"\t[wrapper compat sync]") + print(f"\t\t{lhs:<{w}} = {wrapper_ver}") + print(f"\t\t{rhs:<{w}} = {compat_ver}") + if compat_ver is None: + errors.append( + f"{WRAPPER_JLL_COMPAT_KEY} not found in Project.toml [compat]." + ) + elif compat_ver != wrapper_ver: + errors.append( + f"Project.toml [compat] {WRAPPER_JLL_COMPAT_KEY}={compat_ver} does not match {WRAPPER_VERSION_FILE}={wrapper_ver}.\n" + f"\tSet {WRAPPER_JLL_COMPAT_KEY} = \"{wrapper_ver}\" in Project.toml [compat]." + ) + else: + print(f"\t\tOK") + + +def check_subpkg_version(base_ref: str, pr_toml: str, changed: list, errors: list): + src_changed = [f for f in changed if any(f.startswith(p) for p in SUBPKG_SRC_PREFIXES)] + if not src_changed: + print(f"\t[{SUBPKG_COMPAT_KEY}]\tno source changes — skipping") + return + + print(f"\t[{SUBPKG_COMPAT_KEY}]\t{len(src_changed)} source file(s) changed:") + for f in src_changed: + print(f"\t\t{f}") + + pr_subpkg_toml = (REPO_ROOT / SUBPKG_TOML_PATH).read_text() + try: + main_subpkg_toml = git_show(base_ref, SUBPKG_TOML_PATH) + except subprocess.CalledProcessError: + main_subpkg_toml = 'version = "0.0.0"' + + pr_ver = parse_top_level_version(pr_subpkg_toml) + main_ver = parse_top_level_version(main_subpkg_toml) + + print(f"\t\tversion\tmain={main_ver}\tpr={pr_ver}") + if not version_gt(pr_ver, main_ver): + errors.append( + f"{SUBPKG_TOML_PATH} version must be incremented when {SUBPKG_COMPAT_KEY} source changes.\n" + f"\tmain: {main_ver} → pr: {pr_ver}\n" + f"\tBump the version in {SUBPKG_TOML_PATH}." + ) + else: + print(f"\t\tOK version ({main_ver} → {pr_ver})") + + compat_ver = parse_compat_section(pr_toml).get(SUBPKG_COMPAT_KEY) + if compat_ver is None: + errors.append( + f"{SUBPKG_COMPAT_KEY} not found in Project.toml [compat]." + ) + elif compat_ver != pr_ver: + errors.append( + f"Project.toml [compat] {SUBPKG_COMPAT_KEY}={compat_ver} does not match {SUBPKG_TOML_PATH} version={pr_ver}.\n" + f"\tSet {SUBPKG_COMPAT_KEY} = \"{pr_ver}\" in Project.toml [compat]." + ) + else: + print(f"\t\tOK compat {SUBPKG_COMPAT_KEY}={compat_ver}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--base-ref", default="origin/main", + help="Git ref to compare against (default: origin/main)") + args = parser.parse_args() + + base_ref = args.base_ref + errors = [] + + pr_toml = (REPO_ROOT / "Project.toml").read_text() + changed = changed_files(base_ref) + + print("Version check") + print("─" * 60) + check_package_version(base_ref, pr_toml, errors) + check_wrapper_version(base_ref, changed, errors) + check_wrapper_compat_sync(pr_toml, errors) + check_subpkg_version(base_ref, pr_toml, changed, errors) + print("─" * 60) + + if errors: + print(f"\nFAILED — {len(errors)} error(s):\n") + for i, e in enumerate(errors, 1): + print(f"\t{i}. {e}\n") + sys.exit(1) + else: + print("\nPASSED") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ab384d0..8abb7a6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,12 @@ on: - '.github/workflows/ci.yml' - 'Dockerfile' # container.yml depends on this jobs: + pkg_resolve: + uses: ./.github/workflows/pkg_resolve.yml + test: name: Julia ${{ matrix.julia }} - ${{ matrix.os }} + needs: pkg_resolve runs-on: ${{ matrix.os }} strategy: fail-fast: false diff --git a/.github/workflows/developer.yml b/.github/workflows/developer.yml index 8814c9f8..6936bcbc 100644 --- a/.github/workflows/developer.yml +++ b/.github/workflows/developer.yml @@ -38,8 +38,12 @@ on: - 'lib/LegatePreferences/src/**' - '.github/workflows/developer.yml' jobs: + pkg_resolve: + uses: ./.github/workflows/pkg_resolve.yml + docs: - name : Developer CI test + name: Developer CI test + needs: pkg_resolve permissions: contents: read packages: write diff --git a/.github/workflows/pkg_resolve.yml b/.github/workflows/pkg_resolve.yml new file mode 100644 index 00000000..47c5302d --- /dev/null +++ b/.github/workflows/pkg_resolve.yml @@ -0,0 +1,22 @@ +name: Pkg Resolve + +on: + workflow_call: + +jobs: + resolve: + name: Pkg.resolve + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: julia-actions/setup-julia@v2 + with: + version: '1.10' + + - uses: julia-actions/cache@v2 + + - name: Pkg.resolve + run: | + rm -f Manifest.toml + julia --project -e 'using Pkg; Pkg.Registry.add("General"); Pkg.resolve()' diff --git a/.github/workflows/version_check.yml b/.github/workflows/version_check.yml new file mode 100644 index 00000000..346bc7d6 --- /dev/null +++ b/.github/workflows/version_check.yml @@ -0,0 +1,25 @@ +name: Version Check + +on: + pull_request: + branches: + - main + +jobs: + version-check: + name: Version Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch main branch + run: git fetch origin main + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Run version checks + run: python .github/scripts/check_versions.py From 4469d6430beb138a1d511c3fbc13f9ba451640e7 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Fri, 29 May 2026 16:14:49 -0500 Subject: [PATCH 09/12] Better build system robustness (#87) * add dev_tools.jl. Start to enforce proper cuda toolkit usage. In progress * add CUDA_SDK_jll as a direct dependency allows for correct paths * add a workspaces and ./dev for CUDA_SDK_jll * update cuda tool pathing * dev override functionality * update to support cunumeric * mimic cunumeric's ci runs into branches like develop * modify set_jll_artifact_override * guard docs on wrapper changes * update version semantics to major.minor --- .github/workflows/ci.yml | 20 ++- .github/workflows/developer.yml | 4 +- .github/workflows/docs.yml | 16 ++ .gitignore | 5 + Project.toml | 5 +- deps/build.jl | 145 ++++------------- deps/buildtools/cuda_tools.jl | 43 +++++ deps/buildtools/dev_tools.jl | 229 +++++++++++++++++++++++++++ deps/version.jl | 18 +-- lib/legate_jl_wrapper/CMakeLists.txt | 9 ++ scripts/build_cpp_wrapper.sh | 16 +- src/Legate.jl | 3 +- src/utilities/preference.jl | 2 +- test/runtests.jl | 3 +- 14 files changed, 378 insertions(+), 140 deletions(-) create mode 100644 deps/buildtools/cuda_tools.jl create mode 100644 deps/buildtools/dev_tools.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8abb7a6e..8a18eb2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ on: - 'src/**' - 'scripts/**' - 'deps/build.jl' + - 'deps/buildtools/**' - 'Project.toml' - 'lib/LegatePreferences/src/**' - '.github/workflows/ci.yml' @@ -29,17 +30,30 @@ on: - 'src/**' - 'scripts/**' - 'deps/build.jl' + - 'deps/buildtools/**' - 'Project.toml' - 'lib/LegatePreferences/src/**' - '.github/workflows/ci.yml' - 'Dockerfile' # container.yml depends on this jobs: - pkg_resolve: - uses: ./.github/workflows/pkg_resolve.yml + check_changes: + name: Check for wrapper changes + runs-on: ubuntu-latest + outputs: + wrapper_changed: ${{ steps.filter.outputs.wrapper }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + wrapper: + - 'lib/legate_jl_wrapper/**' test: name: Julia ${{ matrix.julia }} - ${{ matrix.os }} - needs: pkg_resolve + needs: check_changes + if: ${{ github.base_ref == 'main' || needs.check_changes.outputs.wrapper_changed != 'true' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false diff --git a/.github/workflows/developer.yml b/.github/workflows/developer.yml index 6936bcbc..eac822d8 100644 --- a/.github/workflows/developer.yml +++ b/.github/workflows/developer.yml @@ -18,6 +18,7 @@ on: - 'src/**' - 'scripts/**' - 'deps/build.jl' + - 'deps/buildtools/**' - 'Project.toml' - 'lib/legate_jl_wrapper/src/**' - 'lib/legate_jl_wrapper/include/**' @@ -32,6 +33,7 @@ on: - 'src/**' - 'scripts/**' - 'deps/build.jl' + - 'deps/buildtools/**' - 'Project.toml' - 'lib/legate_jl_wrapper/src/**' - 'lib/legate_jl_wrapper/include/**' @@ -59,7 +61,7 @@ jobs: shell: bash env: LEGATE_AUTO_CONFIG: 0 - NO_CUDA: ON + LEGATE_WRAPPER_ENABLE_CUDA: OFF steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 07442b62..ffaa4cb5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,8 +23,24 @@ on: - 'README.md' - 'lib/LegatePreferences/src*' jobs: + check_changes: + name: Check for wrapper changes + runs-on: ubuntu-latest + outputs: + wrapper_changed: ${{ steps.filter.outputs.wrapper }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + wrapper: + - 'lib/legate_jl_wrapper/**' + docs: name : Documentation + needs: check_changes + if: ${{ github.base_ref == 'main' || needs.check_changes.outputs.wrapper_changed != 'true' }} permissions: actions: write contents: write diff --git a/.gitignore b/.gitignore index bd5f2d43..3767a632 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ LocalPreferences.toml # Files generated by the build process libcxxwrap-julia* build* +!deps/buildtools/ +!deps/buildtools/** wrapper/build legate_wrapper_install @@ -55,5 +57,8 @@ docs/site/ # environment. Manifest.toml +# Generated by build.jl — do not commit +dev/Project.toml +dev/Manifest.toml test_crash.sh diff --git a/Project.toml b/Project.toml index 42cc5a98..8e0cd748 100644 --- a/Project.toml +++ b/Project.toml @@ -2,6 +2,9 @@ name = "Legate" uuid = "1238f2cf-6593-4d60-9aca-2f5364e49909" version = "0.1.2" +[workspace] +projects = ["test", "dev"] + [deps] CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" @@ -26,7 +29,7 @@ CxxWrap = "0.17" FunctionWrappers = "1.1.3" JuliaFormatter = "2.2.0" LegatePreferences = "0.1.6" +Preferences = "1" julia = "1.10" legate_jl_wrapper_jll = "25.10.4" legate_jll = "25.10.2" -Preferences = "1" diff --git a/deps/build.jl b/deps/build.jl index 3549b482..15ab1bbc 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -16,138 +16,52 @@ * Author(s): David Krasowska * Ethan Meitz =# +using Pkg using Preferences using LegatePreferences +include("buildtools/dev_tools.jl") include("version.jl") -# Automatically pipes errors to new file -# and appends stdout to build.log -function run_sh(cmd::Cmd, filename::String) - println(cmd) - - build_log = joinpath(@__DIR__, "build.log") - tmp_build_log = joinpath(@__DIR__, "$(filename).log") - err_log = joinpath(@__DIR__, "$(filename).err") - - if isfile(err_log) - rm(err_log) - end - - if isfile(tmp_build_log) - rm(tmp_build_log) - end - - try - run(pipeline(cmd; stdout=tmp_build_log, stderr=err_log, append=false)) - contents = read(tmp_build_log, String) - open(build_log, "a") do io - println(contents) - end - catch e - println("stderr log generated: ", err_log, '\n') - contents = read(err_log, String) - if !isempty(strip(contents)) - println("---- Begin stderr log ----") - println(contents) - println("---- End stderr log ----") - end - end -end - # patch legion. The readme below talks about our compilation error # https://github.com/ejmeitz/cuNumeric.jl/blob/main/scripts/README.md function patch_legion(repo_root::String, legate_root::String) if !check_if_patch(legate_root) legion_patch = joinpath(repo_root, "scripts/patch_legion.sh") @info "Legate.jl: Running legion patch script: $legion_patch" - run_sh(`bash $legion_patch $repo_root $legate_root`, "legion_patch") - end -end - -function build_jlcxxwrap(repo_root, legate_root) - build_libcxxwrap = joinpath(repo_root, "scripts/install_cxxwrap.sh") - version_path = joinpath(DEPOT_PATH[1], "dev/libcxxwrap_julia_jll/override/LEGATE_INSTALL.txt") - if isfile(version_path) - version = VersionNumber(strip(read(version_path, String))) - @info "libcxxwrap: Found Legate $version" - if is_supported_version(version) - @info "libcxxwrap: Found supported version built with Legate.jl: $version" - return nothing - else - @info "libcxxwrap: Unsupported version found: $version. Rebuilding..." - end - else - @info "libcxxwrap: No version file found. Starting build..." - end - - @info "libcxxwrap: Running build script: $build_libcxxwrap" - run_sh(`bash $build_libcxxwrap $repo_root`, "libcxxwrap") - open(version_path, "w") do io - write(io, string(get_legate_version(legate_root))) + BuildTools.run_sh( + `bash $legion_patch $repo_root $legate_root`, "legion_patch"; log_dir=@__DIR__ + ) end end -function build_cpp_wrapper(repo_root, legate_root, install_root) +function build_cpp_wrapper( + repo_root, legate_root, install_root; cuda_root=nothing, cuda_enabled=true +) @info "liblegatewrapper: Building C++ Wrapper Library" - if isdir(install_root) - rm(install_root; recursive=true) - mkdir(install_root) - end - - build_cpp_wrapper = joinpath(repo_root, "scripts/build_cpp_wrapper.sh") - nthreads = Threads.nthreads() - - bld_command = `$build_cpp_wrapper $repo_root $legate_root $install_root $nthreads` - - # write out a bash script for debugging - cmd_str = join(bld_command.exec, " ") - wrapper_path = joinpath(repo_root, "build_wrapper.sh") - open(wrapper_path, "w") do io - println(io, "#!/bin/bash") - println(io, "set -xe") - println(io, cmd_str) - end - chmod(wrapper_path, 0o755) - - @info "Running build command: $bld_command" - run_sh(`bash $bld_command`, "cpp_wrapper") -end - -function _find_jll_artifact_dir(jll) - eval(:(using $(jll))) - jll_mod = getfield(Main, jll) - root = jll_mod.artifact_dir - return root + isdir(install_root) && (rm(install_root; recursive=true); mkdir(install_root)) + bld_command = `$(joinpath(repo_root, "scripts/build_cpp_wrapper.sh")) $repo_root $legate_root $install_root $(Threads.nthreads())` + BuildTools.run_build_wrapper_script( + repo_root, bld_command; cuda_root, cuda_enabled, log_dir=@__DIR__ + ) end -function _start_build() - pkg_root = up_dir(@__DIR__) - deps_dir = joinpath(@__DIR__) - - build_log = joinpath(deps_dir, "build.log") - open(build_log, "w") do io - println(io, "=== Build started ===") - end - - @info "Legate.jl: Parsed Package Dir as: $(pkg_root)" - return pkg_root -end - -""" - build CxxWrap and legate_jl_wrapper -""" -function build_deps(pkg_root, legate_root) +function build_deps(pkg_root, legate_root; cuda_root=nothing, cuda_enabled=true) + BuildTools.check_cmake_version(MIN_CMAKE_VERSION) install_dir = joinpath(pkg_root, "lib", "legate_jl_wrapper", "build") if !legate_valid(legate_root) error( "Legate.jl: Unsupported Legate version at $(legate_root). " * - "Installed version: $(installed_version) not in range supported: " * + "Installed version: $(get_legate_version(legate_root)) not in range supported: " * "$(MIN_LEGATE_VERSION)-$(MAX_LEGATE_VERSION).", ) end - build_jlcxxwrap(pkg_root, legate_root) # $pkg_root/lib/libcxxwrap-julia - build_cpp_wrapper(pkg_root, legate_root, install_dir) # $pkg_root/lib/legate_jl_wrapper + BuildTools.build_jlcxxwrap( + pkg_root, get_legate_version(legate_root); + log_dir=@__DIR__, is_compatible=is_supported_version, + ) + build_cpp_wrapper(pkg_root, legate_root, install_dir; cuda_root, cuda_enabled) + BuildTools.set_jll_artifact_override(:legate_jl_wrapper_jll, install_dir) end function build(::LegatePreferences.JLL) @@ -157,7 +71,7 @@ end function build(::LegatePreferences.Conda) @warn "Conda Build does not currently pass our CI. Proceed with caution." - pkg_root = _start_build() + pkg_root = BuildTools.start_build("Legate.jl", @__DIR__) legate_root = load_preference(LegatePreferences, "legate_conda_env", nothing) if isnothing(legate_root) @@ -170,19 +84,20 @@ function build(::LegatePreferences.Conda) end function build(::LegatePreferences.Developer) - pkg_root = _start_build() + pkg_root = BuildTools.start_build("Legate.jl", @__DIR__) - # can be nothing so this errors if not set legate_root = load_preference(LegatePreferences, "legate_path", nothing) if isnothing(legate_root) - # we are using legate_jll for legate - legate_root = _find_jll_artifact_dir(:legate_jll) + legate_root, cuda_root = BuildTools.setup_jll_build_env(pkg_root, BuildTools.LEGATE_JLL_DEP) + cuda_enabled = !isnothing(cuda_root) # cuda_root resolving to nothing means there is no cuda else - # this means we have a custom path set is_legate_installed(legate_root; throw_errors=true) patch_legion(pkg_root, legate_root) + cuda_enabled, cuda_root = BuildTools.resolve_custom_cuda("legate") # cuda_root is nothing. end - build_deps(pkg_root, legate_root) + + build_deps(pkg_root, legate_root; cuda_root, cuda_enabled) + set_preferences!(LegatePreferences, "LEGATE_LIBDIR" => joinpath(legate_root, "lib"); force=true) end const mode_str = load_preference(LegatePreferences, "legate_mode", LegatePreferences.MODE_JLL) diff --git a/deps/buildtools/cuda_tools.jl b/deps/buildtools/cuda_tools.jl new file mode 100644 index 00000000..8a6e9eae --- /dev/null +++ b/deps/buildtools/cuda_tools.jl @@ -0,0 +1,43 @@ +function resolve_custom_cuda(pkg_name::String) + cuda_enabled = Sys.which("nvcc") !== nothing + if !cuda_enabled + @warn "nvcc not found on PATH — building without CUDA. " * + "If your $(pkg_name) was built with CUDA, add nvcc to PATH and rebuild." + end + return cuda_enabled, nothing +end + +function detect_jll_cuda_enabled(jll_mod) + cuda_val = get(jll_mod.host_platform.tags, "cuda", nothing) + return cuda_val !== nothing && cuda_val != "none" +end + +# try/catch because CUDA_SDK_jll is a weakdep — may not be in the environment. +function try_get_cuda_sdk_jll_dir() + try + Core.eval(Main, :(using CUDA_SDK_jll)) + return joinpath(getfield(Main, :CUDA_SDK_jll).artifact_dir, "cuda") + catch + return nothing + end +end + +function build_cuda_env(cuda_enabled::Bool, cuda_root) + # user-set env vars override auto-detection + if haskey(ENV, "LEGATE_WRAPPER_ENABLE_CUDA") + user_enable = uppercase(ENV["LEGATE_WRAPPER_ENABLE_CUDA"]) == "ON" + if user_enable && !cuda_enabled + @warn "LEGATE_WRAPPER_ENABLE_CUDA=ON overrides auto-detected cuda_enabled=false. " * + "The wrapper will link CUDA, but the underlying legate build may not — expect link/runtime errors if mismatched." + end + cuda_enabled = user_enable + end + if haskey(ENV, "CUDA_TOOLKIT_ROOT") + cuda_root = ENV["CUDA_TOOLKIT_ROOT"] + end + + env = Dict{String,String}() + !cuda_enabled && (env["LEGATE_WRAPPER_ENABLE_CUDA"] = "OFF") + cuda_root !== nothing && (env["CUDA_TOOLKIT_ROOT"] = cuda_root) + return env +end diff --git a/deps/buildtools/dev_tools.jl b/deps/buildtools/dev_tools.jl new file mode 100644 index 00000000..022265b8 --- /dev/null +++ b/deps/buildtools/dev_tools.jl @@ -0,0 +1,229 @@ +module BuildTools +using Pkg +using Pkg.Artifacts: artifact_path + +include("cuda_tools.jl") + +function start_build(pkg_name::String, deps_dir::String) + pkg_root = abspath(joinpath(deps_dir, "..")) + open(joinpath(deps_dir, "build.log"), "w") do io + println(io, "=== Build started ===") + end + @info "$(pkg_name): Parsed Package Dir as: $(pkg_root)" + return pkg_root +end + +struct package + name::String + uuid::String + compat::String +end + +const LEGATE_JLL_DEP = package("legate_jll", "e95fb1d3-fb9e-51b5-bdb8-1a812408cac9", "") +const CUNUMERIC_JLL_DEP = package("cupynumeric_jll", "2862d674-414d-5b0b-a494-b21f8deca547", "") +const CUDA_SDK_JLL_DEP = package("CUDA_SDK_jll", "6cbf2f2e-7e60-5632-ac76-dca2274e0be0", "") + +with_compat(dep::package, compat::String) = package(dep.name, dep.uuid, compat) + +# write dev/Project.toml from an ordered list of package entries. +function write_dev_project(dev_dir::String, deps::Vector{package}) + mkpath(dev_dir) + open(joinpath(dev_dir, "Project.toml"), "w") do io + println(io, "[deps]") + for d in deps + println(io, "$(d.name) = \"$(d.uuid)\"") + end + compat_deps = filter(d -> !isempty(d.compat), deps) + if !isempty(compat_deps) + println(io, "\n[compat]") + for d in compat_deps + println(io, "$(d.name) = \"$(d.compat)\"") + end + end + end +end + +# Loads the primary JLL, writes dev/Project.toml, and instantiates the env. +# Returns (artifact_dir, cuda_root) — cuda_root is nothing when CUDA is disabled. +# Activates pkg_root on exit. +function setup_jll_build_env(pkg_root::String, primary::package) + dev_dir = joinpath(pkg_root, "dev") + artifact_dir = find_jll_artifact_dir(Symbol(primary.name)) + jll_mod = getfield(Main, Symbol(primary.name)) + + cuda_enabled = detect_jll_cuda_enabled(jll_mod) + v = pkgversion(jll_mod) + deps = package[with_compat(primary, "$(v.major).$(v.minor)")] + + cuda_compat = nothing + if cuda_enabled + cv = VersionNumber(jll_mod.host_platform["cuda"]) + cuda_compat = "$(cv.major).$(cv.minor)" + push!(deps, with_compat(CUDA_SDK_JLL_DEP, cuda_compat)) + end + + write_dev_project(dev_dir, deps) + Pkg.activate(dev_dir) + Pkg.resolve() + Pkg.instantiate() + + cuda_root = cuda_enabled ? try_get_cuda_sdk_jll_dir() : nothing + Pkg.activate(pkg_root) + + return artifact_dir, cuda_root +end + +# calls using X_jll on core.main and grabs path of artifact dir +function find_jll_artifact_dir(jll::Symbol) + Core.eval(Main, :(using $(jll))) + return getfield(Main, jll).artifact_dir +end + +# Redirect a JLL package to a locally built artifact by symlinking its override/ directory. +# Julia's JLL wrapper checks for override/ at precompile time; invalidating the .ji cache +# forces a recompile so the new path takes effect on the next session. +function set_jll_artifact_override(jll::Symbol, local_artifact::String) + jll_name = string(jll) + jll_src = Base.find_package(jll_name) + if isnothing(jll_src) + @warn "$jll not found in load path — skipping override. Add it as a dependency to enable automatic path wiring." + return nothing + end + jll_pkg_dir = normpath(joinpath(dirname(jll_src), "..")) + jll_override = joinpath(jll_pkg_dir, "override") + + (islink(jll_override) || isdir(jll_override)) && rm(jll_override; recursive=true) + symlink(local_artifact, jll_override) + + jll_cache_dir = joinpath(DEPOT_PATH[1], "compiled", + "v$(VERSION.major).$(VERSION.minor)", jll_name) + isdir(jll_cache_dir) && foreach( + f -> endswith(f, ".ji") && rm(f; force=true), + readdir(jll_cache_dir; join=true), + ) + @info "$jll: override → $local_artifact" +end + +# wrapper to log stdout / stderr +# will capture and print to screen failures with the catch block +function run_sh(cmd::Cmd, filename::String; log_dir::String) + println(cmd) + + build_log = joinpath(log_dir, "build.log") + tmp_build_log = joinpath(log_dir, "$(filename).log") + err_log = joinpath(log_dir, "$(filename).err") + + isfile(err_log) && rm(err_log) + isfile(tmp_build_log) && rm(tmp_build_log) + + try + run(pipeline(cmd; stdout=tmp_build_log, stderr=err_log, append=false)) + contents = read(tmp_build_log, String) + open(build_log, "a") do io + println(contents) + end + catch + println("stderr log generated: ", err_log, '\n') + contents = read(err_log, String) + if !isempty(strip(contents)) + println("---- Begin stderr log ----") + println(contents) + println("---- End stderr log ----") + end + end +end + +# Parses a C++ version header whose last three lines are VERSION_MAJOR/MINOR/PATCH defines. +function get_version(version_file::String) + version = nothing + open(version_file, "r") do f + data = readlines(f) + major = parse(Int, split(data[end - 2])[end]) + minor = parse(Int, lpad(split(data[end - 1])[end], 2, '0')) + patch = parse(Int, lpad(split(data[end])[end], 2, '0')) + version = VersionNumber(major, minor, patch) + end + isnothing(version) && error("BuildTools: failed to parse version from $(version_file)") + return version +end + +function check_cmake_version(min_version::VersionNumber) + cmake = Sys.which("cmake") + isnothing(cmake) && + error("cmake not found on PATH. Developer builds require cmake >= $(min_version).") + + out = readchomp(`$cmake --version`) + m = match(r"cmake version (\d+\.\d+\.\d+)", out) + isnothing(m) && error( + "Could not parse cmake version from `$cmake --version`. Developer builds require cmake >= $(min_version)." + ) + + ver = VersionNumber(m.captures[1]) + ver < min_version && error( + "cmake $(ver) found at $(cmake), but developer builds require >= $(min_version). " * + "Install a newer cmake (e.g. `pip install --upgrade cmake`) and rebuild.", + ) + + @info "Found cmake $(ver) at $(cmake)" +end + +# constructs bash command to run based on env +function run_build_wrapper_script( + repo_root, bld_command; cuda_root=nothing, cuda_enabled=true, log_dir +) + env = build_cuda_env(cuda_enabled, cuda_root) + + # generates build_wrapper.sh in repo_root with env vars and the build command — this is what gets executed + write_build_script(joinpath(repo_root, "build_wrapper.sh"), bld_command; env) + + bash_cmd = Cmd(`bash ./build_wrapper.sh`; dir=repo_root) + @info "Running build command: $bash_cmd" + run_sh(bash_cmd, "cpp_wrapper"; log_dir) +end + +function write_build_script(path::String, cmd::Cmd; env::Dict{String,String}=Dict{String,String}()) + open(path, "w") do io + println(io, "#!/bin/bash") + println(io, "set -xe") + for (k, v) in env + println(io, "export $(k)=$(v)") + end + println(io, join(cmd.exec, " ")) + end + chmod(path, 0o755) +end + +function build_jlcxxwrap( + repo_root::String, package_version::VersionNumber; + log_dir::String, is_compatible::Function=(v -> true), +) + build_libcxxwrap = joinpath(repo_root, "scripts/install_cxxwrap.sh") + override_dir = joinpath(DEPOT_PATH[1], "dev/libcxxwrap_julia_jll/override") + version_path = joinpath(override_dir, "LEGATE_INSTALL.txt") + lib_path = joinpath(override_dir, "lib/libcxxwrap_julia.so") + + if isfile(lib_path) + if isfile(version_path) + cached = VersionNumber(strip(read(version_path, String))) + if is_compatible(cached) + @info "libcxxwrap: Up to date (version $cached)" + return nothing + end + @info "libcxxwrap: Stale (cached=$cached). Rebuilding..." + else + @info "libcxxwrap: No version file found. Starting build..." + end + else + @info "libcxxwrap: No libcxxwrap_julia.so found. Starting build..." + end + + @info "libcxxwrap: Running build script: $build_libcxxwrap" + run_sh(`bash $build_libcxxwrap $repo_root`, "libcxxwrap"; log_dir) + mkpath(dirname(version_path)) + open(version_path, "w") do io + write(io, string(package_version)) + end + return nothing +end + +end # module BuildTools diff --git a/deps/version.jl b/deps/version.jl index 11672cd7..1ba797de 100644 --- a/deps/version.jl +++ b/deps/version.jl @@ -21,24 +21,10 @@ const MIN_CUDA_VERSION = v"13.0" const MAX_CUDA_VERSION = v"13.9.999" const MIN_LEGATE_VERSION = v"25.10.00" const MAX_LEGATE_VERSION = v"25.11.00" +const MIN_CMAKE_VERSION = v"3.26.4" up_dir(dir::String) = abspath(joinpath(dir, "..")) -function get_version(version_file::String) - version = nothing - open(version_file, "r") do f - data = readlines(f) - major = parse(Int, split(data[end - 2])[end]) - minor = parse(Int, lpad(split(data[end - 1])[end], 2, '0')) - patch = parse(Int, lpad(split(data[end])[end], 2, '0')) - version = VersionNumber(major, minor, patch) - end - if isnothing(version) - error("Legate.jl: Failed to parse version for $(version_file)") - end - return version -end - function check_if_patch(legate_root::String) patch = joinpath(legate_root, "include", "legate/legate", "patch") if isfile(patch) @@ -49,7 +35,7 @@ end function get_legate_version(legate_root::String) version_file = joinpath(legate_root, "include", "legate/legate", "version.h") - return get_version(version_file) + return BuildTools.get_version(version_file) end function is_supported_version(version::VersionNumber) diff --git a/lib/legate_jl_wrapper/CMakeLists.txt b/lib/legate_jl_wrapper/CMakeLists.txt index b79ff883..1238392f 100644 --- a/lib/legate_jl_wrapper/CMakeLists.txt +++ b/lib/legate_jl_wrapper/CMakeLists.txt @@ -10,8 +10,17 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +option(LEGATE_WRAPPER_ENABLE_CUDA "Build legate_jl_wrapper with CUDA support" ON) option(BINARYBUILDER "Building with binary builder" ON) +if(LEGATE_WRAPPER_ENABLE_CUDA) + find_package(CUDAToolkit 13.0 REQUIRED) +else() + # only disables find_package requirement for CUDAToolkit. + # if you have a CUDA enabled Legate install, this really won't do anything. + message(STATUS "LEGATE_WRAPPER_ENABLE_CUDA=OFF: skipping CUDAToolkit in legate_jl_wrapper.") +endif() + find_package(legate REQUIRED) # CxxWrap Stuff diff --git a/scripts/build_cpp_wrapper.sh b/scripts/build_cpp_wrapper.sh index 8044c729..345957d4 100755 --- a/scripts/build_cpp_wrapper.sh +++ b/scripts/build_cpp_wrapper.sh @@ -37,10 +37,24 @@ if [[ ! -d "$INSTALL_DIR" ]]; then mkdir -p $INSTALL_DIR fi +# Optional env vars. Example: +# LEGATE_WRAPPER_ENABLE_CUDA=ON \ +# CUDA_TOOLKIT_ROOT=/usr/local/cuda-13.0 \ +# julia --project=. -e 'using Pkg; Pkg.build("Legate")' +LEGATE_WRAPPER_ENABLE_CUDA=${LEGATE_WRAPPER_ENABLE_CUDA:-ON} +CUDA_TOOLKIT_ROOT=${CUDA_TOOLKIT_ROOT:-} + +CUDA_ARGS=("-DLEGATE_WRAPPER_ENABLE_CUDA=${LEGATE_WRAPPER_ENABLE_CUDA}") +if [[ -n "$CUDA_TOOLKIT_ROOT" ]]; then + CUDA_ARGS+=("-DCUDAToolkit_ROOT=${CUDA_TOOLKIT_ROOT}") + CUDA_ARGS+=("-DCMAKE_LIBRARY_PATH=${CUDA_TOOLKIT_ROOT}/lib/stubs") +fi + cmake -S $LEGATE_WRAPPER_SOURCE -B $BUILD_DIR \ -D CMAKE_INSTALL_PREFIX=$INSTALL_DIR \ -D BINARYBUILDER=OFF \ -D CMAKE_PREFIX_PATH="$LEGATE_CMAKE_DIR;$LEGION_CMAKE_DIR;$REALM_CMAKE_DIR" \ - -D CMAKE_BUILD_TYPE=Release + -D CMAKE_BUILD_TYPE=Release \ + "${CUDA_ARGS[@]}" cmake --build $BUILD_DIR --parallel $NTHREADS --verbose diff --git a/src/Legate.jl b/src/Legate.jl index 2e7caf3a..fd60cbfc 100644 --- a/src/Legate.jl +++ b/src/Legate.jl @@ -28,6 +28,7 @@ using CxxWrap using FunctionWrappers import FunctionWrappers: FunctionWrapper +include(joinpath(@__DIR__, "../deps/buildtools/dev_tools.jl")) include(joinpath(@__DIR__, "../deps/version.jl")) include("utilities/preference.jl") @@ -131,7 +132,7 @@ function _finish_runtime() #end Legate.has_finished() && return nothing - + # finish legate runtime Legate.legate_finish() end diff --git a/src/utilities/preference.jl b/src/utilities/preference.jl index 2d39e7f3..02959d68 100644 --- a/src/utilities/preference.jl +++ b/src/utilities/preference.jl @@ -102,7 +102,7 @@ function _find_paths( check_legate_install(legate_path) else check_jll(legate_jll_module) - legate_path = legate_jll.artifact_dir + legate_path = legate_jll_module.artifact_dir end pkg_root = abspath(joinpath(@__DIR__, "../../")) diff --git a/test/runtests.jl b/test/runtests.jl index df4ef3d1..35fbc7c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,8 @@ using Legate using Test const VERBOSE = get(ENV, "VERBOSE", "1") != "0" -const run_gpu_tests = (get(ENV, "GPUTESTS", "1") != "0") && (get(ENV, "NO_CUDA", "OFF") != "ON") +const run_gpu_tests = + (get(ENV, "GPUTESTS", "1") != "0") && (get(ENV, "LEGATE_WRAPPER_ENABLE_CUDA", "ON") != "OFF") @info "Run GPU Tests: $(run_gpu_tests)" if run_gpu_tests From 7425d6f387e8428da50cf0952a6cb4570d97f285 Mon Sep 17 00:00:00 2001 From: krasow Date: Thu, 4 Jun 2026 20:33:03 -0500 Subject: [PATCH 10/12] add num_gpus and rt fence --- lib/legate_jl_wrapper/include/wrapper.inl | 23 ++++-- lib/legate_jl_wrapper/src/module.cpp | 92 ++++++++++++++--------- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/lib/legate_jl_wrapper/include/wrapper.inl b/lib/legate_jl_wrapper/include/wrapper.inl index 9570310c..41070915 100644 --- a/lib/legate_jl_wrapper/include/wrapper.inl +++ b/lib/legate_jl_wrapper/include/wrapper.inl @@ -68,6 +68,15 @@ inline int32_t num_procs() { return legate::Runtime::get_runtime()->get_machine().count(); } +inline int32_t num_gpus() { + return legate::Runtime::get_runtime()->get_machine().count( + legate::mapping::TaskTarget::GPU); +} + +inline void issue_execution_fence() { + legate::Runtime::get_runtime()->issue_execution_fence(); +} + } // namespace runtime namespace tasking { @@ -315,13 +324,17 @@ inline void* get_ptr(legate::PhysicalStore* store) { return legate::double_dispatch(dim, code, GetPtrFunctor{}, store); } -inline std::shared_ptr partition_by_tiling(LogicalStore& store, std::vector tile_shape) { - return std::make_shared(store.partition_by_tiling(tile_shape)); +inline std::shared_ptr partition_by_tiling( + LogicalStore& store, std::vector tile_shape) { + return std::make_shared( + store.partition_by_tiling(tile_shape)); } -inline std::shared_ptr partition_by_tiling(LogicalStore& store, std::vector tile_shape, - std::vector color_shape) { - return std::make_shared(store.partition_by_tiling(tile_shape, color_shape)); +inline std::shared_ptr partition_by_tiling( + LogicalStore& store, std::vector tile_shape, + std::vector color_shape) { + return std::make_shared( + store.partition_by_tiling(tile_shape, color_shape)); } } // namespace data diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 517fddd8..80a407aa 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -155,21 +155,32 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("dim", [](LogicalStore& s) { return s.dim(); }); mod.method("type", [](LogicalStore& s) { return s.type(); }); - mod.method("reinterpret_as", [](LogicalStore& s, legate::Type t) { return s.reinterpret_as(t); }); - mod.method("promote", [](LogicalStore& s, int32_t extra_dim, size_t dim_size) { return s.promote(extra_dim, dim_size); }); - mod.method("slice", [](LogicalStore& s, int32_t dim, legate::Slice sl) { return s.slice(dim, sl); }); - mod.method("get_physical_store", [](LogicalStore& s) { return s.get_physical_store(); }); - mod.method("equal_storage", [](LogicalStore& s, LogicalStore& other) { return s.equal_storage(other); }); - mod.method("partition_by_tiling", - [](LogicalStore& store, std::vector tile_shape) { - return legate_wrapper::data::partition_by_tiling(store, tile_shape); + mod.method("reinterpret_as", [](LogicalStore& s, legate::Type t) { + return s.reinterpret_as(t); + }); + mod.method("promote", + [](LogicalStore& s, int32_t extra_dim, size_t dim_size) { + return s.promote(extra_dim, dim_size); + }); + mod.method("slice", [](LogicalStore& s, int32_t dim, legate::Slice sl) { + return s.slice(dim, sl); + }); + mod.method("get_physical_store", + [](LogicalStore& s) { return s.get_physical_store(); }); + mod.method("equal_storage", [](LogicalStore& s, LogicalStore& other) { + return s.equal_storage(other); + }); + mod.method("partition_by_tiling", [](LogicalStore& store, + std::vector tile_shape) { + return legate_wrapper::data::partition_by_tiling(store, tile_shape); }); mod.method("partition_by_tiling", - [](LogicalStore& store, std::vector tile_shape, - std::vector color_shape) { - return legate_wrapper::data::partition_by_tiling(store, tile_shape, color_shape); - }); + [](LogicalStore& store, std::vector tile_shape, + std::vector color_shape) { + return legate_wrapper::data::partition_by_tiling( + store, tile_shape, color_shape); + }); mod.method("color_shape", [](std::shared_ptr p) { auto s = p->color_shape(); std::vector result(s.begin(), s.end()); @@ -204,33 +215,43 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("add_constraint", static_cast( &AutoTask::add_constraint)) - .method("add_communicator", [](AutoTask& t, const std::string& name) { - t.add_communicator(std::string_view{name}); - }) + .method("add_communicator", + [](AutoTask& t, const std::string& name) { + t.add_communicator(std::string_view{name}); + }) .method("get_obj_ptr", [](AutoTask& t) { return static_cast(&t); }) - .method("find_or_declare_partition", static_cast(&AutoTask::find_or_declare_partition)) - .method("declare_partition", static_cast(&AutoTask::declare_partition)) - .method("broadcast", [](Variable& v) { - return legate::broadcast(v); - }) + .method("find_or_declare_partition", + static_cast( + &AutoTask::find_or_declare_partition)) + .method("declare_partition", static_cast( + &AutoTask::declare_partition)) + .method("broadcast", [](Variable& v) { return legate::broadcast(v); }) .method("broadcast", [](Variable& v, std::vector axes) { - return legate::broadcast(v, legate::Span{axes.data(), axes.size()}); + return legate::broadcast( + v, legate::Span{axes.data(), axes.size()}); }); mod.add_type("ManualTask") - .method("add_input", static_cast(&ManualTask::add_input)) - .method("add_output", static_cast(&ManualTask::add_output)) - .method("add_input", [](ManualTask& t, std::shared_ptr p) { - t.add_input(*p); - }) - .method("add_output", [](ManualTask& t, std::shared_ptr p) { - t.add_output(*p); - }) - .method("add_scalar", static_cast(&ManualTask::add_scalar_arg)) - .method("add_communicator", [](ManualTask& t, const std::string& name) { - t.add_communicator(std::string_view{name}); - }) - .method("get_obj_ptr", [](ManualTask& t) { return static_cast(&t); }); + .method("add_input", static_cast( + &ManualTask::add_input)) + .method("add_output", static_cast( + &ManualTask::add_output)) + .method("add_input", + [](ManualTask& t, std::shared_ptr p) { + t.add_input(*p); + }) + .method("add_output", + [](ManualTask& t, std::shared_ptr p) { + t.add_output(*p); + }) + .method("add_scalar", static_cast( + &ManualTask::add_scalar_arg)) + .method("add_communicator", + [](ManualTask& t, const std::string& name) { + t.add_communicator(std::string_view{name}); + }) + .method("get_obj_ptr", + [](ManualTask& t) { return static_cast(&t); }); /* runtime */ mod.add_type("Runtime").method( @@ -271,6 +292,9 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("time_nanoseconds", &legate_wrapper::time::time_nanoseconds); mod.method("num_procs", &legate_wrapper::runtime::num_procs); + mod.method("num_gpus", &legate_wrapper::runtime::num_gpus); + mod.method("issue_execution_fence", + &legate_wrapper::runtime::issue_execution_fence); wrap_ufi(mod); } From 5bef049f5e9d83d1bf20662abb6e7020d0140ba8 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Fri, 12 Jun 2026 10:04:57 -0500 Subject: [PATCH 11/12] use CUDA_SDK_jll nvcc (#92) --- deps/buildtools/cuda_tools.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deps/buildtools/cuda_tools.jl b/deps/buildtools/cuda_tools.jl index 8a6e9eae..aa5e5f6d 100644 --- a/deps/buildtools/cuda_tools.jl +++ b/deps/buildtools/cuda_tools.jl @@ -38,6 +38,10 @@ function build_cuda_env(cuda_enabled::Bool, cuda_root) env = Dict{String,String}() !cuda_enabled && (env["LEGATE_WRAPPER_ENABLE_CUDA"] = "OFF") - cuda_root !== nothing && (env["CUDA_TOOLKIT_ROOT"] = cuda_root) - return env + if cuda_root !== nothing + env["CUDA_TOOLKIT_ROOT"] = cuda_root + # Prepend the SDK's bin dir so cmake detects the JLL's nvcc + env["PATH"] = "$(joinpath(cuda_root, "bin")):\$PATH" + end + return env. end From 079106cd5c333b5781636cce249aa5dc7dfd9d82 Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Fri, 12 Jun 2026 10:07:17 -0500 Subject: [PATCH 12/12] remove . --- deps/buildtools/cuda_tools.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/buildtools/cuda_tools.jl b/deps/buildtools/cuda_tools.jl index aa5e5f6d..45ea177a 100644 --- a/deps/buildtools/cuda_tools.jl +++ b/deps/buildtools/cuda_tools.jl @@ -43,5 +43,5 @@ function build_cuda_env(cuda_enabled::Bool, cuda_root) # Prepend the SDK's bin dir so cmake detects the JLL's nvcc env["PATH"] = "$(joinpath(cuda_root, "bin")):\$PATH" end - return env. + return env end