Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit a0a27f0

Browse files
dylanscottDominicKramer
authored andcommitted
feat: pathResolver to allow arbitrary path mapping on behalf of agent (#461)
Fixes #391
1 parent 4099dea commit a0a27f0

File tree

6 files changed

+335
-12
lines changed

6 files changed

+335
-12
lines changed

src/agent/config.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,58 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
164164
*/
165165
appPathRelativeToRepository?: string;
166166

167+
/**
168+
* A function which takes the path of a source file in your repository,
169+
* a list of your project's Javascript files known to the debugger,
170+
* and the file(s) in your project that the debugger thinks is identified
171+
* by the given path.
172+
*
173+
* This function should return the file(s) that is/are identified by the
174+
* given path or `undefined` to specify that the files(s) that the agent
175+
* thinks are associated with the file should be used.
176+
*
177+
* Note that the list of paths must be a subset of the files in `knownFiles`
178+
* and the debug agent can set a breakpoint for the input path if and only
179+
* if there is a unique file that this function returns (an array with
180+
* exactly one entry).
181+
*
182+
* This configuration option is usually unecessary, but can be useful in
183+
* situations where the debug agent cannot not identify the file in your
184+
* application associated with a path.
185+
*
186+
* This could occur if your application uses a structure that the debug
187+
* agent does not understand, or if more than one file in your application
188+
* has the same name.
189+
*
190+
* For example, if your running application (either locally or in the cloud)
191+
* has the Javascript files:
192+
* /x/y/src/index.js
193+
* /x/y/src/someDir/index.js
194+
* /x/y/src/util.js
195+
* and a breakpoint is set in the `/x/y/src/index.js` through the cloud
196+
* console, the `appResolver` function would be invoked with the following
197+
* arguments:
198+
* scriptPath: 'index.js'
199+
* knownFiles: ['/x/y/src/index.js',
200+
* '/x/y/src/someDir/index.js',
201+
* '/x/y/src/util.js']
202+
* resolved: ['/x/y/src/index.js',
203+
* '/x/y/src/someDir/index.js']
204+
* This is because out of the known files, the files, '/x/y/src/index.js'
205+
* and '/x/y/src/someDir/index.js' end with 'index.js'.
206+
*
207+
* If the array `['/x/y/src/index.js', '/x/y/src/someDir/index.js']` or
208+
* equivalently `undefined` is returned by the `pathResolver` function, the
209+
* debug agent will not be able to set the breakpoint.
210+
*
211+
* If, however, the `pathResolver` function returned `['/x/y/src/index.js']`,
212+
* for example, the debug agent would know to set the breakpoint in
213+
* the `/x/y/src/index.js` file.
214+
*/
215+
pathResolver?:
216+
(scriptPath: string, knownFiles: string[],
217+
resolved: string[]) => string[] | undefined;
218+
167219
/**
168220
* agent log level 0-disabled, 1-error, 2-warn, 3-info, 4-debug
169221
*/
@@ -282,6 +334,7 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
282334
{service: undefined, version: undefined, minorVersion_: undefined},
283335

284336
appPathRelativeToRepository: undefined,
337+
pathResolver: undefined,
285338
logLevel: 1,
286339
breakpointUpdateIntervalSec: 10,
287340
breakpointExpirationSec: 60 * 60 * 24, // 24 hours

src/agent/util/utils.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import * as _ from 'lodash';
12
import * as path from 'path';
23
import * as semver from 'semver';
34

45
import {StatusMessage} from '../../client/stackdriver/status-message';
56
import * as stackdriver from '../../types/stackdriver';
67

8+
import consoleLogLevel = require('console-log-level');
9+
710
import {ResolvedDebugAgentConfig} from '../config';
811
import {ScanStats} from '../io/scanner';
912

@@ -34,6 +37,51 @@ export interface Listener {
3437
}
3538
// Exposed for unit testing.
3639
export function findScripts(
40+
scriptPath: string, config: ResolvedDebugAgentConfig, fileStats: ScanStats,
41+
logger: consoleLogLevel.Logger): string[] {
42+
// (path: string, knownFiles: string[], resolved: string[]) => string[]
43+
const resolved = resolveScripts(scriptPath, config, fileStats);
44+
if (config.pathResolver) {
45+
if (!_.isFunction(config.pathResolver)) {
46+
logger.warn(
47+
`The 'pathResolver' config must be a function. Continuing ` +
48+
`with the agent's default behavior.`);
49+
return resolved;
50+
}
51+
52+
const knownFiles = Object.keys(fileStats);
53+
const calculatedPaths =
54+
config.pathResolver(scriptPath, knownFiles, resolved);
55+
if (calculatedPaths === undefined) {
56+
return resolved;
57+
}
58+
59+
if (!calculatedPaths || !Array.isArray(calculatedPaths)) {
60+
logger.warn(
61+
`The 'pathResolver' config function returned a value ` +
62+
`other than 'undefined' or an array of strings. Continuing with ` +
63+
`the agent's default behavior.`);
64+
return resolved;
65+
}
66+
67+
for (const path of calculatedPaths) {
68+
if (knownFiles.indexOf(path) === -1) {
69+
logger.warn(
70+
`The 'pathResolver' config function returned a path ` +
71+
`'${path}' that is not in the list of paths known to the debug agent ` +
72+
JSON.stringify(knownFiles, null, 2) +
73+
` only known paths can be returned. Continuing with the agent's ` +
74+
`default behavior.`);
75+
return resolved;
76+
}
77+
}
78+
79+
return calculatedPaths;
80+
}
81+
return resolved;
82+
}
83+
84+
function resolveScripts(
3785
scriptPath: string, config: ResolvedDebugAgentConfig,
3886
fileStats: ScanStats): string[] {
3987
// Use repository relative mapping if present.

src/agent/v8/inspector-debugapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ export class InspectorDebugApi implements debugapi.DebugApi {
294294
mapInfo ? mapInfo.file :
295295
path.normalize(
296296
(breakpoint.location as stackdriver.SourceLocation).path),
297-
this.config, this.fileStats);
297+
this.config, this.fileStats, this.logger);
298298
if (scripts.length === 0) {
299299
return utils.setErrorStatusAndCallback(
300300
cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION,

src/agent/v8/legacy-debugapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ export class V8DebugApi implements debugapi.DebugApi {
276276
mapInfo ? mapInfo.file :
277277
path.normalize(
278278
(breakpoint.location as stackdriver.SourceLocation).path),
279-
this.config, this.fileStats);
279+
this.config, this.fileStats, this.logger);
280280
if (scripts.length === 0) {
281281
return utils.setErrorStatusAndCallback(
282282
cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION,

test/mock-logger.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import consoleLogLevel = require('console-log-level');
18+
19+
export type Arguments = string[];
20+
export interface Call {
21+
type: 'trace'|'debug'|'info'|'warn'|'error'|'fatal';
22+
args: Arguments;
23+
}
24+
25+
export class MockLogger implements consoleLogLevel.Logger {
26+
traces: Call[] = [];
27+
debugs: Call[] = [];
28+
infos: Call[] = [];
29+
warns: Call[] = [];
30+
errors: Call[] = [];
31+
fatals: Call[] = [];
32+
33+
allCalls() {
34+
return this.traces.concat(
35+
this.debugs, this.infos, this.warns, this.errors, this.fatals);
36+
}
37+
38+
trace(...args: Arguments) {
39+
this.traces.push({type: 'trace', args});
40+
}
41+
42+
debug(...args: Arguments) {
43+
this.debugs.push({type: 'debug', args});
44+
}
45+
46+
info(...args: Arguments) {
47+
this.infos.push({type: 'info', args});
48+
}
49+
50+
warn(...args: Arguments) {
51+
this.warns.push({type: 'warn', args});
52+
}
53+
54+
error(...args: Arguments) {
55+
this.errors.push({type: 'error', args});
56+
}
57+
58+
fatal(...args: Arguments) {
59+
this.fatals.push({type: 'fatal', args});
60+
}
61+
}

0 commit comments

Comments
 (0)