From 5011e6591a1a688dda1908bd1325741767980f95 Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Sun, 3 May 2026 05:21:44 -0700 Subject: [PATCH] Add MANIFOLD_NO_IOSTREAM build-time option 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) --- .gitattributes | 4 + .github/workflows/manifold.yml | 34 ++++ CMakeLists.txt | 18 +++ README.md | 8 + bindings/c/include/manifold/manifoldc.h | 2 + bindings/c/manifoldc.cpp | 4 + cmake/manifoldDeps.cmake | 30 ++++ cmake/patches/0001-clipper2-no-iostream.patch | 153 ++++++++++++++++++ cmake/patches/apply-clipper2-patch.cmake | 32 ++++ include/manifold/manifold.h | 4 + src/CMakeLists.txt | 11 ++ src/impl.cpp | 8 + test/boolean_complex_test.cpp | 2 + test/manifold_test.cpp | 2 + test/manifoldc_test.cpp | 6 + test/polygon_test.cpp | 11 ++ test/test_main.cpp | 15 ++ 17 files changed, 344 insertions(+) create mode 100644 .gitattributes create mode 100644 cmake/patches/0001-clipper2-no-iostream.patch create mode 100644 cmake/patches/apply-clipper2-patch.cmake diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..888e025bd7 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.github/workflows/manifold.yml b/.github/workflows/manifold.yml index 2df52438ab..f260370975 100644 --- a/.github/workflows/manifold.yml +++ b/.github/workflows/manifold.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index e83223ebec..1b543ffbfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index e6d33464cb..5f6ddd8b12 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,14 @@ CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`): See profiling section below. - `ASSIMP_ENABLE=[, ON]`: Enable integration with assimp, which is needed for some of the utilities in `extras`. - `MANIFOLD_STRICT=[, ON]`: Treat compile warnings as fatal build errors. +- `MANIFOLD_NO_IOSTREAM=[, 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=[, ON]`: Use builtin version of tbb. diff --git a/bindings/c/include/manifold/manifoldc.h b/bindings/c/include/manifold/manifoldc.h index 608f6eb19e..08ee60d22e 100644 --- a/bindings/c/include/manifold/manifoldc.h +++ b/bindings/c/include/manifold/manifoldc.h @@ -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 diff --git a/bindings/c/manifoldc.cpp b/bindings/c/manifoldc.cpp index f411de7b94..3e6d3fd7ac 100644 --- a/bindings/c/manifoldc.cpp +++ b/bindings/c/manifoldc.cpp @@ -14,7 +14,9 @@ #include "manifold/manifoldc.h" +#ifndef MANIFOLD_NO_IOSTREAM #include +#endif #include #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 } diff --git a/cmake/manifoldDeps.cmake b/cmake/manifoldDeps.cmake index bf51371ad0..78ba8f076c 100644 --- a/cmake/manifoldDeps.cmake +++ b/cmake/manifoldDeps.cmake @@ -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= + -P ${CMAKE_CURRENT_LIST_DIR}/patches/apply-clipper2-patch.cmake ) FetchContent_MakeAvailable(Clipper2) set_property( diff --git a/cmake/patches/0001-clipper2-no-iostream.patch b/cmake/patches/0001-clipper2-no-iostream.patch new file mode 100644 index 0000000000..4f4c78199d --- /dev/null +++ b/cmake/patches/0001-clipper2-no-iostream.patch @@ -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} + $<$:CLIPPER2_HI_PRECISION> ++ $<$: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} + $<$:CLIPPER2_HI_PRECISION> ++ $<$: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 + #include + #include ++#ifndef CLIPPER2_NO_IOSTREAM + #include ++#endif + #include + #include + #include +@@ -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& rect) { + os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") "; + return os; + } ++#endif + }; + + template +@@ -498,6 +506,7 @@ namespace Clipper2Lib + return Rect(xmin, ymin, xmax, ymax); + } + ++#ifndef CLIPPER2_NO_IOSTREAM + template + std::ostream& operator << (std::ostream& outstream, const Path& path) + { +@@ -518,6 +527,7 @@ namespace Clipper2Lib + outstream << p; + return outstream; + } ++#endif + + + template +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 + 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) + { diff --git a/cmake/patches/apply-clipper2-patch.cmake b/cmake/patches/apply-clipper2-patch.cmake new file mode 100644 index 0000000000..31518fcdad --- /dev/null +++ b/cmake/patches/apply-clipper2-patch.cmake @@ -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= -DSOURCE_DIR= -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() diff --git a/include/manifold/manifold.h b/include/manifold/manifold.h index 20f7c0c0a9..80c49765c7 100644 --- a/include/manifold/manifold.h +++ b/include/manifold/manifold.h @@ -273,8 +273,10 @@ using MeshGL = MeshGLP; */ using MeshGL64 = MeshGLP; +#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. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d9bf89634d..8a760b2b9c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 — `` paths are only useful +# if you can `` 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 diff --git a/src/impl.cpp b/src/impl.cpp index ce4a20900d..0dfb96a014 100644 --- a/src/impl.cpp +++ b/src/impl.cpp @@ -19,11 +19,15 @@ #include #include #include +#ifndef MANIFOLD_NO_IOSTREAM #include +#endif #include #include #include +#ifndef MANIFOLD_NO_IOSTREAM #include +#endif #include "csg_tree.h" #include "disjoint_sets.h" @@ -61,6 +65,7 @@ int GetLabels(std::vector& components, return uf.connectedComponents(components); } +#ifndef MANIFOLD_NO_IOSTREAM template 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 epsilon) { @@ -860,4 +867,5 @@ bool Manifold::WriteOBJ(std::ostream& stream) const { stream << *this->GetCsgLeafNode().GetImpl(); return true; } +#endif } // namespace manifold diff --git a/test/boolean_complex_test.cpp b/test/boolean_complex_test.cpp index 2b83f0a53e..b878702794 100644 --- a/test/boolean_complex_test.cpp +++ b/test/boolean_complex_test.cpp @@ -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); diff --git a/test/manifold_test.cpp b/test/manifold_test.cpp index e8c21be30c..8ae2909fca 100644 --- a/test/manifold_test.cpp +++ b/test/manifold_test.cpp @@ -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; diff --git a/test/manifoldc_test.cpp b/test/manifoldc_test.cpp index e84eeda601..3a27be2fb3 100644 --- a/test/manifoldc_test.cpp +++ b/test/manifoldc_test.cpp @@ -1,7 +1,9 @@ #include "manifold/manifoldc.h" #include +#ifndef MANIFOLD_NO_IOSTREAM #include +#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); diff --git a/test/polygon_test.cpp b/test/polygon_test.cpp index eaf62a2794..2e1824fbda 100644 --- a/test/polygon_test.cpp +++ b/test/polygon_test.cpp @@ -15,7 +15,9 @@ #include "manifold/polygon.h" #include +#ifndef MANIFOLD_NO_IOSTREAM #include +#endif #include #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 diff --git a/test/test_main.cpp b/test/test_main.cpp index 39321a8806..5da56b65a2 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#ifndef MANIFOLD_NO_FILESYSTEM #include #include +#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