src/goDebugConfiguration: remove user set '--gcflags' from config
When building the executable for debugging, delve sets '--gcflags'.
When the user also provides this flag, there is an error in the Go
build system, as the flag can only be specified once. If a user
tries to set this flag, remove it and display a warning.
Fixes golang/vscode-go#117
Change-Id: I84a1e718674b4bafaf31da91711d4b2f06f8b280
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/265580
Trust: Suzy Mueller <suzmue@golang.org>
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/docs/debugging.md b/docs/debugging.md
index e7079ca..48c1bfe 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -9,6 +9,7 @@
* [Configuration](#configuration)
* [Launch Configurations](#launch-configurations)
* [Specifying build tags](#specifying-build-tags)
+ * [Specifying other build flags](#specifying-other-build-flags)
* [Using VS Code Variables](#using-vs-code-variables)
* [Snippets](#snippets)
* [Debugging on Windows Subsystem for Linux (WSL)](#debugging-on-windows-subsystem-for-linux-wsl)
@@ -135,6 +136,22 @@
<!--TODO(rstambler): Confirm that the extension works with a comma (not space) separated list.-->
+### Specifying other build flags
+
+The flags specified in `buildFlags` and `env.GOFLAGS` are passed to the Go compiler when building your program for debugging. Delve adds `--gcflags='all=-N -l'` to the list of build flags to disable optimizations. User specified buildFlags conflict with this setting, so the extension removes them ([Issue #117](https://github.com/golang/vscode-go/issues/117)). If you wish to debug a program using custom `--gcflags`, build the program using `go build` and launch using `exec` mode:
+
+```json
+{
+ "name": "Launch executable",
+ "type": "go",
+ "request": "launch",
+ "mode": "exec",
+ "program": "/absolute/path/to/executable"
+}
+```
+
+Note that it is not recommended to debug optimized executables as Delve may not have the information necessary to properly debug your program.
+
### Using [VS Code variables]
Any property in the launch configuration that requires a file path can be specified in terms of [VS Code variables]. Here are some useful ones to know:
diff --git a/package-lock.json b/package-lock.json
index bf9562c..761fc51 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -157,6 +157,11 @@
"integrity": "sha512-8m9wPEB2mcRqTWNKs9A9Eqs8DrQZt0qNFO8GkxBOnyW6xR//3s77SoMgb/nY1ctzACsZXwZj3YRTDsn4bAoaUw==",
"dev": true
},
+ "@types/yargs-parser": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
+ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
+ },
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
diff --git a/package.json b/package.json
index eea7dac..46ad44a 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
},
"extensionDependencies": [],
"dependencies": {
+ "@types/yargs-parser": "^15.0.0",
"deep-equal": "^2.0.2",
"diff": "^4.0.2",
"glob": "^7.1.6",
diff --git a/src/goDebugConfiguration.ts b/src/goDebugConfiguration.ts
index 3d6f4bc..09a44fc 100644
--- a/src/goDebugConfiguration.ts
+++ b/src/goDebugConfiguration.ts
@@ -7,6 +7,8 @@
import path = require('path');
import vscode = require('vscode');
+import parse = require('yargs-parser');
+import unparse = require('yargs-unparser');
import { toolExecutionEnvironment } from './goEnv';
import { promptForMissingTool } from './goInstallTools';
import { packagePathToGoModPathMap } from './goModules';
@@ -92,6 +94,28 @@
debugConfiguration['cwd'] = resolvePath(debugConfiguration['cwd']);
}
+ // Remove any '--gcflags' entries and show a warning
+ if (debugConfiguration['buildFlags']) {
+ const resp = this.removeFlag(debugConfiguration['buildFlags'], 'gcflags');
+ if (resp.removed) {
+ debugConfiguration['buildFlags'] = resp.args;
+ this.showWarning(
+ 'ignoreDebugGCFlagsWarning',
+ `User specified build flag '--gcflags' in 'buildFlags' is being ignored (see [debugging with build flags](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#specifying-other-build-flags) documentation)`
+ );
+ }
+ }
+ if (debugConfiguration['env'] && debugConfiguration['env']['GOFLAGS']) {
+ const resp = this.removeFlag(debugConfiguration['env']['GOFLAGS'], 'gcflags');
+ if (resp.removed) {
+ debugConfiguration['env']['GOFLAGS'] = resp.args;
+ this.showWarning(
+ 'ignoreDebugGCFlagsWarning',
+ `User specified build flag '--gcflags' in 'GOFLAGS' is being ignored (see [debugging with build flags](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#specifying-other-build-flags) documentation)`
+ );
+ }
+ }
+
debugConfiguration['dlvToolPath'] = getBinPath('dlv');
if (!path.isAbsolute(debugConfiguration['dlvToolPath'])) {
promptForMissingTool('dlv');
@@ -156,4 +180,13 @@
}
});
}
+
+ private removeFlag(args: string, flag: string): {args: string, removed: boolean} {
+ const argv = parse(args, {configuration: {'short-option-groups': false}});
+ if (argv[flag]) {
+ delete argv[flag];
+ return { args: unparse(argv).join(' '), removed: true };
+ }
+ return {args, removed: false};
+ }
}
diff --git a/test/integration/goDebugConfiguration.test.ts b/test/integration/goDebugConfiguration.test.ts
index 61881cd..d65cc82 100644
--- a/test/integration/goDebugConfiguration.test.ts
+++ b/test/integration/goDebugConfiguration.test.ts
@@ -4,6 +4,7 @@
import path = require('path');
import sinon = require('sinon');
import vscode = require('vscode');
+import parse = require('yargs-parser');
import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
import goEnv = require('../../src/goEnv');
import { updateGoVarsFromConfig } from '../../src/goInstallTools';
@@ -279,3 +280,136 @@
});
});
});
+
+suite('Debug Configuration Modify User Config', () => {
+ const debugConfigProvider = new GoDebugConfigurationProvider();
+
+ function checkBuildFlags(input: string, expected: { [key: string]: any }) {
+ // Parse the string result.
+ const actual = parse(input, {configuration: {'short-option-groups': false}} );
+
+ // Delete the empty entry that is created by parse.
+ delete actual['_'];
+
+ // Compare the two maps.
+ assert.strictEqual(actual.size, expected.size);
+
+ const expectedKeys = [];
+ for (const key in expected) {
+ if (expected.hasOwnProperty(key)) {
+ expectedKeys.push(key);
+ }
+ }
+ expectedKeys.sort();
+
+ const actualKeys = [];
+ for (const key in actual) {
+ if (actual.hasOwnProperty(key)) {
+ actualKeys.push(key);
+ }
+ }
+ actualKeys.sort();
+
+ for (let i = 0; i < expectedKeys.length; i ++) {
+ assert.strictEqual(actualKeys[i], expectedKeys[i]);
+ assert.strictEqual(actual[actualKeys[i]], expected[expectedKeys[i]]);
+ }
+ }
+
+ suite('remove gcflags', () => {
+ test('remove user set --gcflags in buildFlags', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {},
+ buildFlags: '--gcflags=all=-l'
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.buildFlags, {});
+ });
+
+ test('remove user set -gcflags in buildFlags', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {},
+ buildFlags: `-gcflags all=-l`
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.buildFlags, {});
+ });
+
+ test('remove user set --gcflags while preserving other build flags in buildFlags', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {},
+ buildFlags: '-race --gcflags=all=-l --mod=mod'
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.buildFlags, {race: true, mod: 'mod'});
+ });
+
+ test('preserve empty buildFlags', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {},
+ buildFlags: ''
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.buildFlags, {});
+ });
+
+ test('preserve buildFlags', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {},
+ buildFlags: '-race --mod=mod'
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.buildFlags, {race: true, mod: 'mod'});
+ });
+
+ test('remove user set --gcflags in GOFLAGS', () => {
+ const config = {
+ name: 'Launch',
+ type: 'go',
+ request: 'launch',
+ mode: 'auto',
+ program: '${fileDirname}',
+ env: {GOFLAGS: '-race --gcflags=-l --mod=mod'},
+ };
+
+ debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+ checkBuildFlags(config.env.GOFLAGS, {race: true, mod: 'mod'});
+ });
+ });
+});
diff --git a/typings/yargs-unparser.d.ts b/typings/yargs-unparser.d.ts
new file mode 100644
index 0000000..f9593e1
--- /dev/null
+++ b/typings/yargs-unparser.d.ts
@@ -0,0 +1,25 @@
+declare module 'yargs-unparser' {
+ // Modified from './node_modules/@types/yargs-parser/index.d.ts'.
+ namespace yargsUnparser {
+ interface Arguments {
+ /** Non-option arguments */
+ _: string[];
+ /** The script name or node command */
+ $0: string;
+ /** All remaining options */
+ [argName: string]: any;
+ }
+
+ interface Options {
+ alias?: { [key: string]: string | string[] };
+ default?: { [key: string]: any };
+ command?: string;
+ }
+
+ interface Unparser {
+ (argv: Arguments, opts?: Options): string[];
+ }
+ }
+ var yargsUnparser: yargsUnparser.Unparser;
+ export = yargsUnparser;
+}