diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02dcc35d01..9bb6c4f51e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -118,5 +118,53 @@ jobs: conda activate anaconda-client-env pytest -xv -n2 - - + msys2: + runs-on: windows-latest + strategy: + matrix: + include: + - { sys: mingw32, env: i686 } + - { sys: mingw64, env: x86_64 } + name: Windows (${{ matrix.sys }}, ${{ matrix.env }}) + defaults: + run: + shell: msys2 {0} + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.6.0 + with: + access_token: ${{ github.token }} + + - name: 'Checkout' + uses: actions/checkout@v2 + with: + submodules: true + + - name: Setup MSYS2 ${{matrix.sys}} + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.sys}} + update: true + install: >- + git + mingw-w64-${{matrix.env}}-toolchain + mingw-w64-${{matrix.env}}-ninja + mingw-w64-${{matrix.env}}-meson + mingw-w64-${{matrix.env}}-cunit + + - name: Fix windows symlinks + working-directory: c + run: | + rm -f subprojects/kastore + cp -r --dereference subprojects/full-projects/kastore/c subprojects/kastore + + - name: Build + working-directory: c + run: | + meson build -Dbuild_examples=false + ninja -C build + + - name: Run tests + working-directory: c + run: | + ninja -C build test diff --git a/c/CHANGELOG.rst b/c/CHANGELOG.rst index 74af5fe37a..fa7b4da575 100644 --- a/c/CHANGELOG.rst +++ b/c/CHANGELOG.rst @@ -28,6 +28,8 @@ space for 1024 additional entries each time we run out of space in the ragged column. (:user:`benjeffery`, :issue:`1703`, :pr:`1709`) +- Support for compiling the C library on Windows using msys2 (:user:`jeromekelleher`, + :pr:`1742`). **Fixes** diff --git a/c/meson.build b/c/meson.build index eb9d1c1761..cf35f4702e 100644 --- a/c/meson.build +++ b/c/meson.build @@ -85,28 +85,37 @@ if not meson.is_subproject() dependencies: kastore_dep) test('minimal_cpp', test_minimal_cpp) - # The development CLI. Don't use extra C args because argtable code won't pass - executable('dev-cli', - sources: ['dev-tools/dev-cli.c', 'dev-tools/argtable3.c'], - link_with: [tskit_lib], c_args:['-Dlint'], dependencies: kastore_dep) - - # Example programs. - executable('api_structure', - sources: ['examples/api_structure.c'], link_with: [tskit_lib], dependencies: lib_deps) - executable('error_handling', - sources: ['examples/error_handling.c'], link_with: [tskit_lib], dependencies: lib_deps) - executable('tree_iteration', - sources: ['examples/tree_iteration.c'], link_with: [tskit_lib], dependencies: lib_deps) - executable('tree_traversal', - sources: ['examples/tree_traversal.c'], link_with: [tskit_lib], dependencies: lib_deps) - executable('streaming', - sources: ['examples/streaming.c'], link_with: [tskit_lib], dependencies: lib_deps) - executable('cpp_sorting_example', - sources: ['examples/cpp_sorting_example.cpp'], link_with: [tskit_lib], dependencies: lib_deps) - executable('haploid_wright_fisher', - sources: ['examples/haploid_wright_fisher.c'], link_with: [tskit_lib], dependencies: lib_deps) + if get_option('build_examples') + # These example programs and the dev-cli use less portable features, + # and we don't want to always compile them. Use, e.g., + # meson build -Dbuild_examples=false + + # The development CLI. Don't use extra C args because argtable code won't pass + executable('dev-cli', + sources: ['dev-tools/dev-cli.c', 'dev-tools/argtable3.c'], + link_with: [tskit_lib], c_args:['-Dlint'], dependencies: kastore_dep) + + # Example programs. + executable('api_structure', + sources: ['examples/api_structure.c'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('error_handling', + sources: ['examples/error_handling.c'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('tree_iteration', + sources: ['examples/tree_iteration.c'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('tree_traversal', + sources: ['examples/tree_traversal.c'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('streaming', + sources: ['examples/streaming.c'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('cpp_sorting_example', + sources: ['examples/cpp_sorting_example.cpp'], + link_with: [tskit_lib], dependencies: lib_deps) + executable('haploid_wright_fisher', + sources: ['examples/haploid_wright_fisher.c'], + link_with: [tskit_lib], dependencies: lib_deps) + endif endif - -# TMP: until we've ported all the tests, keep this compilable. -# gsl_dep = dependency('gsl') -# executable('old_tests', sources: ['tests/old_tests.c'], link_with: tskit_lib, # dependencies: [cunit_dep, gsl_dep]) diff --git a/c/meson_options.txt b/c/meson_options.txt new file mode 100644 index 0000000000..86c27f2e19 --- /dev/null +++ b/c/meson_options.txt @@ -0,0 +1 @@ +option('build_examples', type : 'boolean', value : true) diff --git a/c/tests/test_core.c b/c/tests/test_core.c index cb22812cfa..f7f0a7f49d 100644 --- a/c/tests/test_core.c +++ b/c/tests/test_core.c @@ -337,7 +337,7 @@ test_blkalloc(void) static void test_unknown_time(void) { - CU_ASSERT_TRUE(isnan(TSK_UNKNOWN_TIME)); + CU_ASSERT_TRUE(tsk_isnan(TSK_UNKNOWN_TIME)); CU_ASSERT_TRUE(tsk_is_unknown_time(TSK_UNKNOWN_TIME)); CU_ASSERT_FALSE(tsk_is_unknown_time(NAN)); CU_ASSERT_FALSE(tsk_is_unknown_time(0)); @@ -371,6 +371,36 @@ test_malloc_overflow(void) #endif } +static void +test_debug_stream(void) +{ + FILE *f = fopen(_tmp_file_name, "w"); + CU_ASSERT_FATAL(tsk_get_debug_stream() == stdout); + CU_ASSERT_FATAL(tsk_get_debug_stream() == stdout); + + tsk_set_debug_stream(f); + CU_ASSERT_FATAL(tsk_get_debug_stream() == f); + tsk_set_debug_stream(stdout); + CU_ASSERT_FATAL(tsk_get_debug_stream() == stdout); + + fclose(f); +} + +static void +test_warn_stream(void) +{ + FILE *f = fopen(_tmp_file_name, "w"); + CU_ASSERT_FATAL(tsk_get_warn_stream() == stderr); + CU_ASSERT_FATAL(tsk_get_warn_stream() == stderr); + + tsk_set_warn_stream(f); + CU_ASSERT_FATAL(tsk_get_warn_stream() == f); + tsk_set_warn_stream(stderr); + CU_ASSERT_FATAL(tsk_get_warn_stream() == stderr); + + fclose(f); +} + static int validate_avl_node(tsk_avl_node_int_t *node) { @@ -521,6 +551,8 @@ main(int argc, char **argv) { "test_unknown_time", test_unknown_time }, { "test_malloc_zero", test_malloc_zero }, { "test_malloc_overflow", test_malloc_overflow }, + { "test_debug_stream", test_debug_stream }, + { "test_warn_stream", test_warn_stream }, { "test_avl_empty", test_avl_empty }, { "test_avl_sequential", test_avl_sequential }, { "test_avl_interleaved", test_avl_interleaved }, diff --git a/c/tests/test_file_format.c b/c/tests/test_file_format.c index b08a89e0cd..bff6d8961d 100644 --- a/c/tests/test_file_format.c +++ b/c/tests/test_file_format.c @@ -787,9 +787,13 @@ test_metadata_schemas_optional(void) free(ts); } +/* This test is problematic on windows because of the different off_t + * types. Doesn't seem worth the trouble of getting it working. + */ static void test_load_bad_file_formats(void) { +#if !defined(_WIN32) tsk_table_collection_t tables; tsk_treeseq_t ts; int ret, ret2; @@ -818,6 +822,7 @@ test_load_bad_file_formats(void) CU_ASSERT_EQUAL_FATAL(ret ^ (1 << TSK_KAS_ERR_BIT), KAS_ERR_BAD_FILE_FORMAT); tsk_table_collection_free(&tables); } +#endif } static void diff --git a/c/tests/test_stats.c b/c/tests/test_stats.c index 6a307920d9..f754c96639 100644 --- a/c/tests/test_stats.c +++ b/c/tests/test_stats.c @@ -1102,7 +1102,7 @@ test_paper_ex_diversity(void) ret = tsk_treeseq_diversity( &ts, 1, &sample_set_sizes, samples, 0, NULL, &pi, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) pi)); + CU_ASSERT(tsk_isnan(pi)); tsk_treeseq_free(&ts); } @@ -1292,7 +1292,7 @@ test_paper_ex_Y1(void) sample_set_sizes = 1; ret = tsk_treeseq_Y1(&ts, 1, &sample_set_sizes, samples, 0, NULL, &result, 0); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); tsk_treeseq_free(&ts); } @@ -1331,7 +1331,7 @@ test_paper_ex_divergence(void) ret = tsk_treeseq_divergence(&ts, 2, sample_set_sizes, samples, 1, set_indexes, 0, NULL, &result, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); tsk_treeseq_free(&ts); } @@ -1399,7 +1399,7 @@ test_paper_ex_Y2(void) ret = tsk_treeseq_Y2(&ts, 2, sample_set_sizes, samples, 1, set_indexes, 0, NULL, &result, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); tsk_treeseq_free(&ts); } @@ -1437,7 +1437,7 @@ test_paper_ex_f2(void) ret = tsk_treeseq_f2(&ts, 2, sample_set_sizes, samples, 1, set_indexes, 0, NULL, &result, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); /* sample_set_size of 1 leads to NaN */ sample_set_sizes[0] = 2; @@ -1445,7 +1445,7 @@ test_paper_ex_f2(void) ret = tsk_treeseq_f2(&ts, 2, sample_set_sizes, samples, 1, set_indexes, 0, NULL, &result, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); tsk_treeseq_free(&ts); } @@ -1514,7 +1514,7 @@ test_paper_ex_f3(void) ret = tsk_treeseq_f3(&ts, 3, sample_set_sizes, samples, 1, set_indexes, 0, NULL, &result, TSK_STAT_SITE); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(isnan((float) result)); + CU_ASSERT(tsk_isnan(result)); tsk_treeseq_free(&ts); } diff --git a/c/tests/test_tables.c b/c/tests/test_tables.c index e6cef74bff..6e6973c512 100644 --- a/c/tests/test_tables.c +++ b/c/tests/test_tables.c @@ -5422,16 +5422,15 @@ test_ibd_segments_debug(void) tsk_treeseq_t ts; int ret; tsk_ibd_segments_t result; - FILE *tmp = stdout; tsk_treeseq_from_text(&ts, 1, single_tree_ex_nodes, single_tree_ex_edges, NULL, NULL, NULL, NULL, NULL, 0); /* Run the DEBUG code */ - stdout = _devnull; + tsk_set_debug_stream(_devnull); ret = tsk_table_collection_ibd_segments( ts.tables, &result, NULL, 0, 0.0, DBL_MAX, TSK_DEBUG); - stdout = tmp; + tsk_set_debug_stream(stdout); CU_ASSERT_EQUAL_FATAL(ret, 0); tsk_ibd_segments_free(&result); diff --git a/c/tests/test_trees.c b/c/tests/test_trees.c index 34f16edc3f..ac04e17069 100644 --- a/c/tests/test_trees.c +++ b/c/tests/test_trees.c @@ -1977,7 +1977,7 @@ test_simplest_individuals(void) CU_ASSERT_EQUAL(ret, 0); ret = tsk_treeseq_get_individual(&ts, 0, &individual); CU_ASSERT_EQUAL_FATAL(ret, 0); - CU_ASSERT(!isfinite(individual.location[0])); + CU_ASSERT(!tsk_isfinite(individual.location[0])); tsk_treeseq_free(&ts); tsk_table_collection_free(&tables); @@ -3957,16 +3957,15 @@ test_single_tree_simplify_debug(void) tsk_treeseq_t ts, simplified; tsk_id_t samples[] = { 0, 1 }; int ret; - FILE *save_stdout = stdout; FILE *tmp = fopen(_tmp_file_name, "w"); CU_ASSERT_FATAL(tmp != NULL); tsk_treeseq_from_text(&ts, 1, single_tree_ex_nodes, single_tree_ex_edges, NULL, single_tree_ex_sites, single_tree_ex_mutations, NULL, NULL, 0); - stdout = tmp; + tsk_set_debug_stream(tmp); ret = tsk_treeseq_simplify(&ts, samples, 2, TSK_DEBUG, &simplified, NULL); - stdout = save_stdout; + tsk_set_debug_stream(stdout); CU_ASSERT_EQUAL_FATAL(ret, 0); CU_ASSERT_TRUE(ftell(tmp) > 0); @@ -6143,15 +6142,14 @@ test_sample_counts_deprecated(void) tsk_tree_t tree; int ret; FILE *f = fopen(_tmp_file_name, "w"); - FILE *tmp = stderr; tsk_treeseq_from_text(&ts, 1, single_tree_ex_nodes, single_tree_ex_edges, NULL, NULL, NULL, NULL, NULL, 0); CU_ASSERT_EQUAL(tsk_treeseq_get_num_samples(&ts), 4); - stderr = f; + tsk_set_warn_stream(f); ret = tsk_tree_init(&tree, &ts, TSK_SAMPLE_COUNTS); - stderr = tmp; + tsk_set_warn_stream(stderr); CU_ASSERT_EQUAL_FATAL(ret, 0); CU_ASSERT_FATAL(ftell(f) > 0); diff --git a/c/tests/testlib.h b/c/tests/testlib.h index 266f6dd239..ceee335bda 100644 --- a/c/tests/testlib.h +++ b/c/tests/testlib.h @@ -31,7 +31,6 @@ #include #include -#define _TSK_WORKAROUND_FALSE_CLANG_WARNING #include /* Global variables used in the test suite */ diff --git a/c/tskit/core.c b/c/tskit/core.c index 6ca173ecd6..f13efb2a48 100644 --- a/c/tskit/core.c +++ b/c/tskit/core.c @@ -44,7 +44,7 @@ get_random_bytes(uint8_t *buf) { /* Based on CPython's code in bootstrap_hash.c */ int ret = TSK_ERR_GENERATE_UUID; - HCRYPTPROV hCryptProv = NULL; + HCRYPTPROV hCryptProv = (HCRYPTPROV) NULL; if (!CryptAcquireContext( &hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { @@ -54,13 +54,13 @@ get_random_bytes(uint8_t *buf) goto out; } if (!CryptReleaseContext(hCryptProv, 0)) { - hCryptProv = NULL; + hCryptProv = (HCRYPTPROV) NULL; goto out; } - hCryptProv = NULL; + hCryptProv = (HCRYPTPROV) NULL; ret = 0; out: - if (hCryptProv != NULL) { + if (hCryptProv != (HCRYPTPROV) NULL) { CryptReleaseContext(hCryptProv, 0); } return ret; @@ -715,6 +715,38 @@ tsk_is_unknown_time(double val) return nan_union.i == TSK_UNKNOWN_TIME_HEX; } +/* Work around a bug which seems to show up on various mixtures of + * compiler and libc versions, where isfinite and isnan result in + * spurious warnings about casting down to float. The original issue + * is here: + * https://github.com/tskit-dev/tskit/issues/721 + * + * The simplest approach seems to be to use the builtins where they + * are available (clang and gcc), and to use the library macro + * otherwise. There would be no disadvantage to using the builtin + * version, so there's no real harm in this approach. + */ + +bool +tsk_isnan(double val) +{ +#if defined(__GNUC__) + return __builtin_isnan(val); +#else + return isnan(val); +#endif +} + +bool +tsk_isfinite(double val) +{ +#if defined(__GNUC__) + return __builtin_isfinite(val); +#else + return isfinite(val); +#endif +} + void * tsk_malloc(tsk_size_t size) { @@ -782,6 +814,44 @@ tsk_memcmp(const void *s1, const void *s2, tsk_size_t size) return memcmp(s1, s2, (size_t) size); } +/* We can't initialise the streams to their real default values because + * of limitations on static initialisers. To work around this, we initialise + * them to NULL and then set the value to the required standard stream + * when called. */ + +FILE *_tsk_debug_stream = NULL; +FILE *_tsk_warn_stream = NULL; + +void +tsk_set_debug_stream(FILE *f) +{ + _tsk_debug_stream = f; +} + +FILE * +tsk_get_debug_stream(void) +{ + if (_tsk_debug_stream == NULL) { + _tsk_debug_stream = stdout; + } + return _tsk_debug_stream; +} + +void +tsk_set_warn_stream(FILE *f) +{ + _tsk_warn_stream = f; +} + +FILE * +tsk_get_warn_stream(void) +{ + if (_tsk_warn_stream == NULL) { + _tsk_warn_stream = stderr; + } + return _tsk_warn_stream; +} + /* AVL Tree implementation. This is based directly on Knuth's implementation * in TAOCP. See the python/tests/test_avl_tree.py for more information, * and equivalent code annotated with the original algorithm listing. diff --git a/c/tskit/core.h b/c/tskit/core.h index 030339bcaf..d04d031e38 100644 --- a/c/tskit/core.h +++ b/c/tskit/core.h @@ -40,28 +40,6 @@ extern "C" { #include #include -#if defined(_TSK_WORKAROUND_FALSE_CLANG_WARNING) && defined(__clang__) -/* Work around bug in clang >= 6.0, https://github.com/tskit-dev/tskit/issues/721 - * (note: fixed in clang January 2019) - * This workaround does some nasty fiddling with builtins and is only intended to - * be used within the library. To turn it on, make sure - * _TSK_WORKAROUND_FALSE_CLANG_WARNING is defined before including any tskit - * headers. - */ -#if __has_builtin(__builtin_isnan) -#undef isnan -#define isnan __builtin_isnan -#else -abort(); -#endif -#if __has_builtin(__builtin_isfinite) -#undef isfinite -#define isfinite __builtin_isfinite -#else -abort(); -#endif -#endif - #ifdef __GNUC__ #define TSK_WARN_UNUSED __attribute__((warn_unused_result)) #define TSK_UNUSED(x) TSK_UNUSED_##x __attribute__((__unused__)) @@ -513,6 +491,11 @@ value and returns true iff it is equal to the specific NaN value TSK_UNKNOWN_TIM */ bool tsk_is_unknown_time(double val); +/* We define local versions of isnan and isfinite to workaround some portability + * issues. */ +bool tsk_isnan(double val); +bool tsk_isfinite(double val); + #define TSK_UUID_SIZE 36 int tsk_generate_uuid(char *dest, int flags); @@ -526,6 +509,12 @@ void *tsk_memcpy(void *dest, const void *src, tsk_size_t size); void *tsk_memmove(void *dest, const void *src, tsk_size_t size); int tsk_memcmp(const void *s1, const void *s2, tsk_size_t size); +/* Developer debug utilities. These are **not** threadsafe */ +void tsk_set_debug_stream(FILE *f); +FILE *tsk_get_debug_stream(void); +void tsk_set_warn_stream(FILE *f); +FILE *tsk_get_warn_stream(void); + #ifdef __cplusplus } #endif diff --git a/c/tskit/tables.c b/c/tskit/tables.c index a12898422c..6bea66a66e 100644 --- a/c/tskit/tables.c +++ b/c/tskit/tables.c @@ -32,7 +32,6 @@ #include #include -#define _TSK_WORKAROUND_FALSE_CLANG_WARNING #include #define TABLE_SEP "-----------------------------------------\n" @@ -9120,7 +9119,7 @@ tsk_table_collection_check_node_integrity( for (j = 0; j < self->nodes.num_rows; j++) { node_time = self->nodes.time[j]; - if (!isfinite(node_time)) { + if (!tsk_isfinite(node_time)) { ret = TSK_ERR_TIME_NONFINITE; goto out; } @@ -9191,7 +9190,7 @@ tsk_table_collection_check_edge_integrity( goto out; } /* Spatial requirements for edges */ - if (!(isfinite(left) && isfinite(right))) { + if (!(tsk_isfinite(left) && tsk_isfinite(right))) { ret = TSK_ERR_GENOME_COORDS_NONFINITE; goto out; } @@ -9269,7 +9268,7 @@ tsk_table_collection_check_site_integrity( for (j = 0; j < sites.num_rows; j++) { position = sites.position[j]; /* Spatial requirements */ - if (!isfinite(position)) { + if (!tsk_isfinite(position)) { ret = TSK_ERR_BAD_SITE_POSITION; goto out; } @@ -9335,7 +9334,7 @@ tsk_table_collection_check_mutation_integrity( mutation_time = mutations.time[j]; unknown_time = tsk_is_unknown_time(mutation_time); if (!unknown_time) { - if (!isfinite(mutation_time)) { + if (!tsk_isfinite(mutation_time)) { ret = TSK_ERR_TIME_NONFINITE; goto out; } @@ -9437,7 +9436,7 @@ tsk_table_collection_check_migration_integrity( } } time = migrations.time[j]; - if (!isfinite(time)) { + if (!tsk_isfinite(time)) { ret = TSK_ERR_TIME_NONFINITE; goto out; } @@ -9451,7 +9450,7 @@ tsk_table_collection_check_migration_integrity( right = migrations.right[j]; /* Spatial requirements */ /* TODO it's a bit misleading to use the edge-specific errors here. */ - if (!(isfinite(left) && isfinite(right))) { + if (!(tsk_isfinite(left) && tsk_isfinite(right))) { ret = TSK_ERR_GENOME_COORDS_NONFINITE; goto out; } @@ -10560,7 +10559,7 @@ tsk_table_collection_simplify(tsk_table_collection_t *self, const tsk_id_t *samp goto out; } if (!!(options & TSK_DEBUG)) { - simplifier_print_state(&simplifier, stdout); + simplifier_print_state(&simplifier, tsk_get_debug_stream()); } /* The indexes are invalidated now so drop them */ ret = tsk_table_collection_drop_index(self, 0); @@ -10634,7 +10633,7 @@ tsk_table_collection_ibd_segments(const tsk_table_collection_t *self, goto out; } if (!!(options & TSK_DEBUG)) { - tsk_ibd_finder_print_state(&ibd_finder, stdout); + tsk_ibd_finder_print_state(&ibd_finder, tsk_get_debug_stream()); } out: tsk_ibd_finder_free(&ibd_finder); diff --git a/c/tskit/trees.c b/c/tskit/trees.c index 9c16d884fa..b3f28935a9 100644 --- a/c/tskit/trees.c +++ b/c/tskit/trees.c @@ -3209,9 +3209,10 @@ tsk_tree_init(tsk_tree_t *self, const tsk_treeseq_t *tree_sequence, tsk_flags_t goto out; } if (options & TSK_SAMPLE_COUNTS) { - fprintf(stderr, "TSK_SAMPLE_COUNTS is no longer supported. " - "Sample counts are tracked by default since 0.99.3, " - "Please use TSK_NO_SAMPLE_COUNTS to turn off sample counts."); + fprintf(tsk_get_warn_stream(), + "TSK_SAMPLE_COUNTS is no longer supported. " + "Sample counts are tracked by default since 0.99.3, " + "Please use TSK_NO_SAMPLE_COUNTS to turn off sample counts."); } num_nodes = tree_sequence->tables->nodes.num_rows; num_samples = tree_sequence->num_samples;