@@ -8,13 +8,29 @@ import fileUrl from "file-url";
88import * as jsonschema from "jsonschema" ;
99
1010import * as actionsUtil from "./actions-util" ;
11+ import {
12+ getOptionalInput ,
13+ getRequiredInput ,
14+ getTemporaryDirectory ,
15+ } from "./actions-util" ;
1116import * as api from "./api-client" ;
17+ import { getGitHubVersion } from "./api-client" ;
18+ import { CodeQL , getCodeQL } from "./codeql" ;
19+ import { getConfig } from "./config-utils" ;
1220import { EnvVar } from "./environment" ;
21+ import { Feature , Features } from "./feature-flags" ;
1322import * as fingerprints from "./fingerprints" ;
23+ import { initCodeQL } from "./init" ;
1424import { Logger } from "./logging" ;
1525import { parseRepositoryNwo , RepositoryNwo } from "./repository" ;
1626import * as util from "./util" ;
17- import { SarifFile , ConfigurationError , wrapError } from "./util" ;
27+ import {
28+ SarifFile ,
29+ ConfigurationError ,
30+ wrapError ,
31+ getRequiredEnvParam ,
32+ GitHubVersion ,
33+ } from "./util" ;
1834
1935const GENERIC_403_MSG =
2036 "The repo on which this action is running has not opted-in to CodeQL code scanning." ;
@@ -48,6 +64,88 @@ function combineSarifFiles(sarifFiles: string[]): SarifFile {
4864 return combinedSarif ;
4965}
5066
67+ // Takes a list of paths to sarif files and combines them together using the
68+ // CLI `github merge-results` command when all SARIF files are produced by
69+ // CodeQL. Otherwise, it will fall back to combining the files in the action.
70+ // Returns the contents of the combined sarif file.
71+ async function combineSarifFilesUsingCLI (
72+ sarifFiles : string [ ] ,
73+ gitHubVersion : GitHubVersion ,
74+ features : Features ,
75+ logger : Logger ,
76+ ) : Promise < SarifFile > {
77+ // First check if all files are produced by CodeQL.
78+ let allCodeQL = true ;
79+
80+ for ( const sarifFile of sarifFiles ) {
81+ const sarifObject = JSON . parse (
82+ fs . readFileSync ( sarifFile , "utf8" ) ,
83+ ) as SarifFile ;
84+
85+ const allRunsCodeQL = sarifObject . runs ?. every (
86+ ( run ) => run . tool ?. driver ?. name === "CodeQL" ,
87+ ) ;
88+
89+ if ( ! allRunsCodeQL ) {
90+ allCodeQL = false ;
91+ break ;
92+ }
93+ }
94+
95+ if ( ! allCodeQL ) {
96+ logger . warning (
97+ "Not all SARIF files were produced by CodeQL. Merging files in the action." ,
98+ ) ;
99+
100+ // If not, use the naive method of combining the files.
101+ return combineSarifFiles ( sarifFiles ) ;
102+ }
103+
104+ // Initialize CodeQL, either by using the config file from the 'init' step,
105+ // or by initializing it here.
106+ let codeQL : CodeQL ;
107+ let tempDir : string ;
108+
109+ const config = await getConfig ( actionsUtil . getTemporaryDirectory ( ) , logger ) ;
110+ if ( config !== undefined ) {
111+ codeQL = await getCodeQL ( config . codeQLCmd ) ;
112+ tempDir = config . tempDir ;
113+ } else {
114+ logger . warning (
115+ "Initializing CodeQL since the 'init' Action was not called before this step." ,
116+ ) ;
117+
118+ const apiDetails = {
119+ auth : getRequiredInput ( "token" ) ,
120+ externalRepoAuth : getOptionalInput ( "external-repository-token" ) ,
121+ url : getRequiredEnvParam ( "GITHUB_SERVER_URL" ) ,
122+ apiURL : getRequiredEnvParam ( "GITHUB_API_URL" ) ,
123+ } ;
124+
125+ const codeQLDefaultVersionInfo = await features . getDefaultCliVersion (
126+ gitHubVersion . type ,
127+ ) ;
128+
129+ const initCodeQLResult = await initCodeQL (
130+ undefined , // There is no tools input on the upload action
131+ apiDetails ,
132+ getTemporaryDirectory ( ) ,
133+ gitHubVersion . type ,
134+ codeQLDefaultVersionInfo ,
135+ logger ,
136+ ) ;
137+
138+ codeQL = initCodeQLResult . codeql ;
139+ tempDir = getTemporaryDirectory ( ) ;
140+ }
141+
142+ const outputFile = path . resolve ( tempDir , "combined-sarif.sarif" ) ;
143+
144+ await codeQL . mergeResults ( sarifFiles , outputFile , true ) ;
145+
146+ return JSON . parse ( fs . readFileSync ( outputFile , "utf8" ) ) as SarifFile ;
147+ }
148+
51149// Populates the run.automationDetails.id field using the analysis_key and environment
52150// and return an updated sarif file contents.
53151export function populateRunAutomationDetails (
@@ -363,12 +461,27 @@ async function uploadFiles(
363461 logger . startGroup ( "Uploading results" ) ;
364462 logger . info ( `Processing sarif files: ${ JSON . stringify ( sarifFiles ) } ` ) ;
365463
464+ const gitHubVersion = await getGitHubVersion ( ) ;
465+ const features = new Features (
466+ gitHubVersion ,
467+ repositoryNwo ,
468+ actionsUtil . getTemporaryDirectory ( ) ,
469+ logger ,
470+ ) ;
471+
366472 // Validate that the files we were asked to upload are all valid SARIF files
367473 for ( const file of sarifFiles ) {
368474 validateSarifFileSchema ( file , logger ) ;
369475 }
370476
371- let sarif = combineSarifFiles ( sarifFiles ) ;
477+ let sarif = ( await features . getValue ( Feature . CliSarifMerge ) )
478+ ? await combineSarifFilesUsingCLI (
479+ sarifFiles ,
480+ gitHubVersion ,
481+ features ,
482+ logger ,
483+ )
484+ : combineSarifFiles ( sarifFiles ) ;
372485 sarif = await fingerprints . addFingerprints ( sarif , sourceRoot , logger ) ;
373486
374487 sarif = populateRunAutomationDetails (
0 commit comments