diff --git a/packages/adl-vs/Directory.Build.targets b/packages/adl-vs/Directory.Build.targets deleted file mode 100644 index 60bd66e1c..000000000 --- a/packages/adl-vs/Directory.Build.targets +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/adl-vs/Microsoft.Adl.VisualStudio.csproj b/packages/adl-vs/Microsoft.Adl.VS.props similarity index 64% rename from packages/adl-vs/Microsoft.Adl.VisualStudio.csproj rename to packages/adl-vs/Microsoft.Adl.VS.props index 63700a312..433ef4453 100644 --- a/packages/adl-vs/Microsoft.Adl.VisualStudio.csproj +++ b/packages/adl-vs/Microsoft.Adl.VS.props @@ -1,10 +1,9 @@ - + net472 Embedded true false - false Latest Enable true @@ -12,24 +11,22 @@ strict 42.42.42 + + false - false - $(AssemblyName).vsix + $(MSBuildThisFileDirectory)$(AssemblyName).vsix - - - + + + + - - - - - + diff --git a/packages/adl-vs/Microsoft.Adl.VS.sln b/packages/adl-vs/Microsoft.Adl.VS.sln new file mode 100644 index 000000000..a0d08c157 --- /dev/null +++ b/packages/adl-vs/Microsoft.Adl.VS.sln @@ -0,0 +1,40 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31202.260 +MinimumVisualStudioVersion = 16.0.31202.260 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Adl.VS2019", "VS2019\Microsoft.Adl.VS2019.csproj", "{3B3CF7E4-5B54-47EA-8BE3-60D8F806C69E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Adl.VS2022", "VS2022\Microsoft.Adl.VS2022.csproj", "{C7566715-9191-4D95-B673-4E569800CE32}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{128D9265-5E6B-4C7E-889E-15EE684BEFD6}" + ProjectSection(SolutionItems) = preProject + scripts\build.js = scripts\build.js + Directory.Build.rsp = Directory.Build.rsp + Microsoft.Adl.VS.props = Microsoft.Adl.VS.props + Microsoft.Adl.VS.targets = Microsoft.Adl.VS.targets + nuget.config = nuget.config + package.json = package.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B3CF7E4-5B54-47EA-8BE3-60D8F806C69E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B3CF7E4-5B54-47EA-8BE3-60D8F806C69E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B3CF7E4-5B54-47EA-8BE3-60D8F806C69E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B3CF7E4-5B54-47EA-8BE3-60D8F806C69E}.Release|Any CPU.Build.0 = Release|Any CPU + {C7566715-9191-4D95-B673-4E569800CE32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7566715-9191-4D95-B673-4E569800CE32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7566715-9191-4D95-B673-4E569800CE32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7566715-9191-4D95-B673-4E569800CE32}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1DFC3838-4459-4321-B2A1-D473D4654597} + EndGlobalSection +EndGlobal diff --git a/packages/adl-vs/Microsoft.Adl.VS.targets b/packages/adl-vs/Microsoft.Adl.VS.targets new file mode 100644 index 000000000..2728451ff --- /dev/null +++ b/packages/adl-vs/Microsoft.Adl.VS.targets @@ -0,0 +1,58 @@ + + + + + + true + + + + + + + + + + + + + + + + + + + + diff --git a/packages/adl-vs/Microsoft.Adl.VisualStudio.sln b/packages/adl-vs/Microsoft.Adl.VisualStudio.sln deleted file mode 100644 index a3fdbc56f..000000000 --- a/packages/adl-vs/Microsoft.Adl.VisualStudio.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31202.260 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Adl.VisualStudio", "Microsoft.Adl.VisualStudio.csproj", "{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1DFC3838-4459-4321-B2A1-D473D4654597} - EndGlobalSection -EndGlobal diff --git a/packages/adl-vs/VS2019/Microsoft.Adl.VS2019.csproj b/packages/adl-vs/VS2019/Microsoft.Adl.VS2019.csproj new file mode 100644 index 000000000..32a8b1bb5 --- /dev/null +++ b/packages/adl-vs/VS2019/Microsoft.Adl.VS2019.csproj @@ -0,0 +1,15 @@ + + + + + $(DefineConstants);VS2019 + 16.0 + 17.0 + + + + + + + + \ No newline at end of file diff --git a/packages/adl-vs/Properties/launchSettings.json b/packages/adl-vs/VS2019/Properties/launchSettings.json similarity index 69% rename from packages/adl-vs/Properties/launchSettings.json rename to packages/adl-vs/VS2019/Properties/launchSettings.json index 81f446102..e797e8a9d 100644 --- a/packages/adl-vs/Properties/launchSettings.json +++ b/packages/adl-vs/VS2019/Properties/launchSettings.json @@ -1,9 +1,9 @@ { "profiles": { - "Microsoft.Adl.VisualStudio": { + "Visual Studio Extension": { "commandName": "Executable", "executablePath": "$(DevEnvDir)devenv.exe", - "commandLineArgs": "/rootSuffix Exp ../../../adl-samples/petstore", + "commandLineArgs": "/rootSuffix Exp ../../../../adl-samples/petstore", "environmentVariables": { "ADL_SERVER_NODE_OPTIONS": "--nolazy --inspect=4242", "ADL_DEVELOPMENT_MODE": "true" diff --git a/packages/adl-vs/source.extension.vsixmanifest b/packages/adl-vs/VS2019/source.extension.vsixmanifest similarity index 81% rename from packages/adl-vs/source.extension.vsixmanifest rename to packages/adl-vs/VS2019/source.extension.vsixmanifest index 04ecb6db7..b6439cb25 100644 --- a/packages/adl-vs/source.extension.vsixmanifest +++ b/packages/adl-vs/VS2019/source.extension.vsixmanifest @@ -1,14 +1,14 @@ - + ADL Language Support - ADL Language Support for Visual Studio + ADL Language Support for Visual Studio 2019 https://github.com/azure/adl LICENSE - + @@ -19,6 +19,6 @@ - + diff --git a/packages/adl-vs/VS2022/Microsoft.Adl.VS2022.csproj b/packages/adl-vs/VS2022/Microsoft.Adl.VS2022.csproj new file mode 100644 index 000000000..37fa28ddd --- /dev/null +++ b/packages/adl-vs/VS2022/Microsoft.Adl.VS2022.csproj @@ -0,0 +1,15 @@ + + + + + $(DefineConstants);VS2022 + 17.0 + 18.0 + + + + + + + + \ No newline at end of file diff --git a/packages/adl-vs/VS2022/Properties/launchSettings.json b/packages/adl-vs/VS2022/Properties/launchSettings.json new file mode 100644 index 000000000..3aac303e4 --- /dev/null +++ b/packages/adl-vs/VS2022/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "N/A": { + "commandName": "Executable", + "executablePath": "cmd.exe", + "commandLineArgs": "/c echo Use Microsoft.Adl.VS2019 project to launch, even when using VS 2022." + } + } +} diff --git a/packages/adl-vs/VS2022/source.extension.vsixmanifest b/packages/adl-vs/VS2022/source.extension.vsixmanifest new file mode 100644 index 000000000..7ea9cfd5d --- /dev/null +++ b/packages/adl-vs/VS2022/source.extension.vsixmanifest @@ -0,0 +1,26 @@ + + + + + ADL Language Support + ADL Language Support for Visual Studio 2022 + https://github.com/azure/adl + LICENSE + + + + amd64 + + + + + + + + + + + + + + diff --git a/packages/adl-vs/package.json b/packages/adl-vs/package.json index 59e684774..277473b7e 100644 --- a/packages/adl-vs/package.json +++ b/packages/adl-vs/package.json @@ -21,7 +21,8 @@ "node": ">=14.0.0" }, "files": [ - "Microsoft.Adl.VisualStudio.vsix" + "Microsoft.Adl.VS2019.vsix", + "Microsoft.Adl.VS2022.vsix" ], "scripts": { "build": "node scripts/build.js" diff --git a/packages/adl-vs/scripts/build.js b/packages/adl-vs/scripts/build.js index 9750a3866..b5704e513 100644 --- a/packages/adl-vs/scripts/build.js +++ b/packages/adl-vs/scripts/build.js @@ -65,6 +65,6 @@ if (process.argv[2] === "--restore") { msbuildArgs.push("/restore"); } -msbuildArgs.push(join(dir, "Microsoft.Adl.VisualStudio.csproj")); +msbuildArgs.push(join(dir, "Microsoft.Adl.VS.sln")); proc = run(msbuild, msbuildArgs, { throwOnNonZeroExit: false }); process.exit(proc.status ?? 1); diff --git a/packages/adl-vs/src/VSExtension.cs b/packages/adl-vs/src/VSExtension.cs index c63ff27c5..7dd5f4fcf 100644 --- a/packages/adl-vs/src/VSExtension.cs +++ b/packages/adl-vs/src/VSExtension.cs @@ -51,7 +51,7 @@ namespace Microsoft.Adl.VisualStudio { _outputWindow = window; } - public async Task ActivateAsync(CancellationToken token) { + public async Task ActivateAsync(CancellationToken token) { await Task.Yield(); var options = Environment.GetEnvironmentVariable("ADL_SERVER_NODE_OPTIONS"); diff --git a/packages/adl/compiler/cli.ts b/packages/adl/compiler/cli.ts index bf1d314cf..2171dc7b2 100644 --- a/packages/adl/compiler/cli.ts +++ b/packages/adl/compiler/cli.ts @@ -1,8 +1,8 @@ -import { spawnSync, SpawnSyncOptions } from "child_process"; +import { spawnSync, SpawnSyncOptionsWithStringEncoding } from "child_process"; import { mkdtemp, readdir, rmdir } from "fs/promises"; import mkdirp from "mkdirp"; import os from "os"; -import path, { join, resolve } from "path"; +import { basename, join, resolve } from "path"; import url from "url"; import yargs from "yargs"; import { CompilerOptions } from "../compiler/options.js"; @@ -140,7 +140,7 @@ async function getCompilerOptions(): Promise { } async function generateClient(options: CompilerOptions) { - const clientPath = path.resolve(args["output-path"], "client"); + const clientPath = resolve(args["output-path"], "client"); const autoRestBin = process.platform === "win32" ? "autorest.cmd" : "autorest"; const autoRestPath = new url.URL(`../../node_modules/.bin/${autoRestBin}`, import.meta.url); @@ -168,9 +168,9 @@ async function generateClient(options: CompilerOptions) { } } -async function installVsix(pkg: string, install: (vsixPath: string) => void) { +async function installVsix(pkg: string, install: (vsixPaths: string[]) => void) { // download npm package to temporary directory - const temp = await mkdtemp(path.join(os.tmpdir(), "adl")); + const temp = await mkdtemp(join(os.tmpdir(), "adl")); const npmArgs = ["install"]; // hide npm output unless --debug was passed to adl @@ -183,24 +183,32 @@ async function installVsix(pkg: string, install: (vsixPath: string) => void) { // to pass --prefix even though we're using cwd as otherwise, npm might // find a package.json file in a parent directory and install to that // directory. - npmArgs.push("--prefix", ".", pkg); + npmArgs.push("--prefix", "."); + + // To debug with a locally built package rather than pulling from npm, + // specify the full path to the packed .tgz using ADL_DEBUG_VSIX_TGZ + // environment variable. + npmArgs.push(process.env.ADL_DEBUG_VSIX_TGZ ?? pkg); + run("npm", npmArgs, { cwd: temp }); // locate .vsix - const dir = path.join(temp, "node_modules", pkg); + const dir = join(temp, "node_modules", pkg); const files = await readdir(dir); - let vsix: string | undefined; + let vsixPaths: string[] = []; for (const file of files) { if (file.endsWith(".vsix")) { - vsix = path.join(dir, file); - break; + vsixPaths.push(join(dir, file)); } } - compilerAssert(vsix, `Installed ${pkg} from npm, but didn't find its .vsix file.`); + compilerAssert( + vsixPaths.length > 0, + `Installed ${pkg} from npm, but didn't find any .vsix files in it.` + ); // install extension - install(vsix); + install(vsixPaths); // delete temporary directory await rmdir(temp, { recursive: true }); @@ -214,8 +222,8 @@ async function runCode(codeArgs: string[]) { } async function installVSCodeExtension() { - await installVsix("adl-vscode", (vsix) => { - runCode(["--install-extension", vsix]); + await installVsix("adl-vscode", (vsixPaths) => { + runCode(["--install-extension", vsixPaths[0]]); }); } @@ -224,28 +232,94 @@ async function uninstallVSCodeExtension() { } function getVsixInstallerPath(): string { + return getVSInstallerPath( + "resources/app/ServiceHub/Services/Microsoft.VisualStudio.Setup.Service/VSIXInstaller.exe" + ); +} + +function getVSWherePath(): string { + return getVSInstallerPath("vswhere.exe"); +} + +function getVSInstallerPath(relativePath: string) { if (process.platform !== "win32") { - console.error("error: Visual Studio extension is not supported on non-Windows"); + console.error("error: Visual Studio extension is not supported on non-Windows."); process.exit(1); } return join( process.env["ProgramFiles(x86)"] ?? "", - "Microsoft Visual Studio/Installer/resources/app/ServiceHub/Services/Microsoft.VisualStudio.Setup.Service", - "VSIXInstaller.exe" + "Microsoft Visual Studio/Installer", + relativePath ); } +function isVSInstalled(versionRange: string) { + const vswhere = getVSWherePath(); + const proc = run(vswhere, ["-property", "instanceid", "-prerelease", "-version", versionRange], { + stdio: [null, "pipe", "inherit"], + allowNotFound: true, + }); + return proc.status === 0 && proc.stdout; +} + +const VSIX_ALREADY_INSTALLED = 1001; +const VSIX_NOT_INSTALLED = 1002; +const VSIX_USER_CANCELED = 2005; + async function installVSExtension() { const vsixInstaller = getVsixInstallerPath(); - await installVsix("@azure-tools/adl-vs", (vsix) => { - run(vsixInstaller, [vsix]); + const versionMap = new Map([ + [ + "Microsoft.Adl.VS2019.vsix", + { + friendlyVersion: "2019", + versionRange: "[16.0, 17.0)", + installed: false, + }, + ], + [ + "Microsoft.Adl.VS2022.vsix", + { + friendlyVersion: "2022", + versionRange: "[17.0, 18.0)", + installed: false, + }, + ], + ]); + + let vsFound = false; + for (const entry of versionMap.values()) { + if (isVSInstalled(entry.versionRange)) { + vsFound = entry.installed = true; + } + } + + if (!vsFound) { + console.error("error: No compatible version of Visual Studio found."); + process.exit(1); + } + + await installVsix("@azure-tools/adl-vs", (vsixPaths) => { + for (const vsix of vsixPaths) { + const vsixFilename = basename(vsix); + const entry = versionMap.get(vsixFilename); + compilerAssert(entry, "Unexpected vsix filename:" + vsix); + if (entry.installed) { + console.log(`Installing extension for Visual Studio ${entry?.friendlyVersion}...`); + run(vsixInstaller, [vsix], { + allowedExitCodes: [VSIX_ALREADY_INSTALLED, VSIX_USER_CANCELED], + }); + } + } }); } async function uninstallVSExtension() { const vsixInstaller = getVsixInstallerPath(); - run(vsixInstaller, ["/uninstall:88b9492f-c019-492c-8aeb-f325a7e4cf23"]); + run(vsixInstaller, ["/uninstall:88b9492f-c019-492c-8aeb-f325a7e4cf23"], { + allowedExitCodes: [VSIX_NOT_INSTALLED, VSIX_USER_CANCELED], + }); } /** @@ -274,8 +348,10 @@ async function printInfo() { // ENOENT checking and handles spaces poorly in some cases. const isCmdOnWindows = ["code", "code-insiders", "npm"]; -interface RunOptions extends SpawnSyncOptions { +interface RunOptions extends Partial { extraEnv?: NodeJS.ProcessEnv; + allowNotFound?: boolean; + allowedExitCodes?: number[]; } function run(command: string, commandArgs: string[], options?: RunOptions) { @@ -293,18 +369,24 @@ function run(command: string, commandArgs: string[], options?: RunOptions) { }; } - const baseCommandName = path.basename(command); + const baseCommandName = basename(command); if (process.platform === "win32" && isCmdOnWindows.includes(command)) { command += ".cmd"; } - const proc = spawnSync(command, commandArgs, { + const finalOptions: SpawnSyncOptionsWithStringEncoding = { + encoding: "utf-8", stdio: "inherit", ...(options ?? {}), - }); + }; + + const proc = spawnSync(command, commandArgs, finalOptions); + if (args.debug) { + console.log(proc); + } if (proc.error) { - if ((proc.error as any).code === "ENOENT") { + if ((proc.error as any).code === "ENOENT" && !options?.allowNotFound) { console.error(`error: Command '${baseCommandName}' not found.`); if (args.debug) { console.log(proc.error.stack); @@ -315,7 +397,7 @@ function run(command: string, commandArgs: string[], options?: RunOptions) { } } - if (proc.status !== 0) { + if (proc.status !== 0 && !options?.allowedExitCodes?.includes(proc.status ?? 0)) { console.error( `error: Command '${baseCommandName} ${commandArgs.join(" ")}' failed with exit code ${ proc.status