Skip to content
Merged
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 packages/http-client-csharp/emitter/src/emit-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
configurationFileName,
tspOutputFileName,
} from "./constants.js";
import { execAsync, execCSharpGenerator } from "./lib/exec-utils.js";
import { createDiagnostic } from "./lib/lib.js";
import { execAsync, execCSharpGenerator } from "./lib/utils.js";
import { CSharpEmitterContext } from "./sdk-context.js";

export interface GenerateOptions {
Expand Down
11 changes: 8 additions & 3 deletions packages/http-client-csharp/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core";
import { createDiagnosticCollector, Diagnostic, EmitContext, Program } from "@typespec/compiler";
import { resolve } from "path";
import {
createDiagnosticCollector,
Diagnostic,
EmitContext,
Program,
resolvePath,
} from "@typespec/compiler";
import { serializeCodeModel } from "./code-model-writer.js";
import { generate } from "./emit-generate.js";
import { createModel } from "./lib/client-model-builder.js";
Expand Down Expand Up @@ -49,7 +54,7 @@ export async function emitCodeModel(

// Resolve plugin paths to absolute if specified
if (options["plugins"]) {
options["plugins"] = options["plugins"].map((p) => resolve(outputFolder, p));
options["plugins"] = options["plugins"].map((p) => resolvePath(outputFolder, p));
}

/* set the log level. */
Expand Down
138 changes: 138 additions & 0 deletions packages/http-client-csharp/emitter/src/lib/exec-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

// Node.js-only helpers that wrap `child_process`. These are kept in a separate
// file so that browser bundles (which do not support `child_process`) do not
// pull them in transitively via `lib/utils.ts`.

import { NoTarget, Type } from "@typespec/compiler";
import { spawn, SpawnOptions } from "child_process";
import { CSharpEmitterContext } from "../sdk-context.js";

export async function execCSharpGenerator(
context: CSharpEmitterContext,
options: {
generatorPath: string;
outputFolder: string;
generatorName: string;
newProject: boolean;
debug: boolean;
},
): Promise<{ exitCode: number; stderr: string; proc: any }> {
const command = "dotnet";
const args = [
"--roll-forward",
"Major",
options.generatorPath,
options.outputFolder,
"-g",
options.generatorName,
];
if (options.newProject) {
args.push("--new-project");
}
if (options.debug) {
args.push("--debug");
}
context.logger.info(`${command} ${args.join(" ")}`);

const child = spawn(command, args, { stdio: "pipe" });

const stderr: Buffer[] = [];
return new Promise((resolve, reject) => {
let buffer = "";

child.stdout?.on("data", (data) => {
buffer += data.toString();
let index;
while ((index = buffer.indexOf("\n")) !== -1) {
const message = buffer.slice(0, index);
buffer = buffer.slice(index + 1);
processJsonRpc(context, message);
}
});

child.stderr?.on("data", (data) => {
stderr.push(data);
});

child.on("error", (error) => {
reject(error);
});

child.on("exit", (exitCode) => {
resolve({
exitCode: exitCode ?? -1,
stderr: Buffer.concat(stderr).toString(),
proc: child,
});
});
});
}

function processJsonRpc(context: CSharpEmitterContext, message: string) {
const response = JSON.parse(message);
const method = response.method;
const params = response.params;
switch (method) {
case "trace":
context.logger.trace(params.level, params.message);
break;
case "diagnostic":
let crossLanguageDefinitionId: string | undefined;
if ("crossLanguageDefinitionId" in params) {
crossLanguageDefinitionId = params.crossLanguageDefinitionId;
}
// Use program.reportDiagnostic for diagnostics from C# so that we don't
// have to duplicate the codes in the emitter.
context.program.reportDiagnostic({
code: params.code,
message: params.message,
severity: params.severity,
target: findTarget(crossLanguageDefinitionId) ?? NoTarget,
});
break;
}

function findTarget(crossLanguageDefinitionId: string | undefined): Type | undefined {
if (crossLanguageDefinitionId === undefined) {
return undefined;
}
return context.__typeCache.crossLanguageDefinitionIds.get(crossLanguageDefinitionId);
}
}

export async function execAsync(
command: string,
args: string[] = [],
options: SpawnOptions = {},
): Promise<{ exitCode: number; stdio: string; stdout: string; stderr: string; proc: any }> {
const child = spawn(command, args, options);

return new Promise((resolve, reject) => {
child.on("error", (error) => {
reject(error);
});
const stdio: Buffer[] = [];
const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
child.stdout?.on("data", (data) => {
stdout.push(data);
stdio.push(data);
});
child.stderr?.on("data", (data) => {
stderr.push(data);
stdio.push(data);
});

child.on("exit", (exitCode) => {
resolve({
exitCode: exitCode ?? -1,
stdio: Buffer.concat(stdio).toString(),
stdout: Buffer.concat(stdout).toString(),
stderr: Buffer.concat(stderr).toString(),
proc: child,
});
});
});
}
131 changes: 1 addition & 130 deletions packages/http-client-csharp/emitter/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,139 +7,10 @@ import {
SdkModelPropertyType,
isReadOnly as tcgcIsReadOnly,
} from "@azure-tools/typespec-client-generator-core";
import { getNamespaceFullName, Namespace, NoTarget, Type } from "@typespec/compiler";
import { getNamespaceFullName, Namespace } from "@typespec/compiler";
import { Visibility } from "@typespec/http";
import { spawn, SpawnOptions } from "child_process";
import { CSharpEmitterContext } from "../sdk-context.js";

export async function execCSharpGenerator(
context: CSharpEmitterContext,
options: {
generatorPath: string;
outputFolder: string;
generatorName: string;
newProject: boolean;
debug: boolean;
},
): Promise<{ exitCode: number; stderr: string; proc: any }> {
const command = "dotnet";
const args = [
"--roll-forward",
"Major",
options.generatorPath,
options.outputFolder,
"-g",
options.generatorName,
];
if (options.newProject) {
args.push("--new-project");
}
if (options.debug) {
args.push("--debug");
}
context.logger.info(`${command} ${args.join(" ")}`);

const child = spawn(command, args, { stdio: "pipe" });

const stderr: Buffer[] = [];
return new Promise((resolve, reject) => {
let buffer = "";

child.stdout?.on("data", (data) => {
buffer += data.toString();
let index;
while ((index = buffer.indexOf("\n")) !== -1) {
const message = buffer.slice(0, index);
buffer = buffer.slice(index + 1);
processJsonRpc(context, message);
}
});

child.stderr?.on("data", (data) => {
stderr.push(data);
});

child.on("error", (error) => {
reject(error);
});

child.on("exit", (exitCode) => {
resolve({
exitCode: exitCode ?? -1,
stderr: Buffer.concat(stderr).toString(),
proc: child,
});
});
});
}

function processJsonRpc(context: CSharpEmitterContext, message: string) {
const response = JSON.parse(message);
const method = response.method;
const params = response.params;
switch (method) {
case "trace":
context.logger.trace(params.level, params.message);
break;
case "diagnostic":
let crossLanguageDefinitionId: string | undefined;
if ("crossLanguageDefinitionId" in params) {
crossLanguageDefinitionId = params.crossLanguageDefinitionId;
}
// Use program.reportDiagnostic for diagnostics from C# so that we don't
// have to duplicate the codes in the emitter.
context.program.reportDiagnostic({
code: params.code,
message: params.message,
severity: params.severity,
target: findTarget(crossLanguageDefinitionId) ?? NoTarget,
});
break;
}

function findTarget(crossLanguageDefinitionId: string | undefined): Type | undefined {
if (crossLanguageDefinitionId === undefined) {
return undefined;
}
return context.__typeCache.crossLanguageDefinitionIds.get(crossLanguageDefinitionId);
}
}

export async function execAsync(
command: string,
args: string[] = [],
options: SpawnOptions = {},
): Promise<{ exitCode: number; stdio: string; stdout: string; stderr: string; proc: any }> {
const child = spawn(command, args, options);

return new Promise((resolve, reject) => {
child.on("error", (error) => {
reject(error);
});
const stdio: Buffer[] = [];
const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
child.stdout?.on("data", (data) => {
stdout.push(data);
stdio.push(data);
});
child.stderr?.on("data", (data) => {
stderr.push(data);
stdio.push(data);
});

child.on("exit", (exitCode) => {
resolve({
exitCode: exitCode ?? -1,
stdio: Buffer.concat(stdio).toString(),
stdout: Buffer.concat(stdout).toString(),
stderr: Buffer.concat(stderr).toString(),
proc: child,
});
});
});
}

export function getClientNamespaceString(context: CSharpEmitterContext): string | undefined {
const packageName = context.emitContext.options["package-name"];
const serviceNamespaces = listAllServiceNamespaces(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EmitContext, Program } from "@typespec/compiler";
import { TestHost } from "@typespec/compiler/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { generate } from "../../src/emit-generate.js";
import { execAsync, execCSharpGenerator } from "../../src/lib/utils.js";
import { execAsync, execCSharpGenerator } from "../../src/lib/exec-utils.js";
import { CSharpEmitterOptions } from "../../src/options.js";
import { CodeModel } from "../../src/type/code-model.js";
import {
Expand Down Expand Up @@ -54,7 +54,7 @@ describe("$onEmit tests", () => {
}),
}));

vi.mock("../../src/lib/utils.js", () => ({
vi.mock("../../src/lib/exec-utils.js", () => ({
execCSharpGenerator: vi.fn(),
execAsync: vi.fn(),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { listAllServiceNamespaces } from "@azure-tools/typespec-client-generator
import * as childProcess from "child_process";
import { EventEmitter } from "events";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { execCSharpGenerator, getClientNamespaceStringHelper } from "../../src/lib/utils.js";
import { execCSharpGenerator } from "../../src/lib/exec-utils.js";
import { getClientNamespaceStringHelper } from "../../src/lib/utils.js";
import { CSharpEmitterContext } from "../../src/sdk-context.js";
import {
createCSharpSdkContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Diagnostic, Program } from "@typespec/compiler";
import { TestHost } from "@typespec/compiler/testing";
import { strictEqual } from "assert";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { execAsync } from "../../src/lib/utils.js";
import { execAsync } from "../../src/lib/exec-utils.js";
import {
createCSharpSdkContext,
createEmitterContext,
Expand Down Expand Up @@ -33,7 +33,7 @@ describe("Test _validateDotNetSdk", () => {
);
// Restore all mocks before each test
vi.restoreAllMocks();
vi.mock("../../src/lib/utils.js", () => ({
vi.mock("../../src/lib/exec-utils.js", () => ({
execCSharpGenerator: vi.fn(),
execAsync: vi.fn(),
}));
Expand Down
Loading