name: CI

on:
  push:
    branches: [ master, "dynamic-feature-extraction" ]
  pull_request:
    branches: [ master, "dynamic-feature-extraction" ]

permissions: read-all

# save workspaces to speed up testing
env:
  CAPA_SAVE_WORKSPACE: "True"

jobs:
  changelog_format:
    runs-on: ubuntu-20.04
    steps:
    - name: Checkout capa
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
    # The sync GH action in capa-rules relies on a single '- *$' in the CHANGELOG file
    - name: Ensure CHANGELOG has '- *$'
      run: |
        number=$(grep '\- *$' CHANGELOG.md | wc -l)
        if [ $number != 1 ]; then exit 1; fi

  code_style:
    runs-on: ubuntu-20.04
    steps:
    - name: Checkout capa
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
    # use latest available python to take advantage of best performance
    - name: Set up Python 3.11
      uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
      with:
        python-version: "3.11"
    - name: Install dependencies
      run: pip install -e .[dev]
    - name: Lint with ruff
      run: pre-commit run ruff
    - name: Lint with isort
      run: pre-commit run isort --show-diff-on-failure
    - name: Lint with black
      run: pre-commit run black --show-diff-on-failure
    - name: Lint with flake8
      run: pre-commit run flake8 --hook-stage manual
    - name: Check types with mypy
      run:  pre-commit run mypy --hook-stage manual

  rule_linter:
    runs-on: ubuntu-20.04
    steps:
    - name: Checkout capa with submodules
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
      with:
        submodules: recursive
    - name: Set up Python 3.11
      uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
      with:
        python-version: "3.11"
    - name: Install capa
      run: pip install -e .[dev]
    - name: Run rule linter
      run: python scripts/lint.py rules/

  tests:
    name: Tests in ${{ matrix.python-version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    needs: [code_style, rule_linter]
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-20.04, windows-2019, macos-11]
        # across all operating systems
        python-version: ["3.8", "3.11"]
        include:
          # on Ubuntu run these as well
          - os: ubuntu-20.04
            python-version: "3.8"
          - os: ubuntu-20.04
            python-version: "3.9"
          - os: ubuntu-20.04
            python-version: "3.10"
    steps:
    - name: Checkout capa with submodules
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
      with:
        submodules: recursive
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install pyyaml
      if: matrix.os == 'ubuntu-20.04'
      run: sudo apt-get install -y libyaml-dev
    - name: Install capa
      run: pip install -e .[dev]
    - name: Run tests (fast)
      # this set of tests runs about 80% of the cases in 20% of the time,
      # and should catch most errors quickly.
      run:  pre-commit run pytest-fast --all-files --hook-stage manual
    - name: Run tests
      run: pytest -v tests/

  binja-tests:
    name: Binary Ninja tests for ${{ matrix.python-version }}
    env:
      BN_SERIAL: ${{ secrets.BN_SERIAL }}
    runs-on: ubuntu-20.04
    needs: [tests]
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.8", "3.11"]
    steps:
    - name: Checkout capa with submodules
      # do only run if BN_SERIAL is available, have to do this in every step, see https://github.com/orgs/community/discussions/26726#discussioncomment-3253118
      if: ${{ env.BN_SERIAL != 0 }}
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
      with:
        submodules: recursive
    - name: Set up Python ${{ matrix.python-version }}
      if: ${{ env.BN_SERIAL != 0 }}
      uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install pyyaml
      if: ${{ env.BN_SERIAL != 0 }}
      run: sudo apt-get install -y libyaml-dev
    - name: Install capa
      if: ${{ env.BN_SERIAL != 0 }}
      run: pip install -e .[dev]
    - name: install Binary Ninja
      if: ${{ env.BN_SERIAL != 0 }}
      run: |
        mkdir ./.github/binja
        curl "https://raw.githubusercontent.com/Vector35/binaryninja-api/6812c97/scripts/download_headless.py" -o ./.github/binja/download_headless.py
        python ./.github/binja/download_headless.py --serial ${{ env.BN_SERIAL }} --output .github/binja/BinaryNinja-headless.zip
        unzip .github/binja/BinaryNinja-headless.zip -d .github/binja/
        python .github/binja/binaryninja/scripts/install_api.py --install-on-root --silent
    - name: Run tests
      if: ${{ env.BN_SERIAL != 0 }}
      env:
        BN_LICENSE: ${{ secrets.BN_LICENSE }}
      run: pytest -v tests/test_binja_features.py  # explicitly refer to the binja tests for performance. other tests run above.

  ghidra-tests:
    name: Ghidra tests for ${{ matrix.python-version }}
    runs-on: ubuntu-20.04
    needs: [tests]
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.8", "3.11"]
        java-version: ["17"]
        gradle-version: ["7.3"]
        ghidra-version: ["10.3"]
        public-version: ["PUBLIC_20230510"] # for ghidra releases
        jep-version: ["4.1.1"]
        ghidrathon-version: ["3.0.0"]
    steps:
    - name: Checkout capa with submodules
      uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
      with:
        submodules: true
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
      with:
        python-version: ${{ matrix.python-version }}
    - name: Set up Java ${{ matrix.java-version }}
      uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3
      with:
        distribution: 'temurin'
        java-version: ${{ matrix.java-version }}
    - name: Set up Gradle ${{ matrix.gradle-version }} 
      uses: gradle/gradle-build-action@40b6781dcdec2762ad36556682ac74e31030cfe2 # v2.5.1
      with:
        gradle-version: ${{ matrix.gradle-version }}
    - name: Install Jep ${{ matrix.jep-version }} 
      run : pip install jep==${{ matrix.jep-version }}
    - name: Install Ghidra ${{ matrix.ghidra-version }} 
      run: |
        mkdir ./.github/ghidra
        wget "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${{ matrix.ghidra-version }}_build/ghidra_${{ matrix.ghidra-version }}_${{ matrix.public-version }}.zip" -O ./.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC.zip
        unzip .github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC.zip -d .github/ghidra/
    - name: Install Ghidrathon
      run : |
        mkdir ./.github/ghidrathon
        curl -o ./.github/ghidrathon/ghidrathon-${{ matrix.ghidrathon-version }}.zip "https://codeload.github.com/mandiant/Ghidrathon/zip/refs/tags/v${{ matrix.ghidrathon-version }}"
        unzip .github/ghidrathon/ghidrathon-${{ matrix.ghidrathon-version }}.zip -d .github/ghidrathon/
        gradle -p ./.github/ghidrathon/Ghidrathon-${{ matrix.ghidrathon-version }}/ -PGHIDRA_INSTALL_DIR=$(pwd)/.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC
        unzip .github/ghidrathon/Ghidrathon-${{ matrix.ghidrathon-version }}/dist/*.zip -d .github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC/Ghidra/Extensions
    - name: Install pyyaml
      run: sudo apt-get install -y libyaml-dev
    - name: Install capa
      run: pip install -e .[dev] 
    - name: Run tests
      run: | 
        mkdir ./.github/ghidra/project
        .github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC/support/analyzeHeadless .github/ghidra/project ghidra_test -Import ./tests/data/mimikatz.exe_ -ScriptPath ./tests/ -PostScript test_ghidra_features.py > ../output.log
        cat ../output.log
        exit_code=$(cat ../output.log | grep exit | awk '{print $NF}')
        exit $exit_code
 