diff --git a/.github/workflows/comment-evolution-plot.yml b/.github/workflows/comment-evolution-plot.yml new file mode 100644 index 000000000..ed7208168 --- /dev/null +++ b/.github/workflows/comment-evolution-plot.yml @@ -0,0 +1,98 @@ +name: Comment PR with Evolution Plot + +on: + workflow_run: + workflows: + - COMPAS compile test + types: + - completed + +permissions: + actions: read + contents: read + issues: write + pull-requests: write + +jobs: + comment-with-plot: + name: Post evolution plot comment + runs-on: ubuntu-22.04 + if: > + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.pull_requests[0].number + + env: + ARTIFACT_NAME: detailedEvolutionPlot.png + + steps: + - name: Download evolution plot artifact from triggering run + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + const runId = context.payload.workflow_run.id; + const runNumber = context.payload.workflow_run.run_number; + const artifactName = `evolution-plot-${runNumber}`; + const { owner, repo } = context.repo; + + const artifacts = await github.paginate( + github.rest.actions.listWorkflowRunArtifacts, + { owner, repo, run_id: runId, per_page: 100 } + ); + const artifact = artifacts.find((entry) => entry.name === artifactName); + + if (!artifact) { + core.setFailed(`Artifact '${artifactName}' not found for workflow run ${runId}`); + return; + } + + const download = await github.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + + fs.writeFileSync( + path.join(process.env.GITHUB_WORKSPACE, 'evolution-plot.zip'), + Buffer.from(download.data) + ); + + - name: Unpack evolution plot artifact + run: | + mkdir -p evolution-plot + unzip -o evolution-plot.zip -d evolution-plot + test -f "evolution-plot/${ARTIFACT_NAME}" + + - name: Create report with evolution plot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + RUN_ID: ${{ github.event.workflow_run.id }} + run: | + echo "## ✅ COMPAS Build Successful!" >> report.md + echo "" >> report.md + echo "| Item | Value |" >> report.md + echo "|------|-------|" >> report.md + echo "| **Commit** | [\`$(echo "$HEAD_SHA" | cut -c1-7)\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${HEAD_SHA}) |" >> report.md + echo "| **Logs** | [View workflow](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}) |" >> report.md + echo "" >> report.md + + if [ -f "evolution-plot/${ARTIFACT_NAME}" ]; then + echo "### Detailed Evolution Plot" >> report.md + echo "
Click to view evolution plot" >> report.md + echo "" >> report.md + echo "![](./evolution-plot/${ARTIFACT_NAME})" >> report.md + echo "
" >> report.md + else + echo "### ⚠️ Evolution plot not found" >> report.md + fi + + echo "" >> report.md + echo "---" >> report.md + echo "Generated by COMPAS CI " >> report.md + + npx -y @dvcorg/cml comment create --pr "$PR_NUMBER" report.md diff --git a/.github/workflows/compas-compile-ci.yml b/.github/workflows/compas-compile-ci.yml index 4ee1eb40d..e11255923 100644 --- a/.github/workflows/compas-compile-ci.yml +++ b/.github/workflows/compas-compile-ci.yml @@ -19,6 +19,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: compas: env: @@ -40,7 +43,7 @@ jobs: echo "Building COMPAS from source..." cd src make clean 2>/dev/null || echo 'No existing build to clean' - make -j $(nproc) -f Makefile + make DOCKER_BUILD=1 CPP="g++" -j "$(nproc)" -f Makefile ./COMPAS -v echo "COMPAS build completed successfully!" @@ -106,51 +109,3 @@ jobs: echo "- Python Dependencies: Success" >> $GITHUB_STEP_SUMMARY echo "- Example COMPAS Job: Success" >> $GITHUB_STEP_SUMMARY echo "- Pytest Suite: Success" >> $GITHUB_STEP_SUMMARY - - comment-with-plot: - name: Comment PR with Evolution Plot - runs-on: ubuntu-22.04 - container: docker://ghcr.io/iterative/cml:0-dvc2-base1 - needs: compas - if: github.event_name == 'pull_request' - - env: - ARTIFACT_NAME: detailedEvolutionPlot.png - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download evolution plot - uses: actions/download-artifact@v4 - with: - name: evolution-plot-${{ github.run_number }} - - - name: Create report with evolution plot - env: - REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "## ✅ COMPAS Build Successful!" >> report.md - echo "" >> report.md - echo "| Item | Value |" >> report.md - echo "|------|-------|" >> report.md - echo "| **Commit** | [\`$(echo $GITHUB_SHA | cut -c1-7)\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}) |" >> report.md - echo "| **Logs** | [View workflow](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) |" >> report.md - echo "" >> report.md - - if [ -f "${{ env.ARTIFACT_NAME }}" ]; then - echo "### Detailed Evolution Plot" >> report.md - echo "
Click to view evolution plot" >> report.md - echo "" >> report.md - echo "![](./${{ env.ARTIFACT_NAME }})" >> report.md - echo "
" >> report.md - else - echo "### ⚠️ Evolution plot not found" >> report.md - fi - - echo "" >> report.md - echo "---" >> report.md - echo "Generated by COMPAS CI " >> report.md - - # Post the report using CML - cml comment create report.md \ No newline at end of file diff --git a/.github/workflows/native-linux-bundle.yml b/.github/workflows/native-linux-bundle.yml new file mode 100644 index 000000000..564731330 --- /dev/null +++ b/.github/workflows/native-linux-bundle.yml @@ -0,0 +1,199 @@ +name: Native Linux bundle + +on: + workflow_dispatch: + pull_request: + branches: + - dev + paths: + - src/** + - misc/cicd-scripts/** + - .github/workflows/native-linux-bundle.yml + push: + branches: + - dev + paths: + - src/** + - misc/cicd-scripts/** + - .github/workflows/native-linux-bundle.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + native-linux-bundle: + name: Build native Linux bundle + runs-on: ubuntu-22.04 + + env: + BUNDLE_DIR: dist/COMPAS-linux-x86_64 + BUNDLE_TARBALL: dist/COMPAS-linux-x86_64.tar.gz + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_BASEDIR: ${{ github.workspace }} + CCACHE_COMPILERCHECK: content + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Restore compiler cache + uses: actions/cache@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-native-linux-bundle-ccache-${{ hashFiles('src/Makefile', 'misc/cicd-scripts/linux-dependencies', '.github/workflows/native-linux-bundle.yml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-native-linux-bundle-ccache-${{ hashFiles('src/Makefile', 'misc/cicd-scripts/linux-dependencies', '.github/workflows/native-linux-bundle.yml') }}- + + - name: Install native build dependencies + run: | + # Keep this workflow lean: install only the native compiler and link dependencies. + bash misc/cicd-scripts/linux-dependencies native-build + + - name: Build COMPAS from source + working-directory: src + run: | + ccache --zero-stats || true + ccache --max-size=500M || true + make DOCKER_BUILD=1 CPP="ccache g++" -j "$(nproc)" -f Makefile + ./COMPAS -v + ccache --show-stats || true + + - name: Run native smoke test + working-directory: src + run: | + rm -rf smoke-output + ./COMPAS \ + -n 1 \ + --initial-mass-1 35 \ + --initial-mass-2 31 \ + -a 3.5 \ + --random-seed 0 \ + --metallicity 0.001 \ + --quiet \ + -o smoke-output + test -d smoke-output + find smoke-output -maxdepth 2 -type f | sort | sed -n '1,40p' + + - name: Inspect runtime dependencies + run: | + echo "Runtime dependencies for native build:" + ldd src/COMPAS + + - name: Package portable Linux bundle + run: | + bash misc/cicd-scripts/package-linux-bundle.sh src/COMPAS "$BUNDLE_DIR" + + - name: Test bundled launcher + run: | + "$BUNDLE_DIR/run_compas.sh" -v + + BUNDLE_SMOKE_DIR="${RUNNER_TEMP}/compas-bundle-smoke" + rm -rf "$BUNDLE_SMOKE_DIR" + + "$BUNDLE_DIR/run_compas.sh" \ + -n 1 \ + --initial-mass-1 35 \ + --initial-mass-2 31 \ + -a 3.5 \ + --random-seed 0 \ + --metallicity 0.001 \ + --quiet \ + -o "$BUNDLE_SMOKE_DIR" + + test -d "$BUNDLE_SMOKE_DIR" + + echo "Runtime dependencies for bundled binary with bundled lib/:" + LD_LIBRARY_PATH="${GITHUB_WORKSPACE}/$BUNDLE_DIR/lib" ldd "$BUNDLE_DIR/bin/COMPAS" + + - name: Archive bundle tarball + run: | + tar -C dist -czf "$BUNDLE_TARBALL" COMPAS-linux-x86_64 + + - name: Upload bundle artifact + uses: actions/upload-artifact@v4 + with: + name: COMPAS-linux-x86_64 + path: ${{ env.BUNDLE_TARBALL }} + if-no-files-found: error + + verify-native-linux-bundle: + name: Verify bundled artifact on fresh runner + runs-on: ubuntu-22.04 + needs: native-linux-bundle + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Download bundle artifact + uses: actions/download-artifact@v4 + with: + name: COMPAS-linux-x86_64 + path: downloaded-artifact + + - name: Install Python runner only + run: | + python -m pip install --no-deps -e . + + - name: Unpack bundled artifact + run: | + mkdir -p bundle-test + tar -xzf downloaded-artifact/COMPAS-linux-x86_64.tar.gz -C bundle-test + test -x bundle-test/COMPAS-linux-x86_64/run_compas.sh + + - name: Inspect downloaded bundle dependencies + run: | + LD_LIBRARY_PATH="${GITHUB_WORKSPACE}/bundle-test/COMPAS-linux-x86_64/lib" \ + ldd bundle-test/COMPAS-linux-x86_64/bin/COMPAS | tee bundle-test/ldd.txt + + ! grep -q 'not found' bundle-test/ldd.txt + grep -q 'bundle-test/COMPAS-linux-x86_64/lib/libhdf5_serial' bundle-test/ldd.txt + grep -q 'bundle-test/COMPAS-linux-x86_64/lib/libgsl' bundle-test/ldd.txt + grep -q 'bundle-test/COMPAS-linux-x86_64/lib/libboost_program_options' bundle-test/ldd.txt + + - name: Run downloaded bundle smoke test + run: | + ./bundle-test/COMPAS-linux-x86_64/run_compas.sh -v + + ARTIFACT_SMOKE_DIR="${RUNNER_TEMP}/compas-downloaded-bundle-smoke" + rm -rf "$ARTIFACT_SMOKE_DIR" + + ./bundle-test/COMPAS-linux-x86_64/run_compas.sh \ + -n 1 \ + --initial-mass-1 35 \ + --initial-mass-2 31 \ + -a 3.5 \ + --random-seed 0 \ + --metallicity 0.001 \ + --quiet \ + -o "$ARTIFACT_SMOKE_DIR" + + test -d "$ARTIFACT_SMOKE_DIR" + + - name: Run Python runner against downloaded bundle + env: + COMPAS_BUNDLE_ROOT: ${{ github.workspace }}/bundle-test/COMPAS-linux-x86_64 + run: | + compas_run --print-path + compas_run -v + + PYTHON_RUNNER_SMOKE_DIR="${RUNNER_TEMP}/compas-python-runner-smoke" + rm -rf "$PYTHON_RUNNER_SMOKE_DIR" + + compas_run \ + -n 1 \ + --initial-mass-1 35 \ + --initial-mass-2 31 \ + -a 3.5 \ + --random-seed 0 \ + --metallicity 0.001 \ + --quiet \ + -o "$PYTHON_RUNNER_SMOKE_DIR" + + test -d "$PYTHON_RUNNER_SMOKE_DIR" diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 000000000..5c6f5a72f --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,193 @@ +name: Publish compas-popsynth to PyPI + +on: + push: + branches: + - dev + tags: + - 'v*' + workflow_dispatch: + inputs: + mode: + description: Build only or publish to PyPI + required: true + default: dry-run + type: choice + options: + - dry-run + - publish + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-linux-wheel: + name: Build and verify Linux wheel + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Build Linux wheel with cibuildwheel + uses: pypa/cibuildwheel@v3.1.2 + env: + CIBW_BUILD: cp311-manylinux_x86_64 + CIBW_ARCHS_LINUX: x86_64 + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_ENVIRONMENT_LINUX: COMPAS_BINARY_WHEEL=1 + CIBW_BEFORE_ALL_LINUX: > + bash {project}/misc/cicd-scripts/manylinux-dependencies && + make -C {project}/src DOCKER_BUILD=1 CPP=g++ -j "$(nproc)" -f Makefile && + {project}/src/COMPAS -v && + bash {project}/misc/cicd-scripts/package-linux-bundle.sh {project}/src/COMPAS {project}/dist/COMPAS-linux-x86_64 && + rm -rf {project}/compas_python_utils/bundled/COMPAS-linux-x86_64 && + mkdir -p {project}/compas_python_utils/bundled && + cp -a {project}/dist/COMPAS-linux-x86_64 {project}/compas_python_utils/bundled/ && + test -f {project}/compas_python_utils/bundled/COMPAS-linux-x86_64/run_compas.sh + CIBW_TEST_COMMAND_LINUX: > + compas_run --print-path && + compas_run -v && + WHEEL_SMOKE_DIR="$(mktemp -d)" && + compas_run -n 1 --initial-mass-1 35 --initial-mass-2 31 -a 3.5 --random-seed 0 --metallicity 0.001 --quiet -o "$WHEEL_SMOKE_DIR" && + test -d "$WHEEL_SMOKE_DIR" + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: compas-popsynth-wheel-linux + path: wheelhouse/*.whl + if-no-files-found: error + + build-macos-arm64-wheel: + name: Build and verify macOS arm64 wheel + runs-on: macos-14 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Build macOS arm64 wheel with cibuildwheel + uses: pypa/cibuildwheel@v3.1.2 + env: + CIBW_BUILD: cp311-macosx_arm64 + CIBW_ARCHS_MACOS: arm64 + CIBW_ENVIRONMENT_MACOS: COMPAS_BINARY_WHEEL=1 MACOSX_DEPLOYMENT_TARGET=14.0 + CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" + CIBW_BEFORE_ALL_MACOS: > + bash {project}/misc/cicd-scripts/macos-wheel-dependencies && + make -C {project}/src CPP=clang++ -j "$(sysctl -n hw.logicalcpu)" -f Makefile && + test -x {project}/src/COMPAS && + bash {project}/misc/cicd-scripts/package-macos-bundle.sh {project}/src/COMPAS arm64 {project}/dist/COMPAS-macos-arm64 && + rm -rf {project}/compas_python_utils/bundled/COMPAS-macos-arm64 && + mkdir -p {project}/compas_python_utils/bundled && + cp -R {project}/dist/COMPAS-macos-arm64 {project}/compas_python_utils/bundled/ && + test -f {project}/compas_python_utils/bundled/COMPAS-macos-arm64/run_compas.sh + CIBW_TEST_COMMAND_MACOS: > + compas_run --print-path && + compas_run -v && + WHEEL_SMOKE_DIR="$(mktemp -d)" && + compas_run -n 1 --initial-mass-1 35 --initial-mass-2 31 -a 3.5 --random-seed 0 --metallicity 0.001 --quiet -o "$WHEEL_SMOKE_DIR" && + test -d "$WHEEL_SMOKE_DIR" + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: compas-popsynth-wheel-macos-arm64 + path: wheelhouse/*.whl + if-no-files-found: error + + build-macos-x86_64-wheel: + name: Build and verify macOS x86_64 wheel + runs-on: macos-15-intel + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Build macOS x86_64 wheel with cibuildwheel + uses: pypa/cibuildwheel@v3.1.2 + env: + CIBW_BUILD: cp311-macosx_x86_64 + CIBW_ARCHS_MACOS: x86_64 + CIBW_ENVIRONMENT_MACOS: COMPAS_BINARY_WHEEL=1 MACOSX_DEPLOYMENT_TARGET=14.0 + CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" + CIBW_BEFORE_ALL_MACOS: > + bash {project}/misc/cicd-scripts/macos-wheel-dependencies && + make -C {project}/src CPP=clang++ -j "$(sysctl -n hw.logicalcpu)" -f Makefile && + test -x {project}/src/COMPAS && + bash {project}/misc/cicd-scripts/package-macos-bundle.sh {project}/src/COMPAS x86_64 {project}/dist/COMPAS-macos-x86_64 && + rm -rf {project}/compas_python_utils/bundled/COMPAS-macos-x86_64 && + mkdir -p {project}/compas_python_utils/bundled && + cp -R {project}/dist/COMPAS-macos-x86_64 {project}/compas_python_utils/bundled/ && + test -f {project}/compas_python_utils/bundled/COMPAS-macos-x86_64/run_compas.sh + CIBW_TEST_COMMAND_MACOS: > + compas_run --print-path && + compas_run -v && + WHEEL_SMOKE_DIR="$(mktemp -d)" && + compas_run -n 1 --initial-mass-1 35 --initial-mass-2 31 -a 3.5 --random-seed 0 --metallicity 0.001 --quiet -o "$WHEEL_SMOKE_DIR" && + test -d "$WHEEL_SMOKE_DIR" + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: compas-popsynth-wheel-macos-x86_64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish-pypi: + name: Publish wheel to PyPI + runs-on: ubuntu-22.04 + needs: + - build-linux-wheel + - build-macos-arm64-wheel + - build-macos-x86_64-wheel + if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'publish') }} + environment: pypi + permissions: + id-token: write + + steps: + - name: Download Linux wheel artifact + uses: actions/download-artifact@v4 + with: + name: compas-popsynth-wheel-linux + path: dist + + - name: Download macOS arm64 wheel artifact + uses: actions/download-artifact@v4 + with: + name: compas-popsynth-wheel-macos-arm64 + path: dist + + - name: Download macOS x86_64 wheel artifact + uses: actions/download-artifact@v4 + with: + name: compas-popsynth-wheel-macos-x86_64 + path: dist + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + verbose: true + attestations: false diff --git a/.gitignore b/.gitignore index 519579538..14eb83f46 100644 --- a/.gitignore +++ b/.gitignore @@ -21,10 +21,12 @@ Thumbs.db # Others .ipynb_checkpoints/ __pycache__/ +.ccache/ COMPAS_Output*/ output*/ *.coverage test_artifacts/ +build/ COMPAS *.log @@ -39,6 +41,8 @@ COMPAS *.synctex(busy) *.synctex.gz *.synctex.gz(busy) +dist/ +compas_python_utils/bundled/COMPAS-linux-x86_64/ # docs html *_build diff --git a/README.md b/README.md index 11ff49b4f..bdba8e989 100644 --- a/README.md +++ b/README.md @@ -105,5 +105,3 @@ Furthermore, ### Highlighted papers that have made use of COMPAS are listed at https://compas.science/science.html ; see https://ui.adsabs.harvard.edu/public-libraries/gzRk1qpbRUy4cP2GydR36Q for a full ADS library - - diff --git a/compas_python_utils/__init__.py b/compas_python_utils/__init__.py index bb4b3c31a..3756caee1 100644 --- a/compas_python_utils/__init__.py +++ b/compas_python_utils/__init__.py @@ -1,11 +1,35 @@ +import re +from pathlib import Path + +try: + from importlib.metadata import PackageNotFoundError, version as package_version +except ImportError: # pragma: no cover + from importlib_metadata import PackageNotFoundError, version as package_version + + +def _version_from_changelog() -> str: + changelog_path = Path(__file__).resolve().parent.parent / "src" / "changelog.h" + version_match = re.search( + r'VERSION_STRING = ["\']([^"\']+)["\']', + changelog_path.read_text(encoding="utf-8"), + ) + if not version_match: + return "0.0.0" + return ".".join(str(int(part)) for part in version_match.group(1).split(".")) + + __all__ = [] __author__ = "Team COMPAS" -__email__ = "compas email" +__email__ = "teamcompas@users.noreply.github.com" __uri__ = "https://github.com/TeamCOMPAS/COMPAS" __license__ = "MIT" __description__ = "COMPAS" __copyright__ = "Copyright 2022 COMPAS developers" __contributors__ = "https://github.com/TeamCOMPAS/COMPAS/graphs/contributors" -__version__ = "0.0.1" \ No newline at end of file + +try: + __version__ = package_version("compas-popsynth") +except PackageNotFoundError: + __version__ = _version_from_changelog() diff --git a/compas_python_utils/compas_runner.py b/compas_python_utils/compas_runner.py new file mode 100644 index 000000000..052b440fc --- /dev/null +++ b/compas_python_utils/compas_runner.py @@ -0,0 +1,137 @@ +import argparse +import os +import platform +import subprocess +import sys +from pathlib import Path +from typing import Iterable, Optional, Sequence + + +PACKAGE_ROOT = Path(__file__).resolve().parent +REPO_ROOT = PACKAGE_ROOT.parent +BUNDLED_DIRECTORIES = ( + "COMPAS-linux-x86_64", + "COMPAS-macos-arm64", + "COMPAS-macos-x86_64", +) + + +def _is_runnable_file(path: Path) -> bool: + return path.is_file() and (os.access(path, os.X_OK) or path.suffix == ".sh") + + +def _validate_explicit_path(path: Path, variable_name: str) -> str: + if not _is_runnable_file(path): + raise FileNotFoundError( + f"{variable_name} points to a non-runnable path: {path}" + ) + return str(path) + + +def _normalized_machine() -> str: + machine = platform.machine().lower() + if machine in {"amd64", "x86_64"}: + return "x86_64" + if machine in {"arm64", "aarch64"}: + return "arm64" + return machine + + +def _preferred_bundle_directories() -> Sequence[str]: + system = platform.system() + machine = _normalized_machine() + + if system == "Linux": + preferred = [f"COMPAS-linux-{machine}"] + elif system == "Darwin": + preferred = [f"COMPAS-macos-{machine}"] + else: + preferred = [] + + return tuple(dict.fromkeys([*preferred, *BUNDLED_DIRECTORIES])) + + +def _candidate_paths() -> Iterable[Path]: + bundle_root = os.environ.get("COMPAS_BUNDLE_ROOT") + if bundle_root: + bundle_path = Path(bundle_root) + yield bundle_path / "run_compas.sh" + yield bundle_path / "bin" / "COMPAS" + + for bundle_directory in _preferred_bundle_directories(): + yield PACKAGE_ROOT / "bundled" / bundle_directory / "run_compas.sh" + yield PACKAGE_ROOT / "bundled" / bundle_directory / "bin" / "COMPAS" + + compas_root = Path(os.environ.get("COMPAS_ROOT_DIR", REPO_ROOT)) + yield compas_root / "src" / "COMPAS" + yield compas_root / "bin" / "COMPAS" + + yield REPO_ROOT / "src" / "COMPAS" + yield REPO_ROOT / "bin" / "COMPAS" + + +def resolve_compas_executable() -> str: + explicit_path = os.environ.get("COMPAS_EXECUTABLE_PATH") + if explicit_path: + return _validate_explicit_path(Path(explicit_path), "COMPAS_EXECUTABLE_PATH") + + bundle_root = os.environ.get("COMPAS_BUNDLE_ROOT") + if bundle_root: + candidates = [Path(bundle_root) / "run_compas.sh", Path(bundle_root) / "bin" / "COMPAS"] + for candidate in candidates: + if _is_runnable_file(candidate): + return str(candidate) + raise FileNotFoundError( + "COMPAS_BUNDLE_ROOT is set, but neither run_compas.sh nor bin/COMPAS " + f"exists under {bundle_root}" + ) + + for candidate in _candidate_paths(): + if _is_runnable_file(candidate): + return str(candidate) + + raise FileNotFoundError( + "Unable to locate a COMPAS executable. Set COMPAS_EXECUTABLE_PATH to an " + "existing executable, or set COMPAS_BUNDLE_ROOT to an extracted bundle directory. " + f"Detected platform: {platform.system()} ({_normalized_machine()})." + ) + + +def run_compas( + compas_args: Optional[Sequence[str]] = None, + executable: Optional[str] = None, + check: bool = True, + **kwargs, +) -> subprocess.CompletedProcess: + resolved_executable = executable or resolve_compas_executable() + executable_path = Path(resolved_executable) + if executable_path.suffix == ".sh": + command = ["bash", resolved_executable, *(compas_args or [])] + else: + command = [resolved_executable, *(compas_args or [])] + return subprocess.run(command, check=check, **kwargs) + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser( + description="Run the COMPAS executable from Python.", + ) + parser.add_argument( + "--print-path", + action="store_true", + help="Print the resolved COMPAS executable path and exit.", + ) + args, compas_args = parser.parse_known_args(argv) + + executable = resolve_compas_executable() + + if args.print_path: + print(executable) + return 0 + + completed_process = run_compas(compas_args=compas_args, executable=executable, check=False) + return completed_process.returncode + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/compas_python_utils/preprocessing/runSubmit.py b/compas_python_utils/preprocessing/runSubmit.py index 7df4c46cd..68ed4c00d 100644 --- a/compas_python_utils/preprocessing/runSubmit.py +++ b/compas_python_utils/preprocessing/runSubmit.py @@ -6,6 +6,8 @@ import argparse import warnings +from compas_python_utils.compas_runner import resolve_compas_executable + # Check if we are using python 3 python_version = sys.version_info[0] print("python_version =", python_version) @@ -43,15 +45,14 @@ def __init__(self, config_file=DEFAULT_CONFIG_FILE, grid_filename=None, self.stringChoices = config['stringChoices'] if config['stringChoices'] else {} self.listChoices = config['listChoices'] if config['listChoices'] else {} - compas_root_dir = os.environ.get('COMPAS_ROOT_DIR', REPO_ROOT) + compas_root_dir = os.environ.get('COMPAS_ROOT_DIR') if compas_root_dir is None: warnings.warn( 'COMPAS_ROOT_DIR environment variable not set. Setting ' f'`export COMPAS_ROOT_DIR={REPO_ROOT}`' ) os.environ['COMPAS_ROOT_DIR'] = REPO_ROOT - compas_exe = os.path.join(compas_root_dir, 'src/COMPAS') - compas_executable_override = os.environ.get('COMPAS_EXECUTABLE_PATH', compas_exe) + compas_executable_override = resolve_compas_executable() print('compas_executable_override', compas_executable_override) self.compas_executable = compas_executable_override diff --git a/misc/cicd-scripts/linux-dependencies b/misc/cicd-scripts/linux-dependencies old mode 100644 new mode 100755 index 7accaa532..610dcf7a1 --- a/misc/cicd-scripts/linux-dependencies +++ b/misc/cicd-scripts/linux-dependencies @@ -1,17 +1,48 @@ -#! /bin/sh +#!/usr/bin/env bash +set -euo pipefail -sudo apt-get install -y \ - texlive-latex-recommended \ - texlive-latex-extra \ - texlive-fonts-recommended \ - texlive-fonts-extra \ - texlive-xetex \ - latexmk \ - xindy \ - dvipng \ - cm-super -sudo apt install texlive-latex-extra --fix-missing -sudo apt update && sudo apt install g++ libboost-all-dev libgsl-dev libhdf5-serial-dev -sudo apt-get install gcc libpq-dev -y -sudo apt-get install python3-dev python3-pip python3-venv python3-wheel -y \ No newline at end of file +PROFILE="${1:-full}" + +COMMON_BUILD_PACKAGES=( + ccache + g++ + libboost-all-dev + libgsl-dev + libhdf5-serial-dev +) + +FULL_ONLY_PACKAGES=( + texlive-latex-recommended + texlive-latex-extra + texlive-fonts-recommended + texlive-fonts-extra + texlive-xetex + latexmk + xindy + dvipng + cm-super + gcc + libpq-dev + python3-dev + python3-pip + python3-venv + python3-wheel +) + +sudo apt-get update + +case "$PROFILE" in + native-build) + sudo apt-get install -y "${COMMON_BUILD_PACKAGES[@]}" + ;; + full) + sudo apt-get install -y "${FULL_ONLY_PACKAGES[@]}" + sudo apt-get install -y --fix-missing texlive-latex-extra + sudo apt-get install -y "${COMMON_BUILD_PACKAGES[@]}" + ;; + *) + echo "Usage: $0 [full|native-build]" >&2 + exit 1 + ;; +esac diff --git a/misc/cicd-scripts/macos-wheel-dependencies b/misc/cicd-scripts/macos-wheel-dependencies new file mode 100755 index 000000000..b594fd195 --- /dev/null +++ b/misc/cicd-scripts/macos-wheel-dependencies @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Install the Homebrew packages required to compile and bundle the macOS PyPI +# wheel on GitHub's macOS runners. + +set -euo pipefail + +if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew is required for macOS wheel builds" >&2 + exit 1 +fi + +brew update +brew install boost gsl hdf5 diff --git a/misc/cicd-scripts/manylinux-dependencies b/misc/cicd-scripts/manylinux-dependencies new file mode 100755 index 000000000..9bd7eda9c --- /dev/null +++ b/misc/cicd-scripts/manylinux-dependencies @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Install the native system packages needed to compile and repair the Linux +# PyPI wheel inside the manylinux container used by cibuildwheel. + +set -euo pipefail + +if command -v dnf >/dev/null 2>&1; then + PKG_MANAGER="dnf" +elif command -v yum >/dev/null 2>&1; then + PKG_MANAGER="yum" +else + echo "Expected dnf or yum in the manylinux build environment" >&2 + exit 1 +fi + +"$PKG_MANAGER" install -y \ + boost-devel \ + gcc-c++ \ + gsl-devel \ + hdf5-devel \ + make \ + patchelf \ + which diff --git a/misc/cicd-scripts/package-linux-bundle.sh b/misc/cicd-scripts/package-linux-bundle.sh new file mode 100755 index 000000000..063ea0166 --- /dev/null +++ b/misc/cicd-scripts/package-linux-bundle.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +# Create the redistributable Linux COMPAS bundle used by the native tarball +# workflow and by the Linux PyPI wheel build. + +set -euo pipefail + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)" + +BINARY_PATH="${1:-$REPO_ROOT/src/COMPAS}" +BUNDLE_DIR="${2:-$REPO_ROOT/dist/COMPAS-linux-x86_64}" +BIN_DIR="$BUNDLE_DIR/bin" +LIB_DIR="$BUNDLE_DIR/lib" +README_PATH="$BUNDLE_DIR/README.txt" +TMP_LDD="$(mktemp)" + +cleanup() { + rm -f "$TMP_LDD" +} + +trap cleanup EXIT + +maybe_strip() { + local path="$1" + + if ! command -v strip >/dev/null 2>&1; then + return 0 + fi + + # Best-effort size reduction only. Some binaries or shared libraries may + # already be stripped or may not support the requested mode. + strip --strip-unneeded "$path" 2>/dev/null || strip "$path" 2>/dev/null || true +} + +if [ ! -x "$BINARY_PATH" ]; then + echo "Expected executable COMPAS binary at '$BINARY_PATH'" >&2 + exit 1 +fi + +if ! command -v ldd >/dev/null 2>&1; then + echo "ldd is required to package the Linux bundle" >&2 + exit 1 +fi + +rm -rf "$BUNDLE_DIR" +mkdir -p "$BIN_DIR" "$LIB_DIR" + +cp -Lf "$BINARY_PATH" "$BIN_DIR/COMPAS" +cp "$SCRIPT_DIR/run_compas.sh" "$BUNDLE_DIR/run_compas.sh" +chmod 755 "$BIN_DIR/COMPAS" "$BUNDLE_DIR/run_compas.sh" +maybe_strip "$BIN_DIR/COMPAS" + +ldd "$BIN_DIR/COMPAS" | tee "$TMP_LDD" + +if grep -q 'not found' "$TMP_LDD"; then + echo "Cannot package bundle with unresolved runtime dependencies" >&2 + exit 1 +fi + +awk ' + /=>/ && $3 ~ /^\// { print $3; next } + $1 ~ /^\// { print $1; next } +' "$TMP_LDD" | sort -u | while IFS= read -r lib; do + case "$lib" in + /lib64/ld-linux-*|/lib/x86_64-linux-gnu/ld-linux-*|*/libc.so.*|*/libm.so.*|*/libpthread.so.*|*/libdl.so.*|*/librt.so.*|*/libresolv.so.*|*/libnss_*.so.*|*/libcrypt.so.*|*/libutil.so.*|*/libanl.so.*) + echo "Skipping system runtime: $lib" + ;; + *) + cp -Ln "$lib" "$LIB_DIR/" + maybe_strip "$LIB_DIR/$(basename "$lib")" + ;; + esac +done + +cat > "$README_PATH" <<'EOF' +COMPAS bundled Linux artifact + +Supported platform: +- Ubuntu 22.04 +- Linux x86_64 + +Contents: +- bin/COMPAS: bundled COMPAS executable +- lib/: shared libraries packaged with this build +- run_compas.sh: launcher that sets LD_LIBRARY_PATH for the bundled libs + +How to run: +1. Unpack this directory on a supported Linux x86_64 machine. +2. From inside the unpacked directory, run: + ./run_compas.sh -v + +Notes and caveats: +- This bundle is intended to run without separately installing Boost, GSL, or HDF5. +- It still relies on the target machine's glibc and other base system libraries being compatible with Ubuntu 22.04. +- If you invoke bin/COMPAS directly, the bundled lib/ directory will not be added automatically; use run_compas.sh instead. +EOF + +echo +echo "Created bundle at: $BUNDLE_DIR" +echo "Bundled shared libraries:" +find "$LIB_DIR" -maxdepth 1 -type f | sort diff --git a/misc/cicd-scripts/package-macos-bundle.sh b/misc/cicd-scripts/package-macos-bundle.sh new file mode 100755 index 000000000..57df6f2a8 --- /dev/null +++ b/misc/cicd-scripts/package-macos-bundle.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash + +# Create the redistributable macOS COMPAS bundle embedded in the platform- +# specific macOS wheels built by cibuildwheel. + +set -euo pipefail + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +REPO_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)" + +BINARY_PATH="${1:-$REPO_ROOT/src/COMPAS}" +ARCH_NAME="${2:-$(uname -m)}" +BUNDLE_DIR="${3:-$REPO_ROOT/dist/COMPAS-macos-$ARCH_NAME}" +BIN_DIR="$BUNDLE_DIR/bin" +LIB_DIR="$BUNDLE_DIR/lib" +README_PATH="$BUNDLE_DIR/README.txt" +SEEN_DEPS="$(mktemp)" + +cleanup() { + rm -f "$SEEN_DEPS" +} + +trap cleanup EXIT + +if [ ! -x "$BINARY_PATH" ]; then + echo "Expected executable COMPAS binary at '$BINARY_PATH'" >&2 + exit 1 +fi + +for required_command in codesign install_name_tool otool; do + if ! command -v "$required_command" >/dev/null 2>&1; then + echo "$required_command is required to package the macOS bundle" >&2 + exit 1 + fi +done + +maybe_strip() { + local path="$1" + + if ! command -v strip >/dev/null 2>&1; then + return 0 + fi + + strip -x "$path" 2>/dev/null || true +} + +is_system_library() { + case "$1" in + /System/*|/usr/lib/*) + return 0 + ;; + *) + return 1 + ;; + esac +} + +dependencies_for() { + otool -L "$1" | tail -n +2 | awk '{print $1}' +} + +resolve_dependency_path() { + local dependency="$1" + local referencing_file="$2" + local reference_dir + + case "$dependency" in + @loader_path/*) + reference_dir="$(CDPATH= cd -- "$(dirname -- "$referencing_file")" && pwd)" + echo "$reference_dir/${dependency#@loader_path/}" + ;; + @executable_path/*) + echo "$BIN_DIR/${dependency#@executable_path/}" + ;; + @rpath/*) + return 1 + ;; + *) + echo "$dependency" + ;; + esac +} + +copy_dependency() { + local source_path="$1" + local dest_path="$LIB_DIR/$(basename "$source_path")" + + if [ ! -f "$dest_path" ]; then + cp -f "$source_path" "$dest_path" + chmod 755 "$dest_path" + maybe_strip "$dest_path" + fi +} + +rewrite_references() { + local target="$1" + local target_dir_mode="$2" + local dependency + local dependency_name + + while IFS= read -r dependency; do + dependency_name="$(basename "$dependency")" + if [ -f "$LIB_DIR/$dependency_name" ]; then + if [ "$target_dir_mode" = "binary" ]; then + install_name_tool -change "$dependency" "@loader_path/../lib/$dependency_name" "$target" || true + else + install_name_tool -change "$dependency" "@loader_path/$dependency_name" "$target" || true + fi + fi + done < <(dependencies_for "$target") +} + +rm -rf "$BUNDLE_DIR" +mkdir -p "$BIN_DIR" "$LIB_DIR" + +cp -f "$BINARY_PATH" "$BIN_DIR/COMPAS" +cp "$SCRIPT_DIR/run_compas.sh" "$BUNDLE_DIR/run_compas.sh" +chmod 755 "$BIN_DIR/COMPAS" "$BUNDLE_DIR/run_compas.sh" +maybe_strip "$BIN_DIR/COMPAS" + +declare -a queue=("$BINARY_PATH") + +while [ "${#queue[@]}" -gt 0 ]; do + current_file="${queue[0]}" + queue=("${queue[@]:1}") + + while IFS= read -r dependency; do + resolved_dependency="$(resolve_dependency_path "$dependency" "$current_file" || true)" + + case "$dependency" in + @rpath/*|"") + continue + ;; + esac + + if [ -z "$resolved_dependency" ] || [ ! -e "$resolved_dependency" ]; then + continue + fi + + if is_system_library "$resolved_dependency"; then + continue + fi + + dependency_name="$(basename "$resolved_dependency")" + copy_dependency "$resolved_dependency" + + if ! grep -qx "$dependency_name" "$SEEN_DEPS" 2>/dev/null; then + echo "$dependency_name" >> "$SEEN_DEPS" + queue+=("$resolved_dependency") + fi + done < <(dependencies_for "$current_file") +done + +for library_path in "$LIB_DIR"/*; do + [ -f "$library_path" ] || continue + library_name="$(basename "$library_path")" + install_name_tool -id "@loader_path/$library_name" "$library_path" || true + rewrite_references "$library_path" "library" + codesign --force --sign - "$library_path" >/dev/null 2>&1 || true +done + +rewrite_references "$BIN_DIR/COMPAS" "binary" +codesign --force --sign - "$BIN_DIR/COMPAS" >/dev/null 2>&1 || true + +cat > "$README_PATH" <` for more details. +Running COMPAS via Python +========================= + +A convenient method of managing the many program options provided by COMPAS is to run COMPAS via Python, using a script to manage and +specify the values of the program options. + +Bundled PyPI executable +----------------------- + +On supported platforms, the PyPI package can include the native COMPAS +executable directly. After installing:: + + pip install compas-popsynth + +you can run the packaged executable with:: + + compas_run -v + +and a minimal smoke test with:: + + compas_run -n 1 --initial-mass-1 35 --initial-mass-2 31 -a 3.5 --random-seed 0 --metallicity 0.001 --quiet -o compas-smoke-output + +``compas_run`` resolves to the bundled native executable when a supported wheel +is installed. If you want to point the Python launcher at a separate local build +or an extracted bundle, set ``COMPAS_EXECUTABLE_PATH`` or ``COMPAS_BUNDLE_ROOT``. + +An example Python script is provided in the COMPAS suite on github: ``runSubmit.py``. Additionally, the default COMPAS options are specified on ``compasConfigDefault.yaml``. Users should copy the ``runSubmit.py`` and ``runSubmit.py`` scripts and modify the ``compasConfigDefault.yaml`` copy to match their experimental requirements. Refer to the :doc:`Getting started guide <../../Getting started/getting-started>` for more details. To run COMPAS via Python using the ``runSubmit.py`` script provided, set the shell environment variable ``COMPAS_ROOT_DIR`` to the parent directory of the directory in which the COMPAS executable resides, then type `python /path-to-runSubmit/runSubmit.py`. diff --git a/py_tests/test_compas_runner.py b/py_tests/test_compas_runner.py new file mode 100644 index 000000000..174a0bf1a --- /dev/null +++ b/py_tests/test_compas_runner.py @@ -0,0 +1,52 @@ +import os + +from compas_python_utils.compas_runner import main, resolve_compas_executable, run_compas + + +def _make_fake_executable(path, contents=None): + executable_contents = contents or "#!/usr/bin/env bash\nprintf 'fake-compas %s\\n' \"$*\"\n" + path.write_text(executable_contents) + os.chmod(path, 0o755) + + +def test_resolve_compas_executable_from_env(monkeypatch, tmp_path): + fake_executable = tmp_path / "COMPAS" + _make_fake_executable(fake_executable) + + monkeypatch.setenv("COMPAS_EXECUTABLE_PATH", str(fake_executable)) + monkeypatch.delenv("COMPAS_BUNDLE_ROOT", raising=False) + + assert resolve_compas_executable() == str(fake_executable) + + +def test_resolve_compas_executable_from_bundle_root(monkeypatch, tmp_path): + bundle_root = tmp_path / "COMPAS-linux-x86_64" + bundle_root.mkdir() + launcher = bundle_root / "run_compas.sh" + _make_fake_executable(launcher) + + monkeypatch.delenv("COMPAS_EXECUTABLE_PATH", raising=False) + monkeypatch.setenv("COMPAS_BUNDLE_ROOT", str(bundle_root)) + + assert resolve_compas_executable() == str(launcher) + + +def test_run_compas_executes_resolved_binary(monkeypatch, tmp_path): + fake_executable = tmp_path / "COMPAS" + _make_fake_executable(fake_executable) + + monkeypatch.setenv("COMPAS_EXECUTABLE_PATH", str(fake_executable)) + completed_process = run_compas(["-v"], capture_output=True, text=True) + + assert completed_process.returncode == 0 + assert "fake-compas -v" in completed_process.stdout + + +def test_main_print_path(monkeypatch, tmp_path, capsys): + fake_executable = tmp_path / "COMPAS" + _make_fake_executable(fake_executable) + + monkeypatch.setenv("COMPAS_EXECUTABLE_PATH", str(fake_executable)) + + assert main(["--print-path"]) == 0 + assert str(fake_executable) in capsys.readouterr().out diff --git a/setup.py b/setup.py index 8cf5abe20..928f0df5d 100644 --- a/setup.py +++ b/setup.py @@ -3,44 +3,62 @@ import re import sys -from setuptools import find_packages, setup +from setuptools import Distribution, find_packages, setup + +try: + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel +except ImportError: + _bdist_wheel = None python_version = sys.version_info if python_version < (3, 8): sys.exit("Python < 3.8 is not supported, aborting setup") NAME = "compas_python_utils" +DIST_NAME = "compas-popsynth" PACKAGES = find_packages() HERE = os.path.dirname(os.path.realpath(__file__)) META_PATH = os.path.join(NAME, "__init__.py") CPP_VERSION_FILE = os.path.join("src", "changelog.h") +BUILD_BINARY_WHEEL = os.environ.get("COMPAS_BINARY_WHEEL") == "1" CLASSIFIERS = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", "Programming Language :: Python", "Programming Language :: Python :: 3", ] -INSTALL_REQUIRES = [ +CORE_RUNTIME_REQUIRES = [ "numpy>=1.16", + "PyYAML", +] +ANALYSIS_REQUIRES = [ "h5py", - "argparse", "stroopwafel", - "pytest>=3.6", - "pre-commit", - "flake8", - "black==22.10.0", - "isort", "matplotlib>=3.3.2", "pandas", "astropy>=4.0", "scipy>=1.5.0", - "latex", - "PyYAML", "tqdm", - "corner" + "corner", +] +DEV_ONLY_REQUIRES = [ + "pytest>=3.6", + "pytest-cov", + "pre-commit", + "flake8", + "black==22.10.0", + "isort", + "coverage-badge", + "deepdiff", + "jupytext", + "jupyter-autotime", + "memory_profiler", + "nbconvert", + "ipykernel", ] EXTRA_REQUIRE = dict( docs=[ @@ -58,24 +76,31 @@ "sphinx-togglebutton", "linuxdoc>=20210324" ], - dev=[ - "pytest-cov", - "pre-commit", - "flake8", - "black==22.10.0", - "isort", - "coverage-badge", - "deepdiff", - "jupytext", - "jupyter-autotime", - "memory_profiler", - "nbconvert", - "ipykernel", - ], + full=[], + dev=DEV_ONLY_REQUIRES, gpu=["cupy"], ) +class COMPASDistribution(Distribution): + def has_ext_modules(self): + return BUILD_BINARY_WHEEL + + +cmdclass = {} +if BUILD_BINARY_WHEEL and _bdist_wheel is not None: + class COMPASBdistWheel(_bdist_wheel): + def finalize_options(self): + super().finalize_options() + self.root_is_pure = False + + def get_tag(self): + _, _, platform_tag = super().get_tag() + return "py3", "none", platform_tag + + cmdclass["bdist_wheel"] = COMPASBdistWheel + + def read(*parts): with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f: return f.read() @@ -95,14 +120,16 @@ def find_version(version_file=read(CPP_VERSION_FILE)): r"VERSION_STRING = ['\"]([^'\"]*)['\"]", version_file, re.M ) if version_match: - return version_match.group(1) + raw_version = version_match.group(1) + normalized_parts = [str(int(part)) for part in raw_version.split(".")] + return ".".join(normalized_parts) raise RuntimeError("Unable to find version string.") if __name__ == "__main__": setup( - name=NAME, - version=find_meta("version"), + name=DIST_NAME, + version=find_version(), author=find_meta("author"), author_email=find_meta("email"), maintainer=find_meta("author"), @@ -113,22 +140,32 @@ def find_version(version_file=read(CPP_VERSION_FILE)): long_description=read("README.md"), long_description_content_type="text/markdown", packages=PACKAGES, + python_requires=">=3.8", package_data={ + NAME: [ + "bundled/*", + "bundled/*/*.sh", + "bundled/*/bin/*", + "bundled/*/lib/*", + ], f"{NAME}.preprocessing": ["*.txt", "*.yaml"], f"{NAME}.detailed_evolution_plotter": ["van_den_heuvel_figures/*"], f"{NAME}.cosmic_integration": ["SNR_Grid*"], }, include_package_data=True, - install_requires=INSTALL_REQUIRES, + install_requires=CORE_RUNTIME_REQUIRES + ANALYSIS_REQUIRES, extras_require=EXTRA_REQUIRE, classifiers=CLASSIFIERS, - zip_safe=True, + zip_safe=not BUILD_BINARY_WHEEL, + distclass=COMPASDistribution, + cmdclass=cmdclass, entry_points={ "console_scripts": [ f"compas_h5view= {NAME}.h5view:main", f"compas_h5copy= {NAME}.h5copy:main", f"compas_h5sample= {NAME}.h5sample:main", f"compas_plot_detailed_evolution={NAME}.detailed_evolution_plotter.plot_detailed_evolution:main", + f"compas_run={NAME}.compas_runner:main", f"compas_run_submit={NAME}.preprocessing.runSubmit:main", f"compas_sample_stroopwafel={NAME}.preprocessing.stroopwafelInterface:main", f"compas_sample_moe_di_stefano={NAME}.preprocessing.sampleMoeDiStefano:main", diff --git a/src/Makefile b/src/Makefile index ddab836d3..ae3d83836 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -CPP := g++ +CPP ?= g++ UNAME_S := $(shell uname -s) HOMEBREW_PREFIX := $(shell brew --prefix 2>/dev/null)