Files
go-dependency-submission/src/process.ts
T
2022-06-17 16:44:04 -06:00

122 lines
4.2 KiB
TypeScript

import { PackageURL } from 'packageurl-js'
import * as exec from '@actions/exec'
import * as core from '@actions/core'
import { PackageCache } from '@github/dependency-submission-toolkit'
import { parseGoModGraph, parseGoList } from './parse'
export async function processGoGraph (
goModDir: string,
directDependencies: Array<PackageURL>,
indirectDependencies: Array<PackageURL>
): Promise<PackageCache> {
console.log(`Running 'go mod graph' in ${goModDir}`)
const goModGraph = await exec.getExecOutput('go', ['mod', 'graph'], {
cwd: goModDir
})
if (goModGraph.exitCode !== 0) {
core.error(goModGraph.stderr)
core.setFailed("'go mod graph' failed!")
throw new Error("Failed to execute 'go mod graph'")
}
/* add all direct and indirect packages to a new PackageCache */
const cache = new PackageCache()
directDependencies.forEach((pkg) => {
cache.package(pkg)
})
indirectDependencies.forEach((pkg) => {
cache.package(pkg)
})
const packageAssocList = parseGoModGraph(goModGraph.stdout)
packageAssocList.forEach(([parentPkg, childPkg]) => {
/* Look up the parent package in the cache. go mod graph will return
* multiple versions of packages with the same namespace and name. We
* select only package versions used in the Go build target. */
const targetPackage = cache.lookupPackage(parentPkg)
if (!targetPackage) return
/* Build a matcher to select on the namespace+name of the child package in
* the cache. The child package version specified by go mod graph is not
* the one guaranteed to be selected when building Go build targets. */
const matcher: { name: string; namespace?: string } = {
name: childPkg.name
}
if (childPkg.namespace) matcher.namespace = childPkg.namespace
/* There should only ever be a single package with a namespace+name in the
* build target list. Go does not support multiple versions of the same
* package */
const matches = cache.packagesMatching(matcher)
if (matches.length === 0) return
if (matches.length !== 1) {
throw new Error(
'assertion failed: expected no more than one package in cache with namespace+name. ' +
'Found: ' +
JSON.stringify(matches) +
'for ' +
JSON.stringify(matcher)
)
}
// create the dependency relationship
targetPackage.dependsOn(matches[0])
})
return cache
}
// For a specific Go _build target_, these templates list dependencies used to
// in the build target. It does not provide association between the
// dependencies (i.e. which dependencies depend on which)
// eslint-disable-next-line quotes
// eslint-disable-next-line no-useless-escape
const GO_DIRECT_DEPS_TEMPLATE =
'{{define "M"}}{{if not .Indirect}}{{.Path}}@{{.Version}}{{end}}{{end}}{{with .Module}}{{if not .Main}}{{if .Replace}}{{template "M" .Replace}}{{else}}{{template "M" .}}{{end}}{{end}}{{end}}'
// eslint-disable-next-line quotes
// eslint-disable-next-line no-useless-escape
const GO_INDIRECT_DEPS_TEMPLATE =
'{{define "M"}}{{if .Indirect}}{{.Path}}@{{.Version}}{{end}}{{end}}{{with .Module}}{{if not .Main}}{{if .Replace}}{{template "M" .Replace}}{{else}}{{template "M" .}}{{end}}{{end}}{{end}}'
export async function processGoDirectDependencies (
goModDir: string,
goBuildTarget: string
): Promise<Array<PackageURL>> {
console.log(
`go direct package detection in ${goModDir} on build target ${goBuildTarget}`
)
return processGoList(goModDir, goBuildTarget, GO_DIRECT_DEPS_TEMPLATE)
}
export async function processGoIndirectDependencies (
goModDir: string,
goBuildTarget: string
): Promise<Array<PackageURL>> {
console.log(
`go indirect package detection in ${goModDir} on build target ${goBuildTarget}`
)
return processGoList(goModDir, goBuildTarget, GO_INDIRECT_DEPS_TEMPLATE)
}
async function processGoList (
goModDir: string,
goBuildTarget: string,
goListTemplate: string
): Promise<Array<PackageURL>> {
const goList = await exec.getExecOutput(
'go',
['list', '-deps', '-f', goListTemplate, goBuildTarget],
{ cwd: goModDir }
)
if (goList.exitCode !== 0) {
core.error(goList.stderr)
core.setFailed("'go list' failed!")
throw new Error("Failed to execute 'go list'")
}
return parseGoList(goList.stdout)
}