diff --git a/extension/src/buildcore.ts b/extension/src/buildcore.ts index a751255f..dcb803d7 100644 --- a/extension/src/buildcore.ts +++ b/extension/src/buildcore.ts @@ -4,7 +4,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import type { CodeListingMap, FileData, Segment, WorkerError, WorkerErrorResult, WorkerResult } from "../../src/common/workertypes"; +import type { BuildProducts, FileData, WorkerError, WorkerErrorResult, WorkerResult } from "../../src/common/workertypes"; +import { buildProducts } from "../../src/common/workertypes"; import { toInternalError } from "../../src/common/telemetry"; import { getBasePlatform, isProbablyBinary } from "../../src/common/util"; import { getToolForPlatform } from "../../src/common/toolselect"; @@ -33,16 +34,11 @@ export interface BuildDiagnostic { msg: string; } -export interface BuildOutcome { +export interface BuildOutcome extends BuildProducts { success: boolean; tool: string; output?: Uint8Array; diagnostics: BuildDiagnostic[]; - listings?: CodeListingMap; - symbolmap?: { [sym: string]: number }; - /** symbol -> size in bytes, for the toolchains that report it */ - symbolsizes?: { [sym: string]: number }; - segments?: Segment[]; /** every project path the build read */ paths: string[]; /** @@ -142,7 +138,7 @@ export class Builder { var r = result as any; var outcome: BuildOutcome = { success: true, tool, paths, files, diagnostics: [], - output: r.output, listings: r.listings, symbolmap: r.symbolmap, symbolsizes: r.symbolsizes, segments: r.segments, + output: r.output, ...buildProducts(r), }; this.last.set(key, outcome); return outcome; diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 5727b3d0..67c4463d 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -19,6 +19,7 @@ import { BuildReason, BuildScheduler } from './autobuild'; import { PRESET_SCHEME, PresetFileSystem, Templates } from './templates'; import { chooseForFile, detectDirectoryAt, detectionSummary, describeDetection, describeFinding, dontAskKey, FolderFinding, platformName, scanFolder } from './detection'; import { Detection, classifyFinding, isBuildableSource } from '../../src/common/detect'; +import { buildProducts } from '../../src/common/workertypes'; import { TOOL_META } from '../../src/common/toolmeta'; import { ASSET_URLS, AssetStore } from './assets'; import { projectReadme, writeLauncher } from './terminalcli'; @@ -398,7 +399,7 @@ async function runScriptHeadless(target: Target, build: BuildOutcome, script: st var run = async () => { await worker.call('start', target.platform, build.output, build.files, { paused: true }); var mainPath = path.posix.basename(target.main.path); - return worker.call('script', script, { listings: build.listings, symbols: build.symbolmap, mainPath, paths: build.paths }); + return worker.call('script', script, { ...buildProducts(build), mainPath, paths: build.paths }); }; return await Promise.race([run(), stop]); } finally { @@ -1426,6 +1427,6 @@ async function launchForDebug(folder: vscode.WorkspaceFolder | undefined, args: if (!result.output) throw new Error('the build produced no ROM to run'); if (!await startEmulator(target, result, { paused: true })) throw new Error('the emulator failed to start; see Output'); var mainPath = path.posix.basename(target.main.path); - await emu!.call('debug', 'setBuild', { listings: result.listings, symbols: result.symbolmap, symbolsizes: result.symbolsizes, mainPath, paths: result.paths }); + await emu!.call('debug', 'setBuild', { ...buildProducts(result), mainPath, paths: result.paths }); return { root: path.dirname(target.main.fsPath) }; } diff --git a/extension/test/emuworker.test.ts b/extension/test/emuworker.test.ts index cc8bbeef..f32f4f5a 100644 --- a/extension/test/emuworker.test.ts +++ b/extension/test/emuworker.test.ts @@ -160,7 +160,7 @@ describe('extension emuworker', function () { files: new ProjectFileProvider(read, ROOT, 'nes'), }); await rpc.call('start', 'nes', nes.output, nes.files, { paused: true }); - var build = { listings: nes.listings, symbols: nes.symbolmap, mainPath: 'attributes.c', paths: nes.paths }; + var build = { listings: nes.listings, symbolmap: nes.symbolmap, mainPath: 'attributes.c', paths: nes.paths }; var r = await rpc.call('script', 'run 30; mem main 4', build); assert.equal(r.error, undefined); assert.equal(r.frame, 30); @@ -251,7 +251,7 @@ describe('extension emuworker', function () { it('starts stopped, then stops at a source breakpoint, steps, and steps back', async function () { var s = await rpc.call('start', 'mw8080bw', built.output, built.files, { paused: true }); assert.equal(s.state, 'paused'); - await debug('setBuild', { listings: built.listings, symbols: built.symbolmap, mainPath: 'game2.c', paths: built.paths }); + await debug('setBuild', { listings: built.listings, symbolmap: built.symbolmap, mainPath: 'game2.c', paths: built.paths }); var [bp] = await debug('setBreakpoints', [{ id: 1, type: 'source', file: 'game2.c', line: 166, enabled: true }]); assert.ok(bp.verified, bp.message); await debug('continue'); @@ -280,7 +280,7 @@ describe('extension emuworker', function () { }); assert.deepEqual(nes.diagnostics, []); await rpc.call('start', 'nes', nes.output, nes.files, { paused: true }); - await debug('setBuild', { listings: nes.listings, symbols: nes.symbolmap, mainPath: 'hello.c', paths: nes.paths }); + await debug('setBuild', { listings: nes.listings, symbolmap: nes.symbolmap, mainPath: 'hello.c', paths: nes.paths }); var [bp] = await debug('setBreakpoints', [{ id: 1, type: 'source', file: 'hello.c', line: 19, enabled: true }]); assert.ok(bp.verified, bp.message); await debug('continue'); @@ -306,7 +306,7 @@ describe('extension emuworker', function () { files: new ProjectFileProvider(read, ROOT, 'nes'), }); await rpc.call('start', 'nes', nes.output, nes.files, { paused: true }); - await debug('setBuild', { listings: nes.listings, symbols: nes.symbolmap, mainPath: 'scroll.c', paths: nes.paths }); + await debug('setBuild', { listings: nes.listings, symbolmap: nes.symbolmap, mainPath: 'scroll.c', paths: nes.paths }); var caps = await debug('capabilities'); assert.equal(caps.tree, true); assert.equal(caps.write, true); @@ -333,7 +333,7 @@ describe('extension emuworker', function () { it('drives the debug adapter through the worker, as the extension does', async function () { var backend = new WorkerDebugBackend((method, ...args) => rpc.call('debug', method, ...args), async () => { await rpc.call('start', 'mw8080bw', built.output, built.files, { paused: true }); - await rpc.call('debug', 'setBuild', { listings: built.listings, symbols: built.symbolmap, mainPath: 'game2.c', paths: built.paths }); + await rpc.call('debug', 'setBuild', { listings: built.listings, symbolmap: built.symbolmap, mainPath: 'game2.c', paths: built.paths }); return { root: '/proj' }; }, async () => { }); rpc.on('stopped', e => backend.handleStop(e)); diff --git a/src/common/debugcontroller.ts b/src/common/debugcontroller.ts index fca22567..67b85025 100644 --- a/src/common/debugcontroller.ts +++ b/src/common/debugcontroller.ts @@ -13,7 +13,7 @@ import { DEFAULT_MAX_FRAMES, EmuCore } from "./emucore"; import { getListingForFile, processListings, stripLocalPath } from "./projectcore"; import { Timestamp, timestamp, timestampsEqual } from "./timeline"; import { getFilenameForPath, getFilenamePrefix } from "./util"; -import { CodeListingMap, SourceFile, SourceLine, SourceLocation } from "./workertypes"; +import { BuildProducts, CodeListingMap, SourceFile, SourceLine, SourceLocation } from "./workertypes"; export type StopReason = 'breakpoint' | 'step' | 'pause' | 'goto' | 'entry' | 'halt' | 'exception'; @@ -371,17 +371,18 @@ function sameLine(a: SourceLocation, b: SourceLocation): boolean { return a.line === b.line && a.path === b.path; } +/** A build's products, and the project files they came from. */ +export interface BuildInfo extends BuildProducts { + mainPath: string; + /** the project files, which name the source a listing came from */ + paths?: string[]; +} + /** * A DebugContext from a build: source lines from its listings, and symbols. * `paths` are the project files, which name the source a listing came from. */ -export function buildDebugContext(build: { - listings?: CodeListingMap, - symbols?: { [name: string]: number }, - symbolsizes?: { [name: string]: number }, - mainPath: string, - paths?: string[], -}): DebugContext { +export function buildDebugContext(build: BuildInfo): DebugContext { const listings = build.listings || {}; // listings that came over an RPC have lost their SourceFile methods if (Object.values(listings).some(l => l.lines && !(l.sourcefile instanceof SourceFile))) processListings(listings); @@ -390,9 +391,9 @@ export function buildDebugContext(build: { const want = getFilenamePrefix(getFilenameForPath(stripLocalPath(name, build.mainPath))); return paths.find(p => getFilenamePrefix(getFilenameForPath(p)) === want) || name; }; - const symbolAddrs = [...new Set(Object.values(build.symbols || {}))].sort((a, b) => a - b); + const symbolAddrs = [...new Set(Object.values(build.symbolmap || {}))].sort((a, b) => a - b); return { - symbols: build.symbols, + symbols: build.symbolmap, symbolsizes: build.symbolsizes, getListingForFile: path => getListingForFile(listings, path, build.mainPath), sourceAt(pc: number) { diff --git a/src/common/workertypes.ts b/src/common/workertypes.ts index cbbccf7d..94a30171 100644 --- a/src/common/workertypes.ts +++ b/src/common/workertypes.ts @@ -203,6 +203,18 @@ export interface WorkerOutputResult { origin?: number } +/** + * What a build produces for the debugger and the memory map, besides the ROM. + * Types that carry a build's products extend this, and `buildProducts` copies + * them, so a new field is added here and in that function only. + */ +export type BuildProducts = Pick, 'listings' | 'symbolmap' | 'symbolsizes' | 'segments'>; + +export function buildProducts(r: BuildProducts): BuildProducts { + const { listings, symbolmap, symbolsizes, segments } = r; + return { listings, symbolmap, symbolsizes, segments }; +} + export function isUnchanged(result: WorkerResult) : result is WorkerUnchangedResult { return ('unchanged' in result); } diff --git a/src/tools/8bws.ts b/src/tools/8bws.ts index ff863c15..236d608c 100644 --- a/src/tools/8bws.ts +++ b/src/tools/8bws.ts @@ -19,6 +19,7 @@ import { EmuTarget, loadPlatform } from './emutarget'; import { RUN_SCRIPT_HELP, RunScript, parseNum } from './runscript'; import { verifyReplay } from './verifyreplay'; import { buildDebugContext } from '../common/debugcontroller'; +import { buildProducts } from '../common/workertypes'; import type { BuildInfo } from './debugservice'; import { parseSymbolFile } from '../common/symbols/symbolfile'; import { romBytes, type CompileResult } from './testlib'; @@ -297,7 +298,7 @@ async function openProgram(command: string, args: Args, input: string): Promise< let debugInfo: BuildInfo | undefined; if (built) { const mainPath = path.basename(input); - debugInfo = { listings: built.result.listings, symbols, symbolsizes: built.result.symbolsizes, mainPath, paths: [mainPath] }; + debugInfo = { ...buildProducts(built.result), mainPath, paths: [mainPath] }; } return { target, source: input, romFile, built, symbols, debugInfo }; } diff --git a/src/tools/debugservice.ts b/src/tools/debugservice.ts index a601928c..882a823d 100644 --- a/src/tools/debugservice.ts +++ b/src/tools/debugservice.ts @@ -10,7 +10,7 @@ import { Breakpoint, makeCondContext } from '../common/breakpoints'; import { compileExpression } from '../common/breakcond'; import { TreeEntry, treeChildren } from '../common/debugtree'; import { walkStack } from '../common/stackwalk'; -import { buildDebugContext, DebugController, DebugLocation, StepGranularity, StopEvent } from '../common/debugcontroller'; +import { buildDebugContext, BuildInfo, DebugController, DebugLocation, StepGranularity, StopEvent } from '../common/debugcontroller'; import { resolveSymbolName } from '../common/symbols/symbolfile'; import { formatSymbolValue, MAX_SYMBOL_BYTES, SymbolValue } from '../common/symbols/symbolvalue'; import { Granularity, Timestamp } from '../common/timeline'; @@ -74,6 +74,8 @@ export interface EvalResult { moved?: boolean; } +export type { BuildInfo }; + export interface TimelineInfo { first: number; last: number; @@ -81,14 +83,6 @@ export interface TimelineInfo { past: boolean; } -export interface BuildInfo { - listings?: CodeListingMap; - symbols?: { [name: string]: number }; - symbolsizes?: { [name: string]: number }; - mainPath: string; - paths?: string[]; -} - // how far before an address to start disassembling, per instruction wanted const MAX_INSN_BYTES = 4; diff --git a/src/tools/testlib.ts b/src/tools/testlib.ts index 5242359f..cf561d72 100644 --- a/src/tools/testlib.ts +++ b/src/tools/testlib.ts @@ -5,7 +5,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import type { WorkerResult, WorkerErrorResult, WorkerMessage, BuildArgLists, BuildSymbolLists, FileData } from "../common/workertypes"; +import type { WorkerResult, WorkerErrorResult, WorkerMessage, BuildArgLists, BuildSymbolLists, FileData, BuildProducts } from "../common/workertypes"; +import { buildProducts } from "../common/workertypes"; import { isProbablyBinary, getBasePlatform } from "../common/util"; import { getToolForPlatform } from "../common/toolselect"; import { FileProvider, buildWorkerMessage, resolveDependencies } from "../common/projectcore"; @@ -30,14 +31,10 @@ export interface CompileOptions { buildArgs?: BuildArgLists; } -export interface CompileResult { +export interface CompileResult extends BuildProducts { success: boolean; output?: Uint8Array | any; errors?: { line: number; msg: string; path?: string }[]; - listings?: any; - symbolmap?: any; - symbolsizes?: { [sym: string]: number }; - segments?: any; params?: any; unchanged?: boolean; /** every file the build read, by worker filename (verilog's $readmem reads them at load time) */ @@ -224,11 +221,8 @@ function workerResultToCompileResult(result: WorkerResult): CompileResult { return { success: true, output: result.output, - listings: (result as any).listings, - symbolmap: (result as any).symbolmap, - symbolsizes: (result as any).symbolsizes, - segments: (result as any).segments, params: (result as any).params, + ...buildProducts(result), }; } return { success: false, errors: [{ line: 0, msg: 'Unknown result format' }] }; diff --git a/test/unit/testdapsession.ts b/test/unit/testdapsession.ts index cd90896a..09a49310 100644 --- a/test/unit/testdapsession.ts +++ b/test/unit/testdapsession.ts @@ -19,7 +19,7 @@ async function launch(args: any = {}): Promise { const target = await loadPlatform('mw8080bw'); await target.start(); await target.loadROM(build.output); - return { target, root: ROOT, debugInfo: { listings: build.listings, symbols: build.symbolmap, symbolsizes: { _draw_char: 4, _main: 100 }, mainPath: 'game2.c' } }; + return { target, root: ROOT, debugInfo: { listings: build.listings, symbolmap: build.symbolmap, symbolsizes: { _draw_char: 4, _main: 100 }, mainPath: 'game2.c' } }; }); const c = new DapClient(new EmuDebugSession(backend)); const caps = await c.request('initialize', { adapterID: '8bitworkshop', linesStartAt1: true, columnsStartAt1: true, pathFormat: 'path' }); diff --git a/test/unit/testdebugcontroller.ts b/test/unit/testdebugcontroller.ts index 5768e33b..63e9be6a 100644 --- a/test/unit/testdebugcontroller.ts +++ b/test/unit/testdebugcontroller.ts @@ -17,7 +17,7 @@ async function open(): Promise<{ t: EmuTarget, dc: DebugController }> { const t = await loadPlatform('mw8080bw'); await t.start(); await t.loadROM(build.output); - const dc = new DebugController(t, buildDebugContext({ listings: build.listings, symbols: build.symbolmap, mainPath: 'game2.c' })); + const dc = new DebugController(t, buildDebugContext({ listings: build.listings, symbolmap: build.symbolmap, mainPath: 'game2.c' })); return { t, dc }; }