From 194a6c8df620bda3debd213f975467c4555047db Mon Sep 17 00:00:00 2001 From: Patrick Quist Date: Tue, 25 Nov 2025 00:55:45 +0100 Subject: [PATCH] Add GOCACHE support for Go compiler with writeable cache directory (#8283) --- lib/compilers/golang.ts | 52 ++++++++++++++++++++++++++++++++++++++--- test/golang-tests.ts | 40 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/compilers/golang.ts b/lib/compilers/golang.ts index 1159f8c78..6c413b443 100644 --- a/lib/compilers/golang.ts +++ b/lib/compilers/golang.ts @@ -22,8 +22,11 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -import _ from 'underscore'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import _ from 'underscore'; +import type {ExecutionOptionsWithEnv} from '../../types/compilation/compilation.interfaces.js'; import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js'; import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js'; import type {ResultLine} from '../../types/resultline/resultline.interfaces.js'; @@ -54,6 +57,7 @@ type GoEnv = { export class GolangCompiler extends BaseCompiler { private readonly GOENV: GoEnv; + private readonly sourceCachePath?: string; static get key() { return 'golang'; @@ -63,11 +67,14 @@ export class GolangCompiler extends BaseCompiler { super(compilerInfo, env); const group = this.compiler.group; - const goroot = this.compilerProps( + let goroot = this.compilerProps( 'goroot', this.compilerProps(`group.${group}.goroot`), ); - // GOARCH can be something like '386' which is read out as a number. + if (!goroot && compilerInfo.exe) { + goroot = path.dirname(path.dirname(compilerInfo.exe)); + } + const goarch = this.compilerProps( 'goarch', this.compilerProps(`group.${group}.goarch`), @@ -80,6 +87,7 @@ export class GolangCompiler extends BaseCompiler { this.GOENV = {}; if (goroot) { this.GOENV.GOROOT = goroot; + this.sourceCachePath = path.join(goroot, '..', 'cache'); } if (goarch) { this.GOENV.GOARCH = goarch.toString(); @@ -89,6 +97,44 @@ export class GolangCompiler extends BaseCompiler { } } + override async runCompiler( + compiler: string, + options: string[], + inputFilename: string, + execOptions: ExecutionOptionsWithEnv, + filters?: ParseFiltersAndOutputOptions, + ) { + if (!execOptions) { + execOptions = this.getDefaultExecOptions(); + } + + if (this.sourceCachePath) { + const inputDir = path.dirname(inputFilename); + const tempCachePath = path.join(inputDir, 'cache'); + + execOptions.env = { + ...execOptions.env, + GOCACHE: tempCachePath, + }; + + try { + await fs.mkdir(tempCachePath, {recursive: true}); + + try { + // todo: add actual check instead of relying on exceptions + await fs.access(this.sourceCachePath); + await fs.cp(this.sourceCachePath, tempCachePath, {recursive: true, force: false}); + } catch { + // Source cache doesn't exist, use empty cache + } + } catch { + // Cache setup failed, continue without cache + } + } + + return super.runCompiler(compiler, options, inputFilename, execOptions, filters); + } + convertNewGoL(code: ResultLine[]): string { let prevLine: string | null = null; let file: string | null = null; diff --git a/test/golang-tests.ts b/test/golang-tests.ts index 106195c38..92bcdf21b 100644 --- a/test/golang-tests.ts +++ b/test/golang-tests.ts @@ -83,3 +83,43 @@ describe('GO asm tests', () => { await testGoAsm('test/golang/labels'); }); }); + +describe('GO environment variables', () => { + beforeAll(() => { + ce = makeCompilationEnvironment({languages}); + }); + + it('Derives GOROOT from compiler executable path', () => { + const compilerInfo = makeFakeCompilerInfo({ + exe: '/opt/compiler-explorer/go1.20/bin/go', + lang: languages.go.id, + }); + const compiler = new GolangCompiler(compilerInfo, ce); + const execOptions = compiler.getDefaultExecOptions(); + + expect(execOptions.env.GOROOT).toBe('/opt/compiler-explorer/go1.20'); + // GOCACHE is not set in default exec options, it's set during runCompiler + expect(execOptions.env.GOCACHE).toBeUndefined(); + }); + + it('Uses explicit GOROOT from properties when set', () => { + const compilerInfo = makeFakeCompilerInfo({ + exe: '/opt/compiler-explorer/go1.20/bin/go', + lang: languages.go.id, + id: 'go120', + }); + // Override compilerProps to return a specific GOROOT + const ceWithProps = makeCompilationEnvironment({ + languages, + props: { + goroot: '/custom/goroot/path', + }, + }); + const compiler = new GolangCompiler(compilerInfo, ceWithProps); + const execOptions = compiler.getDefaultExecOptions(); + + expect(execOptions.env.GOROOT).toBe('/custom/goroot/path'); + // GOCACHE is not set in default exec options, it's set during runCompiler + expect(execOptions.env.GOCACHE).toBeUndefined(); + }); +});