From 8e99db34ab4e6b9b73423b748bc76774df8a5aa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Mar 2025 06:39:33 +0000 Subject: [PATCH 01/44] Bump iqtree2 from `1320c4c` to `39cbee5` Bumps [iqtree2](https://github.com/iqtree/iqtree2) from `1320c4c` to `39cbee5`. - [Release notes](https://github.com/iqtree/iqtree2/releases) - [Commits](https://github.com/iqtree/iqtree2/compare/1320c4cc360bbacacc592911459aaa06cd4a7961...39cbee547c4bfc6d2e78f063b4beb9095e15f812) --- updated-dependencies: - dependency-name: iqtree2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- iqtree2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iqtree2 b/iqtree2 index 1320c4c..39cbee5 160000 --- a/iqtree2 +++ b/iqtree2 @@ -1 +1 @@ -Subproject commit 1320c4cc360bbacacc592911459aaa06cd4a7961 +Subproject commit 39cbee547c4bfc6d2e78f063b4beb9095e15f812 From 6911d6a28c2116a326f075ce31932a416c8653e5 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:14:59 +1100 Subject: [PATCH 02/44] DEV: ignore dll files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 87df248..0ea37ac 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ # piqtree specific ignores src/piqtree/_libiqtree/**/*.a +src/piqtree/_libiqtree/**/*.dll +src/piqtree/_libiqtree/**/*.lib +src/*.dll # docs data From 52dab316bdcd14bdf96f02722677ac3e4296333e Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:16:40 +1100 Subject: [PATCH 03/44] DEV: update libiqtree function signatures and add custom type convertors for structs --- src/piqtree/_libiqtree/_piqtree.cpp | 158 ++++++++++++++-------------- src/piqtree/_libiqtree/_piqtree.h | 113 ++++++++++++++++++++ 2 files changed, 194 insertions(+), 77 deletions(-) create mode 100644 src/piqtree/_libiqtree/_piqtree.h diff --git a/src/piqtree/_libiqtree/_piqtree.cpp b/src/piqtree/_libiqtree/_piqtree.cpp index 52210fe..52697f8 100644 --- a/src/piqtree/_libiqtree/_piqtree.cpp +++ b/src/piqtree/_libiqtree/_piqtree.cpp @@ -1,3 +1,5 @@ +#include "_piqtree.h" +#include #include #include #include @@ -8,82 +10,84 @@ using namespace std; namespace py = pybind11; -/* - * Calculates the robinson fould distance between two trees - */ -extern int robinson_fould(const string& tree1, const string& tree2); - -/* - * Generates a set of random phylogenetic trees - * tree_gen_mode allows:"YULE_HARDING", "UNIFORM", "CATERPILLAR", "BALANCED", - * "BIRTH_DEATH", "STAR_TREE" output: a newick tree (in string format) - */ -extern string random_tree(int num_taxa, - string tree_gen_mode, - int num_trees, - int rand_seed = 0); - -/* - * Perform phylogenetic analysis on the input alignment - * With estimation of the best topology - * output: results in YAML format with the tree and the details of parameters - */ -extern string build_tree(vector& names, - vector& seqs, - string model, - int rand_seed = 0, - int bootstrap_rep = 0, - int num_thres = 1); - -/* - * Perform phylogenetic analysis on the input alignment - * With restriction to the input toplogy - * output: results in YAML format with the details of parameters - */ -extern string fit_tree(vector& names, - vector& seqs, - string model, - string intree, - int rand_seed = 0, - int num_thres = 1); - -/* - * Perform phylogenetic analysis with ModelFinder - * on the input alignment (in string format) - * model_set -- a set of models to consider - * freq_set -- a set of frequency types - * rate_set -- a set of RHAS models - * rand_seed -- random seed, if 0, then will generate a new random seed - * output: modelfinder results in YAML format - */ -extern string modelfinder(vector& names, - vector& seqs, - int rand_seed = 0, - string model_set = "", - string freq_set = "", - string rate_set = "", - int num_thres = 1); - -/* - * Build pairwise JC distance matrix - * output: set of distances - * (n * i + j)-th element of the list represents the distance between i-th and - * j-th sequence, where n is the number of sequences - */ -extern vector build_distmatrix(vector& names, - vector& seqs, - int num_thres); - -/* - * Using Rapid-NJ to build tree from a distance matrix - * output: a newick tree (in string format) - */ -extern string build_njtree(vector& names, vector& distances); - -/* - * verion number - */ -extern string version(); +namespace PYBIND11_NAMESPACE { +namespace detail { +template <> +struct type_caster { + public: + PYBIND11_TYPE_CASTER(StringArray, const_name("StringArray")); + + // Conversion from Python to C++ + bool load(handle src, bool) { + /* Extract PyObject from handle */ + PyObject* source = src.ptr(); + if (!py::isinstance(source)) { + return false; + } + + auto seq = reinterpret_borrow(src); + value.length = seq.size(); + + tmpStrings.reserve(value.length); + tmpCStrs.reserve(value.length); + + for (size_t i = 0; i < seq.size(); ++i) { + auto item = seq[i]; + if (!py::isinstance(item)) { + return false; + } + + tmpStrings.push_back(item.cast()); + tmpCStrs.push_back(tmpStrings[i].c_str()); + } + + value.strings = tmpCStrs.data(); + + return true; + } + + // Conversion from C++ to Python + static handle cast(StringArray src, return_value_policy, handle) { + throw std::runtime_error("Unsupported operation"); + } + + private: + vector tmpStrings; + vector tmpCStrs; +}; + +template <> +struct type_caster { + public: + PYBIND11_TYPE_CASTER(DoubleArray, _("DoubleArray")); + + // Conversion from Python to C++ + bool load(handle src, bool) { + if (!py::isinstance>(src)) { + return false; // Only accept numpy arrays of float64 + } + + auto arr = py::cast>(src); + if (arr.ndim() != 1) { + return false; // Only accept 1D arrays + } + + value.length = arr.size(); + value.doubles = new double[value.length]; + std::memcpy(value.doubles, arr.data(), value.length * sizeof(double)); + return true; + } + + // Conversion from C++ to Python + static handle cast(DoubleArray src, return_value_policy, handle) { + auto result = py::array_t(src.length); + std::memcpy(result.mutable_data(), src.doubles, + src.length * sizeof(double)); + return result.release(); + } +}; +} // namespace detail +} // namespace PYBIND11_NAMESPACE int mine() { return 42; @@ -112,5 +116,5 @@ PYBIND11_MODULE(_piqtree, m) { "Construct pairwise distance matrix for alignment."); m.def("iq_nj_tree", &build_njtree, "Build neighbour-joining tree from distance matrix."); - m.def("mine", &mine, "The meaning of life, the universe (and everything)!"); + m.def("mine", &mine, "The meaning of life, the universe (and everything) !"); } diff --git a/src/piqtree/_libiqtree/_piqtree.h b/src/piqtree/_libiqtree/_piqtree.h new file mode 100644 index 0000000..ed77708 --- /dev/null +++ b/src/piqtree/_libiqtree/_piqtree.h @@ -0,0 +1,113 @@ +#ifndef _PIQTREE_H +#define _PIQTREE_H + +using namespace std; + +#ifdef _MSC_VER +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +typedef struct { + const char** strings; + size_t length; +} StringArray; + +typedef struct { + double* doubles; + size_t length; +} DoubleArray; + +#ifdef _MSC_VER +#pragma pack(pop) +#else +#pragma pack() +#endif + +/* + * Calculates the robinson fould distance between two trees + */ +extern "C" int robinson_fould(const char* ctree1, const char* ctree2); + +/* + * Generates a set of random phylogenetic trees + * tree_gen_mode allows:"YULE_HARDING", "UNIFORM", "CATERPILLAR", "BALANCED", + * "BIRTH_DEATH", "STAR_TREE" output: a newick tree (in string format) + */ +extern "C" char* random_tree(int num_taxa, + const char* tree_gen_mode, + int num_trees, + int rand_seed = 0); + +/* + * Perform phylogenetic analysis on the input alignment + * With estimation of the best topology + * num_thres -- number of cpu threads to be used, default: 1; 0 - auto detection + * of the optimal number of cpu threads output: results in YAML format with the + * tree and the details of parameters + */ +extern "C" char* build_tree(StringArray& names, + StringArray& seqs, + const char* model, + int rand_seed = 0, + int bootstrap_rep = 0, + int num_thres = 1); + +/* + * Perform phylogenetic analysis on the input alignment + * With restriction to the input toplogy + * num_thres -- number of cpu threads to be used, default: 1; 0 - auto detection + * of the optimal number of cpu threads output: results in YAML format with the + * details of parameters + */ +extern "C" char* fit_tree(StringArray& names, + StringArray& seqs, + const char* model, + const char* intree, + int rand_seed = 0, + int num_thres = 1); + +/* + * Perform phylogenetic analysis with ModelFinder + * on the input alignment (in string format) + * model_set -- a set of models to consider + * freq_set -- a set of frequency types + * rate_set -- a set of RHAS models + * rand_seed -- random seed, if 0, then will generate a new random seed + * num_thres -- number of cpu threads to be used, default: 1; 0 - auto detection + * of the optimal number of cpu threads output: modelfinder results in YAML + * format + */ +extern "C" char* modelfinder(StringArray& names, + StringArray& seqs, + int rand_seed = 0, + const char* model_set = "", + const char* freq_set = "", + const char* rate_set = "", + int num_thres = 1); + +/* + * Build pairwise JC distance matrix + * output: set of distances + * (n * i + j)-th element of the list represents the distance between i-th and + * j-th sequence, where n is the number of sequences num_thres -- number of cpu + * threads to be used, default: 1; 0 - use all available cpu threads on the + * machine + */ +extern "C" DoubleArray build_distmatrix(StringArray& names, + StringArray& seqs, + int num_thres = 1); + +/* + * Using Rapid-NJ to build tree from a distance matrix + * output: a newick tree (in string format) + */ +extern "C" char* build_njtree(StringArray& names, DoubleArray& distances); + +/* + * verion number + */ +extern "C" char* version(); + +#endif /* LIBIQTREE2_FUN */ From 971bb3ecab355f66aa31fa545584e132e95e06ab Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:17:48 +1100 Subject: [PATCH 04/44] DEV: ensure DLLs are included in build on windows --- pyproject.toml | 7 +++- setup.py | 80 ++++++++++++++++++++++++++++++++--------- src/piqtree/__init__.py | 13 +++++++ 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 28f4cb7..1987151 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 61.0", "pybind11 >= 2.12"] +requires = ["setuptools >= 61.0", "pybind11 >= 2.12", "delvewheel >= 1.10"] build-backend = "setuptools.build_meta" [project] @@ -152,6 +152,11 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" "src/piqtree/_app/__init__.py" = [ "N801", # apps follow function naming convention ] +"src/piqtree/__init__.py" = [ + "E402", # handle DLLs before imports + "PTH118", # os operations for DLL path + "PTH120", # os operations for DLL path +] "src/piqtree/model/_substitution_model.py" = ["N815"] # use IQ-TREE naming scheme [tool.ruff.format] diff --git a/setup.py b/setup.py index fcd22a3..62a6b4c 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext from setuptools import setup -LIBRARY_DIR = "src/piqtree/_libiqtree" +LIBRARY_DIR = "src/piqtree/_libiqtree/" +IQTREE_LIB_NAME = "iqtree2" def get_brew_prefix(package: str) -> Path: @@ -18,7 +19,36 @@ def get_brew_prefix(package: str) -> Path: ) -if platform.system() == "Darwin": +extra_libs = [] +extra_compile_args = [] +include_dirs = [] +library_dirs = [] + + +def include_dlls() -> None: + import shutil + + from delvewheel._dll_utils import get_all_needed + + needed_dll_paths, _, _, _ = get_all_needed( + LIBRARY_DIR + f"{IQTREE_LIB_NAME}.dll", + set(), + None, + "raise", + False, # noqa: FBT003 + False, # noqa: FBT003 + 0, + ) + + for dll_path in needed_dll_paths: + shutil.copy(dll_path, LIBRARY_DIR) + + +def setup_windows() -> None: + include_dlls() + + +def setup_macos() -> None: brew_prefix_llvm = get_brew_prefix("llvm") brew_prefix_libomp = get_brew_prefix("libomp") @@ -27,20 +57,34 @@ def get_brew_prefix(package: str) -> Path: os.environ["CXX"] = str(brew_prefix_llvm / "bin" / "clang++") # Define OpenMP flags and libraries for macOS - openmp_flags = ["-Xpreprocessor", "-fopenmp"] - openmp_libs = ["omp"] + extra_compile_args.extend(["-Xpreprocessor", "-fopenmp"]) + extra_libs.extend(["z", "omp"]) # Use the paths from Homebrew for libomp - openmp_include = str(brew_prefix_libomp / "include") - library_dirs = [ - str(brew_prefix_libomp / "lib"), - str(brew_prefix_llvm / "lib"), - ] -else: - openmp_flags = ["-fopenmp"] - openmp_libs = ["gomp"] - openmp_include = None - library_dirs = [] + include_dirs.extend([str(brew_prefix_libomp / "include")]) + library_dirs.extend( + [ + str(brew_prefix_libomp / "lib"), + str(brew_prefix_llvm / "lib"), + ], + ) + + +def setup_linux() -> None: + extra_compile_args.extend(["-fopenmp"]) + extra_libs.extend(["z", "gomp"]) + + +match system := platform.system(): + case "Windows": + setup_windows() + case "Darwin": + setup_macos() + case "Linux": + setup_linux() + case _: + msg = f"Unsupported platform: {system}" + raise ValueError(msg) ext_modules = [ Pybind11Extension( @@ -50,14 +94,16 @@ def get_brew_prefix(package: str) -> Path: *library_dirs, LIBRARY_DIR, ], - libraries=["iqtree2", "z", *openmp_libs], - extra_compile_args=openmp_flags, - include_dirs=[openmp_include] if openmp_include else [], + libraries=[IQTREE_LIB_NAME, *extra_libs], + extra_compile_args=extra_compile_args, + include_dirs=include_dirs, ), ] setup( + name="piqtree", ext_modules=ext_modules, cmdclass={"build_ext": build_ext}, zip_safe=False, + package_data={"piqtree": ["_libiqtree/*.dll"]}, ) diff --git a/src/piqtree/__init__.py b/src/piqtree/__init__.py index 5461c95..d5392db 100644 --- a/src/piqtree/__init__.py +++ b/src/piqtree/__init__.py @@ -1,5 +1,18 @@ """piqtree - access the power of IQ-TREE within Python.""" + +def _add_dll_path() -> None: + import os + + if "add_dll_directory" in dir(os): + dll_folder = os.path.join(os.path.dirname(__file__), "_libiqtree") + os.add_dll_directory(dll_folder) + + +_add_dll_path() +del _add_dll_path + + from _piqtree import __iqtree_version__ from piqtree._data import dataset_names, download_dataset From 4af4b53802a56336e5c4c9150f4c24ac9d2c83ee Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:18:25 +1100 Subject: [PATCH 05/44] BUG: on windows, change out of tmp directory before cleaning up --- src/piqtree/iqtree/_decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/piqtree/iqtree/_decorator.py b/src/piqtree/iqtree/_decorator.py index d7f4fd6..4d59e10 100644 --- a/src/piqtree/iqtree/_decorator.py +++ b/src/piqtree/iqtree/_decorator.py @@ -84,7 +84,7 @@ def wrapper_iqtree_func(*args: Param.args, **kwargs: Param.kwargs) -> RetType: os.close(devnull_fd) if hide_files: - tempdir.cleanup() os.chdir(original_dir) + tempdir.cleanup() return wrapper_iqtree_func From 48dcde49fcc0eb699a0ac69dd0e0b0e2d3a79014 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:18:56 +1100 Subject: [PATCH 06/44] TST: skip tests which throw error on IQ-TREE side on windows --- tests/test_iqtree/test_build_tree.py | 5 +++++ tests/test_iqtree/test_random_trees.py | 6 ++++++ tests/test_iqtree/test_segmentation_fault.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/tests/test_iqtree/test_build_tree.py b/tests/test_iqtree/test_build_tree.py index 90cbf8a..d8232d1 100644 --- a/tests/test_iqtree/test_build_tree.py +++ b/tests/test_iqtree/test_build_tree.py @@ -1,3 +1,4 @@ +import platform import re import pytest @@ -87,6 +88,10 @@ def test_rate_model_build_tree( ) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="IQ-TREE errors can't be caught yet on Windows", +) def test_build_tree_inadequate_bootstrapping(four_otu: ArrayAlignment) -> None: with pytest.raises(IqTreeError, match=re.escape("#replicates must be >= 1000")): piqtree.build_tree(four_otu, Model(DnaModel.GTR), bootstrap_replicates=10) diff --git a/tests/test_iqtree/test_random_trees.py b/tests/test_iqtree/test_random_trees.py index 60c09ce..82ce8f4 100644 --- a/tests/test_iqtree/test_random_trees.py +++ b/tests/test_iqtree/test_random_trees.py @@ -1,3 +1,5 @@ +import platform + import pytest import piqtree @@ -45,6 +47,10 @@ def test_random_trees_no_seed( @pytest.mark.parametrize("num_taxa", [-1, 0, 1, 2]) @pytest.mark.parametrize("tree_mode", list(piqtree.TreeGenMode)) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="IQ-TREE errors can't be caught yet on Windows", +) def test_invalid_taxa( num_taxa: int, tree_mode: piqtree.TreeGenMode, diff --git a/tests/test_iqtree/test_segmentation_fault.py b/tests/test_iqtree/test_segmentation_fault.py index 701cc40..1fb5857 100644 --- a/tests/test_iqtree/test_segmentation_fault.py +++ b/tests/test_iqtree/test_segmentation_fault.py @@ -1,5 +1,7 @@ """Test combinations of calls which under previous versions resulted in a segmentation fault.""" +import platform + import pytest from cogent3 import make_aligned_seqs, make_tree @@ -8,6 +10,10 @@ from piqtree.model import DiscreteGammaModel, DnaModel, FreeRateModel, Model +@pytest.mark.skipif( + platform.system() == "Windows", + reason="IQ-TREE errors can't be caught yet on Windows", +) def test_two_build_random_trees() -> None: """ Calling build tree twice followed by random trees with a bad input @@ -22,6 +28,10 @@ def test_two_build_random_trees() -> None: random_trees(3, 2, TreeGenMode.BALANCED, 1) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="IQ-TREE errors can't be caught yet on Windows", +) def test_two_fit_random_trees() -> None: """ Calling fit tree twice followed by random trees with a bad input @@ -39,6 +49,10 @@ def test_two_fit_random_trees() -> None: @pytest.mark.parametrize("rate_model_class", [DiscreteGammaModel, FreeRateModel]) @pytest.mark.parametrize("categories", [0, -4]) +@pytest.mark.skipif( + platform.system() == "Windows", + reason="IQ-TREE errors can't be caught yet on Windows", +) def test_two_invalid_models( rate_model_class: type[DiscreteGammaModel] | type[FreeRateModel], categories: int, From b2273f67bf45aaff0f3db9b5c4ef922929538e2f Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:21:38 +1100 Subject: [PATCH 07/44] ENH: include cstddef for other platforms --- src/piqtree/_libiqtree/_piqtree.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/piqtree/_libiqtree/_piqtree.h b/src/piqtree/_libiqtree/_piqtree.h index ed77708..070c621 100644 --- a/src/piqtree/_libiqtree/_piqtree.h +++ b/src/piqtree/_libiqtree/_piqtree.h @@ -1,6 +1,8 @@ #ifndef _PIQTREE_H #define _PIQTREE_H +#include + using namespace std; #ifdef _MSC_VER From fbe6bba6c8fdaf54f8e26b207296494d0aa1b2d6 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Wed, 19 Mar 2025 18:25:48 +1100 Subject: [PATCH 08/44] MAINT: ignore attr-defined typing error for non-windows --- src/piqtree/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/piqtree/__init__.py b/src/piqtree/__init__.py index d5392db..5a91c95 100644 --- a/src/piqtree/__init__.py +++ b/src/piqtree/__init__.py @@ -6,7 +6,7 @@ def _add_dll_path() -> None: if "add_dll_directory" in dir(os): dll_folder = os.path.join(os.path.dirname(__file__), "_libiqtree") - os.add_dll_directory(dll_folder) + os.add_dll_directory(dll_folder) # type: ignore[attr-defined] _add_dll_path() From e814a74172dc15552f6e429a159368c073ff20a5 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:06:34 +1100 Subject: [PATCH 09/44] DEV: add windows build scripts --- build_tools/before_all_windows.sh | 10 ++++++++++ build_tools/build_iqtree.sh | 19 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 build_tools/before_all_windows.sh diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh new file mode 100644 index 0000000..4eac69f --- /dev/null +++ b/build_tools/before_all_windows.sh @@ -0,0 +1,10 @@ +# Install dependencies using choco +choco install -y llvm eigen boost libomp make + +# Set environment variables for LLVM +export CMAKE_C_COMPILER="clang" +export CMAKE_CXX_COMPILER="clang++" +export CMAKE_MAKE_PROGRAM="make" + +# Build IQ-TREE +bash build_tools/build_iqtree.sh \ No newline at end of file diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index 38bb7af..e504878 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -9,11 +9,26 @@ if [[ "$OSTYPE" == "darwin"* ]]; then echo $CXXFLAGS cmake -DBUILD_LIB=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .. gmake -j +elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then + echo "Building for Windows." + cmake -G "MinGW Makefiles" \ + -DCMAKE_C_COMPILER=$CMAKE_C_COMPILER \ + -DCMAKE_CXX_COMPILER=$CMAKE_CXX_COMPILER \ + -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ + -DBUILD_LIB=ON \ + .. + make -j else - echo "Building for linux." + echo "Building for Linux." cmake -DBUILD_LIB=ON .. make -j fi cd ../.. -mv iqtree2/build/libiqtree2.a src/piqtree/_libiqtree/ \ No newline at end of file + +if [[ "$OSTYPE" == "darwin"* || "$OSTYPE" == "linux"* ]]; then + mv iqtree2/build/libiqtree2.a src/piqtree/_libiqtree/ +elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then + mv iqtree2/build/iqtree2.lib src/piqtree/_libiqtree/ + mv iqtree2/build/iqtree2.dll src/piqtree/_libiqtree/ +fi From 40196d3d75468648f80b729d5fbabfd5c46b55fa Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:07:41 +1100 Subject: [PATCH 10/44] DEV: build windows when building iqtree and cache correct files. --- .github/actions/build-iqtree/action.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 6ce0372..d0fc1b7 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -23,7 +23,8 @@ runs: id: cache with: key: libiqtree-${{ inputs.os }}-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} - path: src/piqtree/_libiqtree/libiqtree2.a + path: | + ${{ inputs.os == 'windows-latest' && 'src/piqtree/_libiqtree/iqtree2.lib\nsrc/piqtree/_libiqtree/iqtree2.dll' || 'src/piqtree/_libiqtree/libiqtree2.a' }} lookup-only: true - name: Build IQ-TREE @@ -31,7 +32,12 @@ runs: if: steps.cache.outputs.cache-hit != 'true' run: | if [[ "${{ inputs.os }}" == "ubuntu-latest" ]]; then - sudo ./build_tools/before_all_linux.sh + sudo ./build_tools/before_all_linux.sh + elif [[ "${{ inputs.os }}" == "macOS-latest" ]]; then + ./build_tools/before_all_mac.sh + elif [[ "${{ inputs.os }}" == "windows-latest" ]]; then + ./build_tools/before_all_windows.sh else - ./build_tools/before_all_mac.sh - fi + echo "Unrecognized OS: '${{ inputs.os }}'." + exit 1 + fi \ No newline at end of file From 5e65f8a304e6a2da5ce54855829b79847b88f12b Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:07:52 +1100 Subject: [PATCH 11/44] DEV: test build process on windows --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71e763b..e795b24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-13, macos-14] # Intel linux, Intel Mac, ARM Mac + os: [windows-latest] + # os: [ubuntu-latest, macos-13, macos-14, windows-latest] # Intel linux, Intel Mac, ARM Mac, Windows steps: - uses: "actions/checkout@v4" From 29f586f64e211b36eeee5b3904a5ed5b739e2909 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:17:25 +1100 Subject: [PATCH 12/44] DEV: install boost through action on windows, remove non-available choco packages --- .github/actions/build-iqtree/action.yml | 9 +++++++++ build_tools/before_all_windows.sh | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index d0fc1b7..23c8e6f 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -27,6 +27,15 @@ runs: ${{ inputs.os == 'windows-latest' && 'src/piqtree/_libiqtree/iqtree2.lib\nsrc/piqtree/_libiqtree/iqtree2.dll' || 'src/piqtree/_libiqtree/libiqtree2.a' }} lookup-only: true + - name: Install Boost + if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' + uses: MarkusJx/install-boost@v2.4.5 + id: install-boost + with: + boost_version: 1.84.0 + platform_version: 2022 + toolset: mingw + - name: Build IQ-TREE shell: bash if: steps.cache.outputs.cache-hit != 'true' diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 4eac69f..6cff446 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,5 +1,5 @@ # Install dependencies using choco -choco install -y llvm eigen boost libomp make +choco install -y llvm eigen make # Set environment variables for LLVM export CMAKE_C_COMPILER="clang" From 6d7cf298e2185a9f4bde73e7b305a17af83ca95e Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:27:12 +1100 Subject: [PATCH 13/44] DEV: set boost env variables --- .github/actions/build-iqtree/action.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 23c8e6f..5e03685 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -36,6 +36,13 @@ runs: platform_version: 2022 toolset: mingw + - name: Set Boost Environment Variables + if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' + shell: bash + run: | + echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" + echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + - name: Build IQ-TREE shell: bash if: steps.cache.outputs.cache-hit != 'true' From 7792886b3df7160aaff4c7b3065a8925481b7974 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:33:27 +1100 Subject: [PATCH 14/44] DEV: debug boost env vars --- .github/actions/build-iqtree/action.yml | 9 +++++++++ build_tools/before_all_windows.sh | 5 +++++ build_tools/build_iqtree.sh | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 5e03685..e016c0c 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -42,6 +42,15 @@ runs: run: | echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + + - name: Debug Boost Variables + if: runner.os == 'Windows' + shell: bash + run: | + echo "BOOST_ROOT=$BOOST_ROOT" + echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" + echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" + echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" - name: Build IQ-TREE shell: bash diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 6cff446..8bf7885 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,3 +1,8 @@ +echo "BOOST_ROOT=$BOOST_ROOT" +echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" +echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" +echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" + # Install dependencies using choco choco install -y llvm eigen make diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index e504878..6ae2ee8 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -1,3 +1,8 @@ +echo "BOOST_ROOT=$BOOST_ROOT" +echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" +echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" +echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" + cd iqtree2 rm -rf build mkdir build && cd build From 14cef0a7bec4b321c1c6cada59eda8433afe5262 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:42:52 +1100 Subject: [PATCH 15/44] DEV: removed path debugs, manually specify boost dirs --- .github/actions/build-iqtree/action.yml | 9 --------- build_tools/before_all_windows.sh | 5 ----- build_tools/build_iqtree.sh | 7 ++----- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index e016c0c..5e03685 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -42,15 +42,6 @@ runs: run: | echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - - - name: Debug Boost Variables - if: runner.os == 'Windows' - shell: bash - run: | - echo "BOOST_ROOT=$BOOST_ROOT" - echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" - echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" - echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" - name: Build IQ-TREE shell: bash diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 8bf7885..6cff446 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,8 +1,3 @@ -echo "BOOST_ROOT=$BOOST_ROOT" -echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" -echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" -echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" - # Install dependencies using choco choco install -y llvm eigen make diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index 6ae2ee8..d1d882c 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -1,8 +1,3 @@ -echo "BOOST_ROOT=$BOOST_ROOT" -echo "Boost_INCLUDE_DIR=$Boost_INCLUDE_DIR" -echo "Boost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS" -echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" - cd iqtree2 rm -rf build mkdir build && cd build @@ -20,6 +15,8 @@ elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then -DCMAKE_C_COMPILER=$CMAKE_C_COMPILER \ -DCMAKE_CXX_COMPILER=$CMAKE_CXX_COMPILER \ -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ + -DBoost_INCLUDE_DIR=$Boost_INCLUDE_DIR \ + -DBoost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS \ -DBUILD_LIB=ON \ .. make -j From d570951d78982ab0f680b19b13102e5c8c3fe294 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 14:50:42 +1100 Subject: [PATCH 16/44] DEV: manually specify toolchain, downgrade llvm --- build_tools/before_all_windows.sh | 8 ++------ build_tools/build_iqtree.sh | 9 ++++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 6cff446..763d3b2 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,10 +1,6 @@ # Install dependencies using choco -choco install -y llvm eigen make - -# Set environment variables for LLVM -export CMAKE_C_COMPILER="clang" -export CMAKE_CXX_COMPILER="clang++" -export CMAKE_MAKE_PROGRAM="make" +choco install -y llvm --version=14.0.6 --allow-downgrade +choco install -y eigen # Build IQ-TREE bash build_tools/build_iqtree.sh \ No newline at end of file diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index d1d882c..60a36af 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -12,11 +12,14 @@ if [[ "$OSTYPE" == "darwin"* ]]; then elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then echo "Building for Windows." cmake -G "MinGW Makefiles" \ - -DCMAKE_C_COMPILER=$CMAKE_C_COMPILER \ - -DCMAKE_CXX_COMPILER=$CMAKE_CXX_COMPILER \ - -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_C_FLAGS=--target=x86_64-pc-windows-gnu \ + -DCMAKE_CXX_FLAGS=--target=x86_64-pc-windows-gnu \ + -DCMAKE_MAKE_PROGRAM=mingw32-make \ -DBoost_INCLUDE_DIR=$Boost_INCLUDE_DIR \ -DBoost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS \ + -DIQTREE_FLAGS="cpp14" \ -DBUILD_LIB=ON \ .. make -j From fca8540924b618a30654d5a0a9d0e783cbd7f5c6 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:07:32 +1100 Subject: [PATCH 17/44] DEV: add lib.exe to path --- .github/actions/build-iqtree/action.yml | 17 +++++++++++++++++ .github/workflows/ci.yml | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 5e03685..da23170 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -43,6 +43,23 @@ runs: echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + - name: Set Up MSVC for lib.exe + if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' + shell: bash + run: | + MSVC_BIN_PATH="C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\MSVC" + MSVC_VERSION=$(ls "$MSVC_BIN_PATH" | sort -V | tail -n 1) + LIB_PATH="$MSVC_BIN_PATH\\$MSVC_VERSION\\bin\\Hostx64\\x64" + + echo "Adding $LIB_PATH to PATH" + echo "PATH=$LIB_PATH:$PATH" >> "$GITHUB_ENV" + + - name: Verify lib.exe + if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' + shell: bash + run: | + which lib.exe || echo "lib.exe not found" + - name: Build IQ-TREE shell: bash if: steps.cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e795b24..bd46719 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-13, macos-14] # Intel linux, Intel Mac, ARM Mac + os: [windows-latest] + # os: [ubuntu-latest, macos-13, macos-14] # Intel linux, Intel Mac, ARM Mac python-version: ["3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v4" From 688e589b60064ac2e8045343f55cc4548a17bbce Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:10:35 +1100 Subject: [PATCH 18/44] DEV: debug msvc path --- .github/actions/build-iqtree/action.yml | 29 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index da23170..a4a53fd 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -47,12 +47,29 @@ runs: if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' shell: bash run: | - MSVC_BIN_PATH="C:\\Program Files\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\MSVC" - MSVC_VERSION=$(ls "$MSVC_BIN_PATH" | sort -V | tail -n 1) - LIB_PATH="$MSVC_BIN_PATH\\$MSVC_VERSION\\bin\\Hostx64\\x64" - - echo "Adding $LIB_PATH to PATH" - echo "PATH=$LIB_PATH:$PATH" >> "$GITHUB_ENV" + # Check default VS installation paths + VSWHERE_PATH="/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" + + if [[ -f "$VSWHERE_PATH" ]]; then + echo "Finding latest MSVC installation..." + + MSVC_PATH=$("$VSWHERE_PATH" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) + + if [[ -d "$MSVC_PATH" ]]; then + MSVC_BIN_PATH="$MSVC_PATH/VC/Tools/MSVC" + MSVC_VERSION=$(ls "$MSVC_BIN_PATH" | sort -V | tail -n 1) + LIB_PATH="$MSVC_BIN_PATH/$MSVC_VERSION/bin/Hostx64/x64" + + echo "MSVC found at: $LIB_PATH" + echo "PATH=$LIB_PATH:$PATH" >> "$GITHUB_ENV" + else + echo "MSVC installation not found." + exit 1 + fi + else + echo "vswhere.exe not found, unable to locate MSVC." + exit 1 + fi - name: Verify lib.exe if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' From d1e4d6194926d12ed8d3012143bd5f3bd1fd537d Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:13:35 +1100 Subject: [PATCH 19/44] DEV: try running wswhere through cmd --- .github/actions/build-iqtree/action.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index a4a53fd..788adc0 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -43,6 +43,12 @@ runs: echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + - name: Debug MSVC Installation + if: runner.os == 'Windows' + shell: cmd + run: | + "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -all + - name: Set Up MSVC for lib.exe if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' shell: bash From a189060481f0d57ad0eca1661ed49445b17c8ce5 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:20:41 +1100 Subject: [PATCH 20/44] DEV: try configuring MSVC --- .github/actions/build-iqtree/action.yml | 34 +++---------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 788adc0..5801e2c 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -43,45 +43,19 @@ runs: echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - - name: Debug MSVC Installation - if: runner.os == 'Windows' - shell: cmd - run: | - "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -all - - - name: Set Up MSVC for lib.exe + - name: Setup Visual Studio Environment if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' shell: bash run: | - # Check default VS installation paths - VSWHERE_PATH="/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" - - if [[ -f "$VSWHERE_PATH" ]]; then - echo "Finding latest MSVC installation..." - - MSVC_PATH=$("$VSWHERE_PATH" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath) - - if [[ -d "$MSVC_PATH" ]]; then - MSVC_BIN_PATH="$MSVC_PATH/VC/Tools/MSVC" - MSVC_VERSION=$(ls "$MSVC_BIN_PATH" | sort -V | tail -n 1) - LIB_PATH="$MSVC_BIN_PATH/$MSVC_VERSION/bin/Hostx64/x64" - - echo "MSVC found at: $LIB_PATH" - echo "PATH=$LIB_PATH:$PATH" >> "$GITHUB_ENV" - else - echo "MSVC installation not found." - exit 1 - fi - else - echo "vswhere.exe not found, unable to locate MSVC." - exit 1 - fi + source "C:/Program Files (x86)/Microsoft Visual Studio/2022/Enterprise/Common7/Tools/VsDevCmd.bat" + echo "Visual Studio environment configured" - name: Verify lib.exe if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' shell: bash run: | which lib.exe || echo "lib.exe not found" + exit 1 - name: Build IQ-TREE shell: bash From 26acfa1063177f4c5acc74b028ab15a4ab745ac1 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:27:21 +1100 Subject: [PATCH 21/44] DEV: use cmd to setup VS env --- .github/actions/build-iqtree/action.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 5801e2c..6cacd21 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -44,10 +44,9 @@ runs: echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - name: Setup Visual Studio Environment - if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' - shell: bash + shell: cmd run: | - source "C:/Program Files (x86)/Microsoft Visual Studio/2022/Enterprise/Common7/Tools/VsDevCmd.bat" + "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" echo "Visual Studio environment configured" - name: Verify lib.exe From b6d38b591441369290cad25c874d234607c94760 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:32:58 +1100 Subject: [PATCH 22/44] DEV: use action to setup msvc --- .github/actions/build-iqtree/action.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 6cacd21..694c98b 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -43,11 +43,9 @@ runs: echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - - name: Setup Visual Studio Environment - shell: cmd - run: | - "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" - echo "Visual Studio environment configured" + - name: Setup MSVC Developer Command Prompt + if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' + uses: ilammy/msvc-dev-cmd@v1 - name: Verify lib.exe if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' From d52c8b4ee6fb571aed732bd5c63aa2fc9d0c5e5d Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 15:38:50 +1100 Subject: [PATCH 23/44] DEV: continue build process with found lib --- .github/actions/build-iqtree/action.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 694c98b..b252ecd 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -47,13 +47,6 @@ runs: if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' uses: ilammy/msvc-dev-cmd@v1 - - name: Verify lib.exe - if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' - shell: bash - run: | - which lib.exe || echo "lib.exe not found" - exit 1 - - name: Build IQ-TREE shell: bash if: steps.cache.outputs.cache-hit != 'true' From 25611ac1d56f7025d6f7e16bb5ebcf0087acd512 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 16:05:02 +1100 Subject: [PATCH 24/44] DEV: only brew install on mac --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd46719..292f26d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: cache-key: libiqtree-${{ matrix.os }}-${{ needs.build-iqtree.outputs.iqtree2-sha }} - name: Install llvm - if: matrix.os != 'ubuntu-latest' + if: runner.os == 'macOS' run: | brew install llvm From ecb84ea96e495850484d3e8c00647eb4c5a95a67 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 16:05:32 +1100 Subject: [PATCH 25/44] DEV: use different caches on unix/windows --- .github/actions/build-iqtree/action.yml | 29 +++++++++++++++++++++--- .github/actions/setup-piqtree/action.yml | 25 ++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index b252ecd..f0ca238 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -19,14 +19,37 @@ runs: IQ_TREE_2_SHA=$(git rev-parse HEAD) echo "iqtree2-sha=${IQ_TREE_2_SHA}" >> "$GITHUB_OUTPUT" - - uses: actions/cache@v4 - id: cache + - name: Cache IQ-TREE 2 (Windows) + if: runner.os == 'Windows' + uses: actions/cache@v4 + id: cache-windows + with: + key: libiqtree-windows-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + path: | + src/piqtree/_libiqtree/iqtree2.lib + src/piqtree/_libiqtree/iqtree2.dll + lookup-only: true + + - name: Cache IQ-TREE 2 (Linux/macOS) + if: runner.os != 'Windows' + uses: actions/cache@v4 + id: cache-unix with: key: libiqtree-${{ inputs.os }}-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} path: | - ${{ inputs.os == 'windows-latest' && 'src/piqtree/_libiqtree/iqtree2.lib\nsrc/piqtree/_libiqtree/iqtree2.dll' || 'src/piqtree/_libiqtree/libiqtree2.a' }} + src/piqtree/_libiqtree/libiqtree2.a lookup-only: true + - name: Combine Cache Hits + id: cache + shell: bash + run: | + if [[ "${{ steps.cache-windows.outputs.cache-hit }}" == 'true' || "${{ steps.cache-unix.outputs.cache-hit }}" == 'true' ]]; then + echo "cache-hit=true" >> "$GITHUB_OUTPUT" + else + echo "cache-hit=false" >> "$GITHUB_OUTPUT" + fi + - name: Install Boost if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' uses: MarkusJx/install-boost@v2.4.5 diff --git a/.github/actions/setup-piqtree/action.yml b/.github/actions/setup-piqtree/action.yml index 00ae89b..01dffc5 100644 --- a/.github/actions/setup-piqtree/action.yml +++ b/.github/actions/setup-piqtree/action.yml @@ -13,9 +13,24 @@ runs: - uses: "actions/setup-python@v5" with: python-version: ${{ inputs.python-version }} - - - uses: actions/cache/restore@v4 + + - name: Cache IQ-TREE 2 (Windows) + if: runner.os == 'Windows' + uses: actions/cache/restore@v4 + id: cache-windows with: - key: ${{ inputs.cache-key }} - path: src/piqtree/_libiqtree/libiqtree2.a - fail-on-cache-miss: true \ No newline at end of file + key: libiqtree-windows-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + path: | + src/piqtree/_libiqtree/iqtree2.lib + src/piqtree/_libiqtree/iqtree2.dll + fail-on-cache-miss: true + + - name: Cache IQ-TREE 2 (Linux/macOS) + if: runner.os != 'Windows' + uses: actions/cache/restore@v4 + id: cache-unix + with: + key: libiqtree-${{ inputs.os }}-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + path: | + src/piqtree/_libiqtree/libiqtree2.a + fail-on-cache-miss: true From fa7083dd49a837e70ef11be560135024cab0cbd6 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 16:48:39 +1100 Subject: [PATCH 26/44] DEV: Install llvm when testing windows ci, make build_iqtree compatible on local windows --- .github/workflows/ci.yml | 7 ++++++- build_tools/build_iqtree.sh | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 292f26d..4fce96f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,10 +50,15 @@ jobs: python-version: ${{ matrix.python-version }} cache-key: libiqtree-${{ matrix.os }}-${{ needs.build-iqtree.outputs.iqtree2-sha }} - - name: Install llvm + - name: Install llvm (macOS) if: runner.os == 'macOS' run: | brew install llvm + + - name: Install llvm (Windows) + if: runner.os == 'Windows' + run: | + choco install -y llvm --version=14.0.6 --allow-downgrade - name: Run Nox Testing run: | diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index 60a36af..5aedccd 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -11,12 +11,18 @@ if [[ "$OSTYPE" == "darwin"* ]]; then gmake -j elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then echo "Building for Windows." + + if [[ -n "$BOOST_ROOT" ]]; then + export Boost_INCLUDE_DIR="${BOOST_ROOT}" + export Boost_LIBRARY_DIRS="${BOOST_ROOT}" + fi + cmake -G "MinGW Makefiles" \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_FLAGS=--target=x86_64-pc-windows-gnu \ -DCMAKE_CXX_FLAGS=--target=x86_64-pc-windows-gnu \ - -DCMAKE_MAKE_PROGRAM=mingw32-make \ + -DCMAKE_MAKE_PROGRAM=make \ -DBoost_INCLUDE_DIR=$Boost_INCLUDE_DIR \ -DBoost_LIBRARY_DIRS=$Boost_LIBRARY_DIRS \ -DIQTREE_FLAGS="cpp14" \ From 289e9df11b07519ee89a1d06fe37fb6f02f5007b Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 17:17:17 +1100 Subject: [PATCH 27/44] DEV: re-enable full CI matrix --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fce96f..acf5078 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest] - # os: [ubuntu-latest, macos-13, macos-14, windows-latest] # Intel linux, Intel Mac, ARM Mac, Windows + os: [ubuntu-latest, macos-13, macos-14, windows-latest] # Intel linux, Intel Mac, ARM Mac, Windows steps: - uses: "actions/checkout@v4" @@ -36,8 +35,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest] - # os: [ubuntu-latest, macos-13, macos-14] # Intel linux, Intel Mac, ARM Mac + os: [ubuntu-latest, macos-13, macos-14, windows-latest] # Intel linux, Intel Mac, ARM Mac, Windows python-version: ["3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v4" From 002f74e5f2608a6bda692e0507f9f393694d16e7 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 17:22:55 +1100 Subject: [PATCH 28/44] DEV: use runner.os for choosing before_all script --- .github/actions/build-iqtree/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index f0ca238..0a26c3a 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -74,11 +74,11 @@ runs: shell: bash if: steps.cache.outputs.cache-hit != 'true' run: | - if [[ "${{ inputs.os }}" == "ubuntu-latest" ]]; then + if [[ "${{ runner.os }}" == "Linux" ]]; then sudo ./build_tools/before_all_linux.sh - elif [[ "${{ inputs.os }}" == "macOS-latest" ]]; then + elif [[ "${{ runner.os }}" == "macOS" ]]; then ./build_tools/before_all_mac.sh - elif [[ "${{ inputs.os }}" == "windows-latest" ]]; then + elif [[ "${{ runner.os }}" == "Windows" ]]; then ./build_tools/before_all_windows.sh else echo "Unrecognized OS: '${{ inputs.os }}'." From fec36ab4f54a91450ea75b3ff93e4ccbd8e8178e Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 17:40:11 +1100 Subject: [PATCH 29/44] DEV: standardise cache names --- .github/actions/build-iqtree/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-iqtree/action.yml b/.github/actions/build-iqtree/action.yml index 0a26c3a..9f45763 100644 --- a/.github/actions/build-iqtree/action.yml +++ b/.github/actions/build-iqtree/action.yml @@ -24,7 +24,7 @@ runs: uses: actions/cache@v4 id: cache-windows with: - key: libiqtree-windows-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + key: libiqtree-${{ inputs.os }}-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} path: | src/piqtree/_libiqtree/iqtree2.lib src/piqtree/_libiqtree/iqtree2.dll From 2d0f3b2cf6071d521211631b01e39d095db43b79 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 17:40:25 +1100 Subject: [PATCH 30/44] DEV: use correct input for cache key --- .github/actions/setup-piqtree/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-piqtree/action.yml b/.github/actions/setup-piqtree/action.yml index 01dffc5..fb72ec8 100644 --- a/.github/actions/setup-piqtree/action.yml +++ b/.github/actions/setup-piqtree/action.yml @@ -19,7 +19,7 @@ runs: uses: actions/cache/restore@v4 id: cache-windows with: - key: libiqtree-windows-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + key: ${{ inputs.cache-key }} path: | src/piqtree/_libiqtree/iqtree2.lib src/piqtree/_libiqtree/iqtree2.dll @@ -30,7 +30,7 @@ runs: uses: actions/cache/restore@v4 id: cache-unix with: - key: libiqtree-${{ inputs.os }}-${{ steps.iqtree2-sha.outputs.iqtree2-sha }} + key: ${{ inputs.cache-key }} path: | src/piqtree/_libiqtree/libiqtree2.a fail-on-cache-miss: true From 631298f4f406c0d817ebe94e1023d521657885c3 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 17:56:51 +1100 Subject: [PATCH 31/44] DEV: add windows to cibuildwheel --- .github/workflows/build_wheels.yml | 30 ++++++++++++++++++++++++-- .github/workflows/release.yml | 34 +++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 66dc081..8b7537a 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: include: - # manylinux (x86) + # manylinux x86_64 - os: ubuntu-latest platform_id: manylinux_x86_64 @@ -22,6 +22,10 @@ jobs: - os: macos-14 platform_id: macosx_arm64 + # Windows x86_64 + - os: windows-latest + platform_id: win_amd64 + steps: - uses: "actions/checkout@v4" with: @@ -35,7 +39,7 @@ jobs: platforms: arm64 - name: Set macOS Deployment Target - if: ${{startsWith(matrix.os, 'macos')}} + if: runner.os == 'macOS' run: | if [[ "${{ matrix.os }}" == "macos-13" ]]; then echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> $GITHUB_ENV @@ -43,13 +47,35 @@ jobs: echo "MACOSX_DEPLOYMENT_TARGET=14.0" >> $GITHUB_ENV fi + - name: Install Boost + if: runner.os == 'Windows' + uses: MarkusJx/install-boost@v2.4.5 + id: install-boost + with: + boost_version: 1.84.0 + platform_version: 2022 + toolset: mingw + + - name: Set Boost Environment Variables + if: runner.os == 'Windows' + shell: bash + run: | + echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" + echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + + - name: Setup MSVC Developer Command Prompt + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + - name: Build wheels uses: pypa/cibuildwheel@v2.23.0 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh + CIBW_BEFORE_ALL_WINDOWS: ./build_tools/before_all_windows.sh CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} + CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} CIBW_BUILD: "*${{matrix.platform_id}}" CIBW_TEST_REQUIRES: pytest CIBW_TEST_COMMAND: pytest {package}/tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17158bc..15c5a8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: include: - # manylinux (x86) + # manylinux x86_64 - os: ubuntu-latest platform_id: manylinux_x86_64 @@ -22,6 +22,10 @@ jobs: - os: macos-14 platform_id: macosx_arm64 + # Windows x86_64 + - os: windows-latest + platform_id: win_amd64 + steps: - uses: "actions/checkout@v4" with: @@ -35,23 +39,47 @@ jobs: platforms: arm64 - name: Set macOS Deployment Target - if: ${{startsWith(matrix.os, 'macos')}} + if: runner.os == 'macOS' run: | if [[ "${{ matrix.os }}" == "macos-13" ]]; then echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> $GITHUB_ENV elif [[ "${{ matrix.os }}" == "macos-14" ]]; then echo "MACOSX_DEPLOYMENT_TARGET=14.0" >> $GITHUB_ENV fi - + + - name: Install Boost + if: runner.os == 'Windows' + uses: MarkusJx/install-boost@v2.4.5 + id: install-boost + with: + boost_version: 1.84.0 + platform_version: 2022 + toolset: mingw + + - name: Set Boost Environment Variables + if: runner.os == 'Windows' + shell: bash + run: | + echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" + echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + + - name: Setup MSVC Developer Command Prompt + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + - name: Build wheels uses: pypa/cibuildwheel@v2.23.0 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh + CIBW_BEFORE_ALL_WINDOWS: ./build_tools/before_all_windows.sh CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} + CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} + CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} CIBW_BUILD: "*${{matrix.platform_id}}" CIBW_TEST_REQUIRES: pytest CIBW_TEST_COMMAND: pytest {package}/tests + CIBW_TEST_SKIP: "*-macosx_universal2:x86_64" # skip x86 on m1 mac CIBW_SKIP: pp* # Disable building PyPy wheels on all platforms - name: Upload wheels From 19b06487c7bdb9fd289adf65a9517c5bd0b0d583 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 18:08:42 +1100 Subject: [PATCH 32/44] DEV: run before_all_windows script with bash --- .github/workflows/build_wheels.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 8b7537a..c078cd7 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -72,7 +72,7 @@ jobs: env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh - CIBW_BEFORE_ALL_WINDOWS: ./build_tools/before_all_windows.sh + CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15c5a8e..4b9fc0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh - CIBW_BEFORE_ALL_WINDOWS: ./build_tools/before_all_windows.sh + CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} From 2c221e444a8ac8d4c4fa610f2d1cbe3a3ec467f5 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 18:32:37 +1100 Subject: [PATCH 33/44] DEV: debug BOOST env vars --- .github/workflows/release.yml | 1 + build_tools/before_all_windows.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b9fc0a..2158af1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,6 +73,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh + CIBW_ENVIRONMENT_WINDOWS: BOOST_ROOT='${{ steps.install-boost.outputs.BOOST_ROOT }}' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 763d3b2..5f81b9a 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,4 +1,9 @@ # Install dependencies using choco + +echo "Boost_INCLUDE_DIR: " $Boost_INCLUDE_DIR +echo "Boost_LIBRARY_DIRS: " $Boost_LIBRARY_DIRS +echo "BOOST_ROOT: " $BOOST_ROOT + choco install -y llvm --version=14.0.6 --allow-downgrade choco install -y eigen From 0914a962d8e1c9557c4900e81f089cbe6e5a772c Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 18:45:55 +1100 Subject: [PATCH 34/44] DEV: attempt hardcoding boost paths --- .github/workflows/build_wheels.yml | 1 + .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index c078cd7..41903b1 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -73,6 +73,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/include' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2158af1..ff52508 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: BOOST_ROOT='${{ steps.install-boost.outputs.BOOST_ROOT }}' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/include' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} From 060b659e57773c7a7a20a2b92d73c7e8fff93c39 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 18:50:21 +1100 Subject: [PATCH 35/44] DEV: fix BOOST lib path --- .github/workflows/build_wheels.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 41903b1..28856ed 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -73,7 +73,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/include' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff52508..6ae48e8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/include' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} From d9673dea2fb76f7f5862761ce428e14d5fb1ec86 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 18:53:32 +1100 Subject: [PATCH 36/44] DEV: remove hardcoding of BOOST paths --- .github/workflows/build_wheels.yml | 9 +-------- .github/workflows/release.yml | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 28856ed..a967611 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -56,13 +56,6 @@ jobs: platform_version: 2022 toolset: mingw - - name: Set Boost Environment Variables - if: runner.os == 'Windows' - shell: bash - run: | - echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" - echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -73,7 +66,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include"' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ae48e8..89e4c5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,13 +56,6 @@ jobs: platform_version: 2022 toolset: mingw - - name: Set Boost Environment Variables - if: runner.os == 'Windows' - shell: bash - run: | - echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" - echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -73,7 +66,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include"' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} From a4d9bd192f130d8344654de8cb0f09f91557aaff Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 19:04:23 +1100 Subject: [PATCH 37/44] DEV: hardcode paths again --- .github/workflows/build_wheels.yml | 11 ++++++++++- .github/workflows/release.yml | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index a967611..f89bea4 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -56,6 +56,15 @@ jobs: platform_version: 2022 toolset: mingw + - name: Set Boost Environment Variables + if: runner.os == 'Windows' + shell: bash + run: | + echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" + echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + echo $Boost_INCLUDE_DIR + echo $Boost_LIBRARY_DIRS + - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -66,7 +75,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include"' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89e4c5d..09657f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,6 +56,15 @@ jobs: platform_version: 2022 toolset: mingw + - name: Set Boost Environment Variables + if: runner.os == 'Windows' + shell: bash + run: | + echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" + echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" + echo $Boost_INCLUDE_DIR + echo $Boost_LIBRARY_DIRS + - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -66,7 +75,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include"' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} From a8e8476349aa4705586101a77f3d4da941f2fce4 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 19:16:15 +1100 Subject: [PATCH 38/44] DEV: normalise slashes in before_all_windows --- .github/workflows/build_wheels.yml | 11 +---------- .github/workflows/release.yml | 11 +---------- build_tools/before_all_windows.sh | 8 +++++--- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index f89bea4..5d56787 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -56,15 +56,6 @@ jobs: platform_version: 2022 toolset: mingw - - name: Set Boost Environment Variables - if: runner.os == 'Windows' - shell: bash - run: | - echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" - echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - echo $Boost_INCLUDE_DIR - echo $Boost_LIBRARY_DIRS - - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -75,7 +66,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 09657f3..500c312 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,15 +56,6 @@ jobs: platform_version: 2022 toolset: mingw - - name: Set Boost Environment Variables - if: runner.os == 'Windows' - shell: bash - run: | - echo "Boost_INCLUDE_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }}/include" >> "$GITHUB_ENV" - echo "Boost_LIBRARY_DIRS=${{ steps.install-boost.outputs.BOOST_ROOT }}/lib" >> "$GITHUB_ENV" - echo $Boost_INCLUDE_DIR - echo $Boost_LIBRARY_DIRS - - name: Setup MSVC Developer Command Prompt if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -75,7 +66,7 @@ jobs: CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh CIBW_BEFORE_ALL_WINDOWS: bash ./build_tools/before_all_windows.sh - CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='D:/a/piqtree/piqtree/boost/boost/include' Boost_LIBRARY_DIRS='D:/a/piqtree/piqtree/boost/boost/lib' + CIBW_ENVIRONMENT_WINDOWS: Boost_INCLUDE_DIR='${{ steps.install-boost.outputs.BOOST_ROOT }}/include' Boost_LIBRARY_DIRS='${{ steps.install-boost.outputs.BOOST_ROOT }}/lib' CIBW_ARCHS_LINUX: ${{endsWith(matrix.platform_id, '_x86_64') && 'x86_64' || 'aarch64'}} CIBW_ARCHS_MACOS: ${{endsWith(matrix.platform_id, 'universal2') && 'universal2' || 'auto'}} CIBW_ARCHS_WINDOWS: ${{endsWith(matrix.platform_id, '_amd64') && 'AMD64' || 'ARM64'}} diff --git a/build_tools/before_all_windows.sh b/build_tools/before_all_windows.sh index 5f81b9a..763536e 100644 --- a/build_tools/before_all_windows.sh +++ b/build_tools/before_all_windows.sh @@ -1,8 +1,10 @@ # Install dependencies using choco -echo "Boost_INCLUDE_DIR: " $Boost_INCLUDE_DIR -echo "Boost_LIBRARY_DIRS: " $Boost_LIBRARY_DIRS -echo "BOOST_ROOT: " $BOOST_ROOT +export Boost_INCLUDE_DIR=$(echo $Boost_INCLUDE_DIR | sed 's|\\|/|g') +export Boost_LIBRARY_DIRS=$(echo $Boost_LIBRARY_DIRS | sed 's|\\|/|g') + +echo "Boost_INCLUDE_DIR: $Boost_INCLUDE_DIR" +echo "Boost_LIBRARY_DIRS: $Boost_LIBRARY_DIRS" choco install -y llvm --version=14.0.6 --allow-downgrade choco install -y eigen From 9f9669ccb5ff480a5b2d3b4c5448a31a131c3406 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 19:54:50 +1100 Subject: [PATCH 39/44] MAINT: remove extra space --- src/piqtree/_libiqtree/_piqtree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/piqtree/_libiqtree/_piqtree.cpp b/src/piqtree/_libiqtree/_piqtree.cpp index 52697f8..5101edd 100644 --- a/src/piqtree/_libiqtree/_piqtree.cpp +++ b/src/piqtree/_libiqtree/_piqtree.cpp @@ -116,5 +116,5 @@ PYBIND11_MODULE(_piqtree, m) { "Construct pairwise distance matrix for alignment."); m.def("iq_nj_tree", &build_njtree, "Build neighbour-joining tree from distance matrix."); - m.def("mine", &mine, "The meaning of life, the universe (and everything) !"); + m.def("mine", &mine, "The meaning of life, the universe (and everything)!"); } From c0d4be70a20e214e0d53aeecff0ce8ecb6481a96 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Thu, 20 Mar 2025 21:03:14 +1100 Subject: [PATCH 40/44] MAINT: update python version (#4) * MAINT: Add support for Python 3.13, drop for 3.10 * MAINT: Update nox supported python versions * Bump pypa/cibuildwheel from 2.23.0 to 2.23.1 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.23.0 to 2.23.1. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/v2.23.1/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.23.0...v2.23.1) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build_docs.yml | 2 +- .github/workflows/build_wheels.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 4 ++-- noxfile.py | 2 +- pyproject.toml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index d4c0ec3..ad7ce76 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -32,7 +32,7 @@ jobs: - uses: ./.github/actions/setup-piqtree with: - python-version: "3.12" + python-version: "3.13" cache-key: libiqtree-ubuntu-latest-${{ needs.build-iqtree.outputs.iqtree2-sha }} - name: Install Docs Dependencies diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 5d56787..a6f62be 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -61,7 +61,7 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Build wheels - uses: pypa/cibuildwheel@v2.23.0 + uses: pypa/cibuildwheel@v2.23.1 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acf5078..4fdb7fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-13, macos-14, windows-latest] # Intel linux, Intel Mac, ARM Mac, Windows - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.11", "3.12", "3.13"] steps: - uses: "actions/checkout@v4" with: @@ -90,7 +90,7 @@ jobs: strategy: matrix: - python-version: ["3.12"] + python-version: ["3.13"] os: [ubuntu-latest] steps: @@ -115,7 +115,7 @@ jobs: strategy: matrix: - python-version: ["3.12"] + python-version: ["3.13"] os: [ubuntu-latest] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 500c312..faca30e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Build wheels - uses: pypa/cibuildwheel@v2.23.0 + uses: pypa/cibuildwheel@v2.23.1 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh @@ -111,7 +111,7 @@ jobs: - uses: ./.github/actions/setup-piqtree with: - python-version: "3.12" + python-version: "3.13" cache-key: libiqtree-ubuntu-latest-${{ needs.build-iqtree.outputs.iqtree2-sha }} - name: Install Docs Dependencies diff --git a/noxfile.py b/noxfile.py index 42a3dd2..f4a2385 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,7 +2,7 @@ import nox -_py_versions = range(9, 13) +_py_versions = range(11, 14) @nox.session(python=[f"3.{v}" for v in _py_versions]) diff --git a/pyproject.toml b/pyproject.toml index 1987151..9460368 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "piqtree" dependencies = ["cogent3>=2024.11.29a2", "pyyaml", "requests"] -requires-python = ">=3.10, <3.13" +requires-python = ">=3.11, <3.14" authors = [{name="Gavin Huttley"}, {name="Robert McArthur"}, {name="Bui Quang Minh "}, {name="Richard Morris"}, {name="Thomas Wong"}] description="Python bindings for IQTree" From 505c1d927ea9b58ae6f22ca1f47e75669d0c30f3 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Sun, 23 Mar 2025 23:34:43 +1100 Subject: [PATCH 41/44] DEV: update iqtree to error handling version --- iqtree2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iqtree2 b/iqtree2 index 39cbee5..3ad863a 160000 --- a/iqtree2 +++ b/iqtree2 @@ -1 +1 @@ -Subproject commit 39cbee547c4bfc6d2e78f063b4beb9095e15f812 +Subproject commit 3ad863ad7e26d46aa549327e6c3003b75d288ff0 From e4ad032dc27b1ef5de0001cb691e48981ee990e6 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Mon, 24 Mar 2025 00:16:07 +1100 Subject: [PATCH 42/44] DEV: update return/arg types for calling IQ-TREE --- src/piqtree/_libiqtree/_piqtree.cpp | 67 +++++++++++++++++++++++-- src/piqtree/_libiqtree/_piqtree.h | 76 ++++++++++++++++++----------- 2 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/piqtree/_libiqtree/_piqtree.cpp b/src/piqtree/_libiqtree/_piqtree.cpp index 5101edd..c76af49 100644 --- a/src/piqtree/_libiqtree/_piqtree.cpp +++ b/src/piqtree/_libiqtree/_piqtree.cpp @@ -5,11 +5,16 @@ #include #include #include - -using namespace std; +#include "_piqtree.h" namespace py = pybind11; +void checkError(char* errorStr) { + if (errorStr && std::strlen(errorStr) > 0) { + throw std::runtime_error(errorStr); + } +} + namespace PYBIND11_NAMESPACE { namespace detail { template <> @@ -80,9 +85,63 @@ struct type_caster { // Conversion from C++ to Python static handle cast(DoubleArray src, return_value_policy, handle) { + throw std::runtime_error("Unsupported operation"); + } +}; + +template <> +struct type_caster { + public: + PYBIND11_TYPE_CASTER(IntegerResult, _("IntegerResult")); + + // Conversion from Python to C++ + bool load(handle /* src */, bool /* convert */) { + throw std::runtime_error("Unsupported operation"); + } + + // Conversion from C++ to Python + static handle cast(const IntegerResult& src, return_value_policy, handle) { + checkError(src.errorStr); + + return py::int_(src.value).release(); + } +}; + +template <> +struct type_caster { + public: + // Indicate that this caster only supports conversion from C++ to Python + PYBIND11_TYPE_CASTER(StringResult, _("StringResult")); + + // Reject Python to C++ conversion + bool load(handle src, bool) { + throw std::runtime_error("Unsupported operation"); + } + + // Conversion from C++ to Python + static handle cast(const StringResult& src, return_value_policy, handle) { + checkError(src.errorStr); + + return py::str(src.value).release(); + } +}; + +template <> +struct type_caster { + public: + PYBIND11_TYPE_CASTER(DoubleArrayResult, _("DoubleArrayResult")); + + // Conversion from Python to C++ + bool load(handle src, bool) { + throw std::runtime_error("Unsupported operation"); + } + + // Conversion from C++ to Python + static handle cast(DoubleArrayResult src, return_value_policy, handle) { + checkError(src.errorStr); + auto result = py::array_t(src.length); - std::memcpy(result.mutable_data(), src.doubles, - src.length * sizeof(double)); + std::memcpy(result.mutable_data(), src.value, src.length * sizeof(double)); return result.release(); } }; diff --git a/src/piqtree/_libiqtree/_piqtree.h b/src/piqtree/_libiqtree/_piqtree.h index 070c621..5e37724 100644 --- a/src/piqtree/_libiqtree/_piqtree.h +++ b/src/piqtree/_libiqtree/_piqtree.h @@ -1,6 +1,7 @@ #ifndef _PIQTREE_H #define _PIQTREE_H +#include #include using namespace std; @@ -21,6 +22,22 @@ typedef struct { size_t length; } DoubleArray; +typedef struct { + int value; + char* errorStr; +} IntegerResult; + +typedef struct { + char* value; + char* errorStr; +} StringResult; + +typedef struct { + double* value; + size_t length; + char* errorStr; +} DoubleArrayResult; + #ifdef _MSC_VER #pragma pack(pop) #else @@ -30,17 +47,17 @@ typedef struct { /* * Calculates the robinson fould distance between two trees */ -extern "C" int robinson_fould(const char* ctree1, const char* ctree2); +extern "C" IntegerResult robinson_fould(const char* ctree1, const char* ctree2); /* * Generates a set of random phylogenetic trees * tree_gen_mode allows:"YULE_HARDING", "UNIFORM", "CATERPILLAR", "BALANCED", * "BIRTH_DEATH", "STAR_TREE" output: a newick tree (in string format) */ -extern "C" char* random_tree(int num_taxa, - const char* tree_gen_mode, - int num_trees, - int rand_seed = 0); +extern "C" StringResult random_tree(int num_taxa, + const char* tree_gen_mode, + int num_trees, + int rand_seed = 0); /* * Perform phylogenetic analysis on the input alignment @@ -49,12 +66,12 @@ extern "C" char* random_tree(int num_taxa, * of the optimal number of cpu threads output: results in YAML format with the * tree and the details of parameters */ -extern "C" char* build_tree(StringArray& names, - StringArray& seqs, - const char* model, - int rand_seed = 0, - int bootstrap_rep = 0, - int num_thres = 1); +extern "C" StringResult build_tree(StringArray& names, + StringArray& seqs, + const char* model, + int rand_seed = 0, + int bootstrap_rep = 0, + int num_thres = 1); /* * Perform phylogenetic analysis on the input alignment @@ -63,12 +80,12 @@ extern "C" char* build_tree(StringArray& names, * of the optimal number of cpu threads output: results in YAML format with the * details of parameters */ -extern "C" char* fit_tree(StringArray& names, - StringArray& seqs, - const char* model, - const char* intree, - int rand_seed = 0, - int num_thres = 1); +extern "C" StringResult fit_tree(StringArray& names, + StringArray& seqs, + const char* model, + const char* intree, + int rand_seed = 0, + int num_thres = 1); /* * Perform phylogenetic analysis with ModelFinder @@ -81,13 +98,13 @@ extern "C" char* fit_tree(StringArray& names, * of the optimal number of cpu threads output: modelfinder results in YAML * format */ -extern "C" char* modelfinder(StringArray& names, - StringArray& seqs, - int rand_seed = 0, - const char* model_set = "", - const char* freq_set = "", - const char* rate_set = "", - int num_thres = 1); +extern "C" StringResult modelfinder(StringArray& names, + StringArray& seqs, + int rand_seed = 0, + const char* model_set = "", + const char* freq_set = "", + const char* rate_set = "", + int num_thres = 1); /* * Build pairwise JC distance matrix @@ -97,19 +114,20 @@ extern "C" char* modelfinder(StringArray& names, * threads to be used, default: 1; 0 - use all available cpu threads on the * machine */ -extern "C" DoubleArray build_distmatrix(StringArray& names, - StringArray& seqs, - int num_thres = 1); +extern "C" DoubleArrayResult build_distmatrix(StringArray& names, + StringArray& seqs, + int num_thres = 1); /* * Using Rapid-NJ to build tree from a distance matrix * output: a newick tree (in string format) */ -extern "C" char* build_njtree(StringArray& names, DoubleArray& distances); +extern "C" StringResult build_njtree(StringArray& names, + DoubleArray& distances); /* * verion number */ -extern "C" char* version(); +extern "C" StringResult version(); #endif /* LIBIQTREE2_FUN */ From bc80ae090dd71099912cfe5c6629fd4c37ffbaa9 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Mon, 24 Mar 2025 00:17:10 +1100 Subject: [PATCH 43/44] DEV: remove pytest skips on windows --- tests/test_app/test_app.py | 1 + tests/test_iqtree/test_build_tree.py | 5 ----- tests/test_iqtree/test_random_trees.py | 6 ------ tests/test_iqtree/test_segmentation_fault.py | 14 -------------- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/test_app/test_app.py b/tests/test_app/test_app.py index ce69516..7bd3aa2 100644 --- a/tests/test_app/test_app.py +++ b/tests/test_app/test_app.py @@ -111,6 +111,7 @@ def test_mfinder_result_roundtrip(five_otu: Alignment) -> None: assert isinstance(inflated, ModelFinderResult) assert str(got.best_aicc) == str(inflated.best_aicc) + @pytest.mark.skipif(cogent3_vers < "2025.3.1", reason="requires cogent3 >= 2025.3.1") @pytest.mark.parametrize("use_hook", [None, "piqtree"]) def test_quick_tree_hook(four_otu: Alignment, use_hook: str | None) -> None: diff --git a/tests/test_iqtree/test_build_tree.py b/tests/test_iqtree/test_build_tree.py index d8232d1..90cbf8a 100644 --- a/tests/test_iqtree/test_build_tree.py +++ b/tests/test_iqtree/test_build_tree.py @@ -1,4 +1,3 @@ -import platform import re import pytest @@ -88,10 +87,6 @@ def test_rate_model_build_tree( ) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="IQ-TREE errors can't be caught yet on Windows", -) def test_build_tree_inadequate_bootstrapping(four_otu: ArrayAlignment) -> None: with pytest.raises(IqTreeError, match=re.escape("#replicates must be >= 1000")): piqtree.build_tree(four_otu, Model(DnaModel.GTR), bootstrap_replicates=10) diff --git a/tests/test_iqtree/test_random_trees.py b/tests/test_iqtree/test_random_trees.py index 82ce8f4..60c09ce 100644 --- a/tests/test_iqtree/test_random_trees.py +++ b/tests/test_iqtree/test_random_trees.py @@ -1,5 +1,3 @@ -import platform - import pytest import piqtree @@ -47,10 +45,6 @@ def test_random_trees_no_seed( @pytest.mark.parametrize("num_taxa", [-1, 0, 1, 2]) @pytest.mark.parametrize("tree_mode", list(piqtree.TreeGenMode)) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="IQ-TREE errors can't be caught yet on Windows", -) def test_invalid_taxa( num_taxa: int, tree_mode: piqtree.TreeGenMode, diff --git a/tests/test_iqtree/test_segmentation_fault.py b/tests/test_iqtree/test_segmentation_fault.py index 1fb5857..701cc40 100644 --- a/tests/test_iqtree/test_segmentation_fault.py +++ b/tests/test_iqtree/test_segmentation_fault.py @@ -1,7 +1,5 @@ """Test combinations of calls which under previous versions resulted in a segmentation fault.""" -import platform - import pytest from cogent3 import make_aligned_seqs, make_tree @@ -10,10 +8,6 @@ from piqtree.model import DiscreteGammaModel, DnaModel, FreeRateModel, Model -@pytest.mark.skipif( - platform.system() == "Windows", - reason="IQ-TREE errors can't be caught yet on Windows", -) def test_two_build_random_trees() -> None: """ Calling build tree twice followed by random trees with a bad input @@ -28,10 +22,6 @@ def test_two_build_random_trees() -> None: random_trees(3, 2, TreeGenMode.BALANCED, 1) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="IQ-TREE errors can't be caught yet on Windows", -) def test_two_fit_random_trees() -> None: """ Calling fit tree twice followed by random trees with a bad input @@ -49,10 +39,6 @@ def test_two_fit_random_trees() -> None: @pytest.mark.parametrize("rate_model_class", [DiscreteGammaModel, FreeRateModel]) @pytest.mark.parametrize("categories", [0, -4]) -@pytest.mark.skipif( - platform.system() == "Windows", - reason="IQ-TREE errors can't be caught yet on Windows", -) def test_two_invalid_models( rate_model_class: type[DiscreteGammaModel] | type[FreeRateModel], categories: int, From 466e979d264ae43e46697621615b0ff7e9675542 Mon Sep 17 00:00:00 2001 From: Robert McArthur Date: Mon, 12 May 2025 18:15:41 +1000 Subject: [PATCH 44/44] DEV: rebase windows-support on main (#5) * ENH: provide piqtree_nj and nj_tree option for positive branch lengths (#165) * ENH: parameter to restrict negative value of branch length * TST: allow_negative correctly control the behavior of branch length in nj_tree * ENH: add parameter allow_negative in piqtree_nj app * MAINT: fix double return error in merge * MAINT: ruff formatting * TST: Use full fasta file for checking `nj_tree` negative branch lengths * MAINT: move from `ArrayAlignment` to `Alignment` in preparation for `new_type` default * MAINT: simplify logic for negative branch lengths * MAINT: Sort fixtures by number of taxa * MAINT: Remove unused test data file --------- Co-authored-by: Robert McArthur * DEV: Update minimum required `cogent3` version (support cogent3 hooks) * TST: Remove skip for `quick_tree` `cogent3` hook as `cogent3` min version has updated * Bump ruff from 0.11.0 to 0.11.5 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.0 to 0.11.5. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.0...0.11.5) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump pypa/cibuildwheel from 2.23.1 to 2.23.2 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.23.1 to 2.23.2. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.23.1...v2.23.2) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump ruff from 0.11.5 to 0.11.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.5 to 0.11.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.5...0.11.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump pypa/cibuildwheel from 2.23.2 to 2.23.3 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 2.23.2 to 2.23.3. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v2.23.2...v2.23.3) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 2.23.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * MAINT: upgrade min `cogent3` and use new `Table` API * ENH: support use of `str` for model in `build_tree` and `fit_tree` * TST: add tests for string represnetations of models * API: simplify expression of model in apps (match that used by `build_tree` and `fit_tree`) * DEV: add scriv fragment * DEV: bump version to 0.5.0 * DEV: collate scriv fragment * DOC: fix typo in release checklist * DEV: update trove classifiers for supported python versions * DEV: add minimum cmake version * Bump ruff from 0.11.8 to 0.11.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.8 to 0.11.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.11.8...0.11.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Yapeng Lang Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build_wheels.yml | 2 +- .github/workflows/release.yml | 2 +- build_tools/build_iqtree.sh | 4 +-- changelog.md | 20 +++++++++++ docs/developers/release.md | 2 +- pyproject.toml | 6 ++-- src/piqtree/__init__.py | 2 +- src/piqtree/_app/__init__.py | 32 ++++++------------ src/piqtree/iqtree/_tree.py | 31 +++++++++++++---- src/piqtree/model/_options.py | 10 +++--- tests/conftest.py | 14 +++++--- tests/test_app/test_app.py | 10 +++--- tests/test_iqtree/test_build_tree.py | 26 ++++++++++----- tests/test_iqtree/test_distance.py | 4 +-- tests/test_iqtree/test_fit_tree.py | 46 +++++++++++++++++++++++--- tests/test_iqtree/test_model_finder.py | 6 ++-- tests/test_iqtree/test_nj_tree.py | 17 ++++++++-- 17 files changed, 162 insertions(+), 72 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index a6f62be..ebf7a97 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -61,7 +61,7 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Build wheels - uses: pypa/cibuildwheel@v2.23.1 + uses: pypa/cibuildwheel@v2.23.3 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index faca30e..fc4ff19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Build wheels - uses: pypa/cibuildwheel@v2.23.1 + uses: pypa/cibuildwheel@v2.23.3 env: # Can specify per os - e.g. CIBW_BEFORE_ALL_LINUX, CIBW_BEFORE_ALL_MACOS, CIBW_BEFORE_ALL_WINDOWS CIBW_BEFORE_ALL_LINUX: ./build_tools/before_all_linux.sh CIBW_BEFORE_ALL_MACOS: ./build_tools/before_all_mac.sh diff --git a/build_tools/build_iqtree.sh b/build_tools/build_iqtree.sh index 5aedccd..cdc9526 100755 --- a/build_tools/build_iqtree.sh +++ b/build_tools/build_iqtree.sh @@ -30,8 +30,8 @@ elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then .. make -j else - echo "Building for Linux." - cmake -DBUILD_LIB=ON .. + echo "Building for linux." + cmake -DBUILD_LIB=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. make -j fi diff --git a/changelog.md b/changelog.md index 5978672..818884e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,24 @@ + +# Changes in release "0.5.0" + +## Contributors + +- @GavinHuttley added `nj_tree` as a hook for `cogent3.Alignment.quick_tree`. +- @YapengLang handled negative branch lengths from the rapidNJ tree. +- @rmcar17, @thomaskf general maintanence on the piqtree/IQ-TREE sides including work on windows behind the scenes. + +## ENH + +- Add support for Python 3.13, remove support for 3.10 +- IQ-TREE's rapidNJ implementation can be used as a hook for `quick_tree` on `cogent3` alignment objects. Try `Alignment.quick_tree(use_hook="piqtree")`. +- `nj_tree` now by default does not allow negative branch lengths. Set `allow_negative=True` if this behaviour is desired. +- Allow `str` to be used for `model` in `build_tree` and `fit_tree`. The value is automatically coerced into the `Model` class. + +## API + +- Simplify API for `piqtree_phylo` and `piqtree_fit` apps. Both now take a single parameter for the model, matching the parameter for `model` in `build_tree` and `fit_tree`. + # Changes in release "0.4.0" diff --git a/docs/developers/release.md b/docs/developers/release.md index 974a38d..fae6fdd 100644 --- a/docs/developers/release.md +++ b/docs/developers/release.md @@ -8,7 +8,7 @@ The documentation is fetched by readthedocs from the "Build Docs" GitHub Action. - The `piqtree` version has been correctly bumped. - The testing, linting and type checking all pass on all supported platforms. -- The code has been throuroughly tested (check what's been missed in the coverage report). +- The code has been thoroughly tested (check what's been missed in the coverage report). - The documentation builds and appears correct. - The documentation has been updated on readthedocs (this must be triggered from readthedocs). - The "Release" GitHub Action has correctly uploaded to Test PyPI. diff --git a/pyproject.toml b/pyproject.toml index 9460368..f907df5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "piqtree" -dependencies = ["cogent3>=2024.11.29a2", "pyyaml", "requests"] +dependencies = ["cogent3>=2025.5.8a2", "pyyaml", "requests"] requires-python = ">=3.11, <3.14" authors = [{name="Gavin Huttley"}, {name="Robert McArthur"}, {name="Bui Quang Minh "}, {name="Richard Morris"}, {name="Thomas Wong"}] @@ -28,9 +28,9 @@ classifiers = [ "Programming Language :: C++", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Typing :: Typed" ] @@ -43,7 +43,7 @@ Documentation = "https://piqtree.readthedocs.io" [project.optional-dependencies] dev = ["cibuildwheel", "pybind11", "scriv", "piqtree[test]", "piqtree[lint]", "piqtree[typing]"] test = ["pytest", "pytest-cov", "nox"] -lint = ["ruff==0.11.0"] +lint = ["ruff==0.11.9"] typing = ["mypy==1.15.0", "piqtree[stubs]", "piqtree[test]"] stubs = ["types-PyYAML", "types-requests"] extra = ["cogent3[extra]"] diff --git a/src/piqtree/__init__.py b/src/piqtree/__init__.py index 5a91c95..5d7abd5 100644 --- a/src/piqtree/__init__.py +++ b/src/piqtree/__init__.py @@ -35,7 +35,7 @@ def _add_dll_path() -> None: make_model, ) -__version__ = "0.4.0" +__version__ = "0.5.0" __all__ = [ "Model", diff --git a/src/piqtree/_app/__init__.py b/src/piqtree/_app/__init__.py index e70c9de..32d6cfe 100644 --- a/src/piqtree/_app/__init__.py +++ b/src/piqtree/_app/__init__.py @@ -23,21 +23,13 @@ class piqtree_phylo: @extend_docstring_from(build_tree) def __init__( self, - submod_type: str, - freq_type: str | None = None, - rate_model: str | None = None, + model: Model | str, *, - invariant_sites: bool = False, rand_seed: int | None = None, bootstrap_reps: int | None = None, num_threads: int | None = None, ) -> None: - self._model = Model( - submod_type=submod_type, - invariant_sites=invariant_sites, - rate_model=rate_model, - freq_type=freq_type, - ) + self._model = model self._rand_seed = rand_seed self._bootstrap_reps = bootstrap_reps self._num_threads = num_threads @@ -61,21 +53,13 @@ class piqtree_fit: def __init__( self, tree: cogent3.PhyloNode, - submod_type: str, - freq_type: str | None = None, - rate_model: str | None = None, + model: Model | str, *, rand_seed: int | None = None, num_threads: int | None = None, - invariant_sites: bool = False, ) -> None: self._tree = tree - self._model = Model( - submod_type=submod_type, - invariant_sites=invariant_sites, - rate_model=rate_model, - freq_type=freq_type, - ) + self._model = model self._rand_seed = rand_seed self._num_threads = num_threads @@ -124,8 +108,12 @@ def main( @composable.define_app @extend_docstring_from(nj_tree) -def piqtree_nj(dists: c3_types.PairwiseDistanceType) -> cogent3.PhyloNode: - tree = nj_tree(dists) +def piqtree_nj( + dists: c3_types.PairwiseDistanceType, + *, + allow_negative: bool = False, +) -> cogent3.PhyloNode: + tree = nj_tree(dists, allow_negative=allow_negative) tree.params |= {"provenance": "piqtree"} return tree diff --git a/src/piqtree/iqtree/_tree.py b/src/piqtree/iqtree/_tree.py index 2485db0..a302e6e 100644 --- a/src/piqtree/iqtree/_tree.py +++ b/src/piqtree/iqtree/_tree.py @@ -11,7 +11,7 @@ from piqtree.exceptions import ParseIqTreeError from piqtree.iqtree._decorator import iqtree_func -from piqtree.model import DnaModel, Model +from piqtree.model import DnaModel, Model, make_model iq_build_tree = iqtree_func(iq_build_tree, hide_files=True) iq_fit_tree = iqtree_func(iq_fit_tree, hide_files=True) @@ -196,7 +196,7 @@ def _process_tree_yaml( def build_tree( aln: c3_types.AlignedSeqsType, - model: Model, + model: Model | str, rand_seed: int | None = None, bootstrap_replicates: int | None = None, num_threads: int | None = None, @@ -209,7 +209,7 @@ def build_tree( ---------- aln : c3_types.AlignedSeqsType The sequence alignment. - model : Model + model : Model | str The substitution model with base frequencies and rate heterogeneity. rand_seed : int | None, optional The random seed - 0 or None means no seed, by default None. @@ -227,6 +227,9 @@ def build_tree( The IQ-TREE maximum likelihood tree from the given alignment. """ + if isinstance(model, str): + model = make_model(model) + if rand_seed is None: rand_seed = 0 # The default rand_seed in IQ-TREE @@ -261,7 +264,7 @@ def build_tree( def fit_tree( aln: c3_types.AlignedSeqsType, tree: cogent3.PhyloNode, - model: Model, + model: Model | str, rand_seed: int | None = None, num_threads: int | None = None, ) -> cogent3.PhyloNode: @@ -276,7 +279,7 @@ def fit_tree( The sequence alignment. tree : cogent3.PhyloNode The topology to fit branch lengths to. - model : Model + model : Model | str The substitution model with base frequencies and rate heterogeneity. rand_seed : int | None, optional The random seed - 0 or None means no seed, by default None. @@ -290,6 +293,9 @@ def fit_tree( A phylogenetic tree with same given topology fitted with branch lengths. """ + if isinstance(model, str): + model = make_model(model) + if rand_seed is None: rand_seed = 0 # The default rand_seed in IQ-TREE @@ -312,7 +318,11 @@ def fit_tree( return tree -def nj_tree(pairwise_distances: c3_types.PairwiseDistanceType) -> cogent3.PhyloNode: +def nj_tree( + pairwise_distances: c3_types.PairwiseDistanceType, + *, + allow_negative: bool = False, +) -> cogent3.PhyloNode: """Construct a neighbour joining tree from a pairwise distance matrix. Parameters @@ -334,4 +344,11 @@ def nj_tree(pairwise_distances: c3_types.PairwiseDistanceType) -> cogent3.PhyloN pairwise_distances.keys(), np.array(pairwise_distances).flatten(), ) - return make_tree(newick_tree) + + tree = make_tree(newick_tree) + + if not allow_negative: + for node in tree.traverse(include_self=False): + node.length = max(node.length, 0) + + return tree diff --git a/src/piqtree/model/_options.py b/src/piqtree/model/_options.py index ebfa365..4229ad9 100644 --- a/src/piqtree/model/_options.py +++ b/src/piqtree/model/_options.py @@ -3,7 +3,7 @@ import functools from typing import Literal -from cogent3 import _Table, make_table +from cogent3.core.table import Table, make_table from piqtree.model._freq_type import FreqType from piqtree.model._rate_type import ALL_BASE_RATE_TYPES, get_description @@ -40,7 +40,7 @@ def available_models( model_type: Literal["dna", "protein"] | None = None, *, show_all: bool = True, -) -> _Table: +) -> Table: """Return a table showing available substitution models. Parameters @@ -52,7 +52,7 @@ def available_models( Returns ------- - _Table + Table Table with all available models. """ @@ -78,7 +78,7 @@ def available_models( return table -def available_freq_type() -> _Table: +def available_freq_type() -> Table: """Return a table showing available freq type options.""" data: dict[str, list[str]] = {"Freq Type": [], "Description": []} @@ -89,7 +89,7 @@ def available_freq_type() -> _Table: return make_table(data=data, title="Available frequency types") -def available_rate_type() -> _Table: +def available_rate_type() -> Table: """Return a table showing available rate type options.""" data: dict[str, list[str]] = {"Rate Type": [], "Description": []} diff --git a/tests/conftest.py b/tests/conftest.py index 76778ad..7d4a1eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ import pathlib import pytest -from cogent3 import ArrayAlignment, load_aligned_seqs +from cogent3 import Alignment, load_aligned_seqs @pytest.fixture(scope="session") @@ -10,21 +10,27 @@ def DATA_DIR() -> pathlib.Path: @pytest.fixture -def three_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment: +def three_otu(DATA_DIR: pathlib.Path) -> Alignment: aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True) aln = aln.take_seqs(["Human", "Rhesus", "Mouse"]) return aln.omit_gap_pos(allowed_gap_frac=0) @pytest.fixture -def four_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment: +def four_otu(DATA_DIR: pathlib.Path) -> Alignment: aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True) aln = aln.take_seqs(["Human", "Chimpanzee", "Rhesus", "Mouse"]) return aln.omit_gap_pos(allowed_gap_frac=0) @pytest.fixture -def five_otu(DATA_DIR: pathlib.Path) -> ArrayAlignment: +def five_otu(DATA_DIR: pathlib.Path) -> Alignment: aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True) aln = aln.take_seqs(["Human", "Chimpanzee", "Rhesus", "Manatee", "Dugong"]) return aln.omit_gap_pos(allowed_gap_frac=0) + + +@pytest.fixture +def all_otu(DATA_DIR: pathlib.Path) -> Alignment: + aln = load_aligned_seqs(DATA_DIR / "example.fasta", moltype="dna", new_type=True) + return aln.omit_gap_pos(allowed_gap_frac=0) diff --git a/tests/test_app/test_app.py b/tests/test_app/test_app.py index 7bd3aa2..6470cb9 100644 --- a/tests/test_app/test_app.py +++ b/tests/test_app/test_app.py @@ -1,21 +1,20 @@ import pytest -from cogent3 import __version__ as cogent3_vers from cogent3 import get_app, make_tree from cogent3.core.new_alignment import Alignment import piqtree -from piqtree import jc_distances +from piqtree import jc_distances, make_model def test_piqtree_phylo(four_otu: Alignment) -> None: expected = make_tree("(Human,Chimpanzee,(Rhesus,Mouse));") - app = get_app("piqtree_phylo", submod_type="JC") + app = get_app("piqtree_phylo", model="JC") got = app(four_otu) assert expected.same_topology(got) def test_piqtree_phylo_support(four_otu: Alignment) -> None: - app = get_app("piqtree_phylo", submod_type="JC", bootstrap_reps=1000) + app = get_app("piqtree_phylo", model=make_model("JC"), bootstrap_reps=1000) got = app(four_otu) supports = [ node.params.get("support", None) @@ -29,7 +28,7 @@ def test_piqtree_fit(three_otu: Alignment) -> None: tree = make_tree(tip_names=three_otu.names) app = get_app("model", "JC69", tree=tree) expected = app(three_otu) - piphylo = get_app("piqtree_fit", tree=tree, submod_type="JC") + piphylo = get_app("piqtree_fit", tree=tree, model="JC") got = piphylo(three_otu) assert got.params["lnL"] == pytest.approx(expected.lnL) @@ -112,7 +111,6 @@ def test_mfinder_result_roundtrip(five_otu: Alignment) -> None: assert str(got.best_aicc) == str(inflated.best_aicc) -@pytest.mark.skipif(cogent3_vers < "2025.3.1", reason="requires cogent3 >= 2025.3.1") @pytest.mark.parametrize("use_hook", [None, "piqtree"]) def test_quick_tree_hook(four_otu: Alignment, use_hook: str | None) -> None: tree = four_otu.quick_tree(use_hook=use_hook) diff --git a/tests/test_iqtree/test_build_tree.py b/tests/test_iqtree/test_build_tree.py index 90cbf8a..250cba3 100644 --- a/tests/test_iqtree/test_build_tree.py +++ b/tests/test_iqtree/test_build_tree.py @@ -1,7 +1,7 @@ import re import pytest -from cogent3 import ArrayAlignment, make_tree +from cogent3 import Alignment, make_tree import piqtree from piqtree.exceptions import IqTreeError @@ -16,12 +16,13 @@ def check_build_tree( - four_otu: ArrayAlignment, + four_otu: Alignment, dna_model: DnaModel, freq_type: FreqType | None = None, rate_model: RateModel | None = None, *, invariant_sites: bool = False, + coerce_str: bool = False, ) -> None: expected = make_tree("(Human,Chimpanzee,(Rhesus,Mouse));") @@ -32,7 +33,11 @@ def check_build_tree( rate_model=rate_model, ) - got1 = piqtree.build_tree(four_otu, model, rand_seed=1) + got1 = piqtree.build_tree( + four_otu, + str(model) if coerce_str else model, + rand_seed=1, + ) got1 = got1.unrooted() # Check topology assert expected.same_topology(got1.unrooted()) @@ -49,7 +54,7 @@ def check_build_tree( @pytest.mark.parametrize("dna_model", list(DnaModel)[:22]) @pytest.mark.parametrize("freq_type", list(FreqType)) def test_non_lie_build_tree( - four_otu: ArrayAlignment, + four_otu: Alignment, dna_model: DnaModel, freq_type: FreqType, ) -> None: @@ -57,10 +62,15 @@ def test_non_lie_build_tree( @pytest.mark.parametrize("dna_model", list(DnaModel)[22:]) -def test_lie_build_tree(four_otu: ArrayAlignment, dna_model: DnaModel) -> None: +def test_lie_build_tree(four_otu: Alignment, dna_model: DnaModel) -> None: check_build_tree(four_otu, dna_model) +@pytest.mark.parametrize("dna_model", list(DnaModel)[-3:]) +def test_str_build_tree(four_otu: Alignment, dna_model: DnaModel) -> None: + check_build_tree(four_otu, dna_model, coerce_str=True) + + @pytest.mark.parametrize("dna_model", list(DnaModel)[:5]) @pytest.mark.parametrize("invariant_sites", [False, True]) @pytest.mark.parametrize( @@ -74,7 +84,7 @@ def test_lie_build_tree(four_otu: ArrayAlignment, dna_model: DnaModel) -> None: ], ) def test_rate_model_build_tree( - four_otu: ArrayAlignment, + four_otu: Alignment, dna_model: DnaModel, invariant_sites: bool, rate_model: RateModel, @@ -87,12 +97,12 @@ def test_rate_model_build_tree( ) -def test_build_tree_inadequate_bootstrapping(four_otu: ArrayAlignment) -> None: +def test_build_tree_inadequate_bootstrapping(four_otu: Alignment) -> None: with pytest.raises(IqTreeError, match=re.escape("#replicates must be >= 1000")): piqtree.build_tree(four_otu, Model(DnaModel.GTR), bootstrap_replicates=10) -def test_build_tree_bootstrapping(four_otu: ArrayAlignment) -> None: +def test_build_tree_bootstrapping(four_otu: Alignment) -> None: tree = piqtree.build_tree(four_otu, Model(DnaModel.GTR), bootstrap_replicates=1000) supported_node = max(tree.children, key=lambda x: len(x.children)) diff --git a/tests/test_iqtree/test_distance.py b/tests/test_iqtree/test_distance.py index 38b694b..b3c1848 100644 --- a/tests/test_iqtree/test_distance.py +++ b/tests/test_iqtree/test_distance.py @@ -1,9 +1,9 @@ -from cogent3 import ArrayAlignment +from cogent3 import Alignment from piqtree import jc_distances -def test_jc_distance(five_otu: ArrayAlignment) -> None: +def test_jc_distance(five_otu: Alignment) -> None: dists = jc_distances(five_otu) assert ( diff --git a/tests/test_iqtree/test_fit_tree.py b/tests/test_iqtree/test_fit_tree.py index 91571a6..cf562ea 100644 --- a/tests/test_iqtree/test_fit_tree.py +++ b/tests/test_iqtree/test_fit_tree.py @@ -1,5 +1,5 @@ import pytest -from cogent3 import ArrayAlignment, get_app, make_tree +from cogent3 import Alignment, get_app, make_tree from cogent3.app.result import model_result from cogent3.core.tree import PhyloNode @@ -78,19 +78,57 @@ def check_branch_lengths(got: PhyloNode, expected: PhyloNode) -> None: (DnaModel.F81, "F81"), ], ) -def test_fit_tree(three_otu: ArrayAlignment, iq_model: DnaModel, c3_model: str) -> None: +def test_fit_tree(three_otu: Alignment, iq_model: DnaModel, c3_model: str) -> None: tree_topology = make_tree(tip_names=three_otu.names) app = get_app("model", c3_model, tree=tree_topology) expected = app(three_otu) - got1 = piqtree.fit_tree(three_otu, tree_topology, Model(iq_model), rand_seed=1) + model = Model(iq_model) + + got1 = piqtree.fit_tree(three_otu, tree_topology, model, rand_seed=1) + check_likelihood(got1, expected) + check_motif_probs(got1, expected.tree) + check_rate_parameters(got1, expected.tree) + check_branch_lengths(got1, expected.tree) + + # Should be within an approximation for any seed + got2 = piqtree.fit_tree(three_otu, tree_topology, model, rand_seed=None) + check_likelihood(got2, expected) + check_motif_probs(got2, expected.tree) + check_rate_parameters(got2, expected.tree) + check_branch_lengths(got2, expected.tree) + + +@pytest.mark.parametrize( + ("iq_model", "c3_model"), + [ + (DnaModel.JC, "JC69"), + (DnaModel.K80, "K80"), + (DnaModel.GTR, "GTR"), + (DnaModel.TN, "TN93"), + (DnaModel.HKY, "HKY85"), + (DnaModel.F81, "F81"), + ], +) +def test_fit_tree_str_model( + three_otu: Alignment, + iq_model: DnaModel, + c3_model: str, +) -> None: + tree_topology = make_tree(tip_names=three_otu.names) + app = get_app("model", c3_model, tree=tree_topology) + expected = app(three_otu) + + model = str(Model(iq_model)) + + got1 = piqtree.fit_tree(three_otu, tree_topology, model, rand_seed=1) check_likelihood(got1, expected) check_motif_probs(got1, expected.tree) check_rate_parameters(got1, expected.tree) check_branch_lengths(got1, expected.tree) # Should be within an approximation for any seed - got2 = piqtree.fit_tree(three_otu, tree_topology, Model(iq_model), rand_seed=None) + got2 = piqtree.fit_tree(three_otu, tree_topology, model, rand_seed=None) check_likelihood(got2, expected) check_motif_probs(got2, expected.tree) check_rate_parameters(got2, expected.tree) diff --git a/tests/test_iqtree/test_model_finder.py b/tests/test_iqtree/test_model_finder.py index fa98865..ce0af20 100644 --- a/tests/test_iqtree/test_model_finder.py +++ b/tests/test_iqtree/test_model_finder.py @@ -1,7 +1,7 @@ import multiprocessing import pytest -from cogent3 import ArrayAlignment +from cogent3 import Alignment from piqtree.iqtree import ModelFinderResult, ModelResultValue, model_finder @@ -47,7 +47,7 @@ def test_model_finder_result(model: str) -> None: assert result.model_stats[model].tree_length == 0.678 -def test_model_finder(five_otu: ArrayAlignment) -> None: +def test_model_finder(five_otu: Alignment) -> None: result1 = model_finder(five_otu, rand_seed=1) result2 = model_finder( five_otu, @@ -59,7 +59,7 @@ def test_model_finder(five_otu: ArrayAlignment) -> None: assert str(result1.best_bic) == str(result2.best_bic) -def test_model_finder_restricted_submod(five_otu: ArrayAlignment) -> None: +def test_model_finder_restricted_submod(five_otu: Alignment) -> None: result = model_finder(five_otu, rand_seed=1, model_set={"HKY", "TIM"}) assert str(result.best_aic).startswith(("HKY", "TIM")) assert str(result.best_aicc).startswith(("HKY", "TIM")) diff --git a/tests/test_iqtree/test_nj_tree.py b/tests/test_iqtree/test_nj_tree.py index d3d1949..ae3c68c 100644 --- a/tests/test_iqtree/test_nj_tree.py +++ b/tests/test_iqtree/test_nj_tree.py @@ -1,12 +1,25 @@ -from cogent3 import ArrayAlignment, make_tree +from cogent3 import Alignment, make_tree from piqtree import jc_distances, nj_tree -def test_nj_tree(five_otu: ArrayAlignment) -> None: +def test_nj_tree(five_otu: Alignment) -> None: expected = make_tree("(((Human, Chimpanzee), Rhesus), Manatee, Dugong);") dists = jc_distances(five_otu) actual = nj_tree(dists) assert expected.same_topology(actual) + + +def test_nj_tree_allow_negative(all_otu: Alignment) -> None: + # a distance matrix can produce trees with negative branch lengths + dists = jc_distances(all_otu) + + # check that all branch lengths are non-negative, by default + tree1 = nj_tree(dists) + assert all(node.length >= 0 for node in tree1.traverse(include_self=False)) + + # check that some branch lengths are negative when allow_negative=True + tree2 = nj_tree(dists, allow_negative=True) + assert any(node.length < 0 for node in tree2.traverse(include_self=False))