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
3 changes: 3 additions & 0 deletions PowerShell.Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@

</PropertyGroup>

<!-- Remove existing generated files by source generators because new source code will soon be generated when the build starts -->
<RemoveDir Directories="gen\SourceGenerated" Condition="Exists('gen\SourceGenerated')" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path gen\SourceGenerated is duplicated in here and System.Management.Automation.csproj. I tried creating a property holding the path in the PropertyGroup right above, and use it in RemoveDir and CompilerGeneratedFilesOutputPath. However, it works in RemoveDir, but not in CompilerGeneratedFilesOutputPath, and I cannot figure out why. I'd love if someone can make it work.


<!-- Output For Debugging
<WriteLinesToFile File="targetfile1.txt"
Lines="ReleaseTag=$(ReleaseTag);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Globalization;
using Microsoft.CodeAnalysis;

namespace SMA
{
/// <summary>
/// Source Code Generator to create partial PSVersionInfo class.
/// </summary>
[Generator]
public class PSVersionInfoGenerator : ISourceGenerator
{
/// <summary>
/// Generate output PSVersionInfo.g.cs file.
/// This allows to directly get ProductVersion and others without reflection.
/// </summary>
/// <param name="context">Generator execution context.</param>
public void Execute(GeneratorExecutionContext context)
{
var result = CreatePSVersionInfoPartialClass(context);

// We must use specific file name suffix (*.g.cs,*.g, *.i.cs, *.generated.cs, *.designer.cs)
// so that Roslyn analyzers skip the file.
context.AddSource("PSVersionInfo.g.cs", result);
}

/// <summary>
/// Not used.
/// </summary>
/// <param name="context">Generator initialization context.</param>
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this one.
}

/// <summary>
/// Generate source code for the partial PSVersionInfo class.
/// </summary>
/// <param name="context">Generator execution context.</param>
/// <returns>A string with partial PSVersionInfo class.</returns>
private static string CreatePSVersionInfoPartialClass(GeneratorExecutionContext context)
{
// We must put "<auto-generated" on first line so that Roslyng analyzers skip the file.
const string SourceTemplate = @"// <auto-generated>
// This file is auto-generated by PSVersionInfoGenerator.
// </auto-generated>

namespace System.Management.Automation
{{
public static partial class PSVersionInfo
{{
// Defined in 'PowerShell.Common.props' as 'ProductVersion'
// Example:
// - when built from a commit: ProductVersion = '7.3.0-preview.8 Commits: 29 SHA: 52c6b...'
// - when built from a preview release tag: ProductVersion = '7.3.0-preview.8 SHA: f1ec9...'
// - when built from a stable release tag: ProductVersion = '7.3.0 SHA: f1ec9...'
internal const string ProductVersion = ""{0}"";

// The git commit id that the build is based off.
// Defined in 'PowerShell.Common.props' as 'PowerShellVersion' or 'ReleaseTag',
// depending on whether the '-ReleaseTag' is specified when building.
// Example:
// - when built from a commit: GitCommitId = '7.3.0-preview.8-29-g52c6b...'
// - when built from a preview release tag: GitCommitId = '7.3.0-preview.8'
// - when built from a stable release tag: GitCommitId = '7.3.0'
internal const string GitCommitId = ""{1}"";

// The PowerShell version components.
// The version string is defined in 'PowerShell.Common.props' as 'PSCoreBuildVersion',
// but we break it into components to save the overhead of parsing at runtime.
// Example:
// - '7.3.0-preview.8' for preview release or private build
// - '7.3.0' for stable release
private const int Version_Major = {2};
private const int Version_Minor = {3};
private const int Version_Patch = {4};
private const string Version_Label = ""{5}"";
}}
}}";

context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.ProductVersion", out var productVersion);
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.PSCoreBuildVersion", out var mainVersion);
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.PowerShellVersion", out var gitDescribe);
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.ReleaseTag", out var releaseTag);

string gitCommitId = string.IsNullOrEmpty(releaseTag) ? gitDescribe : releaseTag;
if (gitCommitId.StartsWith("v"))
{
gitCommitId = gitCommitId.Substring(1);
}

var result = ParsePSVersion(mainVersion);

return string.Format(
CultureInfo.InvariantCulture,
SourceTemplate,
productVersion,
gitCommitId,
result.major,
result.minor,
result.patch,
result.preReleaseLabel);
Comment thread
daxian-dbw marked this conversation as resolved.
}

private static (int major, int minor, int patch, string preReleaseLabel) ParsePSVersion(string mainVersion)
{
// We only handle the pre-defined PSVersion format here, e.g. 7.x.x or 7.x.x-preview.x
int dashIndex = mainVersion.IndexOf('-');
bool hasLabel = dashIndex != -1;
string preReleaseLabel = hasLabel ? mainVersion.Substring(dashIndex + 1) : string.Empty;

if (hasLabel)
{
mainVersion = mainVersion.Substring(0, dashIndex);
}

int majorEnd = mainVersion.IndexOf('.');
int minorEnd = mainVersion.LastIndexOf('.');

int major = int.Parse(mainVersion.Substring(0, majorEnd), NumberStyles.Integer, CultureInfo.InvariantCulture);
int minor = int.Parse(mainVersion.Substring(majorEnd + 1, minorEnd - majorEnd - 1), NumberStyles.Integer, CultureInfo.InvariantCulture);
int patch = int.Parse(mainVersion.Substring(minorEnd + 1), NumberStyles.Integer, CultureInfo.InvariantCulture);

return (major, minor, patch, preReleaseLabel);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<Description>Generate code for SMA using source generator</Description>
<AssemblyName>SMA.Generator</AssemblyName>
</PropertyGroup>

<PropertyGroup>
<!-- source generator project needs to target 'netstandard2.0' -->
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<Import Project="..\..\PowerShell.Common.props" />
<PropertyGroup>
<Description>PowerShell's System.Management.Automation project</Description>
<NoWarn>$(NoWarn);CS1570;CS1734;CA1416</NoWarn>
<AssemblyName>System.Management.Automation</AssemblyName>
</PropertyGroup>

<PropertyGroup>
<!-- we persist source generator files under 'gen' folder so that they are visible to IDEs -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>gen\SourceGenerated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generate the file, so VSCode and Visual Studio can always resolve the generated members.
Those files will be removed when building again, by RemoveDir in PowerShell.Common.props.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a commit a day before (but you reverted now) and removed this old workaround because all works well in VS Code. We have no need to emit generated files.
Perhaps you need re-configure your local environment.
I use VS Code 1.72 and C# extension 1.25
image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't see my reply to your comment? It's here: #15603 (comment), and quoted below:

It doesn't work in the following scenarios, for both VSCode and Visual Studio, which are quite common in development:

  1. Run Start-PSBuild first, then open System.Management.Automation in VSCode
  2. Open System.Management.Automation in VSCode to use an existing build, like from yesterday.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, too many comments already....


<ItemGroup>
<!-- these properties are declared in 'PowerShell.Common.props' and used by source generator -->
<CompilerVisibleProperty Include="ProductVersion" />
<CompilerVisibleProperty Include="PSCoreBuildVersion" />
<CompilerVisibleProperty Include="PowerShellVersion" />
<CompilerVisibleProperty Include="ReleaseTag" />

<ProjectReference Include="SourceGenerators\PSVersionInfoGenerator\PSVersionInfoGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup Condition=" '$(IsWindows)' == 'true' ">
<ProjectReference Include="..\Microsoft.PowerShell.CoreCLR.Eventing\Microsoft.PowerShell.CoreCLR.Eventing.csproj" />
</ItemGroup>
Expand Down Expand Up @@ -38,6 +56,9 @@
</PropertyGroup>

<ItemGroup>
<!-- exclude code of source generators from compilation and IDEs (e.g. vscode and visual studio) -->
<Compile Remove="SourceGenerators\**\*.cs" />

<Compile Remove="cimSupport\cmdletization\xml\cmdlets-over-objects.objectModel.autogen.cs" />
<Compile Remove="cimSupport\cmdletization\xml\cmdlets-over-objects.xmlSerializer.autogen.cs" />
<Compile Remove="engine\TransactedString.cs" />
Expand Down
58 changes: 17 additions & 41 deletions src/System.Management.Automation/engine/PSVersionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

Expand All @@ -27,7 +26,7 @@ namespace System.Management.Automation
/// The above statement retrieves the PowerShell edition.
/// </para>
/// </summary>
public static class PSVersionInfo
public static partial class PSVersionInfo
{
internal const string PSVersionTableName = "PSVersionTable";
internal const string PSRemotingProtocolVersionName = "PSRemotingProtocolVersion";
Expand All @@ -42,6 +41,18 @@ public static class PSVersionInfo

private static readonly PSVersionHashTable s_psVersionTable;

/*
Comment thread
daxian-dbw marked this conversation as resolved.
Outdated
The following constants are generated by the source generator 'PSVersionInfoGenerator':

internal const string ProductVersion;
internal const string GitCommitId;

private const int Version_Major
private const int Version_Minor;
private const int Version_Patch;
private const string Version_Label;
Comment thread
daxian-dbw marked this conversation as resolved.
*/

/// <summary>
/// A constant to track current PowerShell Version.
/// </summary>
Expand Down Expand Up @@ -78,39 +89,14 @@ static PSVersionInfo()
{
s_psVersionTable = new PSVersionHashTable(StringComparer.OrdinalIgnoreCase);

Assembly currentAssembly = typeof(PSVersionInfo).Assembly;
ProductVersion = currentAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;

// Get 'GitCommitId' and 'PSVersion' from the 'productVersion' assembly attribute.
//
// The strings can be one of the following format examples:
// when powershell is built from a commit:
// productVersion = '6.0.0-beta.7 Commits: 29 SHA: 52c6b...' convert to GitCommitId = 'v6.0.0-beta.7-29-g52c6b...'
// PSVersion = '6.0.0-beta.7'
// when powershell is built from a release tag:
// productVersion = '6.0.0-beta.7 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0-beta.7'
// PSVersion = '6.0.0-beta.7'
// when powershell is built from a release tag for RTM:
// productVersion = '6.0.0 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0'
// PSVersion = '6.0.0'
string rawGitCommitId;
string mainVersion = ProductVersion.Substring(0, ProductVersion.IndexOf(' '));

if (ProductVersion.Contains(" Commits: "))
{
rawGitCommitId = ProductVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g");
}
else
{
rawGitCommitId = mainVersion;
}

s_psSemVersion = new SemanticVersion(mainVersion);
s_psSemVersion = Version_Label == string.Empty
? new SemanticVersion(Version_Major, Version_Minor, Version_Patch)
: new SemanticVersion(Version_Major, Version_Minor, Version_Patch, Version_Label, buildLabel: null);
s_psVersion = (Version)s_psSemVersion;

s_psVersionTable[PSVersionName] = s_psSemVersion;
s_psVersionTable[PSEditionName] = PSEditionValue;
s_psVersionTable[PSGitCommitIdName] = rawGitCommitId;
s_psVersionTable[PSGitCommitIdName] = GitCommitId;
s_psVersionTable[PSCompatibleVersionsName] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version, s_psV61Version, s_psV62Version, s_psV7Version, s_psV71Version, s_psV72Version, s_psVersion };
s_psVersionTable[SerializationVersionName] = new Version(InternalSerializer.DefaultVersion);
s_psVersionTable[PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion;
Expand Down Expand Up @@ -183,16 +169,6 @@ public static Version PSVersion
}
}

internal static string ProductVersion { get; }

internal static string GitCommitId
{
get
{
return (string)s_psVersionTable[PSGitCommitIdName];
}
}

/// <summary>
/// Gets the edition of PowerShell.
/// </summary>
Expand Down