diff --git a/.github/workflows/ap203min.yml b/.github/workflows/ap203min.yml new file mode 100644 index 000000000..44aadf620 --- /dev/null +++ b/.github/workflows/ap203min.yml @@ -0,0 +1,90 @@ +name: AP203Min static ExternalProject + +# Builds example/ap203min/ExternalProjectBuild, which builds STEPCode as a +# static-only ExternalProject the same way OpenVSP does, then runs the result. + +on: + workflow_dispatch: + push: + pull_request: + +jobs: + ap203min: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: Ubuntu GCC + os: ubuntu-latest + cc: gcc + cxx: g++ + generator: Ninja + - name: Ubuntu Clang + os: ubuntu-latest + cc: clang + cxx: clang++ + generator: Ninja + - name: macOS Clang + os: macos-latest + cc: clang + cxx: clang++ + generator: Ninja + - name: Windows MSVC Ninja + os: windows-latest + cc: cl + cxx: cl + generator: Ninja + - name: Windows Visual Studio + os: windows-latest + # Empty generator: CMake picks the newest installed Visual Studio. + generator: "" + defaults: + run: + shell: bash + steps: + - name: Setup - CMake and Ninja + uses: lukka/get-cmake@latest + + - name: Setup - MSVC + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Checkout + uses: actions/checkout@v5 + + - name: Configure + run: | + # Build outside the checkout: the ExternalProject copies the whole source tree. + BUILD_DIR="$(cd .. && pwd)/ap203min-build" + echo "BUILD_DIR=$BUILD_DIR" >> "$GITHUB_ENV" + args=(-S example/ap203min/ExternalProjectBuild -B "$BUILD_DIR") + if [ -n "${{ matrix.generator }}" ]; then + args+=(-G "${{ matrix.generator }}") + fi + if [ -n "${{ matrix.cc }}" ]; then + args+=(-DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }}) + fi + if [ "${{ matrix.generator }}" = "Ninja" ]; then + args+=(-DCMAKE_BUILD_TYPE=Release) + fi + cmake --version + cmake "${args[@]}" + grep CMAKE_GENERATOR: "$BUILD_DIR/CMakeCache.txt" + + - name: Build + run: cmake --build "$BUILD_DIR" --config Release + + - name: No shared libraries built + run: | + ls -l "$BUILD_DIR"/sc-install/lib + if ls "$BUILD_DIR"/sc-install/lib | grep -Ei '\.(so|dylib|dll)'; then exit 1; fi + if ls "$BUILD_DIR"/sc-install/bin | grep -Ei '\.dll$'; then exit 1; fi + + - name: Test (run, and check for non-system dynamic dependencies) + run: | + cd "$BUILD_DIR" + ctest -C Release -V --output-on-failure + test -s outfile.stp + head -20 outfile.stp diff --git a/CMakeLists.txt b/CMakeLists.txt index f23d48bf3..14ac96390 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ endif (POLICY CMP0077) # SC version set(SC_VERSION_MAJOR 0) -set(SC_VERSION_MINOR 9) +set(SC_VERSION_MINOR 8) set(SC_VERSION_PATCH 1) set(SC_VERSION ${SC_VERSION_MAJOR}.${SC_VERSION_MINOR}.${SC_VERSION_PATCH}) diff --git a/example/ap203min/ExternalProjectBuild/CMakeLists.txt b/example/ap203min/ExternalProjectBuild/CMakeLists.txt index 45cce97e9..9621f2461 100644 --- a/example/ap203min/ExternalProjectBuild/CMakeLists.txt +++ b/example/ap203min/ExternalProjectBuild/CMakeLists.txt @@ -5,58 +5,19 @@ # freely copied in part or in full for any purpose. No acknowledgment is required # for the use of this file. # -project(AP203Minimum) cmake_minimum_required(VERSION 3.12) +project(AP203Minimum) INCLUDE( ExternalProject ) set(CMAKE_MODULE_PATH ${AP203Minimum_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +# Sets STEPCODE_INSTALL_DIR, STEPCODE_INCLUDE_DIR, STEPCODE_LIBRARIES, +# STEPCODE_SYSTEM_LIBRARIES and STEPCODE_DEFINITIONS. INCLUDE( External_STEPCode ) -##### -# Variables ideally set by FindSTEPCode.cmake - -IF(NOT WIN32) - set( STEPCODE_LIBRARIES - ${STEPCODE_INSTALL_DIR}/lib/libstepcore.a - ${STEPCODE_INSTALL_DIR}/lib/libstepeditor.a - ${STEPCODE_INSTALL_DIR}/lib/libstepdai.a - ${STEPCODE_INSTALL_DIR}/lib/libsteputils.a - ${STEPCODE_INSTALL_DIR}/lib/libsdai_ap203.a - ) -ELSE() - set( STEPCODE_LIBRARIES - ${STEPCODE_INSTALL_DIR}/lib/libstepcore.lib - ${STEPCODE_INSTALL_DIR}/lib/libstepeditor.lib - ${STEPCODE_INSTALL_DIR}/lib/libstepdai.lib - ${STEPCODE_INSTALL_DIR}/lib/libsteputils.lib - ${STEPCODE_INSTALL_DIR}/lib/libsdai_ap203.lib - ${STEPCODE_INSTALL_DIR}/lib/libexpress.lib - ${STEPCODE_INSTALL_DIR}/lib/libexppp.lib - shlwapi.lib - ) -ENDIF() - MESSAGE( STATUS "STEPCODE_INSTALL_DIR: " ${STEPCODE_INSTALL_DIR} ) -set( STEPCODE_INCLUDE_DIR -${STEPCODE_INSTALL_DIR}/include/stepcode -${STEPCODE_INSTALL_DIR}/include/stepcode/clstepcore -${STEPCODE_INSTALL_DIR}/include/stepcode/cldai -${STEPCODE_INSTALL_DIR}/include/stepcode/clutils -${STEPCODE_INSTALL_DIR}/include/stepcode/cleditor -${STEPCODE_INSTALL_DIR}/include/schemas/sdai_ap203 -) - -# End of variables ideally set by FindSTEPCode.cmake -###### - - -include_directories( - ${STEPCODE_INCLUDE_DIR} -) - set(SRCS ../ap203min.cpp) set(HDRS ) @@ -65,10 +26,39 @@ add_executable( ${PROJECT_NAME} ${SRCS} ${HDRS}) add_dependencies( ${PROJECT_NAME} STEPCODE ) -target_link_libraries( ${PROJECT_NAME} +target_include_directories( ${PROJECT_NAME} PRIVATE ${STEPCODE_INCLUDE_DIR} ) + +target_compile_definitions( ${PROJECT_NAME} PRIVATE ${STEPCODE_DEFINITIONS} ) + +target_link_libraries( ${PROJECT_NAME} ${STEPCODE_LIBRARIES} + ${STEPCODE_SYSTEM_LIBRARIES} ) +##### +# Tests: run the example, and verify it has no non-system dynamic dependencies. + +enable_testing() + +add_test( NAME run_ap203min COMMAND ${PROJECT_NAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + +if( APPLE ) + find_program( STATIC_DEPS_TOOL otool ) +elseif( WIN32 ) + get_filename_component( _cxx_dir "${CMAKE_CXX_COMPILER}" DIRECTORY ) + get_filename_component( _linker_dir "${CMAKE_LINKER}" DIRECTORY ) + find_program( STATIC_DEPS_TOOL dumpbin HINTS ${_cxx_dir} ${_linker_dir} ) +else() + find_program( STATIC_DEPS_TOOL ldd ) +endif() + +add_test( NAME static_deps_ap203min + COMMAND ${CMAKE_COMMAND} + -DEXE=$ + -DTOOL=${STATIC_DEPS_TOOL} + -P ${AP203Minimum_SOURCE_DIR}/cmake/CheckStaticDeps.cmake ) + # Local Variables: # tab-width: 8 # mode: cmake diff --git a/example/ap203min/ExternalProjectBuild/cmake/CheckStaticDeps.cmake b/example/ap203min/ExternalProjectBuild/cmake/CheckStaticDeps.cmake new file mode 100644 index 000000000..a11704ed2 --- /dev/null +++ b/example/ap203min/ExternalProjectBuild/cmake/CheckStaticDeps.cmake @@ -0,0 +1,128 @@ +# +# Verify that an executable does not dynamically link anything except +# operating system and compiler runtime libraries. +# +# Usage: +# cmake -DEXE= -DTOOL= -P CheckStaticDeps.cmake +# +# The dependency tool's full output is printed as a diagnostic. The check +# fails if any dependency is not a system library, cannot be resolved, or +# looks like a STEPCode library. +# +cmake_minimum_required( VERSION 3.12 ) + +if( NOT EXE OR NOT EXISTS "${EXE}" ) + message( FATAL_ERROR "Executable not found: '${EXE}'" ) +endif() +if( NOT TOOL ) + message( FATAL_ERROR "No dependency tool (otool, ldd or dumpbin) was found." ) +endif() + +get_filename_component( _tool_name "${TOOL}" NAME_WE ) +string( TOLOWER "${_tool_name}" _tool_name ) + +if( _tool_name STREQUAL "otool" ) + set( _args -L "${EXE}" ) +elseif( _tool_name STREQUAL "ldd" ) + set( _args "${EXE}" ) +elseif( _tool_name STREQUAL "dumpbin" ) + set( _args /dependents "${EXE}" ) +else() + message( FATAL_ERROR "Unsupported dependency tool: ${TOOL}" ) +endif() + +execute_process( COMMAND "${TOOL}" ${_args} + OUTPUT_VARIABLE _out + ERROR_VARIABLE _err + RESULT_VARIABLE _res ) + +list( JOIN _args " " _args_str ) +message( "${TOOL} ${_args_str}\n${_out}${_err}" ) + +if( NOT _res EQUAL 0 ) + message( FATAL_ERROR "${TOOL} failed with exit code ${_res}" ) +endif() + +# Collect (name, resolved path) for each dependency. +set( _deps ) +set( _bad ) +string( REPLACE "\r" "" _out "${_out}" ) +string( REPLACE "\n" ";" _lines "${_out}" ) + +if( _tool_name STREQUAL "otool" ) + # First line is the executable itself; dependencies are indented. + foreach( _line ${_lines} ) + if( _line MATCHES "^[ \t]+([^ \t].*) \\(compatibility version" ) + set( _path "${CMAKE_MATCH_1}" ) + list( APPEND _deps "${_path}" ) + if( NOT _path MATCHES "^/usr/lib/" AND NOT _path MATCHES "^/System/Library/" ) + list( APPEND _bad "${_path}" ) + endif() + endif() + endforeach() + +elseif( _tool_name STREQUAL "ldd" ) + foreach( _line ${_lines} ) + string( STRIP "${_line}" _line ) + if( _line STREQUAL "" OR _line MATCHES "statically linked" ) + continue() + endif() + list( APPEND _deps "${_line}" ) + if( _line MATCHES "not found" ) + list( APPEND _bad "${_line}" ) + elseif( _line MATCHES "=> (/[^ ]+)" ) + set( _path "${CMAKE_MATCH_1}" ) + elseif( _line MATCHES "^(/[^ ]+)" ) + set( _path "${CMAKE_MATCH_1}" ) + else() + # Virtual DSO such as linux-vdso.so.1 has no file. + continue() + endif() + if( NOT _path MATCHES "^/(usr/)?lib(32|64|x32)?/" ) + list( APPEND _bad "${_line}" ) + endif() + endforeach() + +elseif( _tool_name STREQUAL "dumpbin" ) + set( _in_deps FALSE ) + foreach( _line ${_lines} ) + string( STRIP "${_line}" _line ) + if( _line MATCHES "has the following dependencies" ) + set( _in_deps TRUE ) + continue() + endif() + if( NOT _in_deps OR _line STREQUAL "" ) + continue() + endif() + if( _line MATCHES "^Summary" OR _line MATCHES "delay load" ) + break() + endif() + list( APPEND _deps "${_line}" ) + # System DLLs (including the MSVC and UCRT runtimes) live in System32. + file( TO_CMAKE_PATH "$ENV{SystemRoot}/System32/${_line}" _sys ) + if( NOT EXISTS "${_sys}" AND NOT _line MATCHES "^(api|ext)-ms-" ) + list( APPEND _bad "${_line}" ) + endif() + endforeach() +endif() + +if( NOT _deps ) + message( FATAL_ERROR "Could not parse any dependencies from ${TOOL} output." ) +endif() + +# Catch STEPCode libraries even if they were somehow installed system-wide. +foreach( _dep ${_deps} ) + get_filename_component( _name "${_dep}" NAME ) + if( _name MATCHES "(step(core|dai|utils|editor|lazyfile)|sdai_|express|exppp)" ) + list( APPEND _bad "${_dep}" ) + endif() +endforeach() + +list( LENGTH _deps _ndeps ) +if( _bad ) + list( REMOVE_DUPLICATES _bad ) + string( REPLACE ";" "\n " _bad "${_bad}" ) + message( FATAL_ERROR "Non-system dynamic dependencies found:\n ${_bad}" ) +endif() + +message( STATUS "OK: all ${_ndeps} dynamic dependencies are system libraries." ) diff --git a/example/ap203min/ExternalProjectBuild/cmake/External_STEPCode.cmake b/example/ap203min/ExternalProjectBuild/cmake/External_STEPCode.cmake index 08a5e306f..7ad31f5f0 100644 --- a/example/ap203min/ExternalProjectBuild/cmake/External_STEPCode.cmake +++ b/example/ap203min/ExternalProjectBuild/cmake/External_STEPCode.cmake @@ -1,24 +1,74 @@ +# +# Build STEPCode as an ExternalProject, static libraries only. +# +# This mirrors how OpenVSP builds STEPCode. After inclusion, the following +# variables are set for use by the consuming project: +# +# STEPCODE_INSTALL_DIR - install prefix of the STEPCode build +# STEPCODE_INCLUDE_DIR - include directories +# STEPCODE_LIBRARIES - full paths to static libraries, in link order +# STEPCODE_SYSTEM_LIBRARIES - platform libraries STEPCode depends on +# STEPCODE_DEFINITIONS - compile definitions required by consumers +# + +set( STEPCODE_INSTALL_DIR ${CMAKE_BINARY_DIR}/sc-install ) + +# Static libraries are named -static. Order matters for single-pass +# linkers: dependents before their dependencies. +set( STEPCODE_LIB_NAMES + sdai_ap203-static + stepeditor-static + stepcore-static + stepdai-static + steputils-static +) + +set( STEPCODE_LIBRARIES ) +foreach( _sclib ${STEPCODE_LIB_NAMES} ) + list( APPEND STEPCODE_LIBRARIES + ${STEPCODE_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${_sclib}${CMAKE_STATIC_LIBRARY_SUFFIX} ) +endforeach() + +set( STEPCODE_SYSTEM_LIBRARIES ) +if( WIN32 ) + set( STEPCODE_SYSTEM_LIBRARIES shlwapi ) +endif() + +set( STEPCODE_INCLUDE_DIR + ${STEPCODE_INSTALL_DIR}/include/stepcode + ${STEPCODE_INSTALL_DIR}/include/stepcode/clstepcore + ${STEPCODE_INSTALL_DIR}/include/stepcode/cldai + ${STEPCODE_INSTALL_DIR}/include/stepcode/clutils + ${STEPCODE_INSTALL_DIR}/include/stepcode/cleditor + ${STEPCODE_INSTALL_DIR}/include/schemas/sdai_ap203 +) + +# Consumers of the static libraries must define SC_STATIC, otherwise the +# headers declare everything __declspec(dllimport) on Windows. +set( STEPCODE_DEFINITIONS SC_STATIC ) + +if( CMAKE_BUILD_TYPE ) + set( STEPCODE_BUILD_TYPE ${CMAKE_BUILD_TYPE} ) +else() + set( STEPCODE_BUILD_TYPE Release ) +endif() + ExternalProject_Add( STEPCODE URL ${CMAKE_CURRENT_SOURCE_DIR}/../../.. CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_BUILD_TYPE=${STEPCODE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX:PATH=${STEPCODE_INSTALL_DIR} + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} + -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} -DSC_BUILD_SCHEMAS=ap203/ap203.exp + -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DSC_PYTHON_GENERATOR=OFF - -DSC_INSTALL_PREFIX:PATH= + -DSC_ENABLE_TESTING=OFF + # Ninja must be told about every file a rule produces. + BUILD_BYPRODUCTS ${STEPCODE_LIBRARIES} ) -ExternalProject_Get_Property( STEPCODE SOURCE_DIR ) -ExternalProject_Get_Property( STEPCODE BINARY_DIR ) -ExternalProject_Get_Property( STEPCODE INSTALL_DIR ) - -IF( NOT WIN32 ) - SET( STEPCODE_INSTALL_DIR ${SOURCE_DIR}/../sc-install ) -ELSE() - SET( STEPCODE_INSTALL_DIR ${INSTALL_DIR} ) -ENDIF() - -SET( STEPCODE_BINARY_DIR ${BINARY_DIR} ) - diff --git a/src/cldai/CMakeLists.txt b/src/cldai/CMakeLists.txt index e37ac06d2..cc9979586 100644 --- a/src/cldai/CMakeLists.txt +++ b/src/cldai/CMakeLists.txt @@ -27,7 +27,16 @@ if(BUILD_SHARED_LIBS) endif() if(BUILD_STATIC_LIBS) - SC_ADDLIB(stepdai-static STATIC SOURCES ${DAI_SRCS} LINK_LIBRARIES $-static) + # Spell the static dependency names out rather than composing them with a + # generator expression. Since 0b950dd1 SC_ADDLIB publishes LINK_LIBRARIES as + # PUBLIC, so anything given here lands in INTERFACE_LINK_LIBRARIES and is + # re-evaluated in each consumer - where a nested $ comes back out + # mangled and reaches the linker verbatim. + set(_static_libdeps) + foreach(_libdep ${_libdeps}) + list(APPEND _static_libdeps ${_libdep}-static) + endforeach() + SC_ADDLIB(stepdai-static STATIC SOURCES ${DAI_SRCS} LINK_LIBRARIES ${_static_libdeps}) endif() # Local Variables: diff --git a/src/exp2cxx/selects.c b/src/exp2cxx/selects.c index f03b6e48f..c600dac6a 100644 --- a/src/exp2cxx/selects.c +++ b/src/exp2cxx/selects.c @@ -2012,7 +2012,8 @@ void TYPEselect_print( Type t, FILES * files, Schema schema ) { DAR - moved to TYPEprint_init() - to keep init info together. */ tag -> complete = 1; - free( tag ); + /* Don't free tag: it stays in the type's clientData as the marker that + this type has been processed, which the checks above rely on. */ } #undef BASE_SELECT