diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a1f6c97..ecbcf06 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -25,11 +25,11 @@ jobs: !contains(github.event.head_commit.message, 'ci skip') runs-on: ${{ matrix.os }} outputs: - result: ${{ steps.test.outcome }} + result: ${{ startsWith(matrix.os, 'ubuntu') && steps.ubuntu-test.outcome || steps.test.outcome }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-24.04, windows-2022, macos-14] python-version: ['3.8', '3.12'] steps: @@ -37,11 +37,73 @@ jobs: with: ref: ${{ github.event_name == 'workflow_call' && inputs.ref || github.ref }} - - name: Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@v1.12.1 + - name: Get runner UID/GID + if: startsWith(matrix.os, 'ubuntu') + id: runner-info + run: echo "uid=$(id -u)" >> $GITHUB_OUTPUT && echo "gid=$(id -g)" >> $GITHUB_OUTPUT + + - name: Build in container for Ubuntu 20.04 (for broader compatibility) + if: startsWith(matrix.os, 'ubuntu') + uses: addnab/docker-run-action@v3 + with: + image: ubuntu:20.04 + options: -v ${{ github.workspace }}:/home/runner/funnel + shell: bash + run: | + export DEBIAN_FRONTEND=noninteractive + export TZ=UTC + apt update + apt install -y software-properties-common build-essential wget + # Python install and venv setup + if [ "${{ matrix.python-version }}" == "3.8" ]; then + apt install -y python3 python3-pip python3-venv + PYTHON_CMD=python3 + else + add-apt-repository ppa:deadsnakes/ppa + apt update && apt install -y \ + python${{ matrix.python-version }} \ + python${{ matrix.python-version }}-venv \ + python${{ matrix.python-version }}-dev \ + curl + PYTHON_CMD=python${{ matrix.python-version }} + # Install pip for the specific Python version + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + $PYTHON_CMD get-pip.py + rm get-pip.py + fi + # Create group and user matching the runner + groupadd -g ${{ steps.runner-info.outputs.gid }} runnergroup + useradd -u ${{ steps.runner-info.outputs.uid }} -g runnergroup -m runner + chown -R runner:runnergroup /home/runner + # Build and test funnel as runner + su runner << 'EOF' + # CMake install + cd /home/runner + CMAKE_VERSION=3.22.6 + wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz + tar -xzf cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz + export PATH=$PWD/cmake-${CMAKE_VERSION}-linux-x86_64/bin:$PATH + echo "CMake version: " && cmake --version + # Create and activate virtual environment + $PYTHON_CMD -m venv /venv + source /venv/bin/activate + echo "Python version: " && python --version + cd /home/runner/funnel + pip install --upgrade pip + pip install ".[dev]" # In Python 3.8 editable mode still requires setup.py + mkdir -p build && cd build && cmake .. + cmake --build . --target install --config ${{ env.BUILD_TYPE }} + ctest -C ${{ env.BUILD_TYPE }} --verbose + cd /home/runner/funnel + rm -rf pyfunnel.egg-info + EOF + + - name: Enable Developer Command Prompt on Windows if: startsWith(matrix.os, 'windows') + uses: ilammy/msvc-dev-cmd@v1.12.1 - name: Setup cmake + if: ${{ !startsWith(matrix.os, 'ubuntu') }} uses: jwlawson/actions-setup-cmake@v1.13 with: cmake-version: '3.22.x' @@ -58,6 +120,7 @@ jobs: python -m pip install --user -e ".[dev]" - name: Create Build Environment + if: ${{ !startsWith(matrix.os, 'ubuntu') }} # Some projects don't allow in-source building, so create a separate build directory # We'll use this as our working directory for all subsequent commands. run: | @@ -65,61 +128,39 @@ jobs: cmake -E make_directory ${{ github.workspace }}/build - name: Configure CMake + if: ${{ !startsWith(matrix.os, 'ubuntu') }} # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash working-directory: ${{ github.workspace }}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 run: cmake .. - name: Build id: build + if: ${{ !startsWith(matrix.os, 'ubuntu') }} working-directory: ${{ github.workspace }}/build shell: bash - run: cmake --build . --target install --config $BUILD_TYPE + run: cmake --build . --target install --config ${{ env.BUILD_TYPE }} - name: Test id: test + if: ${{ !startsWith(matrix.os, 'ubuntu') }} working-directory: ${{ github.workspace }}/build shell: bash - run: ctest -C $BUILD_TYPE --verbose - - - name: Stage binaries - if: | - contains(matrix.python-version, '3.12') && - ( - (github.event_name == 'push' && github.ref == 'refs/heads/master') || - (github.event_name == 'workflow_call') - ) - run: | - git config --global pull.rebase true - git add ${{ github.workspace }}/pyfunnel/lib - - - name: Push Linux & macOS binaries - if: | - (contains(matrix.os, 'ubuntu') || matrix.os == 'macos-latest') && - contains(matrix.python-version, '3.12') && - ( - (github.event_name == 'push' && github.ref == 'refs/heads/master') || - (github.event_name == 'workflow_call') - ) - run: | - git config --global user.name "${GH_USERNAME}" - git config --global user.email "${GH_USERNAME}@users.noreply.github.com" - git diff-index --quiet HEAD || { git commit -m "Add ${{ matrix.os }} binaries" && git pull && git push; } - - # We use cmd /c because powershell <7 does not support chain operators. - - name: Push Windows binaries - if: | - matrix.os == 'windows-latest' && - contains(matrix.python-version, '3.12') && - ( - (github.event_name == 'push' && github.ref == 'refs/heads/master') || - (github.event_name == 'workflow_call') - ) + run: ctest -C ${{ env.BUILD_TYPE }} --verbose + + - name: Test simple test in latest Ubuntu + id: ubuntu-test + if: startsWith(matrix.os, 'ubuntu') run: | - git config --global user.name "${{ env.GH_USERNAME }}" - git config --global user.email "${{ env.GH_USERNAME }}@users.noreply.github.com" - cmd /c 'git diff-index --quiet HEAD || ( git commit -m "Add ${{ matrix.os }} binaries" && git pull && git push )' + # Run the test from the directory with test data + cd tests/test_bin && funnel --reference trended.csv --test simulated.csv --atolx 0.002 --atoly 0.002 + + - name: Upload platform-specific binaries as artifacts + if: contains(matrix.python-version, '3.12') + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }} + path: ${{ github.workspace }}/pyfunnel/lib/ + retention-days: 30 + if-no-files-found: error diff --git a/.github/workflows/master-release.yml b/.github/workflows/master-release.yml index c178e14..7afba59 100644 --- a/.github/workflows/master-release.yml +++ b/.github/workflows/master-release.yml @@ -98,16 +98,6 @@ jobs: exit 1 fi - - name: Check for binaries - run: | - if [ -d "pyfunnel/lib" ] && [ "$(ls -A pyfunnel/lib)" ]; then - echo "✅ Binaries found in pyfunnel/lib" - ls -la pyfunnel/lib/ - else - echo "❌ No binaries found in pyfunnel/lib" - exit 1 - fi - merge-to-master: needs: validate-staging runs-on: ubuntu-latest diff --git a/.github/workflows/staging-release.yml b/.github/workflows/staging-release.yml index 224ae0a..dc42d1e 100644 --- a/.github/workflows/staging-release.yml +++ b/.github/workflows/staging-release.yml @@ -83,6 +83,18 @@ jobs: with: ref: staging + - name: Download all binary artifacts + uses: actions/download-artifact@v4 + with: + pattern: binaries-* + path: pyfunnel/lib/ + merge-multiple: true + + - name: Debug - List downloaded artifacts + run: | + echo "Contents of pyfunnel/lib/ after artifact download:" + find pyfunnel/lib/ -type f -ls || echo "No files found" + - name: Setup Python uses: actions/setup-python@v4 with: @@ -112,6 +124,14 @@ jobs: uses: actions/checkout@v4 with: ref: staging + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download all binary artifacts + uses: actions/download-artifact@v4 + with: + pattern: binaries-* + path: pyfunnel/lib/ + merge-multiple: true - name: Install from Test PyPI if: github.event.inputs.installation_mode == 'test-pypi' @@ -121,14 +141,14 @@ jobs: # Install from Test PyPI # The `--extra-index-url https://pypi.org/simple/` ensures dependencies are installed from regular PyPI - python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pyfunnel==${{ needs.version-bump.outputs.new-release-version }} + python -m pip install --user --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pyfunnel==${{ needs.version-bump.outputs.new-release-version }} - name: Install from source if: github.event.inputs.installation_mode == 'source' run: | # Install from source - python -m pip install --upgrade pip - python -m pip install . + python -m pip install --user --upgrade pip + python -m pip install --user . - name: Run test run: | @@ -140,6 +160,9 @@ jobs: - name: Create staging validation marker run: | + git config --global user.name "${GH_USERNAME}" + git config --global user.email "${GH_USERNAME}@users.noreply.github.com" + # Create validation marker indicating successful staging pipeline echo "Staging pipeline completed successfully on $(date)" > .staging-validated echo "Version: ${{ needs.version-bump.outputs.new-release-version }}" >> .staging-validated @@ -150,8 +173,6 @@ jobs: fi echo "Binaries: Built and committed" >> .staging-validated - git config --global user.name "${GH_USERNAME}" - git config --global user.email "${GH_USERNAME}@users.noreply.github.com" git add .staging-validated git commit -m "chore: mark staging pipeline as validated" git push origin staging diff --git a/.gitignore b/.gitignore index f541cdb..70c18ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Binaries *.py[cod] +pyfunnel/lib # Object files *.o diff --git a/README.md b/README.md index a5644dc..c005c33 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ By convention, the error is `max(0, y - y_up) - min(0, y - y_low)` and hence it The software is tested on the following platforms. -- Linux x64 (Ubuntu 24.04) -- Windows x64 (Windows Server 2022) -- macOS x64 and arm64 (macOS 12) +- Linux x64: Ubuntu 20.04 and 24.04 +- Windows x64: Windows Server 2022 +- macOS x64 and arm64: macOS 14 A Python binding is available to access the library. It is supported on Python versions 3.8 through 3.12. @@ -86,11 +86,7 @@ The package `pyfunnel` provides the following functions. Displays plot in default browser. See function docstring for further details. A standalone CLI script `pyfunnel/cli.py` is available, which is also accessible via the -`funnel` entry point when the package is installed. -To access the usage instructions, use one of the following: - -- Installed package: `funnel --help` -- Standalone: `python pyfunnel/cli.py --help` +`funnel` entry point when the package is installed. To access the usage instructions, run: `funnel --help` ### Example @@ -109,7 +105,7 @@ From a Python shell with `./tests/test_bin` as the current working directory, ru Or from a terminal with `./tests/test_bin` as the current working directory, run ```bash -python ../../pyfunnel/cli.py --reference trended.csv --test simulated.csv --atolx 0.002 --atoly 0.002 +funnel --reference trended.csv --test simulated.csv --atolx 0.002 --atoly 0.002 ``` ## Build from Source diff --git a/pyfunnel/lib/darwin64/libfunnel.dylib b/pyfunnel/lib/darwin64/libfunnel.dylib deleted file mode 100755 index c79acfc..0000000 Binary files a/pyfunnel/lib/darwin64/libfunnel.dylib and /dev/null differ diff --git a/pyfunnel/lib/linux64/libfunnel.so b/pyfunnel/lib/linux64/libfunnel.so deleted file mode 100644 index da9498b..0000000 Binary files a/pyfunnel/lib/linux64/libfunnel.so and /dev/null differ diff --git a/pyfunnel/lib/win64/funnel.dll b/pyfunnel/lib/win64/funnel.dll deleted file mode 100644 index d8a9745..0000000 Binary files a/pyfunnel/lib/win64/funnel.dll and /dev/null differ diff --git a/pyfunnel/lib/win64/funnel.lib b/pyfunnel/lib/win64/funnel.lib deleted file mode 100644 index be2033e..0000000 Binary files a/pyfunnel/lib/win64/funnel.lib and /dev/null differ diff --git a/src/mkdir_p.c b/src/mkdir_p.c index d47382a..6b959f8 100755 --- a/src/mkdir_p.c +++ b/src/mkdir_p.c @@ -20,6 +20,13 @@ int mkdir_p(const char *path) { /* Adapted from http://stackoverflow.com/a/2336245/119527 */ + + // Validate input + if (strpbrk(path, "*|<>?\"") != NULL) { + errno = EINVAL; + return -1; + } + const size_t len = strlen(path); char *_path = NULL; char *p;