Add MANIFOLD_NO_IOSTREAM build-time option (#1690)

Strips iostream- and filesystem-using bits from the public API and
tests, for freestanding/embedded consumers (e.g.,
wasm32-unknown-unknown via wasm-cxx-shim). Default OFF.

When ON, defines MANIFOLD_NO_IOSTREAM and MANIFOLD_NO_FILESYSTEM as
PUBLIC compile defs on manifold, gates iostream-using TESTs at
compile time, and triggers CLIPPER2_NO_IOSTREAM=ON for the bundled
Clipper2 via a carry-patch tracking AngusJohnson/Clipper2#1094
(drops once that lands and the SHA pin moves past it). Incompatible
with MANIFOLD_DEBUG / MANIFOLD_TIMING (FATAL_ERROR).

Discussion: https://github.com/elalish/manifold/discussions/1046

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zach Loafman
2026-05-05 00:49:53 +08:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 5f95a3ac0e
commit e73c0781ac
17 changed files with 344 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# Carry-patches must keep LF line endings even when checked out on
# Windows with default core.autocrlf=true; otherwise `git apply`
# refuses them on EOL mismatch with the (non-CRLF) target tree.
*.patch text eol=lf
+34
View File
@@ -175,6 +175,40 @@ jobs:
sudo cmake --install build
./scripts/test-cmake.sh
build_no_iostream:
name: NO_IOSTREAM (clang)
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install apt packages
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: cmake clang libgtest-dev
- name: Configure + build with MANIFOLD_NO_IOSTREAM=ON, MANIFOLD_TEST=ON
run: |
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DMANIFOLD_NO_IOSTREAM=ON \
-DMANIFOLD_USE_BUILTIN_CLIPPER2=ON \
-DMANIFOLD_STRICT=ON \
-DMANIFOLD_TEST=ON \
-DMANIFOLD_PYBIND=OFF \
. -B build
cmake --build build
- name: Run manifold_test (iostream-using TESTs are gated out)
run: ./build/test/manifold_test
- name: Verify conflict guard fires (NO_IOSTREAM + DEBUG)
run: |
if cmake -DMANIFOLD_NO_IOSTREAM=ON -DMANIFOLD_DEBUG=ON \
-DMANIFOLD_USE_BUILTIN_CLIPPER2=ON \
. -B /tmp/conflict-debug 2>&1; then
echo "FAIL: configure should have failed for NO_IOSTREAM + DEBUG"
exit 1
fi
build_gcc_codecov:
name: code coverage
timeout-minutes: 45
+18
View File
@@ -42,6 +42,11 @@ option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
option(MANIFOLD_ASSERT "Enable assertions - requires MANIFOLD_DEBUG" OFF)
option(MANIFOLD_TIMING "Enable Boolean3 phase timing without debug overhead" OFF)
option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" OFF)
option(
MANIFOLD_NO_IOSTREAM
"Strip iostream- and filesystem-using bits from the public API and tests; useful for freestanding/embedded builds. When ON, defines both MANIFOLD_NO_IOSTREAM and MANIFOLD_NO_FILESYSTEM."
OFF
)
option(
MANIFOLD_DOWNLOADS
"Allow Manifold build to download missing dependencies"
@@ -100,6 +105,19 @@ mark_as_advanced(TRACY_MEMORY_USAGE)
mark_as_advanced(MANIFOLD_FUZZ)
mark_as_advanced(ASSIMP_ENABLE)
# MANIFOLD_NO_IOSTREAM strips iostream- and filesystem-using bits
# from the public API and from the test files that use them.
# Incompatible with options that emit diagnostic output via iostream:
# MANIFOLD_DEBUG and MANIFOLD_TIMING (both use std::cout).
# MANIFOLD_TEST=ON is supported — iostream-using TEST(...) blocks
# in manifold_test/polygon_test/manifoldc_test are gated out under
# the macro; the test executable still builds and runs the rest.
if(MANIFOLD_NO_IOSTREAM AND (MANIFOLD_DEBUG OR MANIFOLD_TIMING))
message(FATAL_ERROR
"MANIFOLD_NO_IOSTREAM is incompatible with MANIFOLD_DEBUG / MANIFOLD_TIMING "
"(those options use std::cout for diagnostic output).")
endif()
# Always build position independent code for relocatability
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+8
View File
@@ -124,6 +124,14 @@ CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
See profiling section below.
- `ASSIMP_ENABLE=[<OFF>, ON]`: Enable integration with assimp, which is needed for some of the utilities in `extras`.
- `MANIFOLD_STRICT=[<OFF>, ON]`: Treat compile warnings as fatal build errors.
- `MANIFOLD_NO_IOSTREAM=[<OFF>, ON]`: Strip iostream- and filesystem-using
bits from the public API and tests; useful for freestanding/embedded
builds (e.g., `wasm32-unknown-unknown`). Defines both
`MANIFOLD_NO_IOSTREAM` and `MANIFOLD_NO_FILESYSTEM` as PUBLIC compile
definitions. The test suite still builds + runs — iostream-using
TESTs in `manifold_test`/`polygon_test`/`manifoldc_test` are gated
out under the macro. Incompatible with `MANIFOLD_DEBUG` /
`MANIFOLD_TIMING` (which use `std::cout` for diagnostic output).
Dependency version override:
- `MANIFOLD_USE_BUILTIN_TBB=[<OFF>, ON]`: Use builtin version of tbb.
+2
View File
@@ -542,6 +542,7 @@ void manifold_delete_rect(ManifoldRect* b);
void manifold_delete_triangulation(ManifoldTriangulation* m);
void manifold_delete_execution_context(ManifoldExecutionContext* ctx);
#ifndef MANIFOLD_NO_IOSTREAM
// MeshIO / Export
// Import a manifold from a Wavefront obj file.
@@ -570,6 +571,7 @@ void manifold_write_obj(ManifoldManifold* manifold,
// passing additional data into the callback.
void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
void (*callback)(char*, void*), void* args);
#endif
#ifdef __cplusplus
}
#endif
+4
View File
@@ -14,7 +14,9 @@
#include "manifold/manifoldc.h"
#ifndef MANIFOLD_NO_IOSTREAM
#include <sstream>
#endif
#include <vector>
#include "conv.h"
@@ -1048,6 +1050,7 @@ void manifold_destruct_execution_context(ManifoldExecutionContext* ctx) {
// IO
#ifndef MANIFOLD_NO_IOSTREAM
ManifoldManifold* manifold_read_obj(void* mem, char* obj_file) {
std::istringstream iss(obj_file);
Manifold m = Manifold::ReadOBJ(iss);
@@ -1075,6 +1078,7 @@ void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
WriteOBJ(ss, *m);
callback(ss.str().data(), args);
}
#endif
#ifdef __cplusplus
}
+30
View File
@@ -119,6 +119,22 @@ if(MANIFOLD_CROSS_SECTION)
CACHE BOOL
"Preempt cache default of USINGZ (we only use 2d)"
)
# When manifold is built with MANIFOLD_NO_IOSTREAM, also strip
# iostream from the bundled Clipper2 — manifold doesn't call any
# of Clipper2's stream operators internally, so passing this
# through is safe regardless. The CLIPPER2_NO_IOSTREAM macro is
# added by the carry-patch below; once Clipper2#1094 lands and
# the SHA pin moves past it, the patch drops and the option is
# honored natively.
if(MANIFOLD_NO_IOSTREAM)
set(
CLIPPER2_NO_IOSTREAM
ON
CACHE BOOL
"Strip iostream-using overloads from Clipper2 (set by manifold when MANIFOLD_NO_IOSTREAM=ON)"
FORCE
)
endif()
FetchContent_Declare(
Clipper2
GIT_REPOSITORY https://github.com/AngusJohnson/Clipper2.git
@@ -127,6 +143,20 @@ if(MANIFOLD_CROSS_SECTION)
GIT_PROGRESS TRUE
SOURCE_SUBDIR
CPP
# Disable Windows autocrlf on the clone so the carry-patch (which
# is LF-only) applies cleanly. Default core.autocrlf=true on
# Windows would convert all LFs to CRLFs in the working tree, and
# `git apply` then fails on the line-ending mismatch.
GIT_CONFIG
core.autocrlf=false
# Carry-patch: tracks AngusJohnson/Clipper2#1094 (CLIPPER2_NO_IOSTREAM
# macro guards). Drops once that PR lands and the SHA pin moves past
# it. Applied via wrapper script so re-configures (which re-trigger
# PATCH_COMMAND) don't fail when the patch is already applied.
PATCH_COMMAND ${CMAKE_COMMAND}
-DPATCH_FILE=${CMAKE_CURRENT_LIST_DIR}/patches/0001-clipper2-no-iostream.patch
-DSOURCE_DIR=<SOURCE_DIR>
-P ${CMAKE_CURRENT_LIST_DIR}/patches/apply-clipper2-patch.cmake
)
FetchContent_MakeAvailable(Clipper2)
set_property(
@@ -0,0 +1,153 @@
Carry-patch: add a CLIPPER2_NO_IOSTREAM build option (off by default)
that strips iostream-using overloads from Clipper2's public headers,
and propagate the macro through Clipper2's PUBLIC compile definitions
so consumers see it. Lets manifold pass MANIFOLD_NO_IOSTREAM=ON
through to the bundled Clipper2 (e.g., for wasm32-unknown-unknown
consumers via the wasm-cxx-shim integration).
Tracks AngusJohnson/Clipper2#1094 — once that lands and manifold's
Clipper2 SHA pin moves past it, this patch drops.
Generated against Clipper2 SHA 46f639177fe418f9689e8ddb74f08a870c71f5b4
(the SHA manifoldDeps.cmake currently pins). The headers at this SHA
are byte-identical to upstream main, so the patch tracks #1094's diff
exactly.
Apply with: git apply -p1 0001-clipper2-no-iostream.patch
diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt
index 7642e86..b327296 100644
--- a/CPP/CMakeLists.txt
+++ b/CPP/CMakeLists.txt
@@ -17,6 +17,7 @@ option(CLIPPER2_HI_PRECISION "Caution: enabling this will compromise performance
option(CLIPPER2_UTILS "Build utilities" ON)
option(CLIPPER2_EXAMPLES "Build examples" ON)
option(CLIPPER2_TESTS "Build tests" ON)
+option(CLIPPER2_NO_IOSTREAM "Disable iostream-using header overloads" OFF)
option(USE_EXTERNAL_GTEST "Use system-wide installed GoogleTest" OFF)
option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" OFF)
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
@@ -69,6 +70,7 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY"))
Clipper2 PUBLIC
CLIPPER2_MAX_DECIMAL_PRECISION=${CLIPPER2_MAX_DECIMAL_PRECISION}
$<$<BOOL:${CLIPPER2_HI_PRECISION}>:CLIPPER2_HI_PRECISION>
+ $<$<BOOL:${CLIPPER2_NO_IOSTREAM}>:CLIPPER2_NO_IOSTREAM>
)
target_include_directories(
@@ -95,6 +97,7 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "OFF"))
USINGZ
CLIPPER2_MAX_DECIMAL_PRECISION=${CLIPPER2_MAX_DECIMAL_PRECISION}
$<$<BOOL:${CLIPPER2_HI_PRECISION}>:CLIPPER2_HI_PRECISION>
+ $<$<BOOL:${CLIPPER2_NO_IOSTREAM}>:CLIPPER2_NO_IOSTREAM>
)
target_include_directories(
Clipper2Z PUBLIC
diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h
index 99a5205..d3af0a0 100644
--- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h
+++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h
@@ -14,7 +14,9 @@
#include <cstdint>
#include <vector>
#include <string>
+#ifndef CLIPPER2_NO_IOSTREAM
#include <iostream>
+#endif
#include <algorithm>
#include <numeric>
#include <cmath>
@@ -166,11 +168,13 @@ namespace Clipper2Lib
void SetZ(const z_type z_value) { z = z_value; }
+#ifndef CLIPPER2_NO_IOSTREAM
friend std::ostream& operator<<(std::ostream& os, const Point& point)
{
os << point.x << "," << point.y << "," << point.z;
return os;
}
+#endif
#else
@@ -203,11 +207,13 @@ namespace Clipper2Lib
return Point(x * scale, y * scale);
}
+#ifndef CLIPPER2_NO_IOSTREAM
friend std::ostream& operator<<(std::ostream& os, const Point& point)
{
os << point.x << "," << point.y;
return os;
}
+#endif
#endif
friend bool operator==(const Point& a, const Point& b)
@@ -396,10 +402,12 @@ namespace Clipper2Lib
return result;
}
+#ifndef CLIPPER2_NO_IOSTREAM
friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") ";
return os;
}
+#endif
};
template <typename T1, typename T2>
@@ -498,6 +506,7 @@ namespace Clipper2Lib
return Rect<T>(xmin, ymin, xmax, ymax);
}
+#ifndef CLIPPER2_NO_IOSTREAM
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
{
@@ -518,6 +527,7 @@ namespace Clipper2Lib
outstream << p;
return outstream;
}
+#endif
template <typename T1, typename T2>
diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h
index fe1e299..deb1f7b 100644
--- a/CPP/Clipper2Lib/include/clipper2/clipper.h
+++ b/CPP/Clipper2Lib/include/clipper2/clipper.h
@@ -309,6 +309,7 @@ namespace Clipper2Lib {
return true;
}
+#ifndef CLIPPER2_NO_IOSTREAM
static void OutlinePolyPath(std::ostream& os,
size_t idx, bool isHole, size_t count, const std::string& preamble)
{
@@ -338,6 +339,7 @@ namespace Clipper2Lib {
if (pp.Child(i)->Count())
details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " ");
}
+#endif
template<typename T, typename U>
inline constexpr void MakePathGeneric(const T an_array,
@@ -377,6 +379,7 @@ namespace Clipper2Lib {
} // end details namespace
+#ifndef CLIPPER2_NO_IOSTREAM
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
{
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
@@ -399,6 +402,7 @@ namespace Clipper2Lib {
if (!pp.Level()) os << std::endl;
return os;
}
+#endif
inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
{
+32
View File
@@ -0,0 +1,32 @@
# Idempotent application of the Clipper2 carry-patch. Runs as the
# PATCH_COMMAND of FetchContent_Declare(Clipper2 ...). Re-runs of
# `cmake` (e.g., when CMakeLists.txt files in the parent project
# are edited) reinvoke the patch step; without this idempotency
# wrapper, `git apply` fails on already-patched files.
#
# Invoked via: cmake -DPATCH_FILE=<path> -DSOURCE_DIR=<src> -P this.cmake
if(NOT PATCH_FILE OR NOT SOURCE_DIR)
message(FATAL_ERROR "PATCH_FILE and SOURCE_DIR are required")
endif()
# `git apply --reverse --check` succeeds iff the patch is already applied.
execute_process(
COMMAND git apply --reverse --check --ignore-whitespace --whitespace=nowarn "${PATCH_FILE}"
WORKING_DIRECTORY "${SOURCE_DIR}"
RESULT_VARIABLE _already_applied
OUTPUT_QUIET
ERROR_QUIET
)
if(_already_applied EQUAL 0)
return()
endif()
execute_process(
COMMAND git apply --ignore-whitespace --whitespace=nowarn "${PATCH_FILE}"
WORKING_DIRECTORY "${SOURCE_DIR}"
RESULT_VARIABLE _apply_result
)
if(NOT _apply_result EQUAL 0)
message(FATAL_ERROR "Failed to apply Clipper2 carry-patch: ${PATCH_FILE}")
endif()
+4
View File
@@ -273,8 +273,10 @@ using MeshGL = MeshGLP<float>;
*/
using MeshGL64 = MeshGLP<double, uint64_t>;
#ifndef MANIFOLD_NO_IOSTREAM
MeshGL64 ReadOBJ(std::istream& stream);
bool WriteOBJ(std::ostream& stream, const MeshGL64& mesh);
#endif
/**
* @brief This library's internal representation of an oriented, 2-manifold,
@@ -517,8 +519,10 @@ class Manifold {
* ofile.close();
* @endcode
*/
#ifndef MANIFOLD_NO_IOSTREAM
static Manifold ReadOBJ(std::istream& stream);
bool WriteOBJ(std::ostream& stream) const;
#endif
/** @name Testing Hooks
* These are just for internal testing.
+11
View File
@@ -80,6 +80,7 @@ set(
MANIFOLD_ASSERT
MANIFOLD_TIMING
MANIFOLD_CROSS_SECTION
MANIFOLD_NO_IOSTREAM
TRACY_ENABLE
TRACY_MEMORY_USAGE
)
@@ -94,6 +95,16 @@ else()
target_compile_definitions(manifold PUBLIC MANIFOLD_PAR=-1)
endif()
# MANIFOLD_NO_IOSTREAM also defines MANIFOLD_NO_FILESYSTEM (the two
# track together in practice — `<filesystem>` paths are only useful
# if you can `<fstream>` them, which needs iostream). Source code
# uses the macros separately for semantic clarity (NO_IOSTREAM gates
# the iostream-using public API; NO_FILESYSTEM gates filesystem use
# in test_main.cpp's fixture helpers + main()).
if(MANIFOLD_NO_IOSTREAM)
target_compile_definitions(manifold PUBLIC MANIFOLD_NO_FILESYSTEM)
endif()
target_include_directories(
manifold
PUBLIC
+8
View File
@@ -19,11 +19,15 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#ifndef MANIFOLD_NO_IOSTREAM
#include <iomanip>
#endif
#include <map>
#include <optional>
#include <regex>
#ifndef MANIFOLD_NO_IOSTREAM
#include <sstream>
#endif
#include "csg_tree.h"
#include "disjoint_sets.h"
@@ -61,6 +65,7 @@ int GetLabels(std::vector<int>& components,
return uf.connectedComponents(components);
}
#ifndef MANIFOLD_NO_IOSTREAM
template <typename T>
double FromChars(T buffer) {
double tmp;
@@ -69,6 +74,7 @@ double FromChars(T buffer) {
iss >> tmp;
return tmp;
}
#endif
} // namespace
namespace manifold {
@@ -699,6 +705,7 @@ void Manifold::Impl::IncrementMeshIDs() {
UpdateMeshID({meshIDold2new.D()}));
}
#ifndef MANIFOLD_NO_IOSTREAM
static std::ostream& WriteOBJWithEpsilon(std::ostream& stream,
const MeshGL64& mesh,
std::optional<double> epsilon) {
@@ -860,4 +867,5 @@ bool Manifold::WriteOBJ(std::ostream& stream) const {
stream << *this->GetCsgLeafNode().GetImpl();
return true;
}
#endif
} // namespace manifold
+2
View File
@@ -1468,6 +1468,7 @@ TEST(BooleanComplex, Ring) {
EXPECT_EQ(result.Status(), Manifold::Error::NoError);
}
#ifndef MANIFOLD_NO_FILESYSTEM
TEST(BooleanComplex, SelfIntersect) {
ManifoldParamGuard guard;
manifold::ManifoldParams().processOverlaps = true;
@@ -1518,6 +1519,7 @@ TEST(BooleanComplex, HullMask) {
Manifold ret = body - mask;
MeshGL mesh = ret.GetMeshGL();
}
#endif
TEST(BooleanComplex, LazyCollider) {
Manifold ele1 = Manifold::Cylinder(50, 50);
+2
View File
@@ -334,6 +334,7 @@ TEST(Manifold, ErrorPropagationSimplify) {
EXPECT_EQ(errored.Simplify().Status(), Manifold::Error::NonFiniteVertex);
}
#ifndef MANIFOLD_NO_IOSTREAM
TEST(Manifold, ObjRoundTrip) {
Manifold m = Manifold::Cube();
std::stringstream ss;
@@ -343,6 +344,7 @@ TEST(Manifold, ObjRoundTrip) {
EXPECT_EQ(m2.Status(), Manifold::Error::NoError);
EXPECT_EQ(m2.Volume(), 1);
}
#endif
TEST(Manifold, OppositeFace) {
MeshGL gl;
+6
View File
@@ -1,7 +1,9 @@
#include "manifold/manifoldc.h"
#include <cmath>
#ifndef MANIFOLD_NO_IOSTREAM
#include <fstream>
#endif
#include "gtest/gtest.h"
#include "manifold/types.h"
@@ -140,6 +142,7 @@ TEST(CBIND, include_pt_mutates_bounds) {
free(box);
}
#ifndef MANIFOLD_NO_IOSTREAM
TEST(CBIND, obj_round_trip) {
ManifoldManifold* cube =
manifold_cube(alloc_manifold_buffer(), 1.0, 1.0, 1.0, 1);
@@ -162,6 +165,7 @@ TEST(CBIND, obj_round_trip) {
free(result);
free(buffer);
}
#endif
TEST(CBIND, level_set) {
// can't convert lambda with captures to funptr
@@ -205,6 +209,7 @@ TEST(CBIND, level_set) {
ManifoldMeshGL* sdf_mesh =
manifold_get_meshgl(alloc_meshgl_buffer(), sdf_man);
#ifndef MANIFOLD_NO_IOSTREAM
if (options.exportModels) {
manifold_write_obj(
sdf_man,
@@ -215,6 +220,7 @@ TEST(CBIND, level_set) {
},
NULL);
}
#endif
EXPECT_EQ(manifold_status(sdf_man), MANIFOLD_NO_ERROR);
EXPECT_EQ(manifold_status(sdf_man_context), MANIFOLD_NO_ERROR);
+11
View File
@@ -15,7 +15,9 @@
#include "manifold/polygon.h"
#include <algorithm>
#ifndef MANIFOLD_NO_IOSTREAM
#include <fstream>
#endif
#include <limits>
#include "test.h"
@@ -79,6 +81,7 @@ class PolygonTestFixture : public testing::Test {
void TestBody() { TestPoly(polys, expectedNumTri, epsilon); }
};
#ifndef MANIFOLD_NO_IOSTREAM
void RegisterPolygonTestsFile(const std::string& filename) {
auto f = std::ifstream(filename);
EXPECT_TRUE(f.is_open());
@@ -117,8 +120,10 @@ void RegisterPolygonTestsFile(const std::string& filename) {
}
f.close();
}
#endif
} // namespace
#ifndef MANIFOLD_NO_IOSTREAM
void RegisterPolygonTests() {
std::string files[] = {"polygon_corpus.txt", "sponge.txt", "zebra.txt",
"zebra3.txt"};
@@ -132,3 +137,9 @@ void RegisterPolygonTests() {
for (auto f : files) RegisterPolygonTestsFile(dir + "/polygons/" + f);
#endif
}
#else
// Stub when MANIFOLD_NO_IOSTREAM is set: polygon corpus tests need
// std::ifstream to load fixtures, so they're skipped. test_main.cpp
// still calls RegisterPolygonTests(); this stub keeps the link clean.
void RegisterPolygonTests() {}
#endif
+15
View File
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#ifndef MANIFOLD_NO_FILESYSTEM
#include <filesystem>
#include <fstream>
#endif
#include "manifold/manifold.h"
#include "test.h"
@@ -485,6 +487,7 @@ void CheckGLEquiv(const MeshGL& mgl1, const MeshGL& mgl2) {
}
}
#ifndef MANIFOLD_NO_FILESYSTEM
Manifold ReadTestOBJ(const std::string& filename) {
return Manifold(ReadTestMeshGL64OBJ(filename));
}
@@ -511,3 +514,15 @@ void WriteTestOBJ(const std::string& filename, Manifold m) {
WriteOBJ(f, m.GetMeshGL64());
f.close();
}
#else
// Stub WriteTestOBJ for MANIFOLD_NO_FILESYSTEM builds: lets the
// `if (options.exportModels) WriteTestOBJ(...)` call sites keep
// linking. exportModels defaults false, so the runtime path is dead.
//
// ReadTestOBJ / ReadTestMeshGL64OBJ are deliberately NOT stubbed —
// tests that read fixtures depend on real geometry, and a stub
// returning an empty Manifold would silently make assertions fail
// by construction. Callers must be compile-time gated; an unguarded
// caller fails to link, which is the bright signal we want.
void WriteTestOBJ(const std::string& /*filename*/, Manifold /*m*/) {}
#endif