Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/desktop/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export const activate = async (context: ExtensionContext): Promise<CsolutionExte
const mergeSessionCoordinator = new MergeSessionCoordinatorImpl(commandsProvider);
const mergeCommand = new MergeCommand(commandsProvider, mergeSessionCoordinator, messageProvider);
const buildCommand = new BuildCommand(buildTaskProvider, commandsProvider, buildTaskDefinitionBuilder);
const runGeneratorCommand = new GeneratorCommand(commandsProvider, solutionManager, outputChannelProvider, cmsisToolboxManager);
const runGeneratorCommand = new GeneratorCommand(commandsProvider, solutionManager, outputChannelProvider, cmsisToolboxManager, eventHub);
const armclangDefineGetter = new ArmclangDefineGetter(processManager, workspaceFsProvider);
const manageSolutionCustomEditorProvider = new ManageSolutionCustomEditorProvider(
context,
Expand Down
37 changes: 33 additions & 4 deletions src/solutions/problem-diagnostic-action-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,21 +143,32 @@ describe('ProblemDiagnosticActionResolver', () => {

describe('run-generator action', () => {

it('encodes generator and context in the command URI arguments (CubeMX example)', () => {
it('uses active target set name when available', () => {
const resolverWithTargetSet = new ProblemDiagnosticActionResolver(() => 'STM32C531CBT6@fvp');

const { command, args } = decodeCodeTarget(resolverWithTargetSet, makeContext({
message: "cgen file was not found, run generator 'CubeMX' for context 'CubeMX.Debug+STM32C531CBT6'",
}));

expect(command).toBe(`command:${RUN_GENERATOR_COMMAND_ID}`);
expect(args).toEqual([{ generator: 'CubeMX', activeTarget: 'STM32C531CBT6@fvp' }]);
});

it('encodes generator and activeTarget in the command URI arguments (CubeMX example)', () => {
const { command, args } = decodeCodeTarget(resolver, makeContext({
message: "cgen file was not found, run generator 'CubeMX' for context 'CubeMX.Debug+STM32C531CBT6'",
}));

expect(command).toBe(`command:${RUN_GENERATOR_COMMAND_ID}`);
expect(args).toEqual([{ generator: 'CubeMX', context: 'CubeMX.Debug+STM32C531CBT6' }]);
expect(args).toEqual([{ generator: 'CubeMX', activeTarget: 'STM32C531CBT6' }]);
});

it('returns a formatted message and a code', () => {
const result = resolver.resolve(makeContext({
message: "cgen file was not found, run generator 'CubeMX' for context 'MyProject.Debug+STM32'",
}));

expect(result?.message).toBe("Run generator 'CubeMX' for project 'MyProject.Debug+STM32'");
expect(result?.message).toBe("Run generator 'CubeMX' for target 'STM32'");
expect(result?.code).toBeDefined();
});

Expand All @@ -175,7 +186,25 @@ describe('ProblemDiagnosticActionResolver', () => {
}));

expect(command).toBe(`command:${RUN_GENERATOR_COMMAND_ID}`);
expect(args).toEqual([{ generator: 'CubeMX', context: 'MyProject.Debug+STM32' }]);
expect(args).toEqual([{ generator: 'CubeMX', activeTarget: 'STM32' }]);
});

it('preserves @TargetSet in activeTarget when the context string contains one', () => {
const { command, args } = decodeCodeTarget(resolver, makeContext({
message: "cgen file was not found, run generator 'CubeMX' for context 'CubeMX.Debug+STM32C531CBT6@fvp'",
}));

expect(command).toBe(`command:${RUN_GENERATOR_COMMAND_ID}`);
expect(args).toEqual([{ generator: 'CubeMX', activeTarget: 'STM32C531CBT6@fvp' }]);
});

it('returns a message with TargetType@Set when the context string contains a target set', () => {
const result = resolver.resolve(makeContext({
message: "cgen file was not found, run generator 'CubeMX' for context 'MyProject.Debug+STM32@board'",
}));

expect(result?.message).toBe("Run generator 'CubeMX' for target 'STM32@board'");
expect(result?.code).toBeDefined();
});
});

Expand Down
32 changes: 24 additions & 8 deletions src/solutions/problem-diagnostic-action-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
RUN_GENERATOR_COMMAND_ID,
} from '../manifest';
import { stripVendor, stripVersion } from '../utils/string-utils';
import { contextDescriptorFromString } from './descriptors/descriptors';


type MergeUpdateLevel = 'recommended' | 'suggested' | 'required';
Expand Down Expand Up @@ -52,7 +53,7 @@ export interface ProblemDiagnosticActionResult {

type ProblemActionDescriptor =
| { kind: 'merge'; localPath: string; updateLevel: MergeUpdateLevel; componentId?: string }
| { kind: 'run-generator'; generator: string; context: string }
| { kind: 'run-generator'; generator: string; activeTarget: string }
| { kind: 'manage-components'; query: string }
| { kind: 'manage-pack'; packId: string }
| { kind: 'open-environment-variables-settings' }
Expand Down Expand Up @@ -95,6 +96,19 @@ const queryActionPatterns: ReadonlyArray<{ pattern: RegExp; action: 'components-
];

export class ProblemDiagnosticActionResolver {
/**
* Optional provider for the active target set name
* If not provided, falls back to the target type parsed from the diagnostic message.
*
* @param getActiveTargetSetName - Function returning the current active target set name,
* or undefined if no target set is active. Called lazily
* only when resolving generator diagnostics.
*/
constructor(
private readonly getActiveTargetSetName?: () => string | undefined,
) {
}

public resolve(context: ProblemDiagnosticActionContext): ProblemDiagnosticActionResult | undefined {
const descriptor = this.resolveDescriptor(context);
if (!descriptor) {
Expand Down Expand Up @@ -125,10 +139,10 @@ export class ProblemDiagnosticActionResolver {

if (descriptor.kind === 'run-generator') {
return {
message: `Run generator '${descriptor.generator}' for project '${descriptor.context}'`,
message: `Run generator '${descriptor.generator}' for target '${descriptor.activeTarget || '""'}'`,
code: {
value: 'Run Generator',
target: this.createRunGeneratorCommandUri(descriptor.generator, descriptor.context),
target: this.createRunGeneratorCommandUri(descriptor.generator, descriptor.activeTarget),
},
};
}
Expand Down Expand Up @@ -200,7 +214,7 @@ export class ProblemDiagnosticActionResolver {
return {
kind: 'run-generator',
generator: request.generator,
context: request.context,
activeTarget: request.activeTarget,
};
}

Expand Down Expand Up @@ -264,7 +278,7 @@ export class ProblemDiagnosticActionResolver {
};
}

private parseGeneratorRequest(message: string): { generator: string; context: string } | undefined {
private parseGeneratorRequest(message: string): { generator: string; activeTarget: string } | undefined {
const normalizedMessage = this.normalizeMessageForPatternMatching(message);

for (const pattern of generatorMissingPatterns) {
Expand All @@ -274,7 +288,9 @@ export class ProblemDiagnosticActionResolver {
}

const [, generator, context] = match;
return { generator, context };
const targetType = contextDescriptorFromString(context).targetType;
const activeTarget = this.getActiveTargetSetName?.() ?? targetType;
return { generator, activeTarget };
}

return undefined;
Expand Down Expand Up @@ -342,8 +358,8 @@ export class ProblemDiagnosticActionResolver {
return vscode.Uri.parse(`command:${MERGE_FILE_COMMAND_ID}?${args}`);
}

private createRunGeneratorCommandUri(generator: string, context: string): vscode.Uri {
const args = this.encodeCommandArgs([{ generator, context }]);
private createRunGeneratorCommandUri(generator: string, activeTarget: string): vscode.Uri {
const args = this.encodeCommandArgs([{ generator, activeTarget }]);
return vscode.Uri.parse(`command:${RUN_GENERATOR_COMMAND_ID}?${args}`);
}

Expand Down
2 changes: 1 addition & 1 deletion src/solutions/solution-event-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('EventHub', () => {
it('should register emitters with context subscriptions', async () => {
await eventHub.activate(mockContext);

expect(mockContext.subscriptions).toHaveLength(5);
expect(mockContext.subscriptions).toHaveLength(6);
});
});

Expand Down
16 changes: 16 additions & 0 deletions src/solutions/solution-event-hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export interface SolutionEventHub {
* Event fired when cbuild setup is requested
*/
readonly onDidCbuildSetupRequested: vscode.Event<void>;
/**
* Fire generator run completion event
*/
fireGeneratorRunCompleted(data: CbuildResultData): Promise<void>;
/**
* Event fired when generator run is completed
*/
readonly onDidGeneratorRunCompleted: vscode.Event<CbuildResultData>;
/**
* Fire configure solution data ready event
*/
Expand All @@ -124,6 +132,9 @@ class SolutionEventHubImpl {
private readonly cbuildSetupRequestEmitter = new vscode.EventEmitter<void>();
public readonly onDidCbuildSetupRequested: vscode.Event<void> = this.cbuildSetupRequestEmitter.event;

private readonly generatorRunCompleteEmitter = new vscode.EventEmitter<CbuildResultData>();
public readonly onDidGeneratorRunCompleted: vscode.Event<CbuildResultData> = this.generatorRunCompleteEmitter.event;

private readonly configureSolutionDataEmitter = new vscode.EventEmitter<ConfigureSolutionData>();
public readonly onDidConfigureSolutionDataReady: vscode.Event<ConfigureSolutionData> = this.configureSolutionDataEmitter.event;

Expand All @@ -132,6 +143,7 @@ class SolutionEventHubImpl {
context.subscriptions.push(this.convertCompleteEmitter);
context.subscriptions.push(this.cbuildCompleteEmitter);
context.subscriptions.push(this.cbuildSetupRequestEmitter);
context.subscriptions.push(this.generatorRunCompleteEmitter);
context.subscriptions.push(this.configureSolutionDataEmitter);
}

Expand All @@ -151,6 +163,10 @@ class SolutionEventHubImpl {
this.cbuildSetupRequestEmitter.fire();
}

public async fireGeneratorRunCompleted(data: CbuildResultData): Promise<void> {
this.generatorRunCompleteEmitter.fire(data);
}

public async fireConfigureSolutionDataReady(data: ConfigureSolutionData): Promise<void> {
this.configureSolutionDataEmitter.fire(data);
}
Expand Down
5 changes: 3 additions & 2 deletions src/solutions/solution-problems.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const buildCsolution = () => {
['ctx', { fileName: '/work/ctx.cbuild.yml' }],
]),
},
getActiveTargetSetName: () => undefined,
};
};

Expand Down Expand Up @@ -70,7 +71,7 @@ describe('SolutionProblems', () => {

await solutionProblems.activate(context);

expect(context.subscriptions).toHaveLength(5);
expect(context.subscriptions).toHaveLength(6);
});

it('clears diagnostics when solution path changes', async () => {
Expand Down Expand Up @@ -366,7 +367,7 @@ describe('SolutionProblems', () => {
const code = runGeneratorDiagnostics[0].code as { value: string; target: vscode.Uri };
const [command, args] = code.target.toString().split('?');
expect(command).toBe(`command:${RUN_GENERATOR_COMMAND_ID}`);
expect(JSON.parse(decodeURIComponent(args))).toEqual([{ generator: 'CubeMX2', context: 'CubeMX2.Debug+STM32C531CBT6' }]);
expect(JSON.parse(decodeURIComponent(args))).toEqual([{ generator: 'CubeMX2', activeTarget: 'STM32C531CBT6' }]);
});

it('falls back to the diagnostic file path for relative merge paths', async () => {
Expand Down
14 changes: 13 additions & 1 deletion src/solutions/solution-problems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export class SolutionProblemsImpl implements SolutionProblems {

private readonly diagnosticCollection: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection('csolution');
private readonly environmentDiagnosticCollection: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection('csolution-environment');
private readonly diagnosticActionResolver = new ProblemDiagnosticActionResolver();
private readonly diagnosticActionResolver: ProblemDiagnosticActionResolver;
private readonly environmentVariablesSetting = '"cmsis-csolution.environmentVariables"';
/**
* source files for diagnostics mapping
Expand All @@ -244,12 +244,16 @@ export class SolutionProblemsImpl implements SolutionProblems {
private readonly solutionManager: SolutionManager,
private readonly eventHub: SolutionEventHub,
) {
this.diagnosticActionResolver = new ProblemDiagnosticActionResolver(
() => this.solutionManager.getCsolution()?.getActiveTargetSetName(),
);
}

public async activate(context: vscode.ExtensionContext): Promise<void> {
context.subscriptions.push(
this.eventHub.onDidConvertCompleted(this.handleConvertCompleted, this),
this.eventHub.onDidCbuildCompleted(this.handleCbuildCompleted, this),
this.eventHub.onDidGeneratorRunCompleted(this.handleGeneratorRunCompleted, this),
this.solutionManager.onDidChangeLoadState(this.handleLoadStateChanged, this),
this.diagnosticCollection,
this.environmentDiagnosticCollection,
Expand Down Expand Up @@ -278,6 +282,14 @@ export class SolutionProblemsImpl implements SolutionProblems {
await this.showProblemsViewIfNeeded(hasGeneralDiagnostics || hasEnvironmentDiagnostics);
}

private async handleGeneratorRunCompleted(data: CbuildResultData): Promise<void> {
// Generator run diagnostics are additive. Do not clear.
// This preserves existing convert and cbuild diagnostics while adding generator issues.
const logMessages: LogMessages = { success: true, errors: [], warnings: [], info: [] };
const hasGeneralDiagnostics = await this.enrichAndUpdateDiagnostics(logMessages, data.toolsOutputMessages);
await this.showProblemsViewIfNeeded(hasGeneralDiagnostics);
}

private async updateEnvironmentDiagnosticsFromConvert(data: ConvertResultData): Promise<boolean> {
const messages: EnvironmentMessage[] = [
...(data.logMessages.errors ?? []).map(message => ({
Expand Down
Loading
Loading