Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions .github/workflows/manifold.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions bindings/c/include/manifold/manifoldc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 changes: 4 additions & 0 deletions bindings/c/manifoldc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

#include "manifold/manifoldc.h"

#ifndef MANIFOLD_NO_IOSTREAM
#include <sstream>
#endif
#include <vector>

#include "conv.h"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1075,6 +1078,7 @@ void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
WriteOBJ(ss, *m);
callback(ss.str().data(), args);
}
#endif

#ifdef __cplusplus
}
Expand Down
30 changes: 30 additions & 0 deletions cmake/manifoldDeps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
153 changes: 153 additions & 0 deletions cmake/patches/0001-clipper2-no-iostream.patch
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions cmake/patches/apply-clipper2-patch.cmake
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions include/manifold/manifold.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading