Skip to content

Commit 8ed3fc7

Browse files
authored
Enhance base image inference for trimmed applications - use -extras when globalization is in use (#39317)
2 parents 1c25e55 + df146e6 commit 8ed3fc7

File tree

7 files changed

+150
-13
lines changed

7 files changed

+150
-13
lines changed

src/Containers/Microsoft.NET.Build.Containers/KnownStrings.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static class Properties
3636
public static readonly string ContainerRuntimeIdentifier = nameof(ContainerRuntimeIdentifier);
3737
public static readonly string RuntimeIdentifier = nameof(RuntimeIdentifier);
3838
public static readonly string PublishAot = nameof(PublishAot);
39+
public static readonly string PublishTrimmed = nameof(PublishTrimmed);
3940
public static readonly string PublishSelfContained = nameof(PublishSelfContained);
4041
public static readonly string InvariantGlobalization = nameof(InvariantGlobalization);
4142
}
@@ -52,7 +53,7 @@ public static class ErrorCodes
5253
public static readonly string CONTAINER1011 = nameof(CONTAINER1011);
5354
public static readonly string CONTAINER1012 = nameof(CONTAINER1012);
5455
public static readonly string CONTAINER1013 = nameof(CONTAINER1013);
55-
56+
5657
public static readonly string CONTAINER2005 = nameof(CONTAINER2005);
5758
public static readonly string CONTAINER2007 = nameof(CONTAINER2007);
5859
public static readonly string CONTAINER2008 = nameof(CONTAINER2008);

src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkRefer
114114
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void
115115
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool
116116
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void
117+
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get -> bool
118+
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
117119
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
118120
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
119121
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!

src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkRefer
77
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void
88
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool
99
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void
10+
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get -> bool
11+
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
1012
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
1113
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
1214
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!

src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Tas
4646
[Required]
4747
public string TargetRuntimeIdentifier { get; set; }
4848

49-
5049
/// <summary>
5150
/// If a project is self-contained then it includes a runtime, and so the runtime-deps image should be used.
5251
/// </summary>
@@ -57,6 +56,8 @@ public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Tas
5756
/// </summary>
5857
public bool IsAotPublished { get; set; }
5958

59+
public bool IsTrimmed { get; set; }
60+
6061
/// <summary>
6162
/// If the project is AOT'd the aot image variant doesn't contain ICU and TZData, so we use this flag to see if we need to use the `-extra` variant that does contain those packages.
6263
/// </summary>
@@ -106,7 +107,7 @@ public override bool Execute()
106107

107108
private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository, [NotNullWhen(true)] out string? tag)
108109
{
109-
if (ComputeVersionPart() is (string baseVersionPart, bool versionAllowsUsingAOTAndExtrasImages))
110+
if (ComputeVersionPart() is (string baseVersionPart, SemanticVersion parsedVersion, bool versionAllowsUsingAOTAndExtrasImages))
110111
{
111112
Log.LogMessage("Computed base version tag of {0} from TFM {1} and SDK {2}", baseVersionPart, TargetFrameworkVersion, SdkVersion);
112113
if (baseVersionPart is null)
@@ -132,6 +133,22 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
132133
// for the inferred image tags, 'family' aka 'flavor' comes after the 'version' portion (including any preview/rc segments).
133134
// so it's safe to just append here
134135
tag += $"-{ContainerFamily}";
136+
Log.LogMessage("Using user-provided ContainerFamily");
137+
138+
// we can do one final check here: if the containerfamily is the 'default' for the RID
139+
// in question, and the app is globalized, we can help and add -extra so the app will actually run
140+
141+
if (
142+
(!IsMuslRid && ContainerFamily == "jammy-chiseled") // default for linux RID
143+
&& !UsesInvariantGlobalization
144+
&& versionAllowsUsingAOTAndExtrasImages
145+
// the extras only became available on the stable tags of the FirstVersionWithNewTaggingScheme
146+
&& (!parsedVersion.IsPrerelease && parsedVersion.Major == FirstVersionWithNewTaggingScheme))
147+
{
148+
Log.LogMessage("Using extra variant because the application needs globalization");
149+
tag += "-extra";
150+
}
151+
135152
return true;
136153
}
137154
else
@@ -141,7 +158,7 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
141158
tag += IsMuslRid switch
142159
{
143160
true => "-alpine",
144-
false => "" // TODO: should we default here to chiseled iamges for < 8 apps?
161+
false => "" // TODO: should we default here to chiseled images for < 8 apps?
145162
};
146163
Log.LogMessage("Selected base image tag {0}", tag);
147164
return true;
@@ -153,16 +170,17 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
153170
{
154171
true => "-alpine",
155172
// default to chiseled for AOT, non-musl Apps
156-
false when IsAotPublished => "-jammy-chiseled", // TODO: should we default here to jammy-chiseled for non-musl RIDs?
173+
false when IsAotPublished || IsTrimmed => "-jammy-chiseled", // TODO: should we default here to jammy-chiseled for non-musl RIDs?
157174
// default to jammy for non-AOT, non-musl Apps
158175
false => ""
159176
};
160177

161178
// now choose the variant, if any - if globalization then -extra, else -aot
162-
tag += (IsAotPublished, UsesInvariantGlobalization) switch
179+
tag += (IsAotPublished, IsTrimmed, UsesInvariantGlobalization) switch
163180
{
164-
(true, false) => "-extra",
165-
(true, true) => "-aot",
181+
(true, _, false) => "-extra",
182+
(_, true, false) => "-extra",
183+
(true, _, true) => "-aot",
166184
_ => ""
167185
};
168186
Log.LogMessage("Selected base image tag {0}", tag);
@@ -178,18 +196,18 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
178196
}
179197
}
180198

181-
private (string, bool)? ComputeVersionPart()
199+
private (string, SemanticVersion, bool)? ComputeVersionPart()
182200
{
183201
if (SemanticVersion.TryParse(TargetFrameworkVersion, out var tfm) && tfm.Major < FirstVersionWithNewTaggingScheme)
184202
{
185203
// < 8 TFMs don't support the -aot and -extras images
186-
return ($"{tfm.Major}.{tfm.Minor}", false);
204+
return ($"{tfm.Major}.{tfm.Minor}", tfm, false);
187205
}
188206
else if (SemanticVersion.TryParse(SdkVersion, out var version))
189207
{
190208
if (ComputeVersionInternal(version, tfm) is string majMinor)
191209
{
192-
return (majMinor, true);
210+
return (majMinor, version, true);
193211
}
194212
else
195213
{

src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
FrameworkReferences="@(FrameworkReference)"
5050
IsSelfContained="$(_ContainerIsSelfContained)"
5151
IsAotPublished="$(PublishAot)"
52+
IsTrimmed="$(PublishTrimmed)"
5253
UsesInvariantGlobalization="$(InvariantGlobalization)"
5354
TargetRuntimeIdentifier="$(ContainerRuntimeIdentifier)"
5455
ContainerFamily="$(ContainerFamily)">

src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary<str
4141
props["TargetFileName"] = "foo.dll";
4242
props["AssemblyName"] = "foo";
4343
props["TargetFrameworkVersion"] = "v7.0";
44+
4445
props["TargetFrameworkIdentifier"] = ".NETCoreApp";
4546
props["TargetFramework"] = "net7.0";
4647
props["_NativeExecutableExtension"] = ".exe"; //TODO: windows/unix split here

src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public void WindowsUsersGetLinuxContainers(string sdkPortableRid, string expecte
288288
[InlineData("8.0.100-preview.2", "v8.0", "jammy", "8.0.0-preview.2-jammy")]
289289
[InlineData("8.0.100-preview.2", "v8.0", "jammy-chiseled", "8.0.0-preview.2-jammy-chiseled")]
290290
[InlineData("8.0.100-rc.2", "v8.0", "jammy-chiseled", "8.0.0-rc.2-jammy-chiseled")]
291-
[InlineData("8.0.100", "v8.0", "jammy-chiseled", "8.0-jammy-chiseled")]
291+
[InlineData("8.0.100", "v8.0", "jammy-chiseled", "8.0-jammy-chiseled-extra")]
292292
[Theory]
293293
public void CanTakeContainerBaseFamilyIntoAccount(string sdkVersion, string tfmMajMin, string containerFamily, string expectedTag)
294294
{
@@ -369,6 +369,77 @@ public void AOTAppsWithCulturesGetExtraImages(string rid, string expectedImage)
369369
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
370370
}
371371

372+
[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]
373+
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
374+
[Theory]
375+
public void TrimmedAppsWithCulturesGetExtraImages(string rid, string expectedImage)
376+
{
377+
var (project, logger, d) = ProjectInitializer.InitProject(new()
378+
{
379+
["NetCoreSdkVersion"] = "8.0.100",
380+
["TargetFrameworkVersion"] = "v8.0",
381+
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
382+
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
383+
[KnownStrings.Properties.PublishTrimmed] = true.ToString(),
384+
[KnownStrings.Properties.InvariantGlobalization] = false.ToString()
385+
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
386+
using var _ = d;
387+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
388+
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
389+
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
390+
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
391+
}
392+
393+
[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine")]
394+
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled")]
395+
[Theory]
396+
public void TrimmedAppsWithoutCulturesGetbaseImages(string rid, string expectedImage)
397+
{
398+
var (project, logger, d) = ProjectInitializer.InitProject(new()
399+
{
400+
["NetCoreSdkVersion"] = "8.0.100",
401+
["TargetFrameworkVersion"] = "v8.0",
402+
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
403+
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
404+
[KnownStrings.Properties.PublishTrimmed] = true.ToString(),
405+
[KnownStrings.Properties.InvariantGlobalization] = true.ToString()
406+
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
407+
using var _ = d;
408+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
409+
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
410+
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
411+
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
412+
}
413+
414+
[InlineData(true, false, "linux-musl-x64", true, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine")]
415+
[InlineData(true, false, "linux-musl-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]
416+
[InlineData(false, true, "linux-musl-x64", true, "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-alpine-aot")]
417+
[InlineData(false, true, "linux-musl-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]
418+
419+
[InlineData(true, false, "linux-x64", true, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled")]
420+
[InlineData(true, false, "linux-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
421+
[InlineData(false, true, "linux-x64", true, "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot")]
422+
[InlineData(false, true, "linux-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
423+
[Theory]
424+
public void TheBigMatrixOfTrimmingInference(bool trimmed, bool aot, string rid, bool invariant, string expectedImage)
425+
{
426+
var (project, logger, d) = ProjectInitializer.InitProject(new()
427+
{
428+
["NetCoreSdkVersion"] = "8.0.100",
429+
["TargetFrameworkVersion"] = "v8.0",
430+
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
431+
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
432+
[KnownStrings.Properties.PublishTrimmed] = trimmed.ToString(),
433+
[KnownStrings.Properties.PublishAot] = aot.ToString(),
434+
[KnownStrings.Properties.InvariantGlobalization] = invariant.ToString()
435+
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
436+
using var _ = d;
437+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
438+
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
439+
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
440+
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
441+
}
442+
372443
[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")]
373444
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")]
374445
[Theory]
@@ -390,6 +461,47 @@ public void AOTAppsLessThan8DoNotGetAOTImages(string rid, string expectedImage)
390461
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
391462
}
392463

464+
[Fact]
465+
public void FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtras()
466+
{
467+
var expectedImage = "mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled-extra";
468+
var (project, logger, d) = ProjectInitializer.InitProject(new()
469+
{
470+
["NetCoreSdkVersion"] = "8.0.100",
471+
["TargetFrameworkVersion"] = "v8.0",
472+
[KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64",
473+
[KnownStrings.Properties.ContainerFamily] = "jammy-chiseled",
474+
[KnownStrings.Properties.InvariantGlobalization] = false.ToString(),
475+
}, projectName: $"{nameof(FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtras)}");
476+
using var _ = d;
477+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
478+
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
479+
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
480+
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
481+
}
482+
483+
[Fact]
484+
public void FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtras()
485+
{
486+
var expectedImage = "mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled-extra";
487+
var (project, logger, d) = ProjectInitializer.InitProject(new()
488+
{
489+
["NetCoreSdkVersion"] = "8.0.100",
490+
["TargetFrameworkVersion"] = "v8.0",
491+
[KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64",
492+
[KnownStrings.Properties.ContainerFamily] = "jammy-chiseled",
493+
[KnownStrings.Properties.InvariantGlobalization] = false.ToString(),
494+
}, bonusItems: new()
495+
{
496+
[KnownStrings.Items.FrameworkReference] = KnownFrameworkReferences.WebApp
497+
}, projectName: $"{nameof(FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtras)}");
498+
using var _ = d;
499+
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
500+
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
501+
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
502+
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
503+
}
504+
393505
[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")]
394506
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")]
395507
[Theory]
@@ -426,7 +538,7 @@ public void AspNetFDDAppsGetAspNetBaseImage()
426538
}, projectName: $"{nameof(AspNetFDDAppsGetAspNetBaseImage)}");
427539
using var _ = d;
428540
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
429-
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
541+
instance.Build(new[] { ComputeContainerBaseImage }, [logger], null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
430542
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
431543
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
432544
}

0 commit comments

Comments
 (0)