Skip to content

Commit

Permalink
Integration tests (#367) + CMake Refactoring + CPack based Packaging …
Browse files Browse the repository at this point in the history
…introduction + CI / CD action improvements

* Added CMake and CPack configuration for building and packaging lib3mf across all platforms, including Debian and RPM builds + VCPKG.
* Updated and debugged workflows, .gitignore, and integration tests, ensuring smooth deployment and artifact handling.
* Fixed paths and linking issues across Windows, macOS, and Linux, including handling symlinks and double zipping problems.
* Enhanced SDK generation and included examples for various build variants, ensuring compatibility and thorough testing.
* Cleaned up and finalized actions, environment variables, and documentation for consistent and efficient builds and deployments.
  • Loading branch information
vijaiaeroastro committed Jun 4, 2024
1 parent c7f7aa8 commit 88a6685
Show file tree
Hide file tree
Showing 28 changed files with 3,377 additions and 56 deletions.
660 changes: 609 additions & 51 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ Include/Model/COM/NMR_COMVersion.h
debug
.DS_Store
.vscode
.idea
cmake-build-*
17 changes: 16 additions & 1 deletion CI/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ RUN \
tar \
gzip \
zip \
rpm-build \
${GCCTOOLSET} \
&& microdnf clean all

Expand Down Expand Up @@ -53,14 +54,28 @@ RUN cmake --build .

RUN ctest -V .

# Add this line to generate a ZIP package with cpack
RUN cpack -G ZIP -C Release

# Generate a debian package
RUN cpack -G DEB -C Release

# Generate a RPM package
RUN cpack -G RPM -C Release

WORKDIR "/../../"

RUN mkdir -p out

RUN cp ./lib3mf-repo/build/lib3mf.so.2 ./out/

RUN cd out && zip -r ../out.zip .
RUN cp ./lib3mf-repo/build/lib3mf-*-Linux.zip ./out/

RUN cp ./lib3mf-repo/build/lib3mf-*-Linux.deb ./out/

RUN cp ./lib3mf-repo/build/lib3mf-*-Linux.rpm ./out/

RUN cd out && zip -r ../out.zip .



Expand Down
32 changes: 32 additions & 0 deletions CI/extract_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import re

def extract_version_from_cmake():
cmake_file = 'CMakeLists.txt'

if not os.path.exists(cmake_file):
raise FileNotFoundError(f"{cmake_file} not found in the current directory")

with open(cmake_file, 'r') as file:
content = file.read()

major = re.search(r'set\(LIB3MF_VERSION_MAJOR\s+([0-9]+)\)', content)
minor = re.search(r'set\(LIB3MF_VERSION_MINOR\s+([0-9]+)\)', content)
micro = re.search(r'set\(LIB3MF_VERSION_MICRO\s+([0-9]+)\)', content)
prerelease = re.search(r'set\(LIB3MF_VERSION_PRERELEASE\s+"([^"]*)"\)', content)

if not major or not minor or not micro:
raise ValueError("Could not find version components in CMakeLists.txt")

version = f"{major.group(1)}.{minor.group(1)}.{micro.group(1)}"
if prerelease and prerelease.group(1):
version += f"-{prerelease.group(1)}"

return version

if __name__ == "__main__":
try:
version = extract_version_from_cmake()
print(version)
except Exception as e:
print(f"Error: {str(e)}")
128 changes: 128 additions & 0 deletions CI/integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
"""
@original author: weismam
lib3mf_integration:
tests whether a large number of 3MF files is parsed correctly by lib3MF /
the Example_ExtractInfo from the SDK
"""

import datetime, time
import subprocess
import os


def listFiles(root, extension):
lFiles = []
for path, _, files in os.walk(root):
for name in files:
[_, fileextension] = os.path.splitext(name)
if extension == fileextension:
lFiles.append(os.path.join(path, name))
return lFiles


# returns a list of files of the following pattern: $root/{*}/$inbetweenFolder/{*}/*.$extension
def listFilesIn(root, inbetweenFolder, extension):
lFiles = []
for path, subdirs, _ in os.walk(root):
for subdir in subdirs:
if subdir == inbetweenFolder:
lFiles += listFiles(os.path.join(path, subdir), extension)
lFiles = list(set(lFiles))
lFiles.sort()
return lFiles


def ExtractInfo(execCommand, fileName):
tStart = time.time()
proc = subprocess.Popen([execCommand, fileName], stdout=subprocess.PIPE)
pOut = ""
errLines = []
for line in proc.stdout:
utf8line = line.decode("utf-8")
if "error" in utf8line:
errLines.append(utf8line)
if "warning" in utf8line:
errLines.append(utf8line)
pOut += utf8line

proc.wait()
info = dict.fromkeys({'success', 'time', 'stdout'})
info['time'] = time.time() - tStart
info['returncode'] = proc.returncode
info['success'] = len(errLines) == 0
info['errLines'] = errLines
info['stdout'] = str(pOut)
return info


if __name__ == "__main__":
tStart = datetime.datetime.now()

root = os.path.dirname(os.path.realpath(__file__))

execCommand = os.path.join(root, "Example_ExtractInfo")

print("Execute once for testing")
os.system(execCommand)
print("Execution done")

positives = []
negatives = []

for suite in os.listdir(root):
suite_path = os.path.join(root, suite)
if os.path.isdir(suite_path) and suite.startswith("suite"):
positives += listFilesIn(suite_path, "positive_test_cases", ".3mf")
negatives += listFilesIn(suite_path, "negative_test_cases", ".3mf")

nPos = len(positives)
nNeg = len(negatives)
nFiles = nPos + nNeg

print(execCommand)

brokenPositives = []
iFile = 0
for fileName in positives:
iFile += 1
print("{:3.0f}%: {:s}".format(100 * (iFile / nFiles), fileName), flush=True)
info = ExtractInfo(execCommand, fileName)
if not info['returncode'] == 0:
print("Fatal Error: MUSTPASS file \"{:s}\" does not work with returncode {:d}:".format(fileName,
info['returncode']))
brokenPositives.append(info)
if not info['success']:
print("Error: MUSTPASS file \"{:s}\" does not work:".format(fileName))
print('Contains {:d} problem{:s}:'.format(len(info['errLines']), ['s', ''][len(info['errLines']) == 0]))
for errLine in info['errLines']:
print(errLine, end='')
print('=== Output === ')
print(info['stdout'])
print('=== /Output === ')
brokenPositives.append(info)

runningNegatives = []
for fileName in negatives:
iFile += 1
print("{:3.0f}%: {:s}".format(100 * (iFile / nFiles), fileName), flush=True)
info = ExtractInfo(execCommand, fileName)
if not info['returncode'] >= 0:
print("Fatal Error: MUSTFAIL file \"{:s}\" does not work with returncode {:d}:".format(fileName,
info['returncode']))
if info['success'] and info['returncode'] == 0:
print("Error: MUSTFAIL file \"{:s}\" works".format(fileName))
runningNegatives.append(info)

duration = datetime.datetime.now() - tStart
print("Tested a total of {:d} = ({:d} positive + {:d} negative) files in {:s}.".format(
nFiles, nPos, nNeg, str(duration).split('.')[0]))

if nPos > 0:
print(" {:3d} / {:3d} MUSTPASS files passed ({:6.2f}%)".format(nPos - len(brokenPositives), nPos,
100 * (1 - len(brokenPositives) / nPos)))
if len(negatives) > 0:
print(" {:3d} / {:3d} MUSTFAIL files failed ({:6.2f}%)".format(nNeg - len(runningNegatives), nNeg,
100 * (1 - len(runningNegatives) / nNeg)))
27 changes: 24 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ if (${MSVC})
# using Visual Studio C++

# this ensures that the min/max macros of minwindef.h are not used
add_definitions(-DNOMINMAX /W3)
add_definitions(-DNOMINMAX)

#add_definitions(/W3)

add_definitions(/W3)

# add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS)
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
Expand Down Expand Up @@ -131,6 +131,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/I
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Include)

if (USE_INCLUDED_LIBZIP)
# Something goes here to check if submodules exist and initialize the submodules if it does not
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Libraries/libzip/Include)
if(MSVC)
target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
Expand Down Expand Up @@ -226,6 +227,8 @@ endif(WIN32)

configure_file(lib3mf.pc.in lib3mf.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/lib3mf.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
install(FILES cmake/lib3mfConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/lib3mf)
install(TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
Expand All @@ -249,3 +252,21 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${STARTUPPROJECT})
ENDIF()
endif()


#########################################################
set(CPACK_PACKAGE_NAME "lib3mf")
set(CPACK_PACKAGE_VENDOR "3MF Consortium")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "lib3mf - An implementation of the 3D Manufacturing Format file standard")
set(CPACK_PACKAGE_VERSION "${LIB3MF_VERSION_MAJOR}.${LIB3MF_VERSION_MINOR}.${LIB3MF_VERSION_MICRO}")
set(CPACK_PACKAGE_VERSION_MAJOR "${LIB3MF_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${LIB3MF_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${LIB3MF_VERSION_MICRO}")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
set(CPACK_PACKAGE_CONTACT "[email protected]")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "3MF Consortium")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-Source")

#########################################################
include(CPack)
70 changes: 70 additions & 0 deletions SDK/CPackExamples/Cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
cmake_minimum_required (VERSION 3.5)
project(Examples)
set(CMAKE_CXX_STANDARD 11)

# Determine the platform and set lib3mf_DIR accordingly
if(WIN32)
# Path for Windows
set(lib3mf_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../lib3mf-2.3.1-Windows/lib/cmake/lib3mf")
find_package(lib3mf REQUIRED COMPONENTS Cpp)
elseif(APPLE)
# Path for macOS (Darwin)
set(lib3mf_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../lib3mf-2.3.1-Darwin/lib/cmake/lib3mf")
find_package(lib3mf REQUIRED COMPONENTS Cpp)
else()
# Path for Linux (Here we check twice to test for Debian / RPM packages properly)
find_package(lib3mf QUIET COMPONENTS Cpp)
# Check if the package was not found
if(NOT lib3mf_FOUND)
# lib3mf not found, so set lib3mf_DIR to the fallback directory
set(lib3mf_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../lib3mf-2.3.1-Linux/lib/cmake/lib3mf")
# Find package (lib3mf)
find_package(lib3mf REQUIRED COMPONENTS Cpp)
endif()
endif()

add_definitions(-DTEXTURESPATH="${CMAKE_CURRENT_SOURCE_DIR}/../Files/Textures/")

add_executable(Example_ColorCube Source/ColorCube.cpp)
target_link_libraries(Example_ColorCube lib3mf::lib3mf)
copy_lib3mf_libraries(Example_ColorCube)

add_executable(Example_Components Source/Components.cpp)
target_link_libraries(Example_Components lib3mf::lib3mf)
copy_lib3mf_libraries(Example_Components)

add_executable(Example_Converter Source/Converter.cpp)
target_link_libraries(Example_Converter lib3mf::lib3mf)
copy_lib3mf_libraries(Example_Converter)

add_executable(Example_Cube Source/Cube.cpp)
target_link_libraries(Example_Cube lib3mf::lib3mf)
copy_lib3mf_libraries(Example_Cube)

add_executable(Example_SecureCube Source/SecureCube.cpp)
target_link_libraries(Example_SecureCube lib3mf::lib3mf)
copy_lib3mf_libraries(Example_SecureCube)

add_executable(Example_ExtractInfo Source/ExtractInfo.cpp)
target_link_libraries(Example_ExtractInfo lib3mf::lib3mf)
copy_lib3mf_libraries(Example_ExtractInfo)

add_executable(Example_TextureCube Source/TextureCube.cpp)
target_link_libraries(Example_TextureCube lib3mf::lib3mf)
copy_lib3mf_libraries(Example_TextureCube)

add_executable(Example_Slice Source/Slice.cpp)
target_link_libraries(Example_Slice lib3mf::lib3mf)
copy_lib3mf_libraries(Example_Slice)

add_executable(Example_BeamLattice Source/BeamLattice.cpp)
target_link_libraries(Example_BeamLattice lib3mf::lib3mf)
copy_lib3mf_libraries(Example_BeamLattice)

if (${MSVC})
IF(${CMAKE_VERSION} VERSION_LESS 3.6.3)
MESSAGE ("Note: You need to manually select a StartUp-project in Visual Studio.")
ELSE()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Example_Cube)
ENDIF()
endif()
7 changes: 7 additions & 0 deletions SDK/CPackExamples/Cpp/GenerateMake.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

basepath="$(cd "$(dirname "$0")" && pwd)"
builddir="$basepath/build"
mkdir -p $builddir
cd $builddir
cmake .. -G "Unix Makefiles" "$@"
10 changes: 10 additions & 0 deletions SDK/CPackExamples/Cpp/GenerateVS2015.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off
set startingDir=%CD%

set basepath=%~dp0
set builddir=%basepath%\build
if not exist %builddir% (mkdir %builddir%)
cd %builddir%
cmake -G "Visual Studio 14 2015 Win64" .. %*

cd %startingDir%
10 changes: 10 additions & 0 deletions SDK/CPackExamples/Cpp/GenerateVS2017.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off
set startingDir=%CD%

set basepath=%~dp0
set builddir=%basepath%\build
if not exist %builddir% (mkdir %builddir%)
cd %builddir%
cmake -G "Visual Studio 15 2017 Win64" .. %*

cd %startingDir%
10 changes: 10 additions & 0 deletions SDK/CPackExamples/Cpp/GenerateVS2019.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off
set startingDir=%CD%

set basepath=%~dp0
set builddir=%basepath%\build
if not exist %builddir% (mkdir %builddir%)
cd %builddir%
cmake -G "Visual Studio 16 2019" .. %*

cd %startingDir%
Loading

0 comments on commit 88a6685

Please sign in to comment.