Skip to content

Commit e9cf142

Browse files
Include file based program sources (#81284)
See dotnet/dotnet#3244 (comment). This should unblock our code flow to VMR. --------- Co-authored-by: Rikki Gibson <[email protected]>
1 parent f8c2dd4 commit e9cf142

23 files changed

+2121
-9
lines changed

azure-pipelines.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,13 @@ stages:
495495
displayName: Validate Generated Syntax Files
496496
condition: or(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['compilerChange'], 'true'))
497497

498+
- task: DotNetCoreCLI@2
499+
displayName: Verify Synchronized Sources
500+
inputs:
501+
command: 'custom'
502+
custom: 'run'
503+
arguments: '--file $(Build.SourcesDirectory)/eng/ensure-sources-synced.cs'
504+
498505
- template: eng/pipelines/publish-logs.yml
499506
parameters:
500507
jobName: Correctness_Build_Artifacts

eng/Version.Details.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,6 @@
113113
<Uri>https://github.com/dotnet/runtime</Uri>
114114
<Sha>9d5a6a9aa463d6d10b0b0ba6d5982cc82f363dc3</Sha>
115115
</Dependency>
116-
<Dependency Name="Microsoft.DotNet.FileBasedPrograms" Version="10.0.200-preview.0.25556.104">
117-
<Uri>https://github.com/dotnet/dotnet</Uri>
118-
<Sha>cc979c006a27c251782bee45e8887213c24bf806</Sha>
119-
</Dependency>
120116
</ProductDependencies>
121117
<ToolsetDependencies>
122118
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="11.0.0-beta.25562.6">
@@ -143,6 +139,10 @@
143139
<Uri>https://github.com/dotnet/arcade-services</Uri>
144140
<Sha>cc62b9386f210bc9e9498ee2fc55f3005c90db30</Sha>
145141
</Dependency>
142+
<Dependency Name="Microsoft.DotNet.FileBasedPrograms" Version="10.0.200-preview.0.25556.104">
143+
<Uri>https://github.com/dotnet/dotnet</Uri>
144+
<Sha>cc979c006a27c251782bee45e8887213c24bf806</Sha>
145+
</Dependency>
146146
<Dependency Name="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.0-preview.25375.1">
147147
<Uri>https://github.com/dotnet/roslyn-analyzers</Uri>
148148
<Sha>dd67b33c8b4164fa2c2c1dd13b616aea3948f7da</Sha>

eng/ensure-sources-synced.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env dotnet
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for more information.
5+
6+
using System.Xml.Linq;
7+
8+
// Verifies the shared source files under `src/Features/CSharp/Portable/SyncedSource/FileBasedPrograms`
9+
// exactly match the copies shipped in the `Microsoft.DotNet.FileBasedPrograms` NuGet package
10+
// If any file is missing or differs, the local file is regenerated from the package content
11+
// and the script fails listing the changes.
12+
//
13+
// We do this instead of including the source package directly as `PackageReference`
14+
// because that would not work in source build (which requires roslyn to build before sdk).
15+
16+
var root = Path.Join(AppContext.GetData("EntryPointFileDirectoryPath") as string, "..");
17+
if (!Directory.Exists(root)) throw new InvalidOperationException($"Could not locate repo root: {root}");
18+
19+
var versionDetailsProps = Path.Combine(root, "eng", "Version.Details.props");
20+
if (!File.Exists(versionDetailsProps)) throw new InvalidOperationException($"'{versionDetailsProps}' not found.");
21+
22+
var packageVersion = GetPackageVersion(versionDetailsProps, "MicrosoftDotNetFileBasedProgramsPackageVersion");
23+
24+
var globalPackagesFolder = GetGlobalPackagesFolder();
25+
if (!Directory.Exists(globalPackagesFolder)) throw new InvalidOperationException($"Global packages folder not found: {globalPackagesFolder}");
26+
27+
var packageRoot = Path.Combine(globalPackagesFolder, "microsoft.dotnet.filebasedprograms", packageVersion);
28+
if (!Directory.Exists(packageRoot)) throw new InvalidOperationException($"Package folder not found: {packageRoot}");
29+
30+
var contentFilesDir1 = Path.Combine(packageRoot, "contentFiles", "cs", "any");
31+
if (!Directory.Exists(contentFilesDir1)) throw new InvalidOperationException($"contentFiles directory not found: {contentFilesDir1}");
32+
33+
var contentFilesDir2 = Path.Combine(packageRoot, "contentFiles", "cs", "netstandard2.0");
34+
if (!Directory.Exists(contentFilesDir2)) throw new InvalidOperationException($"contentFiles directory not found: {contentFilesDir2}");
35+
36+
var localSourceDir = Path.Combine(root, "src", "Features", "CSharp", "Portable", "SyncedSource", "FileBasedPrograms");
37+
if (!Directory.Exists(Path.GetDirectoryName(localSourceDir))) throw new InvalidOperationException($"Local source directory not found: {localSourceDir}");
38+
39+
var extensions = new[] { ".cs", ".resx", ".editorconfig" };
40+
41+
var packageFiles = Directory.GetFiles(contentFilesDir1, "*", SearchOption.TopDirectoryOnly)
42+
.Concat(Directory.GetFiles(contentFilesDir2, "*", SearchOption.TopDirectoryOnly))
43+
.Where(f => extensions.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
44+
.ToList();
45+
if (packageFiles.Count == 0) throw new InvalidOperationException("No package files found.");
46+
47+
var updateSnapshots = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI"));
48+
49+
if (updateSnapshots) Directory.CreateDirectory(localSourceDir);
50+
51+
var mismatches = new List<string>();
52+
foreach (var pkgFile in packageFiles)
53+
{
54+
var fileName = Path.GetFileName(pkgFile);
55+
var localFile = Path.Combine(localSourceDir, fileName);
56+
var pkgContent = File.ReadAllText(pkgFile);
57+
58+
if (!File.Exists(localFile))
59+
{
60+
// Create missing file from package content.
61+
if (updateSnapshots) File.WriteAllText(localFile, pkgContent);
62+
mismatches.Add($"Added missing file: {fileName}");
63+
continue;
64+
}
65+
66+
var localContent = File.ReadAllText(localFile);
67+
if (!string.Equals(localContent.ReplaceLineEndings(), pkgContent.ReplaceLineEndings(), StringComparison.Ordinal))
68+
{
69+
// Regenerate local file to match package.
70+
if (updateSnapshots) File.WriteAllText(localFile, pkgContent);
71+
mismatches.Add($"Updated file: {fileName}");
72+
}
73+
}
74+
75+
// If there are extra local files that are expected to mirror package files, report them but do not delete.
76+
var localMirrorFiles = Directory.GetFiles(localSourceDir, "*", SearchOption.TopDirectoryOnly)
77+
.Where(f => extensions.Any(ext => f.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
78+
.Select(Path.GetFileName)
79+
.ToHashSet(StringComparer.OrdinalIgnoreCase);
80+
foreach (var pkgName in packageFiles.Select(Path.GetFileName))
81+
localMirrorFiles.Remove(pkgName);
82+
if (localMirrorFiles.Count > 0)
83+
{
84+
mismatches.Add("Extra local files (not in package): " + string.Join(", ", localMirrorFiles));
85+
}
86+
87+
if (mismatches.Count > 0)
88+
{
89+
var action = updateSnapshots ? "Regenerated" : "Not regenerated in CI";
90+
throw new InvalidOperationException($"Shared source for FileBasedPrograms is out of sync with package. {action}. Changes:\n" + string.Join("\n", mismatches));
91+
}
92+
93+
Console.WriteLine("OK");
94+
95+
static string GetPackageVersion(string xmlFilePath, string propertyName)
96+
{
97+
var doc = XDocument.Load(xmlFilePath);
98+
// Look for <{propertyName}>{version}</{propertyName}>
99+
var packageVersionElement = doc.Descendants().FirstOrDefault(e =>
100+
string.Equals(e.Name.LocalName, propertyName, StringComparison.OrdinalIgnoreCase))
101+
?? throw new InvalidOperationException($"'{propertyName}' not found in '{xmlFilePath}'");
102+
return packageVersionElement.Value;
103+
}
104+
105+
static string GetGlobalPackagesFolder()
106+
{
107+
// Respect NUGET_PACKAGES if set; otherwise default location under user profile.
108+
var envOverride = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
109+
if (!string.IsNullOrWhiteSpace(envOverride))
110+
return envOverride!;
111+
112+
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
113+
if (string.IsNullOrWhiteSpace(userProfile))
114+
throw new InvalidOperationException("Cannot determine user profile path for global packages folder.");
115+
return Path.Combine(userProfile, ".nuget", "packages");
116+
}

src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,24 @@
6060
</ItemGroup>
6161
<ItemGroup>
6262
<EmbeddedResource Update="CSharpFeaturesResources.resx" GenerateSource="true" />
63+
<EmbeddedResource
64+
Update="SyncedSource\FileBasedPrograms\FileBasedProgramsResources.resx"
65+
GenerateSource="true"
66+
Namespace="Microsoft.DotNet.FileBasedPrograms" />
6367
</ItemGroup>
6468
<ItemGroup>
6569
<PublicAPI Include="PublicAPI.Shipped.txt" />
6670
<PublicAPI Include="PublicAPI.Unshipped.txt" />
6771
</ItemGroup>
6872
<ItemGroup>
6973
<PackageReference Include="Humanizer.Core" PrivateAssets="compile" />
70-
<PackageReference Include="Microsoft.DotNet.FileBasedPrograms" GeneratePathProperty="true" PrivateAssets="All" />
71-
<EmbeddedResource
72-
Include="$(PkgMicrosoft_DotNet_FileBasedPrograms)\contentFiles\cs\any\FileBasedProgramsResources.resx"
73-
GenerateSource="true"
74-
Namespace="Microsoft.DotNet.FileBasedPrograms" />
74+
</ItemGroup>
75+
<ItemGroup>
76+
<!-- Used by `/eng/ensure-sources-synced.cs` to sync sources between the source package and this project's folder `SyncedSource/FileBasedPrograms`. -->
77+
<PackageDownload
78+
Include="Microsoft.DotNet.FileBasedPrograms"
79+
Version="[$(MicrosoftDotNetFileBasedProgramsPackageVersion)]"
80+
Condition="'$(DotNetBuildSourceOnly)' != 'true'" />
7581
</ItemGroup>
7682
<PropertyGroup>
7783
<DefineConstants>$(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION</DefineConstants>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Remove the line below if you want to inherit .editorconfig settings from higher directories
2+
root = true
3+
4+
# C# files
5+
[*.cs]
6+
7+
# We don't want any analyzer diagnostics to be reported for people consuming this as a source package.
8+
dotnet_analyzer_diagnostic.severity = none
9+
10+
generated_code = true
11+
12+
# The above configurations don't apply to compiler warnings. Requiring all params to be documented
13+
# is not something we require for this project, so suppressing it directly here.
14+
dotnet_diagnostic.CS1573.severity = none
15+
16+
# As above, we need to specifically disable compiler warnings that we don't want to break downstream
17+
# builds
18+
dotnet_diagnostic.IDE0005.severity = none
19+
20+
# Disable nullability checks when targeting netstandard2.0
21+
dotnet_diagnostic.CS8774.severity = none # Member not null
22+
dotnet_diagnostic.CS8775.severity = none # Member not null when
23+
dotnet_diagnostic.CS8776.severity = none # Member not null bad member
24+
dotnet_diagnostic.CS8777.severity = none # Parameter disallows null
25+
dotnet_diagnostic.CS8778.severity = none # Const out of range checked
26+
dotnet_diagnostic.CS8600.severity = none # Converting nullable to non-nullable
27+
dotnet_diagnostic.CS8601.severity = none # Null reference assignment
28+
dotnet_diagnostic.CS8602.severity = none # Null reference receiver
29+
dotnet_diagnostic.CS8603.severity = none # Null reference return
30+
dotnet_diagnostic.CS8604.severity = none # Null reference argument
31+
dotnet_diagnostic.CS8605.severity = none # Unbox possible null
32+
dotnet_diagnostic.CS8607.severity = none # Disallow null attribute forbids maybe null assignment
33+
dotnet_diagnostic.CS8608.severity = none # Nullability mismatch in type on override
34+
dotnet_diagnostic.CS8609.severity = none # Nullability mismatch in return type on override
35+
dotnet_diagnostic.CS8610.severity = none # Nullability mismatch in parameter type on override
36+
dotnet_diagnostic.CS8611.severity = none # Nullability mismatch in parameter type on partial
37+
dotnet_diagnostic.CS8612.severity = none # Nullability mismatch in type on implicit implementation
38+
dotnet_diagnostic.CS8613.severity = none # Nullability mismatch in return type on implicit implementation
39+
dotnet_diagnostic.CS8614.severity = none # Nullability mismatch in parameter type on implicit implementation
40+
dotnet_diagnostic.CS8615.severity = none # Nullability mismatch in type on explicit implementation
41+
dotnet_diagnostic.CS8616.severity = none # Nullability mismatch in return type on explicit implementation
42+
dotnet_diagnostic.CS8617.severity = none # Nullability mismatch in parameter type on explicit implementation
43+
dotnet_diagnostic.CS8618.severity = none # Uninitialized non-nullable field
44+
dotnet_diagnostic.CS8619.severity = none # Nullability mismatch in assignment
45+
dotnet_diagnostic.CS8620.severity = none # Nullability mismatch in argument
46+
dotnet_diagnostic.CS8621.severity = none # Nullability mismatch in return type of target delegate
47+
dotnet_diagnostic.CS8622.severity = none # Nullability mismatch in parameter type of target delegate
48+
dotnet_diagnostic.CS8624.severity = none # Nullability mismatch in argument for output
49+
dotnet_diagnostic.CS8625.severity = none # Null as non-nullable
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
using System;
6+
using System.IO;
7+
8+
namespace Microsoft.DotNet.FileBasedPrograms;
9+
10+
/// <summary>
11+
/// When targeting netstandard2.0, the user of the source package must "implement" certain methods by declaring members in this type.
12+
/// </summary>
13+
internal partial class ExternalHelpers
14+
{
15+
public static partial int CombineHashCodes(int value1, int value2);
16+
public static partial string GetRelativePath(string relativeTo, string path);
17+
18+
public static partial bool IsPathFullyQualified(string path);
19+
20+
#if NET
21+
public static partial int CombineHashCodes(int value1, int value2)
22+
=> HashCode.Combine(value1, value2);
23+
24+
public static partial string GetRelativePath(string relativeTo, string path)
25+
=> Path.GetRelativePath(relativeTo, path);
26+
27+
public static partial bool IsPathFullyQualified(string path)
28+
=> Path.IsPathFullyQualified(path);
29+
30+
#elif FILE_BASED_PROGRAMS_SOURCE_PACKAGE_BUILD
31+
// This path should only be used when we are verifying that the source package itself builds under netstandard2.0.
32+
public static partial int CombineHashCodes(int value1, int value2)
33+
=> throw new NotImplementedException();
34+
35+
public static partial string GetRelativePath(string relativeTo, string path)
36+
=> throw new NotImplementedException();
37+
38+
public static partial bool IsPathFullyQualified(string path)
39+
=> throw new NotImplementedException();
40+
41+
#endif
42+
}
43+
44+
// https://github.com/dotnet/sdk/issues/51487: Remove usage of GracefulException from the source package
45+
#if FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION
46+
internal class GracefulException : Exception
47+
{
48+
public GracefulException()
49+
{
50+
}
51+
52+
public GracefulException(string? message) : base(message)
53+
{
54+
}
55+
56+
public GracefulException(string format, string arg) : this(string.Format(format, arg))
57+
{
58+
}
59+
60+
public GracefulException(string? message, Exception? innerException) : base(message, innerException)
61+
{
62+
}
63+
}
64+
#endif

0 commit comments

Comments
 (0)