Correctly identify dependency versions chosen by Go
This commit is contained in:
+41
-6
@@ -3,9 +3,17 @@ import fs from 'fs'
|
||||
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import { Snapshot, submitSnapshot } from '@github/dependency-submission-toolkit'
|
||||
import {
|
||||
Snapshot,
|
||||
Manifest,
|
||||
submitSnapshot
|
||||
} from '@github/dependency-submission-toolkit'
|
||||
|
||||
import { processGoGraph, processGoBuildTarget } from './process'
|
||||
import {
|
||||
processGoGraph,
|
||||
processGoDirectDependencies,
|
||||
processGoIndirectDependencies
|
||||
} from './process'
|
||||
|
||||
async function main () {
|
||||
const goModPath = path.normalize(core.getInput('go-mod-path'))
|
||||
@@ -35,12 +43,39 @@ async function main () {
|
||||
}
|
||||
}
|
||||
|
||||
const packageCache = await processGoGraph(goModDir)
|
||||
const manifest = await processGoBuildTarget(
|
||||
const directDeps = await processGoDirectDependencies(goModDir, goBuildTarget)
|
||||
const indirectDeps = await processGoIndirectDependencies(
|
||||
goModDir,
|
||||
goBuildTarget,
|
||||
packageCache
|
||||
goBuildTarget
|
||||
)
|
||||
const packageCache = await processGoGraph(goModDir, directDeps, indirectDeps)
|
||||
// no file path if using the pseudotargets "all" or "./..."
|
||||
const filepath =
|
||||
goBuildTarget === 'all' || goBuildTarget === './...'
|
||||
? undefined
|
||||
: path.join(goModDir, goBuildTarget)
|
||||
const manifest = new Manifest(goBuildTarget, filepath)
|
||||
|
||||
directDeps.forEach((pkgUrl) => {
|
||||
const dep = packageCache.lookupPackage(pkgUrl)
|
||||
if (!dep) {
|
||||
throw new Error(
|
||||
'assertion failed: expected all direct dependencies to have entries in PackageCache'
|
||||
)
|
||||
}
|
||||
manifest.addDirectDependency(dep)
|
||||
})
|
||||
|
||||
indirectDeps.forEach((pkgUrl) => {
|
||||
const dep = packageCache.lookupPackage(pkgUrl)
|
||||
if (!dep) {
|
||||
throw new Error(
|
||||
'assertion failed: expected all indirect dependencies to have entries in PackageCache'
|
||||
)
|
||||
}
|
||||
manifest.addIndirectDependency(dep)
|
||||
})
|
||||
|
||||
const snapshot = new Snapshot(
|
||||
{
|
||||
name: 'actions/go-dependency-submission',
|
||||
|
||||
+78
-9
@@ -1,17 +1,86 @@
|
||||
import { processGoGraph, processGoBuildTarget } from './process'
|
||||
import {
|
||||
processGoGraph,
|
||||
processGoDirectDependencies,
|
||||
processGoIndirectDependencies
|
||||
} from './process'
|
||||
|
||||
// NOTE: these tests all require "go" to be installed and available on the PATH!
|
||||
//
|
||||
describe('processGoGraph', () => {
|
||||
describe('processGoDirectDependencies', () => {
|
||||
test('run in go-example', async () => {
|
||||
const cache = await processGoGraph('go-example')
|
||||
expect(cache.countPackages()).toEqual(8)
|
||||
const manifest = await processGoBuildTarget(
|
||||
const purls = await processGoDirectDependencies(
|
||||
'go-example',
|
||||
'cmd/octocat.go',
|
||||
cache
|
||||
'cmd/octocat.go'
|
||||
)
|
||||
expect(manifest.directDependencies()).toHaveLength(4)
|
||||
expect(manifest.indirectDependencies()).toHaveLength(2)
|
||||
expect(purls).toHaveLength(1)
|
||||
expect(purls).toEqual([
|
||||
{
|
||||
type: 'golang',
|
||||
name: 'color',
|
||||
namespace: 'github.com/fatih',
|
||||
version: 'v1.13.0',
|
||||
qualifiers: null,
|
||||
subpath: null
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('processGoIndirectDependencies', () => {
|
||||
test('run in go-example', async () => {
|
||||
const purls = await processGoIndirectDependencies(
|
||||
'go-example',
|
||||
'cmd/octocat.go'
|
||||
)
|
||||
expect(purls).toHaveLength(3)
|
||||
expect(purls).toEqual([
|
||||
{
|
||||
type: 'golang',
|
||||
name: 'sys',
|
||||
namespace: 'golang.org/x',
|
||||
version: 'v0.0.0-20210630005230-0f9fa26af87c',
|
||||
qualifiers: null,
|
||||
subpath: null
|
||||
},
|
||||
{
|
||||
type: 'golang',
|
||||
name: 'go-isatty',
|
||||
namespace: 'github.com/mattn',
|
||||
version: 'v0.0.14',
|
||||
qualifiers: null,
|
||||
subpath: null
|
||||
},
|
||||
{
|
||||
type: 'golang',
|
||||
name: 'go-colorable',
|
||||
namespace: 'github.com/mattn',
|
||||
version: 'v0.1.9',
|
||||
qualifiers: null,
|
||||
subpath: null
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('processGoGraph', () => {
|
||||
test.only('run in go-example', async () => {
|
||||
const directDeps = await processGoDirectDependencies(
|
||||
'go-example',
|
||||
'cmd/octocat.go'
|
||||
)
|
||||
const indirectDeps = await processGoIndirectDependencies(
|
||||
'go-example',
|
||||
'cmd/octocat.go'
|
||||
)
|
||||
const cache = await processGoGraph('go-example', directDeps, indirectDeps)
|
||||
|
||||
// we expect the number of direct dependencies + indirect
|
||||
expect(cache.countPackages()).toEqual(4)
|
||||
|
||||
const colorDep = cache.lookupPackage(
|
||||
'pkg:golang/github.com/fatih/[email protected]'
|
||||
)
|
||||
expect(colorDep).not.toBeUndefined()
|
||||
if (colorDep) expect(colorDep.dependencies).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
+75
-30
@@ -1,16 +1,16 @@
|
||||
import path from 'path'
|
||||
import { PackageURL } from 'packageurl-js'
|
||||
|
||||
import * as exec from '@actions/exec'
|
||||
import * as core from '@actions/core'
|
||||
import {
|
||||
Manifest,
|
||||
BuildTarget,
|
||||
PackageCache
|
||||
} from '@github/dependency-submission-toolkit'
|
||||
import { PackageCache } from '@github/dependency-submission-toolkit'
|
||||
|
||||
import { parseGoModGraph, parseGoList } from './parse'
|
||||
|
||||
export async function processGoGraph (goModDir: string): Promise<PackageCache> {
|
||||
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
|
||||
@@ -21,35 +21,89 @@ export async function processGoGraph (goModDir: string): Promise<PackageCache> {
|
||||
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]) => {
|
||||
cache.package(parentPkg).dependsOn(cache.package(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 !== 1) {
|
||||
throw new Error(
|
||||
'assertion failed: expected one package in cache with namespace+name. ' +
|
||||
'Found: ' +
|
||||
JSON.stringify(matches)
|
||||
)
|
||||
}
|
||||
// create the dependency relationship
|
||||
targetPackage.dependsOn(matches[0])
|
||||
})
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// For a specific Go _build target_, this template lists all dependencies used
|
||||
// to build the build target It does not provide association between the
|
||||
// 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_LIST_DEP_TEMPLATE =
|
||||
'{{define "M"}}{{.Path}}@{{.Version}}{{end}}{{with .Module}}{{if not .Main}}{{if .Replace}}{{template "M" .Replace}}{{else}}{{template "M" .}}{{end}}{{end}}{{end}}'
|
||||
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 processGoBuildTarget (
|
||||
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,
|
||||
cache: PackageCache
|
||||
): Promise<Manifest> {
|
||||
console.log(
|
||||
`Running go package detection in ${goModDir} on build target ${goBuildTarget}`
|
||||
)
|
||||
|
||||
goListTemplate: string
|
||||
): Promise<Array<PackageURL>> {
|
||||
const goList = await exec.getExecOutput(
|
||||
'go',
|
||||
['list', '-deps', '-f', GO_LIST_DEP_TEMPLATE, goBuildTarget],
|
||||
['list', '-deps', '-f', goListTemplate, goBuildTarget],
|
||||
{ cwd: goModDir }
|
||||
)
|
||||
if (goList.exitCode !== 0) {
|
||||
@@ -58,14 +112,5 @@ export async function processGoBuildTarget (
|
||||
throw new Error("Failed to execute 'go list'")
|
||||
}
|
||||
|
||||
const dependencies = parseGoList(goList.stdout)
|
||||
const manifest = new BuildTarget(
|
||||
goBuildTarget,
|
||||
path.join(goModDir, goBuildTarget)
|
||||
)
|
||||
dependencies.forEach((dep) => {
|
||||
manifest.addBuildDependency(cache.package(dep))
|
||||
})
|
||||
|
||||
return manifest
|
||||
return parseGoList(goList.stdout)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user