diff --git a/.github/workflows/self_hosted_build_and_test.yml b/.github/workflows/self_hosted_build_and_test.yml index 23830d94ffd..9dfdafcaf17 100644 --- a/.github/workflows/self_hosted_build_and_test.yml +++ b/.github/workflows/self_hosted_build_and_test.yml @@ -1,7 +1,7 @@ name: Self-Hosted Build and Test on: - push: + pull_request: branches: [ develop ] workflow_dispatch: inputs: diff --git a/.github/workflows/self_hosted_decent_ci.yml b/.github/workflows/self_hosted_decent_ci.yml new file mode 100644 index 00000000000..55c6bce9b2f --- /dev/null +++ b/.github/workflows/self_hosted_decent_ci.yml @@ -0,0 +1,171 @@ +name: Self-Hosted Decent CI + +on: + pull_request: + branches: [ develop ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + BUILD_DIR: build-decent-ci + Python_REQUIRED_VERSION: "3.12.2" + +jobs: + build_and_test: + name: GCC 13.3 build and split tests + if: >- + ${{ + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.repo.fork == false + }} + runs-on: [ self-hosted, linux, x64, ubuntu-24.04 ] + + steps: + - name: Checkout EnergyPlus + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Setup runner + id: setup-runner + uses: ./.github/actions/setup-runner + with: + python-version: ${{ env.Python_REQUIRED_VERSION }} + python-arch: x64 + + - name: Select GCC 13.3 toolchain + id: compiler + shell: bash + run: | + sudo apt-get -qq update + sudo apt-get -qq install -y gcc-13 g++-13 gfortran-13 + + gcc_version="$(gcc-13 -dumpfullversion -dumpversion)" + case "$gcc_version" in + 13.3*) ;; + *) + echo "::error::Expected GCC 13.3.x, found ${gcc_version}" + exit 1 + ;; + esac + + { + echo "CC=$(command -v gcc-13)" + echo "CXX=$(command -v g++-13)" + echo "FC=$(command -v gfortran-13)" + } >> "$GITHUB_ENV" + echo "compiler-id=gcc-${gcc_version}" >> "$GITHUB_OUTPUT" + gcc-13 --version + g++-13 --version + gfortran-13 --version + + - name: Restore ccache + id: cacheccache-restore + uses: actions/cache/restore@v5 + with: + path: | + ${{ steps.setup-runner.outputs.ccache-dir }} + key: ccache-self-hosted-decent-ci-${{ runner.os }}-${{ steps.compiler.outputs.compiler-id }}-pr-${{ github.event.pull_request.number }} + + - name: Show restored ccache state + shell: bash + run: | + begin_group() { echo -e "::group::\033[93m$1\033[0m"; } + + if [ "${{ steps.cacheccache-restore.outputs.cache-hit }}" = "true" ]; then + echo "CCache primary key was hit" + else + echo "No exact ccache hit" + fi + + begin_group "Existing ccache stats" + ccache --show-stats -vv || ccache --show-stats + echo "::endgroup::" + + ccache --zero-stats + begin_group "CCache config" + ccache -p + echo "::endgroup::" + + - name: Configure build + shell: bash + run: | + cmake -S . -B "$BUILD_DIR" \ + -G Ninja \ + -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \ + -DCMAKE_C_COMPILER:FILEPATH="$CC" \ + -DCMAKE_CXX_COMPILER:FILEPATH="$CXX" \ + -DCMAKE_Fortran_COMPILER:FILEPATH="$FC" \ + -DLINK_WITH_PYTHON:BOOL=ON \ + -DPYTHON_CLI:BOOL=OFF \ + -DPython_REQUIRED_VERSION:STRING="$Python_REQUIRED_VERSION" \ + -DPython_ROOT_DIR:PATH="${{ steps.setup-runner.outputs.python-root-dir }}" \ + -DPython_EXECUTABLE:FILEPATH="${{ steps.setup-runner.outputs.python-executable }}" \ + -DBUILD_FORTRAN:BOOL=ON \ + -DBUILD_TESTING:BOOL=ON \ + -DENABLE_REGRESSION_TESTING:BOOL=OFF \ + -DCOMMIT_SHA:STRING="${{ github.event.pull_request.head.sha }}" \ + -DENABLE_GTEST_DEBUG_MODE:BOOL=OFF \ + -DENABLE_PCH:BOOL=OFF \ + -DFORCE_DEBUG_ARITHM_GCC_OR_CLANG:BOOL=ON \ + -DBUILD_PACKAGE:BOOL=OFF \ + -DDOCUMENTATION_BUILD:STRING=DoNotBuild + + - name: Build + shell: bash + run: | + echo "::add-matcher::./.github/workflows/cpp-problem-matcher.json" + cmake --build "$BUILD_DIR" -j "${NPROC:-$(nproc)}" + echo "::remove-matcher owner=gcc-problem-matcher::" + + - name: Show build ccache stats + if: always() + shell: bash + run: | + ccache --show-stats -vv || ccache --show-stats + + - name: Save ccache + if: always() && steps.cacheccache-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: | + ${{ steps.setup-runner.outputs.ccache-dir }} + key: ${{ steps.cacheccache-restore.outputs.cache-primary-key }} + + - name: Run unit tests + working-directory: ${{ env.BUILD_DIR }} + shell: bash + run: | + begin_group() { echo -e "::group::\033[93m$1\033[0m"; } + + begin_group "Running non-integration CTests" + if ctest -j "${NPROC:-$(nproc)}" --output-on-failure -E "integration.*"; then + echo "::endgroup::" + else + echo "::endgroup::" + begin_group "Re-running failed non-integration tests verbosely" + ctest --rerun-failed -VV + echo "::endgroup::" + fi + + - name: Run integration tests + working-directory: ${{ env.BUILD_DIR }} + shell: bash + run: | + begin_group() { echo -e "::group::\033[93m$1\033[0m"; } + + begin_group "Running integration CTests" + if ctest -j "${NPROC:-$(nproc)}" --output-on-failure -R "integration.*"; then + echo "::endgroup::" + else + echo "::endgroup::" + begin_group "Re-running failed integration tests verbosely" + ctest --rerun-failed -VV + echo "::endgroup::" + fi diff --git a/.github/workflows/test_develop_commits.yml b/.github/workflows/test_develop_commits.yml index 41e1f343f32..c23eb8f1973 100644 --- a/.github/workflows/test_develop_commits.yml +++ b/.github/workflows/test_develop_commits.yml @@ -30,7 +30,7 @@ jobs: # pretty: "Standard Build on Mac x64" # alternate: false - os: macos-14 - macos_dev_target: 13.0 + macos_dev_target: 14.0 arch: arm64 python-arch: arm64 generator: "Unix Makefiles" diff --git a/src/EnergyPlus/api/datatransfer.cc b/src/EnergyPlus/api/datatransfer.cc index e5f39d092af..4f2589148c0 100644 --- a/src/EnergyPlus/api/datatransfer.cc +++ b/src/EnergyPlus/api/datatransfer.cc @@ -47,7 +47,8 @@ // C++ Headers #include -#include +#include +#include // ObjexxFCL Headers #include @@ -72,6 +73,20 @@ using namespace EnergyPlus; +namespace { + +char *copyStringForAPI(std::string const &value) +{ + auto *result = static_cast(std::malloc(value.size() + 1)); + if (result == nullptr) { + return nullptr; + } + std::memcpy(result, value.c_str(), value.size() + 1); + return result; +} + +} // namespace + APIDataEntry *getAPIData(EnergyPlusState state, unsigned int *resultingSize) { struct LocalAPIDataEntry @@ -218,13 +233,7 @@ char *listAllAPIDataCSV(EnergyPlusState state) ? variable->unitNameCustomEMS : EnergyPlus::Constant::unitNames[(int)variable->units])); } - // note that we cannot just return a c_str to the local string, as the string will be destructed upon leaving - // this function, and undefined behavior will occur. - // instead make a deep copy, and the user must manage the new char * pointer - // strcpy copies including the null-terminator, strlen doesn't include it - char *p = new char[std::strlen(output.c_str()) + 1]; - std::strcpy(p, output.c_str()); - return p; + return copyStringForAPI(output); } int apiDataFullyReady(EnergyPlusState state) @@ -255,18 +264,14 @@ char *inputFilePath(EnergyPlusState state) { const auto *thisState = static_cast(state); std::string const path_utf8 = EnergyPlus::FileSystem::toGenericString(thisState->dataStrGlobals->inputFilePath); - char *p = new char[std::strlen(path_utf8.c_str()) + 1]; - std::strcpy(p, path_utf8.c_str()); - return p; + return copyStringForAPI(path_utf8); } char *epwFilePath(EnergyPlusState state) { const auto *thisState = static_cast(state); std::string const path_utf8 = EnergyPlus::FileSystem::toGenericString(thisState->files.inputWeatherFilePath.filePath); - char *p = new char[std::strlen(path_utf8.c_str()) + 1]; - std::strcpy(p, path_utf8.c_str()); - return p; + return copyStringForAPI(path_utf8); } char **getObjectNames(EnergyPlusState state, const char *objectType, unsigned int *resultingSize)