diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2b6e82e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,38 @@ +version: 2 +updates: + # Python dependencies + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + open-pull-requests-limit: 10 + reviewers: + - "elevator-py-team" + assignees: + - "elevator-py-team" + labels: + - "dependencies" + - "python" + commit-message: + prefix: "deps" + include: "scope" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + open-pull-requests-limit: 5 + reviewers: + - "elevator-py-team" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f5ca0fb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,220 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + schedule: + # Run tests daily at 6 AM UTC + - cron: '0 6 * * *' + +jobs: + test: + name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Check dependencies + run: | + python run_all_tests.py --check-deps + + - name: Run linting + run: | + black --check msgcenterpy tests + isort --check-only msgcenterpy tests + + - name: Run type checking + run: | + mypy msgcenterpy + + - name: Run tests with coverage + run: | + python -m pytest --cov=msgcenterpy --cov-report=xml --cov-report=term-missing + + - name: Upload coverage to Codecov + if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest' + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + + test-with-ros2: + name: Test with ROS2 (Ubuntu) + runs-on: ubuntu-latest + container: + image: ros:humble + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + run: | + apt-get update + apt-get install -y python3-pip python3-dev + + - name: Install ROS2 dependencies + run: | + apt-get update + apt-get install -y \ + python3-rosidl-runtime-py \ + python3-rclpy \ + ros-humble-std-msgs \ + ros-humble-geometry-msgs + + - name: Install package + run: | + python3 -m pip install --upgrade pip + pip3 install -e .[dev,ros2] + + - name: Run ROS2 tests + run: | + . /opt/ros/humble/setup.sh + python3 run_all_tests.py --type ros2 + + - name: Run conversion tests + run: | + . /opt/ros/humble/setup.sh + python3 run_all_tests.py --type conversion + + build: + name: Build and check package + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build twine check-manifest + + - name: Check manifest + run: check-manifest + + - name: Build package + run: python -m build + + - name: Check package + run: twine check dist/* + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/ + + security: + name: Security scan + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install security tools + run: | + python -m pip install --upgrade pip + pip install bandit safety + + - name: Run bandit security scan + run: bandit -r msgcenterpy/ -f json -o bandit-report.json + continue-on-error: true + + - name: Run safety security scan + run: safety check --json --output safety-report.json + continue-on-error: true + + - name: Upload security reports + uses: actions/upload-artifact@v3 + with: + name: security-reports + path: | + bandit-report.json + safety-report.json + if: always() + + docs: + name: Build documentation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e .[docs] + + # 为将来的文档构建预留 + - name: Check documentation + run: | + echo "Documentation build placeholder" + # sphinx-build -b html docs docs/_build/html + + performance: + name: Performance benchmarks + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install pytest-benchmark + + # 为将来的性能测试预留 + - name: Run benchmarks + run: | + echo "Performance benchmarks placeholder" + # python -m pytest tests/benchmarks/ --benchmark-json=benchmark.json + diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..f5cbf6e --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,269 @@ +name: Code Quality + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + schedule: + # Run weekly code quality checks + - cron: '0 2 * * 1' + +jobs: + lint: + name: Linting and formatting + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-lint-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip-lint- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install flake8 pylint + + - name: Run Black formatting check + run: | + black --check --diff elevator_saga tests + + - name: Run isort import sorting check + run: | + isort --check-only --diff elevator_saga tests + + - name: Run flake8 + run: | + flake8 elevator_saga tests --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 elevator_saga tests --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics + + - name: Run pylint + run: | + pylint elevator_saga --exit-zero --output-format=parseable --reports=no | tee pylint-report.txt + + - name: Upload lint reports + uses: actions/upload-artifact@v3 + with: + name: lint-reports + path: | + pylint-report.txt + if: always() + + type-check: + name: Type checking + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run mypy type checking + run: | + mypy elevator_saga --html-report mypy-report --txt-report mypy-report + + - name: Upload type check reports + uses: actions/upload-artifact@v3 + with: + name: type-check-reports + path: mypy-report/ + if: always() + + complexity: + name: Code complexity analysis + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install radon xenon + + - name: Run cyclomatic complexity check + run: | + radon cc elevator_saga --min B --total-average + + - name: Run maintainability index + run: | + radon mi elevator_saga --min B + + - name: Run complexity with xenon + run: | + xenon --max-absolute B --max-modules B --max-average A elevator_saga + continue-on-error: true + + dependencies: + name: Dependency analysis + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install pip-audit pipdeptree + + - name: Generate dependency tree + run: | + pipdeptree --freeze > requirements-freeze.txt + pipdeptree --graph-output png > dependency-graph.png + + - name: Check for known vulnerabilities + run: | + pip-audit --format=json --output=vulnerability-report.json + continue-on-error: true + + - name: Upload dependency reports + uses: actions/upload-artifact@v3 + with: + name: dependency-reports + path: | + requirements-freeze.txt + dependency-graph.png + vulnerability-report.json + if: always() + + documentation: + name: Documentation quality + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install pydocstyle interrogate + + - name: Check docstring style + run: | + pydocstyle elevator_saga --count --explain --source + continue-on-error: true + + - name: Check docstring coverage + run: | + interrogate elevator_saga --ignore-init-method --ignore-magic --ignore-module --ignore-nested-functions --fail-under=70 + continue-on-error: true + + pre-commit: + name: Pre-commit hooks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + + - name: Cache pre-commit + uses: actions/cache@v3 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Run pre-commit hooks + run: | + pre-commit run --all-files --show-diff-on-failure + continue-on-error: true + + summary: + name: Quality summary + runs-on: ubuntu-latest + needs: [lint, type-check, complexity, dependencies, documentation, pre-commit] + if: always() + + steps: + - name: Create quality report + run: | + echo "## 📊 Code Quality Report" >> $GITHUB_STEP_SUMMARY + echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + + # Format results + if [ "${{ needs.lint.result }}" = "success" ]; then + echo "| Linting | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + else + echo "| Linting | ❌ Failed |" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.type-check.result }}" = "success" ]; then + echo "| Type Check | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + else + echo "| Type Check | ❌ Failed |" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.complexity.result }}" = "success" ]; then + echo "| Complexity | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + else + echo "| Complexity | ⚠️ Warning |" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.dependencies.result }}" = "success" ]; then + echo "| Dependencies | ✅ Secure |" >> $GITHUB_STEP_SUMMARY + else + echo "| Dependencies | ⚠️ Check needed |" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.documentation.result }}" = "success" ]; then + echo "| Documentation | ✅ Good |" >> $GITHUB_STEP_SUMMARY + else + echo "| Documentation | ⚠️ Needs improvement |" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.pre-commit.result }}" = "success" ]; then + echo "| Pre-commit | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + else + echo "| Pre-commit | ⚠️ Some hooks failed |" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "📈 **Overall Quality:** ${{ needs.lint.result == 'success' && needs.type-check.result == 'success' && 'Good' || 'Needs attention' }}" >> $GITHUB_STEP_SUMMARY + diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 0000000..3b309d2 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,34 @@ +name: Dependabot Auto-merge + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + dependabot: + name: Auto-merge Dependabot PRs + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Enable auto-merge for Dependabot PRs + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Comment on major version updates + if: steps.metadata.outputs.update-type == 'version-update:semver-major' + run: | + gh pr comment "$PR_URL" --body "🚨 **Major version update detected!** Please review this PR carefully before merging." + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..28efe5b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,184 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: + inputs: + test_pypi: + description: 'Publish to Test PyPI instead of PyPI' + required: false + default: 'false' + type: boolean + +jobs: + test: + name: Run tests before publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Run comprehensive tests + run: | + python -m pytest --cov=msgcenterpy --cov-fail-under=80 + + - name: Run linting + run: | + black --check msgcenterpy tests + isort --check-only msgcenterpy tests + mypy msgcenterpy + + build: + name: Build package + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + # - name: Install build dependencies + # run: | + # python -m pip install --upgrade pip + # pip install build twine check-manifest + + # - name: Verify version consistency + # run: | + # # 检查版本号一致性 + # VERSION=$(python -c "import msgcenterpy; print(msgcenterpy.__version__)" 2>/dev/null || echo "unknown") + # TAG_VERSION="${GITHUB_REF#refs/tags/v}" + # if [ "$GITHUB_EVENT_NAME" = "release" ]; then + # if [ "$VERSION" != "$TAG_VERSION" ]; then + # echo "Version mismatch: package=$VERSION, tag=$TAG_VERSION" + # exit 1 + # fi + # fi + + # - name: Check manifest + # run: check-manifest + + - name: Build package + run: | + python -m build + + - name: Check package + run: | + twine check dist/* + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: dist-${{ github.run_number }} + path: dist/ + retention-days: 30 + + publish-test: + name: Publish to Test PyPI + runs-on: ubuntu-latest + needs: build + if: github.event.inputs.test_pypi == 'true' || (github.event_name == 'release' && github.event.release.prerelease) + environment: + name: test-pypi + url: https://test.pypi.org/p/msgcenterpy + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: dist-${{ github.run_number }} + path: dist/ + + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + verbose: true + + publish-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'release' && !github.event.release.prerelease && github.event.inputs.test_pypi != 'true' + environment: + name: pypi + url: https://pypi.org/p/msgcenterpy + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: dist-${{ github.run_number }} + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + verbose: true + + create-github-release-assets: + name: Add assets to GitHub release + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'release' + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: dist-${{ github.run_number }} + path: dist/ + + - name: Upload release assets + uses: softprops/action-gh-release@v1 + with: + files: dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + post-publish: + name: Post-publish tasks + runs-on: ubuntu-latest + needs: [publish-pypi, publish-test] + if: always() && (needs.publish-pypi.result == 'success' || needs.publish-test.result == 'success') + + steps: + - uses: actions/checkout@v4 + + - name: Create deployment summary + run: | + echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "| Item | Status |" >> $GITHUB_STEP_SUMMARY + echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.publish-pypi.result }}" = "success" ]; then + echo "| PyPI | ✅ Published |" >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.publish-test.result }}" = "success" ]; then + echo "| Test PyPI | ✅ Published |" >> $GITHUB_STEP_SUMMARY + fi + + echo "| GitHub Release | ✅ Assets uploaded |" >> $GITHUB_STEP_SUMMARY + echo "| Version | ${{ github.event.release.tag_name || 'test' }} |" >> $GITHUB_STEP_SUMMARY + + # 为将来的通知预留 + - name: Notify team + run: | + echo "Package published successfully!" + # 可以添加 Slack、Discord 等通知 + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a98f731 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,113 @@ +repos: + # Code formatting + - repo: https://github.com/psf/black + rev: 23.12.1 + hooks: + - id: black + language_version: python3 + args: ["--line-length=88"] + + # Import sorting + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black", "--multi-line", "3"] + + # Linting + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + args: + - "--max-line-length=88" + - "--extend-ignore=E203,W503,F401" + - "--exclude=build,dist,.eggs" + + # Type checking + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + additional_dependencies: [types-PyYAML] + args: ["--ignore-missing-imports", "--scripts-are-modules"] + exclude: "^(tests/|examples/)" + + # General pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + # File checks + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-xml + + # Security + - id: check-merge-conflict + - id: check-case-conflict + - id: check-symlinks + - id: check-added-large-files + args: ["--maxkb=1000"] + + # Python specific + - id: check-ast + - id: check-builtin-literals + - id: check-docstring-first + - id: debug-statements + - id: name-tests-test + args: ["--django"] + + # Security scanning + - repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: ["-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] + exclude: "^tests/" + + # Documentation + - repo: https://github.com/pycqa/pydocstyle + rev: 6.3.0 + hooks: + - id: pydocstyle + args: ["--convention=google"] + exclude: "^(tests/|examples/)" + + # YAML/JSON formatting + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + types_or: [yaml, json, markdown] + exclude: "^(.github/)" + + # Spell checking (optional) + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + args: ["--write-changes"] + exclude: "^(.*\\.po|.*\\.pot|CHANGELOG\\.md)$" + +# Global settings +default_stages: [commit, push] +fail_fast: false + +# CI settings +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit.com hooks + + for more information, see https://pre-commit.ci + autofix_prs: true + autoupdate_branch: '' + autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' + autoupdate_schedule: weekly + skip: [] + submodules: false +