From 9611433a989c7532f5b2f122aeaa7dbef13dc376 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 27 Apr 2022 16:45:15 -0500 Subject: [PATCH] Build a NuGet Package for ConPTY (#12980) This pull request introduces a packaging phase that emits Microsoft.Windows.Console.ConPTY, a nuget package that contains the pseudoconsole API as well as the requisite copies of conhost. * winconpty learned to load a version of OpenConsole.exe specific to the processor architecture on its hosting machine * the package, as well as its contents, is signed properly and is nearly ready for distribution via nuget.org * the API in conpty-static.h has been adjusted to expose CreatePseudoConsoleAsUser and stamp out the correct DLL import/export annotations. * getting .NET to play right was somewhat challenging, but I tested this against .NET 6.0 and it seemed to work properly; it shipped conpty.dll in the right places, and it shipped OpenConsole.exe next to the published application. In the future, we could provide an interop assembly for C# consumers; that is, unfortunately, out of scope today. Closes #3577 Closes #3568 Obsoletes #1130 --- .github/actions/spelling/excludes.txt | 1 + .github/actions/spelling/expect/expect.txt | 2 + build/config/ESRPSigning_ConPTY.json | 51 +++++++ build/pipelines/release.yml | 131 +++++++++++++++++- .../TerminalConnection.vcxproj | 2 + src/host/ft_host/Host.FeatureTests.vcxproj | 2 + src/inc/conpty-static.h | 28 ++-- src/winconpty/dll/winconpty.def | 13 ++ .../Microsoft.Windows.Console.ConPTY.props | 24 ++++ .../Microsoft.Windows.Console.ConPTY.targets | 20 +++ .../Microsoft.Windows.Console.ConPTY.props | 4 + .../Microsoft.Windows.Console.ConPTY.targets | 23 +++ src/winconpty/package/winconpty.nuspec | 34 +++++ src/winconpty/winconpty.cpp | 31 +++++ 14 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 build/config/ESRPSigning_ConPTY.json create mode 100644 src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.props create mode 100644 src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.targets create mode 100644 src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.props create mode 100644 src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.targets create mode 100644 src/winconpty/package/winconpty.nuspec diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index ec940e1dbfc..4b755b30e6b 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -52,6 +52,7 @@ SUMS$ \.xpm$ \.yml$ \.zip$ +^build/config/ ^consolegit2gitfilters\.json$ ^dep/ ^doc/reference/master-sequence-list.csv$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index f196d7ce493..804d81e0c8d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1116,6 +1116,7 @@ Imatch ime Imm IMouse +IMPEXP impl inbox inclusivity @@ -2018,6 +2019,7 @@ reparenting replatformed Replymessage repositorypath +Requiresx rescap Resequence RESETCONTENT diff --git a/build/config/ESRPSigning_ConPTY.json b/build/config/ESRPSigning_ConPTY.json new file mode 100644 index 00000000000..d01e9c9eabe --- /dev/null +++ b/build/config/ESRPSigning_ConPTY.json @@ -0,0 +1,51 @@ +{ + "Version": "1.0.0", + "UseMinimatch": false, + "SignBatches": [ + { + "MatchedPath": [ + "conpty.dll", + "OpenConsole.exe" + ], + "SigningInfo": { + "Operations": [ + { + "KeyCode": "CP-230012", + "OperationSetCode": "SigntoolSign", + "Parameters": [ + { + "parameterName": "OpusName", + "parameterValue": "Microsoft" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "http://www.microsoft.com" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-230012", + "OperationSetCode": "SigntoolVerify", + "Parameters": [], + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + } + } + ] +} diff --git a/build/pipelines/release.yml b/build/pipelines/release.yml index d4c65950474..c78a75978a2 100644 --- a/build/pipelines/release.yml +++ b/build/pipelines/release.yml @@ -30,6 +30,10 @@ parameters: displayName: "Build Windows Terminal VPack" type: boolean default: false + - name: buildConPTY + displayName: "Build ConPTY NuGet" + type: boolean + default: false - name: buildWPF displayName: "Build Terminal WPF Control" type: boolean @@ -75,6 +79,13 @@ variables: NoNuGetPackBetaVersion: true ${{ else }}: NuGetPackBetaVersion: preview + # The NuGet packages have to use *somebody's* DLLs. We used to force them to + # use the Win10 build outputs, but if there isn't a Win10 build we should use + # the Win11 one. + ${{ if containsValue(parameters.buildWindowsVersions, 'Win10') }}: + TerminalBestVersionForNuGetPackages: Win10 + ${{ else }}: + TerminalBestVersionForNuGetPackages: Win11 name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) resources: @@ -208,6 +219,15 @@ jobs: msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore platform: $(BuildPlatform) configuration: $(BuildConfiguration) + - ${{ if eq(parameters.buildConPTY, true) }}: + - task: VSBuild@1 + displayName: Build solution **\OpenConsole.sln for ConPTY + inputs: + solution: '**\OpenConsole.sln' + vsVersion: 16.0 + msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /p:WindowsTerminalReleaseBuild=true /t:Conhost\Host_EXE;Conhost\winconpty_DLL + platform: $(BuildPlatform) + configuration: $(BuildConfiguration) - task: PowerShell@2 displayName: Source Index PDBs inputs: @@ -252,6 +272,24 @@ jobs: inputs: PathtoPublish: $(Build.ArtifactStagingDirectory)/appx ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion) + - ${{ if eq(parameters.buildConPTY, true) }}: + - task: CopyFiles@2 + displayName: Copy ConPTY to Artifacts + inputs: + Contents: |- + $(Build.SourcesDirectory)/bin/**/conpty.dll + $(Build.SourcesDirectory)/bin/**/conpty.lib + $(Build.SourcesDirectory)/bin/**/conpty.pdb + $(Build.SourcesDirectory)/bin/**/OpenConsole.exe + $(Build.SourcesDirectory)/bin/**/OpenConsole.pdb + TargetFolder: $(Build.ArtifactStagingDirectory)/conpty + OverWrite: true + flattenFolders: true + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact (ConPTY) + inputs: + PathtoPublish: $(Build.ArtifactStagingDirectory)/conpty + ArtifactName: conpty-dll-$(BuildPlatform)-$(BuildConfiguration)-$(TerminalTargetWindowsVersion) - ${{ if eq(parameters.buildWPF, true) }}: - task: CopyFiles@2 displayName: Copy PublicTerminalCore.dll to Artifacts @@ -358,6 +396,97 @@ jobs: PathtoPublish: $(System.ArtifactsDirectory) ArtifactName: appxbundle-signed-$(TerminalTargetWindowsVersion) +- ${{ if eq(parameters.buildConPTY, true) }}: + - job: PackageAndSignConPTY + strategy: + matrix: + ${{ each config in parameters.buildConfigurations }}: + ${{ config }}: + BuildConfiguration: ${{ config }} + displayName: Create NuGet Package (ConPTY) + dependsOn: Build + steps: + - checkout: self + clean: true + fetchDepth: 1 + submodules: true + persistCredentials: True + - task: PkgESSetupBuild@12 + displayName: Package ES - Setup Build + inputs: + disableOutputRedirect: true + - ${{ each platform in parameters.buildPlatforms }}: + - task: DownloadBuildArtifacts@0 + displayName: Download ${{ platform }} ConPTY binaries + inputs: + artifactName: conpty-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages) + downloadPath: bin\${{ platform }}\$(BuildConfiguration)\ + extractTars: false + - task: PowerShell@2 + displayName: Move downloaded artifacts around + inputs: + targetType: inline + # Find all artifact files and move them up a directory. Ugh. + script: |- + Get-ChildItem bin -Recurse -Directory -Filter conpty-dll-* | % { + $_ | Get-ChildItem -Recurse -File | % { + Move-Item -Verbose $_.FullName $_.Directory.Parent.FullName + } + } + Move-Item bin\x86 bin\Win32 + + - task: EsrpCodeSigning@1 + displayName: Submit ConPTY libraries and OpenConsole for code signing + inputs: + ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a + FolderPath: '$(Build.SourcesDirectory)/bin' + signType: batchSigning + batchSignPolicyFile: '$(Build.SourcesDirectory)\build\config\ESRPSigning_ConPTY.json' + + - task: NuGetToolInstaller@1 + displayName: Use NuGet 5.10.0 + inputs: + versionSpec: 5.10.0 + - task: NuGetCommand@2 + displayName: NuGet pack + inputs: + command: pack + packagesToPack: $(Build.SourcesDirectory)\src\winconpty\package\winconpty.nuspec + packDestination: '$(Build.ArtifactStagingDirectory)/nupkg' + versioningScheme: byEnvVar + versionEnvVar: XES_PACKAGEVERSIONNUMBER + - task: EsrpCodeSigning@1 + displayName: Submit *.nupkg to ESRP for code signing + inputs: + ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a + FolderPath: $(Build.ArtifactStagingDirectory)/nupkg + Pattern: '*.nupkg' + UseMinimatch: true + signConfigType: inlineSignParams + inlineOperation: >- + [ + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetSign", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + }, + { + "KeyCode": "CP-401405", + "OperationCode": "NuGetVerify", + "Parameters": {}, + "ToolName": "sign", + "ToolVersion": "1.0" + } + ] + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact (nupkg) + inputs: + PathtoPublish: $(Build.ArtifactStagingDirectory)\nupkg + ArtifactName: conpty-nupkg-$(BuildConfiguration) + + - ${{ if eq(parameters.buildWPF, true) }}: - job: PackageAndSignWPF strategy: @@ -381,7 +510,7 @@ jobs: - task: DownloadBuildArtifacts@0 displayName: Download ${{ platform }} PublicTerminalCore inputs: - artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)-Win10 + artifactName: wpf-dll-${{ platform }}-$(BuildConfiguration)-$(TerminalBestVersionForNuGetPackages) itemPattern: '**/*.dll' downloadPath: bin\${{ platform }}\$(BuildConfiguration)\ extractTars: false diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 0fed7d60aa1..ce6a067c0e9 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -87,6 +87,8 @@ + + CONPTY_IMPEXP=;%(PreprocessorDefinitions) $(IntDir)..\OpenConsoleProxy;%(AdditionalIncludeDirectories) diff --git a/src/host/ft_host/Host.FeatureTests.vcxproj b/src/host/ft_host/Host.FeatureTests.vcxproj index 6f782a5404e..507dcfab0b4 100644 --- a/src/host/ft_host/Host.FeatureTests.vcxproj +++ b/src/host/ft_host/Host.FeatureTests.vcxproj @@ -55,6 +55,8 @@ + + CONPTY_IMPEXP=;%(PreprocessorDefinitions) $(ProjectDir);%(AdditionalIncludeDirectories) diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index a23d971615b..82982864dd6 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -11,28 +11,34 @@ #include +#ifndef CONPTY_IMPEXP +#define CONPTY_IMPEXP __declspec(dllimport) +#endif + +#ifndef CONPTY_EXPORT #ifdef __cplusplus -extern "C" { +#define CONPTY_EXPORT extern "C" CONPTY_IMPEXP +#else +#define CONPTY_EXPORT extern CONPTY_IMPEXP +#endif #endif #define PSEUDOCONSOLE_RESIZE_QUIRK (2u) #define PSEUDOCONSOLE_WIN32_INPUT_MODE (4u) #define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u) -HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); +CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); -HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); +CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); -HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); +CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); -HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show); +CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC); -HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent); +CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show); -VOID WINAPI ConptyClosePseudoConsole(HPCON hPC); +CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent); -HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC); +CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC); -#ifdef __cplusplus -} -#endif +CONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC); diff --git a/src/winconpty/dll/winconpty.def b/src/winconpty/dll/winconpty.def index da8a5653533..1a1952bf421 100644 --- a/src/winconpty/dll/winconpty.def +++ b/src/winconpty/dll/winconpty.def @@ -1,4 +1,17 @@ EXPORTS + ; Plain old normal aliases + ConptyCreatePseudoConsole + ConptyCreatePseudoConsoleAsUser + ConptyResizePseudoConsole + ConptyClosePseudoConsole + ConptyClearPseudoConsole + ConptyShowHidePseudoConsole + ConptyReparentPseudoConsole + ConptyPackPseudoConsole + + ; Compatibility aliases for P/Invoke; only required for compatibility + ; with the ConPTY surface exported from kernel32. + ; New consumers are expected to use the Conpty* versions CreatePseudoConsole = ConptyCreatePseudoConsole ResizePseudoConsole = ConptyResizePseudoConsole ClosePseudoConsole = ConptyClosePseudoConsole diff --git a/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.props b/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.props new file mode 100644 index 00000000000..2aa5b62015d --- /dev/null +++ b/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.props @@ -0,0 +1,24 @@ + + + + + $(PlatformTarget) + $(Platform) + x86 + + + true + true + true + + + + true + true + + + + + true + + diff --git a/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.targets b/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.targets new file mode 100644 index 00000000000..325cf10a5b8 --- /dev/null +++ b/src/winconpty/package/managed/Microsoft.Windows.Console.ConPTY.targets @@ -0,0 +1,20 @@ + + + + + x86\OpenConsole.exe + Always + Always + + + x64\OpenConsole.exe + Always + Always + + + arm64\OpenConsole.exe + Always + Always + + + diff --git a/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.props b/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.props new file mode 100644 index 00000000000..879cbb4ddde --- /dev/null +++ b/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.props @@ -0,0 +1,4 @@ + + + + diff --git a/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.targets b/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.targets new file mode 100644 index 00000000000..00a7a31065b --- /dev/null +++ b/src/winconpty/package/native/Microsoft.Windows.Console.ConPTY.targets @@ -0,0 +1,23 @@ + + + + + $(MSBuildThisFileDirectory)..\..\inc\;%(AdditionalIncludeDirectories) + + + $(MSBuildThisFileDirectory)..\..\runtimes\win10-$(ConptyNativePlatform)\lib\uap10.0\conpty.lib;%(AdditionalDependencies) + + + + + + x86\ + + + x64\ + + + arm64\ + + + diff --git a/src/winconpty/package/winconpty.nuspec b/src/winconpty/package/winconpty.nuspec new file mode 100644 index 00000000000..ba34b1511fa --- /dev/null +++ b/src/winconpty/package/winconpty.nuspec @@ -0,0 +1,34 @@ + + + + Microsoft.Windows.Console.ConPTY + 0.0.1 + Microsoft + false + MIT + https://github.com/microsoft/terminal + This package allows applications to host Windows Console sessions. It should work on all versions of Windows 10.0.17763.0 and above. + © Microsoft Corporation + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index f2db69c1652..19b91b797b3 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -48,6 +48,37 @@ static wchar_t* _ConsoleHostPath() modulePath.replace_filename(L"OpenConsole.exe"); if (!std::filesystem::exists(modulePath)) { + std::wstring_view architectureInfix{}; + USHORT unusedImageFileMachine{}, nativeMachine{}; + if (IsWow64Process2(GetCurrentProcess(), &unusedImageFileMachine, &nativeMachine)) + { + // Despite being a machine type, the values IsWow64Process2 returns are *image* types + switch (nativeMachine) + { + case IMAGE_FILE_MACHINE_AMD64: + architectureInfix = L"x64"; + break; + case IMAGE_FILE_MACHINE_ARM64: + architectureInfix = L"arm64"; + break; + case IMAGE_FILE_MACHINE_I386: + architectureInfix = L"x86"; + break; + default: + break; + } + } + if (architectureInfix.empty()) + { + // WHAT? + return _InboxConsoleHostPath(); + } + modulePath.replace_filename(architectureInfix); + modulePath.append(L"OpenConsole.exe"); + } + if (!std::filesystem::exists(modulePath)) + { + // We tried the architecture infix version and failed, fall back to conhost. return _InboxConsoleHostPath(); } auto modulePathAsString{ modulePath.wstring() };