mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-04 01:20:02 +02:00
Compare commits
2 Commits
user/muyua
...
yuleng/cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb8b016f28 | ||
|
|
dbb1f76906 |
@@ -64,6 +64,10 @@
|
||||
"FancyZonesCLI.exe",
|
||||
"FancyZonesCLI.dll",
|
||||
|
||||
"cli\\fancyzones.exe",
|
||||
"cli\\imageresizer.exe",
|
||||
"cli\\filelocksmith.exe",
|
||||
|
||||
"PowerToys.GcodePreviewHandler.dll",
|
||||
"PowerToys.GcodePreviewHandler.exe",
|
||||
"PowerToys.GcodePreviewHandlerCpp.dll",
|
||||
|
||||
@@ -454,6 +454,18 @@ jobs:
|
||||
msbuildArchitecture: x64
|
||||
maximumCpuCount: true
|
||||
|
||||
# CLI shims: publish ONE Native AOT binary and stage one exe per command name into
|
||||
# <BuildPlatform>\<BuildConfiguration>\cli so the files exist BEFORE the signing step
|
||||
# (ESRPSigning_core.json) and the installer harvest. The shared script owns the command
|
||||
# list (single source of truth) and the drift validation; it is the same code path the
|
||||
# local installer build (tools/build/build-installer.ps1) uses.
|
||||
- pwsh: |-
|
||||
& "$(Build.SourcesDirectory)\tools\build\publish-cli-shims.ps1" `
|
||||
-Platform '$(BuildPlatform)' `
|
||||
-Configuration '$(BuildConfiguration)' `
|
||||
-OutDir "$(Build.SourcesDirectory)\$(BuildPlatform)\$(BuildConfiguration)\cli"
|
||||
displayName: Publish CLI shims (Native AOT)
|
||||
|
||||
### HACK: On ARM64 builds, building an app with Windows App SDK copies the x64 WebView2 dll instead of the ARM64 one. This task makes sure the right dll is used.
|
||||
- task: CopyFiles@2
|
||||
displayName: HACK Copy core WebView2 ARM64 dll to output directory
|
||||
|
||||
@@ -1138,6 +1138,14 @@
|
||||
</Project>
|
||||
<Project Path="src/Update/PowerToys.Update.vcxproj" Id="44ce9ae1-4390-42c5-bacc-0fd6b40aa203" />
|
||||
<Project Path="tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj" Id="64a80062-4d8b-4229-8a38-dfa1d7497749" />
|
||||
<Project Path="tools/CliShim/CliShim.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="tools/CliShim.UnitTests/CliShim.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Solution>
|
||||
|
||||
|
||||
|
||||
72
installer/PowerToysSetupVNext/CliShims.wxs
Normal file
72
installer/PowerToysSetupVNext/CliShims.wxs
Normal file
@@ -0,0 +1,72 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<?include $(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<!--
|
||||
CLI shims: tiny Native AOT launchers installed into the "cli" subfolder. Each shim
|
||||
forwards to a real PowerToys CLI (one level up, or under WinUI3Apps). The cli folder
|
||||
is appended to PATH so the shims are callable by name from a terminal.
|
||||
-->
|
||||
<Fragment>
|
||||
<DirectoryRef Id="CliFolder">
|
||||
<Component Id="CliShim_fancyzones_exe" Guid="63800E2A-21B9-4A92-AD25-9C1DC7593F4F" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="CliShim_fancyzones_exe" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Source="$(var.BinDir)cli\fancyzones.exe" Id="CliShim_fancyzones.exe" Checksum="yes" />
|
||||
</Component>
|
||||
<Component Id="CliShim_imageresizer_exe" Guid="2BC274AA-FECD-4A85-BF70-06670A6B99A5" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="CliShim_imageresizer_exe" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Source="$(var.BinDir)cli\imageresizer.exe" Id="CliShim_imageresizer.exe" Checksum="yes" />
|
||||
</Component>
|
||||
<Component Id="CliShim_filelocksmith_exe" Guid="2DD830DD-78D4-46EE-B23B-3EAB3D838EDF" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="CliShim_filelocksmith_exe" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Source="$(var.BinDir)cli\filelocksmith.exe" Id="CliShim_filelocksmith.exe" Checksum="yes" />
|
||||
</Component>
|
||||
|
||||
<!--
|
||||
Append the cli folder to PATH (per-user vs per-machine, mirroring Core.wxs).
|
||||
MSI broadcasts WM_SETTINGCHANGE on commit, so Explorer and newly-opened terminals
|
||||
pick up the new PATH; terminals already open at install time must be reopened before
|
||||
the short command names (fancyzones / imageresizer / filelocksmith) resolve.
|
||||
-->
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<Component Id="cli_env_path_user" Guid="87AA86E8-81E8-44CB-9519-FC70CF7BEA56" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="cli_env_path_user" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<Environment Id="AddCliToUserPath" Name="PATH" Action="set" Part="last" System="no" Value="[CliFolder]" />
|
||||
</Component>
|
||||
<?else?>
|
||||
<Component Id="cli_env_path_machine" Guid="739F3916-2247-4F1B-8BBD-0949A5B870F7" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="cli_env_path_machine" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<Environment Id="AddCliToMachinePath" Name="PATH" Action="set" Part="last" System="yes" Value="[CliFolder]" />
|
||||
</Component>
|
||||
<?endif?>
|
||||
</DirectoryRef>
|
||||
|
||||
<ComponentGroup Id="CliShimsComponentGroup">
|
||||
<Component Id="RemoveCliFolder" Guid="831C56D8-9FEB-41B2-8A8C-BD49487479BB" Directory="CliFolder">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveCliFolder" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveFolderCliFolder" Directory="CliFolder" On="uninstall" />
|
||||
</Component>
|
||||
<ComponentRef Id="CliShim_fancyzones_exe" />
|
||||
<ComponentRef Id="CliShim_imageresizer_exe" />
|
||||
<ComponentRef Id="CliShim_filelocksmith_exe" />
|
||||
<?if $(var.PerUser) = "true" ?>
|
||||
<ComponentRef Id="cli_env_path_user" />
|
||||
<?else?>
|
||||
<ComponentRef Id="cli_env_path_machine" />
|
||||
<?endif?>
|
||||
</ComponentGroup>
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -131,6 +131,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
<Compile Include="Settings.wxs" />
|
||||
<Compile Include="ShortcutGuide.wxs" />
|
||||
<Compile Include="Tools.wxs" />
|
||||
<Compile Include="CliShims.wxs" />
|
||||
<Compile Include="MouseWithoutBorders.wxs" />
|
||||
<Compile Include="WinUI3Applications.wxs" />
|
||||
<Compile Include="MonacoSRC.wxs" />
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<ComponentGroupRef Id="DscResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
|
||||
<ComponentGroupRef Id="ToolComponentGroup" />
|
||||
<ComponentGroupRef Id="CliShimsComponentGroup" />
|
||||
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
|
||||
<ComponentGroupRef Id="WorkspacesComponentGroup" />
|
||||
<ComponentGroupRef Id="CmdPalComponentGroup" />
|
||||
@@ -299,6 +300,7 @@
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Directory Id="ToolsFolder" Name="Tools" />
|
||||
<Directory Id="CliFolder" Name="cli" />
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
|
||||
26
tools/CliShim.UnitTests/CliShim.UnitTests.csproj
Normal file
26
tools/CliShim.UnitTests/CliShim.UnitTests.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Plain net10.0: the code under test (CommandLine) uses only BCL string handling. -->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>PowerToys.CliShim.UnitTests</RootNamespace>
|
||||
<AssemblyName>PowerToys.CliShim.UnitTests</AssemblyName>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Link the parser source directly (instead of a ProjectReference to the AOT exe) so the
|
||||
tests run on the normal runtime, free of RID/AOT/Platform entanglement. -->
|
||||
<Compile Include="..\CliShim\CommandLine.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
47
tools/CliShim.UnitTests/CommandLineTests.cs
Normal file
47
tools/CliShim.UnitTests/CommandLineTests.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace PowerToys.CliShim.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class CommandLineTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
|
||||
// Normal shell launches: unquoted program name, ends at first whitespace.
|
||||
[DataRow("fancyzones arg", "arg")]
|
||||
[DataRow("fancyzones a b c", "a b c")]
|
||||
[DataRow("fancyzones", "")]
|
||||
[DataRow("filelocksmith", "")]
|
||||
|
||||
// Quoted program name (path with spaces): ends at the closing quote, no backslash-escaping.
|
||||
[DataRow(@"""C:\Program Files\PowerToys\cli\fancyzones.exe"" arg", "arg")]
|
||||
[DataRow(@"""C:\Program Files\PowerToys\cli\fancyzones.exe""", "")]
|
||||
|
||||
// The user's exact quoting in the tail is preserved verbatim (the whole point of the shim).
|
||||
[DataRow(@"""C:\cli\fancyzones.exe"" ""a b""", @"""a b""")]
|
||||
[DataRow(@"fancyzones --path ""C:\a b\c.png""", @"--path ""C:\a b\c.png""")]
|
||||
|
||||
// Tabs count as whitespace for both the argv[0] terminator and the trim.
|
||||
[DataRow("fancyzones\targ", "arg")]
|
||||
[DataRow("fancyzones \t arg", "arg")]
|
||||
|
||||
// Regression: a command line padded with leading whitespace must NOT leak the program name
|
||||
// (a non-shell parent can pass this via CreateProcessW; the OS loader never does).
|
||||
[DataRow(" fancyzones arg", "arg")]
|
||||
[DataRow(" fancyzones", "")]
|
||||
[DataRow(@" ""C:\cli\fancyzones.exe"" arg", "arg")]
|
||||
|
||||
// Degenerate inputs.
|
||||
[DataRow("", "")]
|
||||
|
||||
// Unterminated argv[0] quote: ends at end-of-string (CRT-faithful), so no arguments remain.
|
||||
[DataRow(@"""C:\Program Files\app", "")]
|
||||
public void StripArgumentZero_ReturnsForwardedTail(string commandLine, string expected)
|
||||
{
|
||||
Assert.AreEqual(expected, CommandLine.StripArgumentZero(commandLine));
|
||||
}
|
||||
}
|
||||
30
tools/CliShim/CliShim.csproj
Normal file
30
tools/CliShim/CliShim.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Shared settings (analyzers, RepoRoot, Platforms, versioning) come from the root Directory.Build.props. -->
|
||||
<!--
|
||||
No Common.SelfContained.props import: that sets SelfContained=true, which would make a
|
||||
plain (non-publish) solution build copy the whole .NET runtime (~77 MB) into bin\ for an
|
||||
artifact nothing consumes. Native AOT only needs a RuntimeIdentifier, which the publish
|
||||
step supplies via "-r win-<arch>" (and PublishAot implies self-contained on publish).
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Plain net10.0: this shim only uses BCL types (Process/Environment/Console), so it needs no Windows/WinRT projection. -->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>PowerToys.CliShim</RootNamespace>
|
||||
<AssemblyName>PowerToys.CliShim</AssemblyName>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- Native AOT: a tiny, dependency-free single exe (no .NET runtime shipped alongside). -->
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<StackTraceSupport>false</StackTraceSupport>
|
||||
<OptimizationPreference>Size</OptimizationPreference>
|
||||
<!-- Don't emit the native sidecar .pdb; it is never harvested/signed and only clutters the staging dir. -->
|
||||
<NativeDebugSymbols>false</NativeDebugSymbols>
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
64
tools/CliShim/CommandLine.cs
Normal file
64
tools/CliShim/CommandLine.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PowerToys.CliShim;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the raw process command line. Kept in its own internal type
|
||||
/// (rather than inside <see cref="Program"/>) so the parsing logic can be unit tested by
|
||||
/// linking this single source file into the test project.
|
||||
/// </summary>
|
||||
internal static class CommandLine
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the command line with its first token (argv[0]) removed, following the C
|
||||
/// runtime rule for the program name: leading whitespace is skipped first; then, when
|
||||
/// argv[0] starts with a quote it ends at the next quote (no backslash-escaping for the
|
||||
/// program name), otherwise it ends at the first whitespace. Whitespace before the first
|
||||
/// real argument is then trimmed.
|
||||
/// </summary>
|
||||
/// <param name="commandLine">The raw process command line (for example <see cref="System.Environment.CommandLine"/>).</param>
|
||||
/// <returns>The remaining arguments, verbatim.</returns>
|
||||
internal static string StripArgumentZero(string commandLine)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
// Skip leading whitespace before argv[0]. The OS loader never produces this, but a
|
||||
// non-shell parent that calls CreateProcessW with a padded lpCommandLine can, and
|
||||
// without this the unquoted scan below would stall at index 0 and leak the program
|
||||
// name into the forwarded arguments.
|
||||
while (index < commandLine.Length && (commandLine[index] == ' ' || commandLine[index] == '\t'))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < commandLine.Length && commandLine[index] == '"')
|
||||
{
|
||||
index++;
|
||||
while (index < commandLine.Length && commandLine[index] != '"')
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < commandLine.Length)
|
||||
{
|
||||
index++; // Consume the closing quote.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (index < commandLine.Length && commandLine[index] != ' ' && commandLine[index] != '\t')
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
while (index < commandLine.Length && (commandLine[index] == ' ' || commandLine[index] == '\t'))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
return commandLine[index..];
|
||||
}
|
||||
}
|
||||
98
tools/CliShim/Program.cs
Normal file
98
tools/CliShim/Program.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace PowerToys.CliShim;
|
||||
|
||||
/// <summary>
|
||||
/// A tiny multi-call launcher ("shim"). One Native AOT binary is copied to several
|
||||
/// command names (for example fancyzones.exe). At run time it resolves its own file
|
||||
/// name to the matching PowerToys CLI, forwards the user's arguments verbatim, shares
|
||||
/// the console, and returns the launched process's exit code unchanged.
|
||||
/// </summary>
|
||||
internal static class Program
|
||||
{
|
||||
// The exit code cmd.exe returns for "command not found". Used only when the shim is
|
||||
// invoked under a name that is not in the Targets table (for example a renamed copy).
|
||||
private const int ExitCommandNotMapped = 9009;
|
||||
|
||||
// A distinct code for "the command is known, but its target could not be launched"
|
||||
// (missing target, Process.Start failure). Keeping this separate from 9009 lets a
|
||||
// calling script tell a typo'd/unmapped command from a broken install.
|
||||
private const int ExitLaunchFailed = 1;
|
||||
|
||||
// Command name (the shim's own file name without extension) -> target CLI path,
|
||||
// relative to the shim's own directory. The shims are installed in "<install>\cli\",
|
||||
// so the targets sit one level up (install root) or under the WinUI3Apps subfolder.
|
||||
//
|
||||
// This dictionary is the single source of truth for the command names: tools/build/
|
||||
// publish-cli-shims.ps1 parses the keys below to decide which exe copies to stage, and
|
||||
// validates that CliShims.wxs and ESRPSigning_core.json reference exactly the same set.
|
||||
private static readonly Dictionary<string, string> Targets = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["fancyzones"] = @"..\FancyZonesCLI.exe",
|
||||
["imageresizer"] = @"..\WinUI3Apps\PowerToys.ImageResizerCLI.exe",
|
||||
["filelocksmith"] = @"..\FileLocksmithCLI.exe",
|
||||
};
|
||||
|
||||
private static int Main()
|
||||
{
|
||||
// Stay alive on Ctrl+C / Ctrl+Break so we can still capture the child's exit
|
||||
// code; the child shares the console group and receives the signal directly.
|
||||
Console.CancelKeyPress += static (_, e) => e.Cancel = true;
|
||||
|
||||
string shimPath = Environment.ProcessPath ?? string.Empty;
|
||||
string shimDirectory = Path.GetDirectoryName(shimPath) ?? Directory.GetCurrentDirectory();
|
||||
string commandName = Path.GetFileNameWithoutExtension(shimPath);
|
||||
|
||||
if (!Targets.TryGetValue(commandName, out string? relativeTarget))
|
||||
{
|
||||
Console.Error.WriteLine($"cli-shim: no PowerToys CLI is mapped to the command '{commandName}'.");
|
||||
Console.Error.WriteLine($"cli-shim: known commands: {string.Join(", ", Targets.Keys)}.");
|
||||
return ExitCommandNotMapped;
|
||||
}
|
||||
|
||||
string targetPath = Path.GetFullPath(Path.Combine(shimDirectory, relativeTarget));
|
||||
|
||||
if (!File.Exists(targetPath))
|
||||
{
|
||||
Console.Error.WriteLine($"cli-shim: target not found: \"{targetPath}\".");
|
||||
return ExitLaunchFailed;
|
||||
}
|
||||
|
||||
// Forward the user's arguments byte-for-byte. Environment.CommandLine is the raw
|
||||
// command line (the managed equivalent of GetCommandLineW); stripping argv[0]
|
||||
// preserves the user's exact quoting, which re-quoting parsed args would corrupt.
|
||||
string forwardedArguments = CommandLine.StripArgumentZero(Environment.CommandLine);
|
||||
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
FileName = targetPath,
|
||||
Arguments = forwardedArguments,
|
||||
UseShellExecute = false, // Inherit stdin/stdout/stderr and stay in this console.
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using Process? child = Process.Start(startInfo);
|
||||
if (child is null)
|
||||
{
|
||||
Console.Error.WriteLine($"cli-shim: failed to start \"{targetPath}\".");
|
||||
return ExitLaunchFailed;
|
||||
}
|
||||
|
||||
child.WaitForExit();
|
||||
return child.ExitCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"cli-shim: failed to launch \"{targetPath}\": {ex.Message}");
|
||||
return ExitLaunchFailed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,6 +381,12 @@ try {
|
||||
if (-not $SkipBuild) {
|
||||
RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration
|
||||
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
|
||||
|
||||
# CLI shims: publish ONE Native AOT binary and stage one exe per command name into
|
||||
# <Platform>\<Configuration>\cli for the installer to harvest. The shared script owns
|
||||
# the single-source-of-truth command list and the drift validation; AOT linking relies
|
||||
# on the VC toolchain already set up by the installer build environment.
|
||||
& (Join-Path $repoRoot 'tools\build\publish-cli-shims.ps1') -Platform $Platform -Configuration $Configuration -OutDir (Join-Path $buildOutputPath 'cli')
|
||||
}
|
||||
|
||||
# Set NUGET_PACKAGES environment variable if not set, to help wixproj find heat.exe
|
||||
|
||||
98
tools/build/publish-cli-shims.ps1
Normal file
98
tools/build/publish-cli-shims.ps1
Normal file
@@ -0,0 +1,98 @@
|
||||
# Copyright (c) Microsoft Corporation
|
||||
# The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
# See the LICENSE file in the project root for more information.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Publishes the Native AOT CLI shim and stages one exe per command name.
|
||||
|
||||
.DESCRIPTION
|
||||
Publishes tools/CliShim as a single Native AOT binary, then copies it to one exe per
|
||||
command name into -OutDir (the installer's "cli" staging folder), and removes the source
|
||||
launcher so the folder holds exactly what the installer harvests and ESRP signs.
|
||||
|
||||
Shared by both entry points so the logic lives in one place:
|
||||
- the local installer build (tools/build/build-installer.ps1)
|
||||
- the CI build (.pipelines/v2/templates/job-build-project.yml)
|
||||
|
||||
The command names come from a single source of truth: the keys of the Targets dictionary
|
||||
in tools/CliShim/Program.cs. This script also verifies that CliShims.wxs and
|
||||
ESRPSigning_core.json reference exactly that set, failing the build on any drift (which
|
||||
would otherwise ship a shim that always exits 9009, or silently omit one).
|
||||
|
||||
.NOTES
|
||||
Native AOT linking needs the Desktop C++ workload; the .NET ILCompiler targets locate it
|
||||
automatically via findvcvarsall.bat (no separate vcvars activation required).
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string] $Platform, # x64 | ARM64 (any case)
|
||||
[Parameter(Mandatory = $true)][string] $Configuration, # Debug | Release
|
||||
[Parameter(Mandatory = $true)][string] $OutDir # staging "cli" folder
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# tools/build/<this>.ps1 -> repo root is two levels up.
|
||||
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
||||
$shimProj = Join-Path $repoRoot 'tools\CliShim\CliShim.csproj'
|
||||
$programCs = Join-Path $repoRoot 'tools\CliShim\Program.cs'
|
||||
$shimsWxs = Join-Path $repoRoot 'installer\PowerToysSetupVNext\CliShims.wxs'
|
||||
$esrpJson = Join-Path $repoRoot '.pipelines\ESRPSigning_core.json'
|
||||
|
||||
function Get-CapturedValues([string] $Path, [string] $Pattern) {
|
||||
if (-not (Test-Path $Path)) { throw "publish-cli-shims: file not found: $Path" }
|
||||
$found = Select-String -Path $Path -Pattern $Pattern -AllMatches
|
||||
if (-not $found) { return @() }
|
||||
@($found.Matches | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique)
|
||||
}
|
||||
|
||||
# Single source of truth: the command names are the keys of the Targets dictionary in Program.cs.
|
||||
$commandNames = Get-CapturedValues $programCs '\["([^"]+)"\]\s*=\s*@"'
|
||||
if (-not $commandNames) { throw "publish-cli-shims: could not parse any command names from $programCs" }
|
||||
|
||||
# Drift guard: the installer harvest and the signing list must reference exactly these names.
|
||||
$consumers = @(
|
||||
@{ Name = 'CliShims.wxs'; Actual = (Get-CapturedValues $shimsWxs 'cli\\([^"\\]+)\.exe') }
|
||||
@{ Name = 'ESRPSigning_core.json'; Actual = (Get-CapturedValues $esrpJson 'cli\\\\([^"\\]+)\.exe') }
|
||||
)
|
||||
foreach ($consumer in $consumers) {
|
||||
if (Compare-Object -ReferenceObject $commandNames -DifferenceObject $consumer.Actual) {
|
||||
throw ("publish-cli-shims: command set mismatch. Program.cs has [$($commandNames -join ', ')] " +
|
||||
"but $($consumer.Name) has [$($consumer.Actual -join ', ')]. " +
|
||||
'Add/rename the shim in Program.cs, CliShims.wxs, and ESRPSigning_core.json together.')
|
||||
}
|
||||
}
|
||||
|
||||
# Native AOT linking shells out to vswhere.exe to locate the MSVC toolchain (which then sets
|
||||
# LIB/INCLUDE via vcvars). In a plain, non-developer shell -- e.g. the CI 'pwsh' step, which
|
||||
# unlike the local installer build does not activate a VS Dev environment -- vswhere may not be
|
||||
# on PATH, and the link step fails with "'vswhere.exe' is not recognized". Add the fixed VS
|
||||
# Installer location so the publish works without requiring callers to pre-activate vcvars.
|
||||
if (-not (Get-Command 'vswhere.exe' -ErrorAction SilentlyContinue)) {
|
||||
$vsInstaller = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer'
|
||||
if (Test-Path (Join-Path $vsInstaller 'vswhere.exe')) {
|
||||
$env:PATH = "$vsInstaller;$env:PATH"
|
||||
Write-Host "[CLI-SHIM] Added VS Installer to PATH so AOT linking can find vswhere.exe."
|
||||
}
|
||||
}
|
||||
|
||||
# RID from platform (project config is x64;ARM64).
|
||||
$cliPlatform = if ($Platform -ieq 'arm64') { 'ARM64' } else { 'x64' }
|
||||
$rid = "win-$($cliPlatform.ToLower())"
|
||||
|
||||
Write-Host "[CLI-SHIM] Publishing Native AOT shim ($rid) -> $OutDir"
|
||||
dotnet publish $shimProj -c $Configuration -r $rid -p:Platform=$cliPlatform -o $OutDir --nologo
|
||||
if ($LASTEXITCODE -ne 0) { throw "publish-cli-shims: dotnet publish failed with exit code $LASTEXITCODE" }
|
||||
|
||||
# Copy the single published binary to one exe per command name, then drop the source launcher
|
||||
# (and any sidecar pdb) so the staging dir holds exactly what the installer harvests and signs.
|
||||
$srcExe = Join-Path $OutDir 'PowerToys.CliShim.exe'
|
||||
if (-not (Test-Path $srcExe)) { throw "publish-cli-shims: expected '$srcExe' was not produced by dotnet publish." }
|
||||
foreach ($name in $commandNames) {
|
||||
Copy-Item $srcExe (Join-Path $OutDir "$name.exe") -Force
|
||||
}
|
||||
Remove-Item $srcExe -Force
|
||||
Remove-Item (Join-Path $OutDir 'PowerToys.CliShim.pdb') -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "[CLI-SHIM] Staged: $($commandNames -join ', ')"
|
||||
Reference in New Issue
Block a user