Skip to content

Commit 83c0317

Browse files
author
DavidObando
committed
Filter SDK-generated friend assembly attributes
Emit InternalsVisibleTo annotations only for attributes backed by translated source documents, leaving SDK-generated obj AssemblyInfo attributes to the preserved MSBuild items. Verify SDK and hand-authored declarations each appear once in compiled metadata.
1 parent a6c819c commit 83c0317

2 files changed

Lines changed: 81 additions & 11 deletions

File tree

tools/cs2gs/Cs2Gs.Pipeline/TranslateStage.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,11 +468,16 @@ private static void EmitFriendAssemblyAnnotations(
468468
ISet<string> usedOutputPaths)
469469
{
470470
const string attributeName = "System.Runtime.CompilerServices.InternalsVisibleToAttribute";
471+
472+
// Documents are loader-classified hand-authored sources; generated obj
473+
// AssemblyInfo trees remain in the compilation but are excluded here.
471474
List<string> friendAssemblies = project.Compilation.Assembly.GetAttributes()
472475
.Where(attribute =>
473476
attribute.AttributeClass?.ToDisplayString() == attributeName &&
474477
attribute.ConstructorArguments.Length == 1 &&
475-
attribute.ConstructorArguments[0].Value is string)
478+
attribute.ConstructorArguments[0].Value is string &&
479+
attribute.ApplicationSyntaxReference is { SyntaxTree: { } syntaxTree } &&
480+
project.Documents.Any(document => document.SyntaxTree == syntaxTree))
476481
.Select(attribute => (string)attribute.ConstructorArguments[0].Value)
477482
.Distinct(StringComparer.Ordinal)
478483
.ToList();

tools/cs2gs/Cs2Gs.Tests/MigrationPipelineTests.cs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Linq;
88
using System.Reflection;
9+
using System.Runtime.CompilerServices;
910
using System.Runtime.InteropServices;
1011
using System.Text.Json;
1112
using System.Threading.Tasks;
@@ -201,11 +202,11 @@ public async Task L2_StagesOneAndTwo_AreGreen_WithZeroArtifacts()
201202
}
202203

203204
/// <summary>
204-
/// MSBuild-generated friend-assembly metadata must become the equivalent
205-
/// G# assembly annotation so migrated test projects can access internals.
205+
/// SDK-generated and hand-authored friend-assembly declarations must each
206+
/// reach the compiled G# assembly exactly once.
206207
/// </summary>
207208
[Fact]
208-
public async Task L2_Translate_PreservesInternalsVisibleTo()
209+
public async Task L2_Compile_EmitsInternalsVisibleToExactlyOnce()
209210
{
210211
string compiler = FindCompiler();
211212
if (compiler is null)
@@ -214,22 +215,86 @@ public async Task L2_Translate_PreservesInternalsVisibleTo()
214215
}
215216

216217
string corpus = ResolveCorpusDir();
218+
string repoRoot = GsharpTestProjectRunner.FindRepoRoot();
219+
(string NupkgPath, string Version)? sdk =
220+
GsharpTestProjectRunner.ResolveLocalSdkPackage(repoRoot);
221+
if (sdk is null)
222+
{
223+
return;
224+
}
225+
226+
CorpusApp originalL2 = CorpusDiscovery.FindById(corpus, "corpus/L2-Library");
227+
string sourceRoot = NewOutputRoot("l2-friend-source");
228+
string sourceProjectDir = Path.Combine(sourceRoot, "L2-Library");
229+
Directory.CreateDirectory(sourceProjectDir);
230+
File.Copy(
231+
Path.Combine(corpus, "Directory.Build.props"),
232+
Path.Combine(sourceRoot, "Directory.Build.props"));
233+
string projectPath = Path.Combine(sourceProjectDir, Path.GetFileName(originalL2.ProjectPath));
234+
File.Copy(originalL2.ProjectPath, projectPath);
235+
File.WriteAllText(
236+
projectPath,
237+
File.ReadAllText(projectPath).Replace(
238+
"<PropertyGroup>",
239+
"<PropertyGroup>" + Environment.NewLine + " <TargetFramework>net10.0</TargetFramework>",
240+
StringComparison.Ordinal));
241+
File.WriteAllText(Path.Combine(sourceProjectDir, "FriendAssembly.cs"), """
242+
using System.Runtime.CompilerServices;
243+
244+
[assembly: InternalsVisibleTo("L2-Library.Source.Tests")]
245+
246+
public sealed class Marker
247+
{
248+
}
249+
""");
250+
var l2 = new CorpusApp(
251+
originalL2.Id,
252+
projectPath,
253+
originalL2.TargetKind);
217254
string outRoot = NewOutputRoot("l2-friend-assembly");
218255
var options = new PipelineOptions { GscPath = compiler, OutputRoot = outRoot };
219256
var pipeline = new MigrationPipeline(options, new IMigrationStage[] { new TranslateStage() });
220257

221-
CorpusApp l2 = CorpusDiscovery.FindById(corpus, "corpus/L2-Library");
222258
RunResult result = await pipeline.RunAsync(new[] { l2 });
223259
AppResult app = Assert.Single(result.Apps);
224260

225261
Assert.True(app.Succeeded);
226-
string assemblyInfo = Directory.GetFiles(
227-
Path.Combine(outRoot, result.RunId, MigrationPipeline.SanitizeAppId(l2.Id)),
228-
"AssemblyInfo.gs",
229-
SearchOption.AllDirectories).Single();
262+
string appDir = Path.Combine(outRoot, result.RunId, MigrationPipeline.SanitizeAppId(l2.Id));
263+
string generatedProjectPath = Path.Combine(appDir, "L2-Library.gsproj");
264+
GSharpProjectTransformer.Transform(
265+
projectPath,
266+
appDir,
267+
"Gsharp.NET.Sdk/" + sdk.Value.Version,
268+
generatedProjectPaths: null).Save(
269+
generatedProjectPath,
270+
System.Xml.Linq.SaveOptions.DisableFormatting);
271+
GsharpTestProjectRunner.EnsureInLocalFeed(repoRoot, sdk.Value.NupkgPath);
272+
GsharpTestProjectRunner.WriteIsolationBoundary(appDir);
273+
ProcessRunResult build = ProcessRunner.Run(
274+
"dotnet",
275+
new[]
276+
{
277+
"build",
278+
generatedProjectPath,
279+
"-c",
280+
"Release",
281+
"-p:RestoreConfigFile=" + Path.Combine(repoRoot, "nuget.config"),
282+
},
283+
appDir,
284+
TimeSpan.FromMinutes(5));
285+
Assert.True(build.ExitCode == 0, build.Output);
286+
287+
string assemblyPath = Path.Combine(appDir, "bin", "Release", "net10.0", "L2-Library.dll");
288+
Assembly assembly = Assembly.LoadFile(assemblyPath);
289+
string[] friendAssemblies = assembly.GetCustomAttributesData()
290+
.Where(attribute => attribute.AttributeType == typeof(InternalsVisibleToAttribute))
291+
.Select(attribute => (string)Assert.Single(attribute.ConstructorArguments).Value)
292+
.OrderBy(name => name, StringComparer.Ordinal)
293+
.ToArray();
294+
230295
Assert.Equal(
231-
"@assembly:InternalsVisibleTo(\"L2-Library.Tests\")" + Environment.NewLine,
232-
File.ReadAllText(assemblyInfo));
296+
new[] { "L2-Library.Source.Tests", "L2-Library.Tests" },
297+
friendAssemblies);
233298
}
234299

235300
/// <summary>

0 commit comments

Comments
 (0)