diff --git a/.gitignore b/.gitignore index eba74f4..53ac36d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -venv/ \ No newline at end of file +venv/ +__pycache__/ +*.pyc \ No newline at end of file diff --git a/README.md b/README.md index 62d1045..df9bcb6 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,18 @@ on: jobs: create-matrix: runs-on: ubuntu-latest + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read outputs: - matrix: ${{ steps.set-matrix.outputs.languages }} + matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Get languages from repo id: set-matrix @@ -43,7 +53,7 @@ jobs: needs: create-matrix if: ${{ needs.create-matrix.outputs.matrix != '[]' }} name: Analyze - runs-on: ubuntu-latest + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: actions: read contents: read @@ -51,8 +61,7 @@ jobs: strategy: fail-fast: false - matrix: - language: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} + matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} steps: - name: Checkout repository @@ -63,10 +72,17 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + build-mode: ${{ matrix.build-mode }} + + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 @@ -82,7 +98,7 @@ Example: create-matrix: runs-on: ubuntu-latest outputs: - matrix: ${{ steps.set-matrix.outputs.languages }} + matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Get languages from repo id: set-matrix @@ -94,6 +110,28 @@ Example: ``` +### Build Mode Override +By default, the action sets the build mode to: +- `none` for most languages (python, javascript, ruby, rust, actions, etc.) +- `manual` for languages that typically require custom build steps (go, swift, java) + +If you want to override this behavior and use manual build mode for specific languages, use the `build-mode-manual-override` input: + +``` yaml + create-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Get languages from repo + id: set-matrix + uses: advanced-security/set-codeql-language-matrix@v1 + with: + access-token: ${{ secrets.GITHUB_TOKEN }} + endpoint: ${{ github.event.repository.languages_url }} + build-mode-manual-override: 'java, csharp' +``` + ### Actions support The GitHub API for [List repository languages](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-languages) does not by default include "YAML"/"GitHub Actions". This is particularly useful if your repository contains GitHub Actions workflows that you want to include in CodeQL analysis. diff --git a/action.yml b/action.yml index cdca95e..f54207f 100644 --- a/action.yml +++ b/action.yml @@ -12,9 +12,14 @@ inputs: exclude: description: 'Use a comma separated list here to exclude specific languges from your CodeQL scan. Example: "python, java"' required: false + build-mode-manual-override: + description: 'Use a comma separated list here to specify languages that should use manual build mode instead of the default. Example: "java, csharp"' + required: false outputs: + matrix: + description: 'Matrix definition including language and build-mode configurations' languages: - description: 'List of languages that will set the job matrix' + description: 'List of languages that will set the job matrix (deprecated - use matrix instead)' runs: using: 'docker' image: 'Dockerfile' @@ -22,4 +27,5 @@ runs: - ${{ inputs.access-token }} - ${{ inputs.endpoint }} - ${{ inputs.exclude }} + - ${{ inputs.build-mode-manual-override }} diff --git a/entrypoint.sh b/entrypoint.sh index 2424ea5..2280414 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,4 +1,4 @@ #!/bin/sh -l # kick off the command -python /main.py $1 $2 "$3" \ No newline at end of file +python /main.py $1 $2 "$3" "$4" \ No newline at end of file diff --git a/main.py b/main.py index 7480e4e..afd4a2f 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,8 @@ token = sys.argv[1] endpoint = sys.argv[2] -exclude = sys.argv[3] +exclude = sys.argv[3] if len(sys.argv) > 3 else "" +build_mode_manual_override = sys.argv[4] if len(sys.argv) > 4 else "" codeql_languages = ["actions", "cpp", "csharp", "go", "java", "javascript", "python", "ruby", "rust", "typescript", "kotlin", "swift"] @@ -17,32 +18,93 @@ def get_languages(): # Find the intersection of the languages returned by the API and the languages supported by CodeQL def build_languages_list(languages): - languages = [language.lower() for language in languages.keys()] - for i in range(len(languages)): - if languages[i] == "c#": - languages[i] = ("csharp") - if languages[i] == "c++": - languages[i] = ("cpp") - if languages[i] == "c": - languages[i] = ("cpp") - if languages[i] == "typescript": - languages[i] = ("javascript") - if languages[i] == "kotlin": - languages[i] = ("java") - if languages[i] == "yaml": - languages[i] = ("actions") - print("After mapping:", languages) - intersection = list(set(languages) & set(codeql_languages)) + original_languages = [language.lower() for language in languages.keys()] + mapped_languages = [] + language_mapping = {} # Track mapped language -> list of original languages + + for orig_lang in original_languages: + mapped_lang = orig_lang + if orig_lang == "c#": + mapped_lang = "csharp" + elif orig_lang == "c++": + mapped_lang = "cpp" + elif orig_lang == "c": + mapped_lang = "cpp" + elif orig_lang == "typescript": + mapped_lang = "javascript" + elif orig_lang == "kotlin": + mapped_lang = "java" + elif orig_lang == "yaml": + mapped_lang = "actions" + + mapped_languages.append(mapped_lang) + + # Track all original languages that map to this CodeQL language + if mapped_lang not in language_mapping: + language_mapping[mapped_lang] = [] + language_mapping[mapped_lang].append(orig_lang) + + print("After mapping:", mapped_languages) + intersection = list(set(mapped_languages) & set(codeql_languages)) print("Intersection:", intersection) - return intersection + return intersection, language_mapping # return a list of objects from language list if they are not in the exclude list def exclude_languages(language_list): + if not exclude: + return language_list excluded = [x.strip() for x in exclude.split(',')] output = list(set(language_list).difference(excluded)) print("languages={}".format(output)) return output +# Determine build mode for each language +def get_build_mode(language, original_languages=None): + # Languages that should use manual build mode by default + # Check original languages first if available + if original_languages: + # If any of the original languages require manual build mode, use manual + for orig_lang in original_languages: + if orig_lang in ["kotlin", "go", "swift"]: + manual_by_default = True + break + else: + manual_by_default = False + else: + # Fallback to mapped language check + manual_by_default = language in ["go", "swift", "java"] + + # Check if user overrode build mode to manual + if build_mode_manual_override: + override_languages = [x.strip() for x in build_mode_manual_override.split(',')] + if language in override_languages: + return "manual" + if original_languages: + for orig_lang in original_languages: + if orig_lang in override_languages: + return "manual" + + # Use default logic + if manual_by_default: + return "manual" + else: + return "none" + +# Build the matrix include format +def build_matrix(language_list, language_mapping): + include = [] + for language in language_list: + original_languages = language_mapping.get(language, [language]) + build_mode = get_build_mode(language, original_languages) + include.append({ + "language": language, + "build-mode": build_mode + }) + + matrix = {"include": include} + print("Matrix:", matrix) + return matrix + # Set the output of the action def set_action_output(output_name, value) : if "GITHUB_OUTPUT" in os.environ : @@ -51,9 +113,12 @@ def set_action_output(output_name, value) : def main(): languages = get_languages() - language_list = build_languages_list(languages) - output = exclude_languages(language_list) - set_action_output("languages", json.dumps(output)) + language_list, language_mapping = build_languages_list(languages) + filtered_languages = exclude_languages(language_list) + matrix = build_matrix(filtered_languages, language_mapping) + set_action_output("matrix", json.dumps(matrix)) + # Keep the old output for backward compatibility + set_action_output("languages", json.dumps(filtered_languages)) if __name__ == '__main__': main()