Skip to content

Commit

Permalink
feat(gomod): Support workspace vendoring
Browse files Browse the repository at this point in the history
If go.work is present and a vendor/ is present, then the module is using
go workspaces with vendoring, recently introduced with Go 1.22 (see
golang/go#60056 for details).

When using Go workspaces, you must use `go work vendor` instead of `go
mod vendor`.
  • Loading branch information
chancez committed May 22, 2024
1 parent 29a70a0 commit 478e84a
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 8 deletions.
117 changes: 116 additions & 1 deletion lib/modules/manager/gomod/artifacts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { codeBlock } from 'common-tags';
import { mockDeep } from 'jest-mock-extended';
import { join } from 'upath';
import { envMock, mockExecAll } from '../../../../test/exec-util';
import { envMock, mockExecAll, mockExecSequence } from '../../../../test/exec-util';
import { env, fs, git, mocked, partial } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import type { RepoGlobalConfig } from '../../../config/types';
Expand Down Expand Up @@ -252,6 +252,10 @@ describe('modules/manager/gomod/artifacts', () => {
]);

expect(execSnapshots).toMatchObject([
{
cmd: 'go env GOWORK',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go get -d -t ./...',
options: { cwd: '/tmp/github/some/repo' },
Expand All @@ -271,6 +275,117 @@ describe('modules/manager/gomod/artifacts', () => {
]);
});

it('supports vendor directory update with go.work', async () => {
const foo = join('vendor/github.com/foo/foo/go.mod');
const bar = join('vendor/github.com/bar/bar/go.mod');
const baz = join('vendor/github.com/baz/baz/go.mod');

fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
fs.readLocalFile.mockResolvedValueOnce('modules.txt content'); // vendor modules filename
fs.readLocalFile.mockResolvedValueOnce('Current go.work'); // go.work
const execSnapshots = mockExecSequence([
// Set the output returned by go env GOWORK
{stdout: '/tmp/github/some/repo/go.work', stderr: ''},
// Remaining output does not matter
{stdout: '', stderr: ''},
{stdout: '', stderr: ''},
{stdout: '', stderr: ''},
{stdout: '', stderr: ''},
{stdout: '', stderr: ''},
]);
git.getRepoStatus.mockResolvedValueOnce(
partial<StatusResult>({
modified: ['go.sum', 'go.work.sum', foo],
not_added: [bar],
deleted: [baz],
}),
);
fs.readLocalFile.mockResolvedValueOnce('New go.sum');
fs.readLocalFile.mockResolvedValueOnce('New go.work.sum');
fs.readLocalFile.mockResolvedValueOnce('Foo go.sum');
fs.readLocalFile.mockResolvedValueOnce('Bar go.sum');
fs.readLocalFile.mockResolvedValueOnce('New go.mod');
const res = await gomod.updateArtifacts({
packageFileName: 'go.mod',
updatedDeps: [],
newPackageFileContent: gomod1,
config: {
...config,
postUpdateOptions: ['gomodTidy'],
},
});
expect(res).toEqual([
{
file: {
contents: 'New go.sum',
path: 'go.sum',
type: 'addition',
},
},
{
file: {
contents: 'New go.work.sum',
path: 'go.work.sum',
type: 'addition',
},
},
{
file: {
contents: 'Foo go.sum',
path: 'vendor/github.com/foo/foo/go.mod',
type: 'addition',
},
},
{
file: {
contents: 'Bar go.sum',
path: 'vendor/github.com/bar/bar/go.mod',
type: 'addition',
},
},
{
file: {
path: 'vendor/github.com/baz/baz/go.mod',
type: 'deletion',
},
},
{
file: {
contents: 'New go.mod',
path: 'go.mod',
type: 'addition',
},
},
]);

expect(execSnapshots).toMatchObject([
{
cmd: 'go env GOWORK',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go get -d -t ./...',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go mod tidy',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go work vendor',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go work sync',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: 'go mod tidy',
options: { cwd: '/tmp/github/some/repo' },
},
]);
});

it('supports docker mode without credentials', async () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
Expand Down
47 changes: 40 additions & 7 deletions lib/modules/manager/gomod/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ export async function updateArtifacts({
return null;
}

const vendorDir = upath.join(upath.dirname(goModFileName), 'vendor/');
const goModDir = upath.dirname(goModFileName);

const vendorDir = upath.join(goModDir, 'vendor/');
const vendorModulesFileName = upath.join(vendorDir, 'modules.txt');
const useVendor = (await readLocalFile(vendorModulesFileName)) !== null;

Expand Down Expand Up @@ -235,7 +237,6 @@ export async function updateArtifacts({
throw new Error('Invalid goGetDirs');
}
}

let args = `get -d -t ${goGetDirs ?? './...'}`;
logger.trace({ cmd, args }, 'go get command included');
execCommands.push(`${cmd} ${args}`);
Expand Down Expand Up @@ -275,18 +276,38 @@ export async function updateArtifacts({
config.postUpdateOptions?.includes('gomodTidy1.17') === true ||
config.postUpdateOptions?.includes('gomodTidyE') === true ||
(config.updateType === 'major' && isImportPathUpdateRequired));

if (isGoModTidyRequired) {
args = 'mod tidy' + tidyOpts;
logger.debug('go mod tidy command included');
execCommands.push(`${cmd} ${args}`);
}

const goWorkSumFileName = upath.join(goModDir, 'go.work.sum');
if (useVendor) {
args = 'mod vendor';
logger.debug('go mod tidy command included');
execCommands.push(`${cmd} ${args}`);
}
// If go env GOWORK returns a non-empty path, check that it exists and if
// it does, then use go workspace vendoring.
const goWorkEnv = await exec(`${cmd} env GOWORK`, execOptions);
const goWorkFile = goWorkEnv?.stdout?.trim() || '';
const useGoWork = goWorkFile.length && ((await readLocalFile(goWorkFile)) !== null);
if (useGoWork) {
logger.debug('No go.work found');
}

if (useGoWork) {
args = 'work vendor';
logger.debug('using go work vendor');
execCommands.push(`${cmd} ${args}`);

args = 'work sync';
logger.debug('using go work sync');
execCommands.push(`${cmd} ${args}`);
} else {
args = 'mod vendor';
logger.debug('using go mod vendor');
execCommands.push(`${cmd} ${args}`);
}
}
// We tidy one more time as a solution for #6795
if (isGoModTidyRequired) {
args = 'mod tidy' + tidyOpts;
Expand All @@ -299,7 +320,8 @@ export async function updateArtifacts({
const status = await getRepoStatus();
if (
!status.modified.includes(sumFileName) &&
!status.modified.includes(goModFileName)
!status.modified.includes(goModFileName) &&
!status.modified.includes(goWorkSumFileName)
) {
return null;
}
Expand All @@ -316,6 +338,17 @@ export async function updateArtifacts({
});
}

if (status.modified.includes(goWorkSumFileName)) {
logger.debug('Returning updated go.work.sum');
res.push({
file: {
type: 'addition',
path: goWorkSumFileName,
contents: await readLocalFile(goWorkSumFileName),
},
});
}

// Include all the .go file import changes
if (isImportPathUpdateRequired) {
logger.debug('Returning updated go source files for import path changes');
Expand Down

0 comments on commit 478e84a

Please sign in to comment.