#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" FREECAD_SOURCE_DIR="${ROOT_DIR}/.cache/freecad/FreeCAD" OVERLAY_DIR="${ROOT_DIR}/.cache/toolchains/freecad-naming-sdk/source-occt8" OCCT_MIGRATION="${ROOT_DIR}/.cache/occt/occt/adm/scripts/migration_800/migrate_raise_to_throw.py" OCCT_TYPEDEF_MIGRATION="${ROOT_DIR}/.cache/occt/occt/adm/scripts/migration_800/replace_typedefs.py" OCCT_TYPEDEF_MAP="${ROOT_DIR}/.cache/occt/occt/adm/scripts/migration_800/collected_typedefs.json" OCCT_TYPEDEF_RESULTS="adm/scripts/migration_800/replacement_results.json" LOCKED_COMMIT="0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" [[ -f "${FREECAD_SOURCE_DIR}/CMakeLists.txt" ]] || { echo "FreeCAD source is missing: ${FREECAD_SOURCE_DIR}" >&2; exit 1; } [[ -f "${OCCT_MIGRATION}" ]] || { echo "OCCT 8 migration script is missing: ${OCCT_MIGRATION}" >&2; exit 1; } [[ -f "${OCCT_TYPEDEF_MIGRATION}" && -f "${OCCT_TYPEDEF_MAP}" ]] || { echo "OCCT 8 typedef migration inputs are missing" >&2; exit 1; } git -C "${ROOT_DIR}/.cache/occt/occt" diff --quiet -- "${OCCT_TYPEDEF_RESULTS}" || { echo "OCCT migration result file is already modified" >&2; exit 1; } cleanup_migration_result() { git -C "${ROOT_DIR}/.cache/occt/occt" restore -- "${OCCT_TYPEDEF_RESULTS}" } trap cleanup_migration_result EXIT [[ "$(git -C "${FREECAD_SOURCE_DIR}" rev-parse HEAD)" == "${LOCKED_COMMIT}" ]] || { echo "FreeCAD source commit is not locked" >&2; exit 1; } git -C "${FREECAD_SOURCE_DIR}" diff --quiet git -C "${FREECAD_SOURCE_DIR}" diff --cached --quiet if [[ -e "${OVERLAY_DIR}" && ! -f "${OVERLAY_DIR}/.git" ]]; then echo "Refusing to replace non-worktree overlay: ${OVERLAY_DIR}" >&2 exit 1 fi if [[ -f "${OVERLAY_DIR}/.git" ]]; then git -C "${FREECAD_SOURCE_DIR}" worktree remove --force "${OVERLAY_DIR}" fi git -C "${FREECAD_SOURCE_DIR}" worktree prune git -C "${FREECAD_SOURCE_DIR}" worktree add --detach "${OVERLAY_DIR}" "${LOCKED_COMMIT}" # The OCCT 8 script locates its own source root, so import it as a module and # apply the documented Raise-to-throw phase only to FreeCAD's source overlay. PYTHONDONTWRITEBYTECODE=1 python3 - "${OCCT_MIGRATION}" "${OVERLAY_DIR}/src" <<'PY' import importlib.util import pathlib import sys script = pathlib.Path(sys.argv[1]) source = pathlib.Path(sys.argv[2]) spec = importlib.util.spec_from_file_location("occt8_raise_migration", script) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) files = module.find_files_with_raise(source) for path in files: module.process_file(path) print(f"Applied OCCT 8 Raise-to-throw migration to {len(files)} FreeCAD source files") PY PYTHONDONTWRITEBYTECODE=1 python3 "${OCCT_TYPEDEF_MIGRATION}" \ "${OVERLAY_DIR}/src" --input "${OCCT_TYPEDEF_MAP}" --jobs "${JOBS:-4}" # replace_typedefs.py always writes a diagnostic beside the pinned tool. Its # content is not an SDK input, so restore it immediately after the overlay run. cleanup_migration_result # OCCT 8 moved iterator typedefs into their collection headers. The official # typedef migration replaces usages but intentionally leaves include-only # references untouched, so map the removed OCCT 7 header names explicitly. while IFS='|' read -r removed_header collection_header; do mapfile -d '' include_files < <( rg -l -0 -F "#include <${removed_header}>" "${OVERLAY_DIR}/src" || true ) if (( ${#include_files[@]} > 0 )); then sed -i "s|#include <${removed_header}>|#include <${collection_header}>|g" "${include_files[@]}" fi done <<'EOF' BRepCheck_ListIteratorOfListOfStatus.hxx|BRepCheck_ListOfStatus.hxx TColStd_ListIteratorOfListOfTransient.hxx|TColStd_ListOfTransient.hxx TColStd_MapIteratorOfMapOfTransient.hxx|TColStd_MapOfTransient.hxx TopTools_DataMapIteratorOfDataMapOfIntegerListOfShape.hxx|TopTools_DataMapOfIntegerListOfShape.hxx TopTools_DataMapIteratorOfDataMapOfShapeShape.hxx|TopTools_DataMapOfShapeShape.hxx TopTools_ListIteratorOfListOfShape.hxx|TopTools_ListOfShape.hxx EOF # Standard_Failure no longer participates in OCCT RTTI. OCCT 8 documents # ExceptionType() as the source-compatible exception class-name API. while IFS='|' read -r legacy_expression replacement_expression; do mapfile -d '' exception_files < <( rg -l -0 -F "${legacy_expression}" "${OVERLAY_DIR}/src" || true ) if (( ${#exception_files[@]} > 0 )); then sed -i "s|${legacy_expression}|${replacement_expression}|g" "${exception_files[@]}" fi done <<'EOF' e.DynamicType()->get_type_name()|e.ExceptionType() e.DynamicType()->Name()|e.ExceptionType() ex.DynamicType()->Name()|ex.ExceptionType() EOF # BRepAdaptor_Curve exposes these inherited parameter accessors directly in # OCCT 8; the removed BRepLProp_CurveTool only forwarded these calls here. while IFS='|' read -r legacy_expression replacement_expression; do mapfile -d '' curve_tool_files < <( rg -l -0 -F "${legacy_expression}" "${OVERLAY_DIR}/src" || true ) if (( ${#curve_tool_files[@]} > 0 )); then sed -i "s|${legacy_expression}|${replacement_expression}|g" "${curve_tool_files[@]}" fi done <<'EOF' BRepLProp_CurveTool::FirstParameter(adapt)|adapt.FirstParameter() BRepLProp_CurveTool::LastParameter(adapt)|adapt.LastParameter() EOF mapfile -d '' curve_tool_include_files < <( rg -l -0 '^[[:space:]]*#[[:space:]]*include[[:space:]]*' \ "${OVERLAY_DIR}/src" || true ) if (( ${#curve_tool_include_files[@]} > 0 )); then sed -i -E '\|^[[:space:]]*#[[:space:]]*include[[:space:]]*|d' \ "${curve_tool_include_files[@]}" fi # OCCT 8 consolidates Geom2dLProp into GeomLProp while preserving the 2D # specialization's constructor and methods under the unified class name. mapfile -d '' geom2d_lprop_files < <( rg -l -0 -F 'Geom2dLProp_CLProps2d' "${OVERLAY_DIR}/src" || true ) if (( ${#geom2d_lprop_files[@]} > 0 )); then sed -i \ -e 's|#include |#include |g' \ -e 's|\bGeom2dLProp_CLProps2d\b|GeomLProp_CLProps2d|g' \ "${geom2d_lprop_files[@]}" fi # OCCT 8 adds an initializer-list constructor to NCollection_List, making # these calls ambiguous with FreeCAD's std::vector overload. The # latter was the only viable overload with the pinned FreeCAD/OCCT 7 sources. TOPO_SHAPE_EXPANSION="${OVERLAY_DIR}/src/Mod/Part/App/TopoShapeExpansion.cpp" sed -i \ -e 's|mapper.populate(MappingStatus::Modified, e, {e1, e2, e3, e4});|mapper.populate(MappingStatus::Modified, e, std::vector{e1, e2, e3, e4});|' \ -e 's|mapper.populate(MappingStatus::Generated, v, {TopExp::FirstVertex(e1)});|mapper.populate(MappingStatus::Generated, v, std::vector{TopExp::FirstVertex(e1)});|' \ -e 's|mapper.populate(MappingStatus::Generated, v, {TopExp::LastVertex(e4)});|mapper.populate(MappingStatus::Generated, v, std::vector{TopExp::LastVertex(e4)});|' \ "${TOPO_SHAPE_EXPANSION}" # Standard_Failure inherits std::exception in OCCT 8 and what() returns the # same message as the deprecated GetMessageString(). Remove the three now # unreachable duplicate fallbacks that immediately follow an identical # std::exception handler. TOPO_SHAPE_PY="${OVERLAY_DIR}/src/Mod/Part/App/TopoShapePyImp.cpp" perl -0pi -e ' s|( catch \(const std::exception& e\) \{\n PyErr_SetString\(PartExceptionOCCError, e\.what\(\)\);\n return nullptr;\n \})\n catch \(Standard_Failure& e\) \{\n(?:\n)? PyErr_SetString\(PartExceptionOCCError, e\.GetMessageString\(\)\);\n return nullptr;\n \}|$1|g ' "${TOPO_SHAPE_PY}" if git -C "${OVERLAY_DIR}" status --porcelain | awk '{print $2}' | grep -Ev '^src/' >/dev/null; then echo "OCCT 8 migration changed files outside the FreeCAD src directory" >&2 exit 1 fi remaining="$( (rg -l 'Standard_[A-Za-z0-9_]+::Raise\s*\(' "${OVERLAY_DIR}/src" -g '*.{h,hpp,hxx,c,cpp,cxx,lxx,pxx}' || true) | wc -l)" [[ "${remaining}" == "0" ]] || { echo "OCCT 8 migration left ${remaining} source files with legacy Raise calls" >&2; exit 1; } removed_iterator_headers="$( (rg -l '#include <(BRepCheck_ListIteratorOfListOfStatus|TColStd_ListIteratorOfListOfTransient|TColStd_MapIteratorOfMapOfTransient|TopTools_DataMapIteratorOfDataMapOfIntegerListOfShape|TopTools_DataMapIteratorOfDataMapOfShapeShape|TopTools_ListIteratorOfListOfShape)\.hxx>' "${OVERLAY_DIR}/src" || true) | wc -l)" [[ "${removed_iterator_headers}" == "0" ]] || { echo "OCCT 8 migration left ${removed_iterator_headers} files with removed iterator headers" >&2; exit 1; } legacy_exception_rtti="$( (rg -l '\b(e|ex)\.DynamicType\(\)->(Name|get_type_name)\(\)' "${OVERLAY_DIR}/src" || true) | wc -l)" [[ "${legacy_exception_rtti}" == "0" ]] || { echo "OCCT 8 migration left ${legacy_exception_rtti} files with legacy exception RTTI" >&2; exit 1; } legacy_curve_tool="$( (rg -l '\bBRepLProp_CurveTool\b' "${OVERLAY_DIR}/src" || true) | wc -l)" [[ "${legacy_curve_tool}" == "0" ]] || { echo "OCCT 8 migration left ${legacy_curve_tool} files using removed BRepLProp_CurveTool" >&2; exit 1; } legacy_geom2d_lprop="$( (rg -l '\bGeom2dLProp_CLProps2d\b' "${OVERLAY_DIR}/src" || true) | wc -l)" [[ "${legacy_geom2d_lprop}" == "0" ]] || { echo "OCCT 8 migration left ${legacy_geom2d_lprop} files using removed Geom2dLProp_CLProps2d" >&2; exit 1; } ambiguous_shape_mapper_calls="$( (rg -n 'mapper\.populate\(MappingStatus::(Modified|Generated), (e|v), \{' "${TOPO_SHAPE_EXPANSION}" || true) | wc -l)" [[ "${ambiguous_shape_mapper_calls}" == "0" ]] || { echo "OCCT 8 migration left ${ambiguous_shape_mapper_calls} ambiguous ShapeMapper calls" >&2; exit 1; } unreachable_occt_exception_fallbacks="$( (rg -U -n 'catch \(const std::exception& e\) \{\n PyErr_SetString\(PartExceptionOCCError, e\.what\(\)\);\n return nullptr;\n \}\n catch \(Standard_Failure& e\)' "${TOPO_SHAPE_PY}" || true) | wc -l)" [[ "${unreachable_occt_exception_fallbacks}" == "0" ]] || { echo "OCCT 8 migration left ${unreachable_occt_exception_fallbacks} unreachable Standard_Failure handlers" >&2; exit 1; } git -C "${FREECAD_SOURCE_DIR}" diff --quiet git -C "${FREECAD_SOURCE_DIR}" diff --cached --quiet echo "FreeCAD OCCT 8 candidate overlay ready: ${OVERLAY_DIR}"