From cb9bc0d172924946eb409600852007ecccdd59e8 Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:03:53 +0200 Subject: [PATCH 1/4] Make API more pythonic - Use typed dicts whenever possible - Use std::filesystem::path to represent file paths whenever possible --- src/cooler_file_writer.cpp | 22 ++++++------ src/file.cpp | 42 +++++++++++++--------- src/hic_file_writer.cpp | 41 +++++++++++---------- src/include/hictkpy/cooler_file_writer.hpp | 9 ++--- src/include/hictkpy/file.hpp | 10 +++--- src/include/hictkpy/hic_file_writer.hpp | 15 ++++---- src/include/hictkpy/multires_file.hpp | 4 +-- src/include/hictkpy/reference.hpp | 4 ++- src/include/hictkpy/singlecell_file.hpp | 4 +-- src/multires_file.cpp | 13 +++---- src/reference.cpp | 2 +- src/singlecell_file.cpp | 15 +++++--- 12 files changed, 104 insertions(+), 77 deletions(-) diff --git a/src/cooler_file_writer.cpp b/src/cooler_file_writer.cpp index 6324f42..145b734 100644 --- a/src/cooler_file_writer.cpp +++ b/src/cooler_file_writer.cpp @@ -5,6 +5,7 @@ #include "hictkpy/cooler_file_writer.hpp" #include +#include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -28,14 +30,14 @@ namespace nb = nanobind; namespace hictkpy { -CoolerFileWriter::CoolerFileWriter(std::string_view path_, const nb::dict &chromosomes_, +CoolerFileWriter::CoolerFileWriter(std::filesystem::path path_, const ChromosomeDict &chromosomes_, std::uint32_t resolution_, std::string_view assembly, const std::filesystem::path &tmpdir, std::uint32_t compression_lvl) - : _path(std::string{path_}), - _tmpdir(tmpdir / (_path + ".tmp")), - _w(create_file(_path, chromosome_dict_to_reference(chromosomes_), resolution_, assembly, - _tmpdir())), + : _path(std::move(path_)), + _tmpdir(tmpdir / (_path.string() + ".tmp")), + _w(create_file(_path.string(), chromosome_dict_to_reference(chromosomes_), resolution_, + assembly, _tmpdir())), _compression_lvl(compression_lvl) { if (std::filesystem::exists(_path)) { throw std::runtime_error( @@ -43,7 +45,7 @@ CoolerFileWriter::CoolerFileWriter(std::string_view path_, const nb::dict &chrom } } -std::string_view CoolerFileWriter::path() const noexcept { return _path; } +const std::filesystem::path &CoolerFileWriter::path() const noexcept { return _path; } std::uint32_t CoolerFileWriter::resolution() const noexcept { if (_w.has_value()) { @@ -111,7 +113,7 @@ void CoolerFileWriter::serialize([[maybe_unused]] const std::string &log_lvl_str std::visit( [&](const auto &num) { using N = hictk::remove_cvref_t; - _w->aggregate(_path, false, _compression_lvl); + _w->aggregate(_path.string(), false, _compression_lvl); }, _w->open("0").pixel_variant()); @@ -150,11 +152,11 @@ void CoolerFileWriter::bind(nb::module_ &m) { cooler, "FileWriter", "Class representing a file handle to create .cool files."); // NOLINTBEGIN(*-avoid-magic-numbers) - writer.def(nb::init(), + writer.def(nb::init(), nb::arg("path"), nb::arg("chromosomes"), nb::arg("resolution"), nb::arg("assembly") = "unknown", - nb::arg("tmpdir") = std::filesystem::temp_directory_path().string(), + nb::arg("tmpdir") = hictk::internal::TmpDir::default_temp_directory_path(), nb::arg("compression_lvl") = 6, "Open a .cool file for writing."); // NOLINTEND(*-avoid-magic-numbers) diff --git a/src/file.cpp b/src/file.cpp index ef8702e..52a8c28 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -39,23 +40,28 @@ namespace nb = nanobind; namespace hictkpy::file { -void ctor(hictk::File *fp, std::string_view path, std::optional resolution, - std::string_view matrix_type, std::string_view matrix_unit) { - new (fp) hictk::File{std::string{path}, - !!resolution ? static_cast(*resolution) : std::uint32_t{0}, +static void ctor(hictk::File *fp, const std::filesystem::path &path, + std::optional resolution, std::string_view matrix_type, + std::string_view matrix_unit) { + new (fp) hictk::File{path.string(), static_cast(resolution.value_or(0)), hictk::hic::ParseMatrixTypeStr(std::string{matrix_type}), hictk::hic::ParseUnitStr(std::string{matrix_unit})}; } -std::string repr(const hictk::File &f) { return fmt::format(FMT_STRING("File({})"), f.uri()); } +static std::string repr(const hictk::File &f) { + return fmt::format(FMT_STRING("File({})"), f.uri()); +} -bool is_cooler(std::string_view uri) { return bool(hictk::cooler::utils::is_cooler(uri)); } +bool is_cooler(const std::filesystem::path &uri) { + return bool(hictk::cooler::utils::is_cooler(uri.string())); +} -bool is_hic(std::string_view uri) { return hictk::hic::utils::is_hic_file(std::string{uri}); } +bool is_hic(const std::filesystem::path &uri) { return hictk::hic::utils::is_hic_file(uri); } -hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, std::string_view range2, - std::string_view normalization, std::string_view count_type, bool join, - std::string_view query_type) { +static hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, + std::string_view range2, std::string_view normalization, + std::string_view count_type, bool join, + std::string_view query_type) { if (count_type != "float" && count_type != "int") { throw std::runtime_error(R"(count_type should be either "float" or "int")"); } @@ -102,7 +108,7 @@ hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, std: f.get()); } -[[nodiscard]] inline nb::dict get_cooler_attrs(const hictk::cooler::File &clr) { +static nb::dict get_cooler_attrs(const hictk::cooler::File &clr) { nb::dict py_attrs; const auto &attrs = clr.attributes(); @@ -153,7 +159,7 @@ hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, std: return py_attrs; } -[[nodiscard]] inline nb::dict get_hic_attrs(const hictk::hic::File &hf) { +static nb::dict get_hic_attrs(const hictk::hic::File &hf) { nb::dict py_attrs; py_attrs["bin_size"] = hf.resolution(); @@ -167,14 +173,14 @@ hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, std: return py_attrs; } -nb::dict attributes(const hictk::File &f) { +static nb::dict attributes(const hictk::File &f) { if (f.is_cooler()) { return get_cooler_attrs(f.get()); } return get_hic_attrs(f.get()); } -std::vector avail_normalizations(const hictk::File &f) { +static std::vector avail_normalizations(const hictk::File &f) { const auto norms_ = f.avail_normalizations(); std::vector norms{norms_.size()}; std::transform(norms_.begin(), norms_.end(), norms.begin(), @@ -183,13 +189,15 @@ std::vector avail_normalizations(const hictk::File &f) { return norms; } -[[nodiscard]] std::vector weights(const hictk::File &f, std::string_view normalization, - bool divisive) { +static std::vector weights(const hictk::File &f, std::string_view normalization, + bool divisive) { const auto type = divisive ? hictk::balancing::Weights::Type::DIVISIVE : hictk::balancing::Weights::Type::MULTIPLICATIVE; return f.normalization(normalization).to_vector(type); } +static std::filesystem::path get_path(const hictk::File &f) { return f.path(); } + void declare_file_class(nb::module_ &m) { auto file = nb::class_(m, "File", "Class representing a file handle to a .cool or .hic file."); @@ -203,7 +211,7 @@ void declare_file_class(nb::module_ &m) { file.def("__repr__", &file::repr); file.def("uri", &hictk::File::uri, "Return the file URI."); - file.def("path", &hictk::File::path, "Return the file path."); + file.def("path", &file::get_path, "Return the file path."); file.def("is_hic", &hictk::File::is_hic, "Test whether file is in .hic format."); file.def("is_cooler", &hictk::File::is_cooler, "Test whether file is in .cool format."); diff --git a/src/hic_file_writer.cpp b/src/hic_file_writer.cpp index 0d1cb3b..71cc5a8 100644 --- a/src/hic_file_writer.cpp +++ b/src/hic_file_writer.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -25,20 +26,20 @@ namespace nb = nanobind; namespace hictkpy { -HiCFileWriter::HiCFileWriter(std::string_view path, const nb::dict &chromosomes, - const std::vector &resolutions, +HiCFileWriter::HiCFileWriter(const std::filesystem::path &path_, const ChromosomeDict &chromosomes, + const std::vector &resolutions_, std::string_view assembly, std::size_t n_threads, std::size_t chunk_size, const std::filesystem::path &tmpdir, std::uint32_t compression_lvl, bool skip_all_vs_all_matrix) - : _w(path, chromosome_dict_to_reference(chromosomes), resolutions, assembly, n_threads, - chunk_size, tmpdir, compression_lvl, skip_all_vs_all_matrix) {} + : _w(path_.string(), chromosome_dict_to_reference(chromosomes), resolutions_, assembly, + n_threads, chunk_size, tmpdir, compression_lvl, skip_all_vs_all_matrix) {} -HiCFileWriter::HiCFileWriter(std::string_view path, const nb::dict &chromosomes, +HiCFileWriter::HiCFileWriter(const std::filesystem::path &path_, const ChromosomeDict &chromosomes, std::uint32_t resolution, std::string_view assembly, std::size_t n_threads, std::size_t chunk_size, const std::filesystem::path &tmpdir, std::uint32_t compression_lvl, bool skip_all_vs_all_matrix) - : HiCFileWriter(path, chromosomes, std::vector{resolution}, assembly, n_threads, + : HiCFileWriter(path_, chromosomes, std::vector{resolution}, assembly, n_threads, chunk_size, tmpdir, compression_lvl, skip_all_vs_all_matrix) {} void HiCFileWriter::serialize([[maybe_unused]] const std::string &log_lvl_str) { @@ -62,7 +63,9 @@ void HiCFileWriter::serialize([[maybe_unused]] const std::string &log_lvl_str) { #endif } -std::string_view HiCFileWriter::path() const noexcept { return _w.path(); } +std::filesystem::path HiCFileWriter::path() const noexcept { + return std::filesystem::path{_w.path()}; +} const std::vector &HiCFileWriter::resolutions() const noexcept { return _w.resolutions(); @@ -89,23 +92,25 @@ void HiCFileWriter::bind(nb::module_ &m) { hic, "FileWriter", "Class representing a file handle to create .hic files."); // NOLINTBEGIN(*-avoid-magic-numbers) - writer.def(nb::init(), + writer.def(nb::init(), nb::arg("path"), nb::arg("chromosomes"), nb::arg("resolution"), nb::arg("assembly") = "unknown", nb::arg("n_threads") = 1, nb::arg("chunk_size") = 10'000'000, - nb::arg("tmpdir") = std::filesystem::temp_directory_path().string(), + nb::arg("tmpdir") = hictk::internal::TmpDir::default_temp_directory_path(), nb::arg("compression_lvl") = 10, nb::arg("skip_all_vs_all_matrix") = false, "Open a .hic file for writing."); - writer.def( - nb::init &, std::string_view, - std::size_t, std::size_t, const std::filesystem::path &, std::uint32_t, bool>(), - nb::arg("path"), nb::arg("chromosomes"), nb::arg("resolutions"), - nb::arg("assembly") = "unknown", nb::arg("n_threads") = 1, nb::arg("chunk_size") = 10'000'000, - nb::arg("tmpdir") = std::filesystem::temp_directory_path().string(), - nb::arg("compression_lvl") = 10, nb::arg("skip_all_vs_all_matrix") = false, - "Open a .hic file for writing."); + writer.def(nb::init &, std::string_view, std::size_t, + std::size_t, const std::filesystem::path &, std::uint32_t, bool>(), + nb::arg("path"), nb::arg("chromosomes"), nb::arg("resolutions"), + nb::arg("assembly") = "unknown", nb::arg("n_threads") = 1, + nb::arg("chunk_size") = 10'000'000, + nb::arg("tmpdir") = hictk::internal::TmpDir::default_temp_directory_path(), + nb::arg("compression_lvl") = 10, nb::arg("skip_all_vs_all_matrix") = false, + "Open a .hic file for writing."); // NOLINTEND(*-avoid-magic-numbers) writer.def("__repr__", &hictkpy::HiCFileWriter::repr); diff --git a/src/include/hictkpy/cooler_file_writer.hpp b/src/include/hictkpy/cooler_file_writer.hpp index 5960f40..61f0af8 100644 --- a/src/include/hictkpy/cooler_file_writer.hpp +++ b/src/include/hictkpy/cooler_file_writer.hpp @@ -14,11 +14,12 @@ #include #include "hictkpy/nanobind.hpp" +#include "hictkpy/reference.hpp" namespace hictkpy { class CoolerFileWriter { - std::string _path{}; + std::filesystem::path _path{}; hictk::internal::TmpDir _tmpdir{}; std::optional _w{}; std::uint32_t _compression_lvl{}; @@ -26,11 +27,11 @@ class CoolerFileWriter { public: CoolerFileWriter() = delete; - CoolerFileWriter(std::string_view path_, const nanobind::dict& chromosomes_, + CoolerFileWriter(std::filesystem::path path_, const ChromosomeDict& chromosomes_, std::uint32_t resolution_, std::string_view assembly, - const std::filesystem::path& tmpdir, std::uint32_t); + const std::filesystem::path& tmpdir, std::uint32_t compression_lvl); - [[nodiscard]] std::string_view path() const noexcept; + [[nodiscard]] const std::filesystem::path& path() const noexcept; [[nodiscard]] std::uint32_t resolution() const noexcept; [[nodiscard]] const hictk::Reference& chromosomes() const; diff --git a/src/include/hictkpy/file.hpp b/src/include/hictkpy/file.hpp index 512f52e..186df5c 100644 --- a/src/include/hictkpy/file.hpp +++ b/src/include/hictkpy/file.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -15,13 +16,14 @@ #include "hictkpy/pixel_selector.hpp" namespace hictkpy::file { -void ctor(hictk::File *fp, std::string_view path, std::optional resolution, - std::string_view matrix_type, std::string_view matrix_unit); +void ctor(hictk::File *fp, const std::filesystem::path &path, + std::optional resolution, std::string_view matrix_type, + std::string_view matrix_unit); [[nodiscard]] std::string repr(const hictk::File &f); -[[nodiscard]] bool is_cooler(std::string_view uri); -[[nodiscard]] bool is_hic(std::string_view uri); +[[nodiscard]] bool is_cooler(const std::filesystem::path &uri); +[[nodiscard]] bool is_hic(const std::filesystem::path &uri); [[nodiscard]] hictkpy::PixelSelector fetch(const hictk::File &f, std::string_view range1, std::string_view range2, std::string_view normalization, diff --git a/src/include/hictkpy/hic_file_writer.hpp b/src/include/hictkpy/hic_file_writer.hpp index 49860fb..3a755b4 100644 --- a/src/include/hictkpy/hic_file_writer.hpp +++ b/src/include/hictkpy/hic_file_writer.hpp @@ -14,6 +14,7 @@ #include #include "hictkpy/nanobind.hpp" +#include "hictkpy/reference.hpp" namespace hictkpy { @@ -22,16 +23,16 @@ class HiCFileWriter { bool _finalized{false}; public: - HiCFileWriter(std::string_view path, const nanobind::dict& chromosomes, - const std::vector& resolutions, std::string_view assembly, + HiCFileWriter(const std::filesystem::path& path_, const ChromosomeDict& chromosomes, + const std::vector& resolutions_, std::string_view assembly, std::size_t n_threads, std::size_t chunk_size, const std::filesystem::path& tmpdir, std::uint32_t compression_lvl, bool skip_all_vs_all_matrix); - HiCFileWriter(std::string_view path, const nanobind::dict& chromosomes, std::uint32_t resolution, - std::string_view assembly, std::size_t n_threads, std::size_t chunk_size, - const std::filesystem::path& tmpdir, std::uint32_t compression_lvl, - bool skip_all_vs_all_matrix); + HiCFileWriter(const std::filesystem::path& path_, const ChromosomeDict& chromosomes, + std::uint32_t resolution, std::string_view assembly, std::size_t n_threads, + std::size_t chunk_size, const std::filesystem::path& tmpdir, + std::uint32_t compression_lvl, bool skip_all_vs_all_matrix); - [[nodiscard]] std::string_view path() const noexcept; + [[nodiscard]] std::filesystem::path path() const noexcept; [[nodiscard]] const std::vector& resolutions() const noexcept; [[nodiscard]] const hictk::Reference& chromosomes() const; diff --git a/src/include/hictkpy/multires_file.hpp b/src/include/hictkpy/multires_file.hpp index e3b25a6..dbbcefc 100644 --- a/src/include/hictkpy/multires_file.hpp +++ b/src/include/hictkpy/multires_file.hpp @@ -4,13 +4,13 @@ #pragma once -#include +#include #include "hictkpy/nanobind.hpp" namespace hictkpy::multires_file { -[[nodiscard]] bool is_mcool_file(std::string_view path); +[[nodiscard]] bool is_mcool_file(const std::filesystem::path& path); void declare_multires_file_class(nanobind::module_& m); diff --git a/src/include/hictkpy/reference.hpp b/src/include/hictkpy/reference.hpp index 150cc81..0e1dd53 100644 --- a/src/include/hictkpy/reference.hpp +++ b/src/include/hictkpy/reference.hpp @@ -12,7 +12,9 @@ namespace hictkpy { -[[nodiscard]] hictk::Reference chromosome_dict_to_reference(const nanobind::dict &chromosomes); +using ChromosomeDict = nanobind::typed; + +[[nodiscard]] hictk::Reference chromosome_dict_to_reference(const ChromosomeDict &chromosomes); template inline nanobind::typed get_chromosomes_from_file( diff --git a/src/include/hictkpy/singlecell_file.hpp b/src/include/hictkpy/singlecell_file.hpp index 94cc1b1..077ded1 100644 --- a/src/include/hictkpy/singlecell_file.hpp +++ b/src/include/hictkpy/singlecell_file.hpp @@ -4,13 +4,13 @@ #pragma once -#include +#include #include "hictkpy/nanobind.hpp" namespace hictkpy::singlecell_file { -[[nodiscard]] bool is_scool_file(std::string_view path); +[[nodiscard]] bool is_scool_file(const std::filesystem::path& path); void declare_singlecell_file_class(nanobind::module_& m); diff --git a/src/multires_file.cpp b/src/multires_file.cpp index 71fe5a6..a101ecb 100644 --- a/src/multires_file.cpp +++ b/src/multires_file.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include "hictkpy/nanobind.hpp" #include "hictkpy/reference.hpp" @@ -18,16 +17,18 @@ namespace nb = nanobind; namespace hictkpy::multires_file { -static void ctor(hictk::MultiResFile* fp, std::string_view path) { - new (fp) hictk::MultiResFile(std::string{path}); +static void ctor(hictk::MultiResFile* fp, const std::filesystem::path& path) { + new (fp) hictk::MultiResFile{path.string()}; } static std::string repr(const hictk::MultiResFile& mrf) { return fmt::format(FMT_STRING("MultiResFile({})"), mrf.path()); } -bool is_mcool_file(std::string_view path) { - return bool(hictk::cooler::utils::is_multires_file(path)); +static std::filesystem::path get_path(const hictk::MultiResFile& mrf) { return mrf.path(); } + +bool is_mcool_file(const std::filesystem::path& path) { + return bool(hictk::cooler::utils::is_multires_file(path.string())); } void declare_multires_file_class(nb::module_& m) { @@ -38,7 +39,7 @@ void declare_multires_file_class(nb::module_& m) { mres_file.def("__repr__", &multires_file::repr); - mres_file.def("path", &hictk::MultiResFile::path, "Get the file path."); + mres_file.def("path", &multires_file::get_path, "Get the file path."); mres_file.def("chromosomes", &get_chromosomes_from_file, nb::arg("include_all") = false, "Get chromosomes sizes as a dictionary mapping names to sizes."); diff --git a/src/reference.cpp b/src/reference.cpp index a44b5cc..16402db 100644 --- a/src/reference.cpp +++ b/src/reference.cpp @@ -16,7 +16,7 @@ namespace nb = nanobind; namespace hictkpy { -hictk::Reference chromosome_dict_to_reference(const nb::dict& chromosomes) { +hictk::Reference chromosome_dict_to_reference(const ChromosomeDict& chromosomes) { const auto chrom_list = chromosomes.items(); std::vector chrom_names(chrom_list.size()); diff --git a/src/singlecell_file.cpp b/src/singlecell_file.cpp index df6b3b3..eaa2fcb 100644 --- a/src/singlecell_file.cpp +++ b/src/singlecell_file.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -22,18 +23,22 @@ namespace nb = nanobind; namespace hictkpy::singlecell_file { -bool is_scool_file(std::string_view path) { - return bool(hictk::cooler::utils::is_scool_file(path)); +bool is_scool_file(const std::filesystem::path& path) { + return bool(hictk::cooler::utils::is_scool_file(path.string())); } -static void ctor(hictk::cooler::SingleCellFile* fp, std::string_view path) { - new (fp) hictk::cooler::SingleCellFile(std::string{path}); +static void ctor(hictk::cooler::SingleCellFile* fp, const std::filesystem::path& path) { + new (fp) hictk::cooler::SingleCellFile{path}; } static std::string repr(const hictk::cooler::SingleCellFile& sclr) { return fmt::format(FMT_STRING("SingleCellFile({})"), sclr.path()); } +static std::filesystem::path get_path(const hictk::cooler::SingleCellFile& sclr) { + return sclr.path(); +} + static nb::dict get_attrs(const hictk::cooler::SingleCellFile& sclr) { nb::dict py_attrs; @@ -103,7 +108,7 @@ void declare_singlecell_file_class(nb::module_& m) { scell_file.def("__repr__", &singlecell_file::repr); - scell_file.def("path", &hictk::cooler::SingleCellFile::path, "Get the file path."); + scell_file.def("path", &singlecell_file::get_path, "Get the file path."); scell_file.def("resolution", &hictk::cooler::SingleCellFile::resolution, "Get the bin size in bp."); scell_file.def("chromosomes", &get_chromosomes_from_file, From 0e51ba03f4b81a88281e0f10801f02336b94f578 Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:10:29 +0200 Subject: [PATCH 2/4] Update tests --- test/scripts/fuzzer.py | 2 +- test/test_fetch_dense.py | 8 ++++---- test/test_fetch_df.py | 8 ++++---- test/test_fetch_iters.py | 8 ++++---- test/test_fetch_nnz.py | 8 ++++---- test/test_fetch_sparse.py | 8 ++++---- test/test_fetch_sum.py | 8 ++++---- test/test_file_accessors.py | 10 +++++----- test/test_file_creation_cool.py | 12 ++++++------ test/test_file_creation_hic.py | 10 +++++----- test/test_file_validators.py | 12 ++++++------ test/test_multires_file_accessors.py | 6 +++--- test/test_singlecell_file_accessors.py | 6 +++--- 13 files changed, 53 insertions(+), 53 deletions(-) diff --git a/test/scripts/fuzzer.py b/test/scripts/fuzzer.py index fe7de27..276dd80 100755 --- a/test/scripts/fuzzer.py +++ b/test/scripts/fuzzer.py @@ -415,7 +415,7 @@ def worker( clr = cooler.Cooler(str(path_to_reference_file)) sel = clr.matrix(**clr_matrix_args) - f = hictkpy.File(str(path_to_file), resolution) + f = hictkpy.File(path_to_file, resolution) while time.time() < end_time: if early_return.value: diff --git a/test/test_fetch_dense.py b/test/test_fetch_dense.py index c4e5b76..d728174 100644 --- a/test/test_fetch_dense.py +++ b/test/test_fetch_dense.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import numpy as np import pytest @@ -10,13 +10,13 @@ import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_fetch_df.py b/test/test_fetch_df.py index 20247e3..19b61b5 100644 --- a/test/test_fetch_df.py +++ b/test/test_fetch_df.py @@ -2,20 +2,20 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import numpy as np import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_fetch_iters.py b/test/test_fetch_iters.py index be456a2..e3cb757 100644 --- a/test/test_fetch_iters.py +++ b/test/test_fetch_iters.py @@ -2,20 +2,20 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import numpy as np import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_fetch_nnz.py b/test/test_fetch_nnz.py index 873ed5e..0cf0fea 100644 --- a/test/test_fetch_nnz.py +++ b/test/test_fetch_nnz.py @@ -2,19 +2,19 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_fetch_sparse.py b/test/test_fetch_sparse.py index b098356..0cc7f68 100644 --- a/test/test_fetch_sparse.py +++ b/test/test_fetch_sparse.py @@ -2,20 +2,20 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import numpy as np import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_fetch_sum.py b/test/test_fetch_sum.py index 9a5728d..459cb3e 100644 --- a/test/test_fetch_sum.py +++ b/test/test_fetch_sum.py @@ -2,20 +2,20 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_file_accessors.py b/test/test_file_accessors.py index 2fdf3ac..42ddb69 100644 --- a/test/test_file_accessors.py +++ b/test/test_file_accessors.py @@ -2,20 +2,20 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool::/resolutions/100000"), None), - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "cooler_test_file.mcool::/resolutions/100000", None), + (testdir / "data" / "cooler_test_file.mcool", 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) diff --git a/test/test_file_creation_cool.py b/test/test_file_creation_cool.py index ac1a8de..cd23084 100644 --- a/test/test_file_creation_cool.py +++ b/test/test_file_creation_cool.py @@ -3,20 +3,20 @@ # SPDX-License-Identifier: MIT import gc -import os +import pathlib import tempfile import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool"), 100_000), + (testdir / "data" / "cooler_test_file.mcool", 100_000), ], ) @@ -28,7 +28,7 @@ def test_file_creation_thin_pixel(self, file, resolution, tmpdir): df = f.fetch(join=False).to_df() expected_sum = df["count"].sum() - path = os.path.join(tmpdir, "test1.cool") + path = tmpdir / "test1.cool" w = hictkpy.cooler.FileWriter(path, f.chromosomes(), f.resolution()) chunk_size = 1000 @@ -49,7 +49,7 @@ def test_file_creation(self, file, resolution, tmpdir): df = f.fetch(join=True).to_df() expected_sum = df["count"].sum() - path = os.path.join(tmpdir, "test2.cool") + path = tmpdir / "test2.cool" w = hictkpy.cooler.FileWriter(path, f.chromosomes(), f.resolution()) chunk_size = 1000 @@ -71,7 +71,7 @@ def test_file_creation_float_counts(self, file, resolution, tmpdir): df["count"] += 0.12345 expected_sum = df["count"].sum() - path = os.path.join(tmpdir, "test2.cool") + path = tmpdir / "test3.cool" w = hictkpy.cooler.FileWriter(path, f.chromosomes(), f.resolution()) chunk_size = 1000 diff --git a/test/test_file_creation_hic.py b/test/test_file_creation_hic.py index a68b6e1..152ee9b 100644 --- a/test/test_file_creation_hic.py +++ b/test/test_file_creation_hic.py @@ -3,20 +3,20 @@ # SPDX-License-Identifier: MIT import gc -import os +import pathlib import tempfile import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file,resolution", [ - (os.path.join(testdir, "data", "hic_test_file.hic"), 100_000), + (testdir / "data" / "hic_test_file.hic", 100_000), ], ) @@ -28,7 +28,7 @@ def test_file_creation_thin_pixel(self, file, resolution, tmpdir): df = f.fetch(join=False).to_df() expected_sum = df["count"].sum() - path = os.path.join(tmpdir, "test1.hic") + path = tmpdir / "test1.hic" w = hictkpy.hic.FileWriter(path, f.chromosomes(), f.resolution()) chunk_size = 1000 @@ -49,7 +49,7 @@ def test_file_creation(self, file, resolution, tmpdir): df = f.fetch(join=True).to_df() expected_sum = df["count"].sum() - path = os.path.join(tmpdir, "test2.hic") + path = tmpdir / "test2.hic" w = hictkpy.hic.FileWriter(path, f.chromosomes(), f.resolution()) chunk_size = 1000 diff --git a/test/test_file_validators.py b/test/test_file_validators.py index dab7688..26cdfab 100644 --- a/test/test_file_validators.py +++ b/test/test_file_validators.py @@ -2,18 +2,18 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent -cool_file = os.path.join(testdir, "data", "cooler_test_file.mcool::/resolutions/100000") -mcool_file = os.path.join(testdir, "data", "cooler_test_file.mcool") -scool_file = os.path.join(testdir, "data", "cooler_test_file.scool") -hic_file = os.path.join(testdir, "data", "hic_test_file.hic") +cool_file = testdir / "data" / "cooler_test_file.mcool::/resolutions/100000" +mcool_file = testdir / "data" / "cooler_test_file.mcool" +scool_file = testdir / "data" / "cooler_test_file.scool" +hic_file = testdir / "data" / "hic_test_file.hic" class TestClass: diff --git a/test/test_multires_file_accessors.py b/test/test_multires_file_accessors.py index 0124dd7..707fe7f 100644 --- a/test/test_multires_file_accessors.py +++ b/test/test_multires_file_accessors.py @@ -2,18 +2,18 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file", [ - (os.path.join(testdir, "data", "cooler_test_file.mcool")), + testdir / "data" / "cooler_test_file.mcool", ], ) diff --git a/test/test_singlecell_file_accessors.py b/test/test_singlecell_file_accessors.py index 95d060e..f48f1c4 100644 --- a/test/test_singlecell_file_accessors.py +++ b/test/test_singlecell_file_accessors.py @@ -2,18 +2,18 @@ # # SPDX-License-Identifier: MIT -import os +import pathlib import pytest import hictkpy -testdir = os.path.dirname(os.path.abspath(__file__)) +testdir = pathlib.Path(__file__).resolve().parent pytestmark = pytest.mark.parametrize( "file", [ - (os.path.join(testdir, "data", "cooler_test_file.scool")), + testdir / "data" / "cooler_test_file.scool", ], ) From ae53ef22a2a903a61fb0db7b1eef49f203ad9d39 Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:57:46 +0200 Subject: [PATCH 3/4] Fix tests on Windows --- test/test_file_accessors.py | 2 +- test/test_file_validators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_file_accessors.py b/test/test_file_accessors.py index 42ddb69..7d9c393 100644 --- a/test/test_file_accessors.py +++ b/test/test_file_accessors.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.parametrize( "file,resolution", [ - (testdir / "data" / "cooler_test_file.mcool::/resolutions/100000", None), + ((testdir / "data" / "cooler_test_file.mcool::/resolutions/100000").as_posix(), None), (testdir / "data" / "cooler_test_file.mcool", 100_000), (testdir / "data" / "hic_test_file.hic", 100_000), ], diff --git a/test/test_file_validators.py b/test/test_file_validators.py index 26cdfab..a1ad54e 100644 --- a/test/test_file_validators.py +++ b/test/test_file_validators.py @@ -10,7 +10,7 @@ testdir = pathlib.Path(__file__).resolve().parent -cool_file = testdir / "data" / "cooler_test_file.mcool::/resolutions/100000" +cool_file = (testdir / "data" / "cooler_test_file.mcool::/resolutions/100000").as_posix() mcool_file = testdir / "data" / "cooler_test_file.mcool" scool_file = testdir / "data" / "cooler_test_file.scool" hic_file = testdir / "data" / "hic_test_file.hic" From e0632703f581fb97a103e0a0f5512e2025fbae8f Mon Sep 17 00:00:00 2001 From: Roberto Rossini <71787608+robomics@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:57:11 +0200 Subject: [PATCH 4/4] Add build-conan-deps.yml workflow [no ci] --- .github/workflows/build-conan-deps.yml | 460 +++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 .github/workflows/build-conan-deps.yml diff --git a/.github/workflows/build-conan-deps.yml b/.github/workflows/build-conan-deps.yml new file mode 100644 index 0000000..bee0b07 --- /dev/null +++ b/.github/workflows/build-conan-deps.yml @@ -0,0 +1,460 @@ +# Copyright (C) 2024 Roberto Rossini +# SPDX-License-Identifier: MIT + +name: Build dependencies with Conan + +on: + workflow_call: + outputs: + conan-key: + description: "Conan packages" + value: ${{ jobs.collect-outputs.outputs.conan-key }} + conan-home: + description: "Conan home folder" + value: ${{ jobs.collect-outputs.outputs.conan-home }} + + inputs: + conan-version: + default: "2.8.*" + type: string + required: false + description: "Conan version to be installed with pip." + cppstd: + default: "17" + type: string + required: false + description: "Value to pass to compiler.cppstd." + os: + type: string + required: true + description: "OS used to build Conan deps." + arch: + type: string + required: true + description: "Architecture used to build deps." + +defaults: + run: + shell: bash + +jobs: + build-deps-linux: + if: contains(inputs.os, 'linux') + name: Build dependencies with Conan (${{ inputs.os }}) + runs-on: ubuntu-latest + + env: + CONAN_HOME: "${{ github.workspace }}/.conan2/" + IMAGE: quay.io/pypa/manylinux_2_28_${{ inputs.arch }} + + outputs: + conan-key: ${{ steps.generate-cache-key.outputs.conan-key }} + conan-home: ${{ env.CONAN_HOME }} + + steps: + - name: Checkout workflow + uses: actions/checkout@v4 + with: + ref: main + path: hictkpy-workflow + + - name: Checkout conanfile.py + uses: actions/checkout@v4 + with: + path: hictkpy-conanfile + + - name: Stage input files + run: | + # This is required because sparse cloning does not seem to work reliably + mkdir -p .github/workflows/ + mv hictkpy-workflow/.github/workflows/build-conan-deps.yml .github/workflows/ + mv hictkpy-conanfile/conanfile.py . + rm -rf hictkpy-workflow hictkpy-conanfile + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Generate cache key + id: generate-cache-key + run: | + cat << 'EOF' | tee script.sh > /dev/null + #!usr/bin/env bash + set -u + set -e + + hash="${{ hashFiles('.github/workflows/build-conan-deps.yml', 'conanfile.py') }}" + + compiler="$(cc --version | head -n 1 | tr -c '[:alnum:]._-' '-' | sed 's/-\+/-/g' | sed 's/-$//')" + + suffix="${{ inputs.os }}-${{ inputs.arch }}-$compiler-c++${{ inputs.cppstd }}-$hash" + + echo "conan-key=conan-$suffix" + EOF + + chmod 755 script.sh + + docker run \ + -v "$PWD/script.sh:/tmp/script.sh:ro" \ + "$IMAGE" /tmp/script.sh | tee -a "$GITHUB_OUTPUT" + + - name: Restore package cache + id: cache-conan + uses: actions/cache/restore@v4 + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}/p + + - name: Clean Conan cache (pre-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + cat << 'EOF' | tee script.sh > /dev/null + #!usr/bin/env bash + set -u + set -e + + conan_version="$1" + + PATH="/opt/python/cp312-cp312/bin:$PATH" + pip install "conan==$conan_version" + conan profile detect --force + + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + conan remove --confirm "*" + EOF + + chmod 755 script.sh + + docker run \ + -e "CONAN_HOME=$CONAN_HOME" \ + -v "$PWD/script.sh:/tmp/script.sh:ro" \ + -v "$CONAN_HOME:$CONAN_HOME" \ + "$IMAGE" /tmp/script.sh '${{ inputs.conan-version }}' + + - name: Install dependencies + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + cat << 'EOF' | tee script.sh > /dev/null + #!usr/bin/env bash + set -u + set -e + + conan_version="$1" + cppstd="$2" + + PATH="/opt/python/cp312-cp312/bin:$PATH" + pip install "conan==$conan_version" + conan profile detect --force + + conan install /tmp/conanfile.py \ + --update \ + --build='missing' \ + --build='b2/*' \ + -pr:b=default \ + -pr:h=default \ + -s build_type=Release \ + -s compiler.cppstd="$cppstd" \ + --options=*/*:shared=False + EOF + + chmod 755 script.sh + + docker run \ + -e "CONAN_HOME=$CONAN_HOME" \ + -v "$PWD/script.sh:/tmp/script.sh:ro" \ + -v "$PWD/conanfile.py:/tmp/conanfile.py:ro" \ + -v "$CONAN_HOME:$CONAN_HOME" \ + "$IMAGE" /tmp/script.sh '${{ inputs.conan-version }}' '${{ inputs.cppstd }}' + + - name: Clean Conan cache (post-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + cat << 'EOF' | tee script.sh > /dev/null + #!usr/bin/env bash + set -u + set -e + + conan_version="$1" + + PATH="/opt/python/cp312-cp312/bin:$PATH" + pip install "conan==$conan_version" + conan profile detect --force + + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + EOF + + chmod 755 script.sh + + docker run \ + -e "CONAN_HOME=$CONAN_HOME" \ + -v "$PWD/script.sh:/tmp/script.sh:ro" \ + -v "$CONAN_HOME:$CONAN_HOME" \ + "$IMAGE" /tmp/script.sh '${{ inputs.conan-version }}' + + - name: Save Conan cache + uses: actions/cache/save@v4 + if: steps.cache-conan.outputs.cache-hit != 'true' + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}/p + env: + ZSTD_CLEVEL: 19 + + build-deps-macos: + if: startsWith(inputs.os, 'macos') + name: Build dependencies with Conan (${{ inputs.os }}) + runs-on: ${{ inputs.os }} + + env: + CONAN_HOME: "${{ github.workspace }}/.conan2" + HOMEBREW_NO_AUTO_UPDATE: "1" + + outputs: + conan-key: ${{ steps.generate-cache-key.outputs.conan-key }} + conan-home: ${{ env.CONAN_HOME }} + + steps: + - name: Checkout workflow + uses: actions/checkout@v4 + with: + ref: main + path: hictkpy-workflow + + - name: Checkout conanfile.py + uses: actions/checkout@v4 + with: + path: hictkpy-conanfile + + - name: Stage input files + run: | + # This is required because sparse cloning does not seem to work reliably + mkdir -p .github/workflows/ + mv hictkpy-workflow/.github/workflows/build-conan-deps.yml .github/workflows/ + mv hictkpy-conanfile/conanfile.py . + rm -rf hictkpy-workflow hictkpy-conanfile + + - name: Generate cache key + id: generate-cache-key + run: | + set -u + set -e + + hash="${{ hashFiles('.github/workflows/build-conan-deps.yml', 'conanfile.py') }}" + + compiler="$(cc --version | head -n 1 | tr -c '[:alnum:]._-' '-' | sed 's/-\+/-/g' | sed 's/-$//')" + + suffix="${{ inputs.os }}-${{ inputs.arch }}-$compiler-c++${{ inputs.cppstd }}-$hash" + + echo "conan-key=conan-$suffix" | tee -a "$GITHUB_OUTPUT" + + - name: Restore package cache + id: cache-conan + uses: actions/cache/restore@v4 + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}/p + + - uses: actions/setup-python@v5 + if: steps.cache-conan.outputs.cache-hit != 'true' + with: + python-version: "3.12" + + - name: Update build deps + if: steps.cache-conan.outputs.cache-hit != 'true' + run: pip install "conan==${{ inputs.conan-version }}" + + - name: Configure Conan + if: steps.cache-conan.outputs.cache-hit != 'true' + run: conan profile detect --force + + - name: Clean Conan cache (pre-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + conan remove --confirm "*" + + - name: Install dependencies + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan install conanfile.py \ + --update \ + --build='missing' \ + --build='b2/*' \ + -pr:b=default \ + -pr:h=default \ + -s build_type=Release \ + -s compiler.libcxx=libc++ \ + -s compiler.cppstd=${{ inputs.cppstd }} \ + --options=*/*:shared=False + + - name: Clean Conan cache (post-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + + - name: Save Conan cache + uses: actions/cache/save@v4 + if: steps.cache-conan.outputs.cache-hit != 'true' + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}/p + env: + ZSTD_CLEVEL: 19 + + build-deps-windows: + if: startsWith(inputs.os, 'windows') + name: Build dependencies with Conan (${{ inputs.os }}) + runs-on: ${{ inputs.os }} + + env: + CONAN_HOME: "${{ github.workspace }}\\.conan2" + + outputs: + conan-key: ${{ steps.generate-cache-key.outputs.conan-key }} + conan-home: ${{ env.CONAN_HOME }} + + steps: + - name: Checkout workflow + uses: actions/checkout@v4 + with: + ref: main + path: hictkpy-workflow + + - name: Checkout conanfile.py + uses: actions/checkout@v4 + with: + path: hictkpy-conanfile + + - name: Stage input files + run: | + # This is required because sparse cloning does not seem to work reliably + mkdir -p .github/workflows/ + mv hictkpy-workflow/.github/workflows/build-conan-deps.yml .github/workflows/ + mv hictkpy-conanfile/conanfile.py . + rm -rf hictkpy-workflow hictkpy-conanfile + + - name: Add devtools to PATH + uses: ilammy/msvc-dev-cmd@v1 + + - name: Generate cache key + id: generate-cache-key + run: | + set -u + set -e + + hash="${{ hashFiles('.github/workflows/build-conan-deps.yml', 'conanfile.py') }}" + + cl.exe 1> /dev/null 2> version.txt + compiler="msvc-$(head -n 1 version.txt | grep -o '[[:digit:].]\+' | head -n 1)" + + suffix="${{ inputs.os }}-${{ inputs.arch }}-$compiler-c++${{ inputs.cppstd }}-$hash" + + echo "conan-key=conan-$suffix" | tee -a "$GITHUB_OUTPUT" + + - name: Restore package cache + id: cache-conan + uses: actions/cache/restore@v4 + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}\p + + - uses: actions/setup-python@v5 + if: steps.cache-conan.outputs.cache-hit != 'true' + with: + python-version: "3.12" + + - name: Update build deps + if: steps.cache-conan.outputs.cache-hit != 'true' + run: pip install "conan==${{ inputs.conan-version }}" + + - name: Configure Conan + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan profile detect --force + conan_profile="$(conan profile path default)" + + sed -i 's/compiler\.cppstd=.*/compiler.cppstd=${{ inputs.cppstd }}/' "$conan_profile" + + - name: Clean Conan cache (pre-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + conan remove --confirm "*" + + - name: Install dependencies + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan install conanfile.py \ + --update \ + --build='missing' \ + --build='b2/*' \ + -pr:b=default \ + -pr:h=default \ + -s build_type=Release \ + -s compiler.runtime_type=Release \ + -s compiler.cppstd=${{ inputs.cppstd }} \ + --options=*/*:shared=False + + - name: Clean Conan cache (post-build) + if: steps.cache-conan.outputs.cache-hit != 'true' + run: | + conan cache clean "*" --build + conan cache clean "*" --download + conan cache clean "*" --source + + - name: Save Conan cache + uses: actions/cache/save@v4 + if: steps.cache-conan.outputs.cache-hit != 'true' + with: + key: ${{ steps.generate-cache-key.outputs.conan-key }} + path: ${{ env.CONAN_HOME }}\p + env: + ZSTD_CLEVEL: 19 + + collect-outputs: + name: Collect output + runs-on: ubuntu-latest + if: always() + needs: + - build-deps-macos + - build-deps-linux + - build-deps-windows + + outputs: + conan-key: ${{ steps.collect-cache-key.outputs.conan-key }} + conan-home: ${{ steps.collect-cache-key.outputs.conan-home }} + + steps: + - name: Collect job outputs + id: collect-cache-key + run: | + if [ "${{ needs.build-deps-linux.result }}" == 'success' ]; then + echo "conan-key=${{ needs.build-deps-linux.outputs.conan-key }}" | tee -a "$GITHUB_OUTPUT" + echo "conan-home=${{ needs.build-deps-linux.outputs.conan-home }}" | tee -a "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ needs.build-deps-macos.result }}" == 'success' ]; then + echo "conan-key=${{ needs.build-deps-macos.outputs.conan-key }}" | tee -a "$GITHUB_OUTPUT" + echo "conan-home=${{ needs.build-deps-macos.outputs.conan-home }}" | tee -a "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ needs.build-deps-windows.result }}" == 'success' ]; then + echo "conan-key=${{ needs.build-deps-windows.outputs.conan-key }}" | tee -a "$GITHUB_OUTPUT" + echo "conan-home=${{ needs.build-deps-windows.outputs.conan-home }}" | tee -a "$GITHUB_OUTPUT" + exit 0 + fi + + exit 1