mirror of
				https://github.com/gradle/gradle-build-action.git
				synced 2025-11-04 09:58:56 +08:00 
			
		
		
		
	Configure Gradle User Home for dependency-graph
Instead of requiring an action step to generate the graph, configure Gradle User Home so that subsequent Gradle invocations can generate a graph. Any generated graph files are uploaded as artifacts on job completion. - Construct job.correlator from workflow/job/matrix - Export job.correlator as an environment var - Upload artifacts at job completion in post-action step - Specify the location of dependency graph report - Only apply dependency graph init script when explicitly enabled
This commit is contained in:
		@@ -175,7 +175,8 @@ export class GradleStateCache {
 | 
			
		||||
        const initScriptFilenames = [
 | 
			
		||||
            'build-result-capture.init.gradle',
 | 
			
		||||
            'build-result-capture-service.plugin.groovy',
 | 
			
		||||
            'github-dependency-graph.init.gradle'
 | 
			
		||||
            'github-dependency-graph.init.gradle',
 | 
			
		||||
            'github-dependency-graph-gradle-plugin-apply.groovy'
 | 
			
		||||
        ]
 | 
			
		||||
        for (const initScriptFilename of initScriptFilenames) {
 | 
			
		||||
            const initScriptContent = this.readInitScriptAsString(initScriptFilename)
 | 
			
		||||
 
 | 
			
		||||
@@ -125,10 +125,25 @@ function getCacheKeyJobInstance(): string {
 | 
			
		||||
 | 
			
		||||
    // By default, we hash the full `matrix` data for the run, to uniquely identify this job invocation
 | 
			
		||||
    // The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml.
 | 
			
		||||
    const workflowJobContext = params.getJobContext()
 | 
			
		||||
    const workflowJobContext = params.getJobMatrix()
 | 
			
		||||
    return hashStrings([workflowJobContext])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getUniqueLabelForJobInstance(): string {
 | 
			
		||||
    return getUniqueLabelForJobInstanceValues(github.context.workflow, github.context.job, params.getJobMatrix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getUniqueLabelForJobInstanceValues(workflow: string, jobId: string, matrixJson: string): string {
 | 
			
		||||
    const matrix = JSON.parse(matrixJson)
 | 
			
		||||
    const matrixString = Object.values(matrix).join('-')
 | 
			
		||||
    const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`
 | 
			
		||||
    return sanitize(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sanitize(value: string): string {
 | 
			
		||||
    return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCacheKeyJobExecution(): string {
 | 
			
		||||
    // Used to associate a cache key with a particular execution (default is bound to the git commit sha)
 | 
			
		||||
    return process.env[CACHE_KEY_JOB_EXECUTION_VAR] || github.context.sha
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import * as dependencyGraph from './dependency-graph'
 | 
			
		||||
export async function run(): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
        // Retrieve the dependency graph artifact and submit via Dependency Submission API
 | 
			
		||||
        await dependencyGraph.submitDependencyGraph()
 | 
			
		||||
        await dependencyGraph.downloadAndSubmitDependencyGraphs()
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        core.setFailed(String(error))
 | 
			
		||||
        if (error instanceof Error && error.stack) {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,57 +10,50 @@ import fs from 'fs'
 | 
			
		||||
 | 
			
		||||
import * as execution from './execution'
 | 
			
		||||
import * as layout from './repository-layout'
 | 
			
		||||
import * as params from './input-params'
 | 
			
		||||
 | 
			
		||||
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph'
 | 
			
		||||
const DEPENDENCY_GRAPH_FILE = 'dependency-graph.json'
 | 
			
		||||
 | 
			
		||||
export function prepare(): void {
 | 
			
		||||
    core.info('Enabling dependency graph')
 | 
			
		||||
    const jobCorrelator = getJobCorrelator()
 | 
			
		||||
    core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
 | 
			
		||||
    core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', jobCorrelator)
 | 
			
		||||
    core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId)
 | 
			
		||||
    core.exportVariable(
 | 
			
		||||
        'GITHUB_DEPENDENCY_GRAPH_REPORT_DIR',
 | 
			
		||||
        path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports')
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function generateDependencyGraph(executable: string | undefined): Promise<void> {
 | 
			
		||||
    const workspaceDirectory = layout.workspaceDirectory()
 | 
			
		||||
    const buildRootDirectory = layout.buildRootDirectory()
 | 
			
		||||
    const buildPath = getRelativePathFromWorkspace(buildRootDirectory)
 | 
			
		||||
 | 
			
		||||
    const initScript = path.resolve(
 | 
			
		||||
        __dirname,
 | 
			
		||||
        '..',
 | 
			
		||||
        '..',
 | 
			
		||||
        'src',
 | 
			
		||||
        'resources',
 | 
			
		||||
        'init-scripts',
 | 
			
		||||
        'github-dependency-graph.init.gradle'
 | 
			
		||||
    )
 | 
			
		||||
    const args = [
 | 
			
		||||
        `-Dorg.gradle.github.env.GRADLE_BUILD_PATH=${buildPath}`,
 | 
			
		||||
        '--init-script',
 | 
			
		||||
        initScript,
 | 
			
		||||
        ':GitHubDependencyGraphPlugin_generateDependencyGraph'
 | 
			
		||||
    ]
 | 
			
		||||
    const args = [':GitHubDependencyGraphPlugin_generateDependencyGraph']
 | 
			
		||||
 | 
			
		||||
    await execution.executeGradleBuild(executable, buildRootDirectory, args)
 | 
			
		||||
    const dependencyGraphJson = copyDependencyGraphToBuildRoot(buildRootDirectory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function uploadDependencyGraphs(): Promise<void> {
 | 
			
		||||
    const workspaceDirectory = layout.workspaceDirectory()
 | 
			
		||||
    const graphFiles = await findDependencyGraphFiles(workspaceDirectory)
 | 
			
		||||
 | 
			
		||||
    const relativeGraphFiles = graphFiles.map(x => getRelativePathFromWorkspace(x))
 | 
			
		||||
    core.info(`Uploading dependency graph files: ${relativeGraphFiles}`)
 | 
			
		||||
 | 
			
		||||
    const artifactClient = artifact.create()
 | 
			
		||||
    artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, [dependencyGraphJson], workspaceDirectory)
 | 
			
		||||
    artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, graphFiles, workspaceDirectory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyDependencyGraphToBuildRoot(buildRootDirectory: string): string {
 | 
			
		||||
    const sourceFile = path.resolve(
 | 
			
		||||
        buildRootDirectory,
 | 
			
		||||
        'build',
 | 
			
		||||
        'reports',
 | 
			
		||||
        'github-dependency-graph-plugin',
 | 
			
		||||
        'github-dependency-snapshot.json'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const destFile = path.resolve(buildRootDirectory, DEPENDENCY_GRAPH_FILE)
 | 
			
		||||
    fs.copyFileSync(sourceFile, destFile)
 | 
			
		||||
    return destFile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function submitDependencyGraph(): Promise<void> {
 | 
			
		||||
export async function downloadAndSubmitDependencyGraphs(): Promise<void> {
 | 
			
		||||
    const workspaceDirectory = layout.workspaceDirectory()
 | 
			
		||||
    submitDependencyGraphs(await retrieveDependencyGraphs(workspaceDirectory))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
 | 
			
		||||
    const octokit: Octokit = getOctokit()
 | 
			
		||||
 | 
			
		||||
    for (const jsonFile of await retrieveDependencyGraphs(octokit, workspaceDirectory)) {
 | 
			
		||||
    for (const jsonFile of dependencyGraphFiles) {
 | 
			
		||||
        const jsonContent = fs.readFileSync(jsonFile, 'utf8')
 | 
			
		||||
 | 
			
		||||
        const jsonObject = JSON.parse(jsonContent)
 | 
			
		||||
@@ -69,34 +62,20 @@ export async function submitDependencyGraph(): Promise<void> {
 | 
			
		||||
        const response = await octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject)
 | 
			
		||||
 | 
			
		||||
        const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
 | 
			
		||||
        core.info(`Submitted ${relativeJsonFile}: ${JSON.stringify(response)}`)
 | 
			
		||||
        core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function findDependencyGraphFiles(dir: string): Promise<string[]> {
 | 
			
		||||
    const globber = await glob.create(`${dir}/**/${DEPENDENCY_GRAPH_FILE}`)
 | 
			
		||||
    const graphFiles = globber.glob()
 | 
			
		||||
    core.info(`Found graph files in ${dir}: ${graphFiles}`)
 | 
			
		||||
    return graphFiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function retrieveDependencyGraphs(octokit: Octokit, workspaceDirectory: string): Promise<string[]> {
 | 
			
		||||
async function retrieveDependencyGraphs(workspaceDirectory: string): Promise<string[]> {
 | 
			
		||||
    if (github.context.payload.workflow_run) {
 | 
			
		||||
        return await retrieveDependencyGraphsForWorkflowRun(
 | 
			
		||||
            github.context.payload.workflow_run.id,
 | 
			
		||||
            octokit,
 | 
			
		||||
            workspaceDirectory
 | 
			
		||||
        )
 | 
			
		||||
        return await retrieveDependencyGraphsForWorkflowRun(github.context.payload.workflow_run.id, workspaceDirectory)
 | 
			
		||||
    }
 | 
			
		||||
    return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function retrieveDependencyGraphsForWorkflowRun(
 | 
			
		||||
    runId: number,
 | 
			
		||||
    octokit: Octokit,
 | 
			
		||||
    workspaceDirectory: string
 | 
			
		||||
): Promise<string[]> {
 | 
			
		||||
async function retrieveDependencyGraphsForWorkflowRun(runId: number, workspaceDirectory: string): Promise<string[]> {
 | 
			
		||||
    const octokit: Octokit = getOctokit()
 | 
			
		||||
 | 
			
		||||
    // Find the workflow run artifacts named "dependency-graph"
 | 
			
		||||
    const artifacts = await octokit.rest.actions.listWorkflowRunArtifacts({
 | 
			
		||||
        owner: github.context.repo.owner,
 | 
			
		||||
@@ -139,6 +118,12 @@ async function retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory: st
 | 
			
		||||
    return await findDependencyGraphFiles(downloadPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function findDependencyGraphFiles(dir: string): Promise<string[]> {
 | 
			
		||||
    const globber = await glob.create(`${dir}/dependency-graph-reports/*.json`)
 | 
			
		||||
    const graphFiles = globber.glob()
 | 
			
		||||
    return graphFiles
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOctokit(): Octokit {
 | 
			
		||||
    return new Octokit({
 | 
			
		||||
        auth: getGithubToken()
 | 
			
		||||
@@ -153,3 +138,26 @@ function getRelativePathFromWorkspace(file: string): string {
 | 
			
		||||
    const workspaceDirectory = layout.workspaceDirectory()
 | 
			
		||||
    return path.relative(workspaceDirectory, file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getJobCorrelator(): string {
 | 
			
		||||
    return constructJobCorrelator(github.context.workflow, github.context.job, params.getJobMatrix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function constructJobCorrelator(workflow: string, jobId: string, matrixJson: string): string {
 | 
			
		||||
    const matrixString = describeMatrix(matrixJson)
 | 
			
		||||
    const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`
 | 
			
		||||
    return sanitize(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function describeMatrix(matrixJson: string): string {
 | 
			
		||||
    core.info(`Got matrix json: ${matrixJson}`)
 | 
			
		||||
    const matrix = JSON.parse(matrixJson)
 | 
			
		||||
    if (matrix) {
 | 
			
		||||
        return Object.values(matrix).join('-')
 | 
			
		||||
    }
 | 
			
		||||
    return ''
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sanitize(value: string): string {
 | 
			
		||||
    return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ export function getArguments(): string[] {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Internal parameters
 | 
			
		||||
export function getJobContext(): string {
 | 
			
		||||
export function getJobMatrix(): string {
 | 
			
		||||
    return core.getInput('workflow-job-context')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -63,6 +63,10 @@ export function isJobSummaryEnabled(): boolean {
 | 
			
		||||
    return getBooleanInput('generate-job-summary', true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isDependencyGraphEnabled(): boolean {
 | 
			
		||||
    return getBooleanInput('generate-dependency-graph', true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getBooleanInput(paramName: string, paramDefault = false): boolean {
 | 
			
		||||
    const paramValue = core.getInput(paramName)
 | 
			
		||||
    switch (paramValue.toLowerCase().trim()) {
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
buildscript {
 | 
			
		||||
  dependencies {
 | 
			
		||||
    classpath files("github-dependency-graph-gradle-plugin-0.0.3.jar")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
apply plugin: org.gradle.github.GitHubDependencyGraphPlugin
 | 
			
		||||
@@ -1,7 +1,17 @@
 | 
			
		||||
// TODO:DAZ This should be conditionally applied, since the script may be present when not required.
 | 
			
		||||
initscript {
 | 
			
		||||
  dependencies {
 | 
			
		||||
    classpath files("github-dependency-graph-gradle-plugin-0.0.3.jar")
 | 
			
		||||
  }
 | 
			
		||||
if (System.env.GITHUB_DEPENDENCY_GRAPH_ENABLED != "true") {
 | 
			
		||||
  return
 | 
			
		||||
}
 | 
			
		||||
apply plugin: org.gradle.github.GitHubDependencyGraphPlugin
 | 
			
		||||
 | 
			
		||||
def reportDir = System.env.GITHUB_DEPENDENCY_GRAPH_REPORT_DIR
 | 
			
		||||
def jobCorrelator = System.env.GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR
 | 
			
		||||
def reportFile = new File(reportDir, jobCorrelator + ".json")
 | 
			
		||||
 | 
			
		||||
if (reportFile.exists()) {
 | 
			
		||||
  println "::warning::No dependency report generated for step: report file for '${jobCorrelator}' created in earlier step. Each build invocation requires a unique job correlator: specify GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR var for this step."
 | 
			
		||||
  return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
println "Generating dependency graph for '${jobCorrelator}'"
 | 
			
		||||
 | 
			
		||||
// TODO:DAZ This should be conditionally applied, since the script may be present when not required.
 | 
			
		||||
apply from: 'github-dependency-graph-gradle-plugin-apply.groovy'
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import * as os from 'os'
 | 
			
		||||
import * as caches from './caches'
 | 
			
		||||
import * as layout from './repository-layout'
 | 
			
		||||
import * as params from './input-params'
 | 
			
		||||
import * as dependencyGraph from './dependency-graph'
 | 
			
		||||
 | 
			
		||||
import {logJobSummary, writeJobSummary} from './job-summary'
 | 
			
		||||
import {loadBuildResults} from './build-results'
 | 
			
		||||
@@ -36,6 +37,10 @@ export async function setup(): Promise<void> {
 | 
			
		||||
    await caches.restore(gradleUserHome, cacheListener)
 | 
			
		||||
 | 
			
		||||
    core.saveState(CACHE_LISTENER, cacheListener.stringify())
 | 
			
		||||
 | 
			
		||||
    if (params.isDependencyGraphEnabled()) {
 | 
			
		||||
        dependencyGraph.prepare()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function complete(): Promise<void> {
 | 
			
		||||
@@ -58,6 +63,10 @@ export async function complete(): Promise<void> {
 | 
			
		||||
    } else {
 | 
			
		||||
        logJobSummary(buildResults, cacheListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (params.isDependencyGraphEnabled()) {
 | 
			
		||||
        dependencyGraph.uploadDependencyGraphs()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function determineGradleUserHome(): Promise<string> {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user