Skip to content

Commit

Permalink
Enhance base image inference for trimmed applications - use -extras w…
Browse files Browse the repository at this point in the history
…hen globalization is in use (#39317)
  • Loading branch information
baronfel authored Mar 19, 2024
2 parents 1c25e55 + df146e6 commit 8ed3fc7
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class Properties
public static readonly string ContainerRuntimeIdentifier = nameof(ContainerRuntimeIdentifier);
public static readonly string RuntimeIdentifier = nameof(RuntimeIdentifier);
public static readonly string PublishAot = nameof(PublishAot);
public static readonly string PublishTrimmed = nameof(PublishTrimmed);
public static readonly string PublishSelfContained = nameof(PublishSelfContained);
public static readonly string InvariantGlobalization = nameof(InvariantGlobalization);
}
Expand All @@ -52,7 +53,7 @@ public static class ErrorCodes
public static readonly string CONTAINER1011 = nameof(CONTAINER1011);
public static readonly string CONTAINER1012 = nameof(CONTAINER1012);
public static readonly string CONTAINER1013 = nameof(CONTAINER1013);

public static readonly string CONTAINER2005 = nameof(CONTAINER2005);
public static readonly string CONTAINER2007 = nameof(CONTAINER2007);
public static readonly string CONTAINER2008 = nameof(CONTAINER2008);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkRefer
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkRefer
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsTrimmed.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void
Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Tas
[Required]
public string TargetRuntimeIdentifier { get; set; }


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

public bool IsTrimmed { get; set; }

/// <summary>
/// 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.
/// </summary>
Expand Down Expand Up @@ -106,7 +107,7 @@ public override bool Execute()

private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository, [NotNullWhen(true)] out string? tag)
{
if (ComputeVersionPart() is (string baseVersionPart, bool versionAllowsUsingAOTAndExtrasImages))
if (ComputeVersionPart() is (string baseVersionPart, SemanticVersion parsedVersion, bool versionAllowsUsingAOTAndExtrasImages))
{
Log.LogMessage("Computed base version tag of {0} from TFM {1} and SDK {2}", baseVersionPart, TargetFrameworkVersion, SdkVersion);
if (baseVersionPart is null)
Expand All @@ -132,6 +133,22 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
// for the inferred image tags, 'family' aka 'flavor' comes after the 'version' portion (including any preview/rc segments).
// so it's safe to just append here
tag += $"-{ContainerFamily}";
Log.LogMessage("Using user-provided ContainerFamily");

// we can do one final check here: if the containerfamily is the 'default' for the RID
// in question, and the app is globalized, we can help and add -extra so the app will actually run

if (
(!IsMuslRid && ContainerFamily == "jammy-chiseled") // default for linux RID
&& !UsesInvariantGlobalization
&& versionAllowsUsingAOTAndExtrasImages
// the extras only became available on the stable tags of the FirstVersionWithNewTaggingScheme
&& (!parsedVersion.IsPrerelease && parsedVersion.Major == FirstVersionWithNewTaggingScheme))
{
Log.LogMessage("Using extra variant because the application needs globalization");
tag += "-extra";
}

return true;
}
else
Expand All @@ -141,7 +158,7 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
tag += IsMuslRid switch
{
true => "-alpine",
false => "" // TODO: should we default here to chiseled iamges for < 8 apps?
false => "" // TODO: should we default here to chiseled images for < 8 apps?
};
Log.LogMessage("Selected base image tag {0}", tag);
return true;
Expand All @@ -153,16 +170,17 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
{
true => "-alpine",
// default to chiseled for AOT, non-musl Apps
false when IsAotPublished => "-jammy-chiseled", // TODO: should we default here to jammy-chiseled for non-musl RIDs?
false when IsAotPublished || IsTrimmed => "-jammy-chiseled", // TODO: should we default here to jammy-chiseled for non-musl RIDs?
// default to jammy for non-AOT, non-musl Apps
false => ""
};

// now choose the variant, if any - if globalization then -extra, else -aot
tag += (IsAotPublished, UsesInvariantGlobalization) switch
tag += (IsAotPublished, IsTrimmed, UsesInvariantGlobalization) switch
{
(true, false) => "-extra",
(true, true) => "-aot",
(true, _, false) => "-extra",
(_, true, false) => "-extra",
(true, _, true) => "-aot",
_ => ""
};
Log.LogMessage("Selected base image tag {0}", tag);
Expand All @@ -178,18 +196,18 @@ private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository,
}
}

private (string, bool)? ComputeVersionPart()
private (string, SemanticVersion, bool)? ComputeVersionPart()
{
if (SemanticVersion.TryParse(TargetFrameworkVersion, out var tfm) && tfm.Major < FirstVersionWithNewTaggingScheme)
{
// < 8 TFMs don't support the -aot and -extras images
return ($"{tfm.Major}.{tfm.Minor}", false);
return ($"{tfm.Major}.{tfm.Minor}", tfm, false);
}
else if (SemanticVersion.TryParse(SdkVersion, out var version))
{
if (ComputeVersionInternal(version, tfm) is string majMinor)
{
return (majMinor, true);
return (majMinor, version, true);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
FrameworkReferences="@(FrameworkReference)"
IsSelfContained="$(_ContainerIsSelfContained)"
IsAotPublished="$(PublishAot)"
IsTrimmed="$(PublishTrimmed)"
UsesInvariantGlobalization="$(InvariantGlobalization)"
TargetRuntimeIdentifier="$(ContainerRuntimeIdentifier)"
ContainerFamily="$(ContainerFamily)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary<str
props["TargetFileName"] = "foo.dll";
props["AssemblyName"] = "foo";
props["TargetFrameworkVersion"] = "v7.0";

props["TargetFrameworkIdentifier"] = ".NETCoreApp";
props["TargetFramework"] = "net7.0";
props["_NativeExecutableExtension"] = ".exe"; //TODO: windows/unix split here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public void WindowsUsersGetLinuxContainers(string sdkPortableRid, string expecte
[InlineData("8.0.100-preview.2", "v8.0", "jammy", "8.0.0-preview.2-jammy")]
[InlineData("8.0.100-preview.2", "v8.0", "jammy-chiseled", "8.0.0-preview.2-jammy-chiseled")]
[InlineData("8.0.100-rc.2", "v8.0", "jammy-chiseled", "8.0.0-rc.2-jammy-chiseled")]
[InlineData("8.0.100", "v8.0", "jammy-chiseled", "8.0-jammy-chiseled")]
[InlineData("8.0.100", "v8.0", "jammy-chiseled", "8.0-jammy-chiseled-extra")]
[Theory]
public void CanTakeContainerBaseFamilyIntoAccount(string sdkVersion, string tfmMajMin, string containerFamily, string expectedTag)
{
Expand Down Expand Up @@ -369,6 +369,77 @@ public void AOTAppsWithCulturesGetExtraImages(string rid, string expectedImage)
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
[Theory]
public void TrimmedAppsWithCulturesGetExtraImages(string rid, string expectedImage)
{
var (project, logger, d) = ProjectInitializer.InitProject(new()
{
["NetCoreSdkVersion"] = "8.0.100",
["TargetFrameworkVersion"] = "v8.0",
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
[KnownStrings.Properties.PublishTrimmed] = true.ToString(),
[KnownStrings.Properties.InvariantGlobalization] = false.ToString()
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine")]
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled")]
[Theory]
public void TrimmedAppsWithoutCulturesGetbaseImages(string rid, string expectedImage)
{
var (project, logger, d) = ProjectInitializer.InitProject(new()
{
["NetCoreSdkVersion"] = "8.0.100",
["TargetFrameworkVersion"] = "v8.0",
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
[KnownStrings.Properties.PublishTrimmed] = true.ToString(),
[KnownStrings.Properties.InvariantGlobalization] = true.ToString()
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[InlineData(true, false, "linux-musl-x64", true, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine")]
[InlineData(true, false, "linux-musl-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]
[InlineData(false, true, "linux-musl-x64", true, "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-alpine-aot")]
[InlineData(false, true, "linux-musl-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine-extra")]

[InlineData(true, false, "linux-x64", true, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled")]
[InlineData(true, false, "linux-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
[InlineData(false, true, "linux-x64", true, "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot")]
[InlineData(false, true, "linux-x64", false, "mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra")]
[Theory]
public void TheBigMatrixOfTrimmingInference(bool trimmed, bool aot, string rid, bool invariant, string expectedImage)
{
var (project, logger, d) = ProjectInitializer.InitProject(new()
{
["NetCoreSdkVersion"] = "8.0.100",
["TargetFrameworkVersion"] = "v8.0",
[KnownStrings.Properties.ContainerRuntimeIdentifier] = rid,
[KnownStrings.Properties.PublishSelfContained] = true.ToString(),
[KnownStrings.Properties.PublishTrimmed] = trimmed.ToString(),
[KnownStrings.Properties.PublishAot] = aot.ToString(),
[KnownStrings.Properties.InvariantGlobalization] = invariant.ToString()
}, projectName: $"{nameof(TrimmedAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")]
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")]
[Theory]
Expand All @@ -390,6 +461,47 @@ public void AOTAppsLessThan8DoNotGetAOTImages(string rid, string expectedImage)
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[Fact]
public void FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtras()
{
var expectedImage = "mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled-extra";
var (project, logger, d) = ProjectInitializer.InitProject(new()
{
["NetCoreSdkVersion"] = "8.0.100",
["TargetFrameworkVersion"] = "v8.0",
[KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64",
[KnownStrings.Properties.ContainerFamily] = "jammy-chiseled",
[KnownStrings.Properties.InvariantGlobalization] = false.ToString(),
}, projectName: $"{nameof(FDDConsoleAppWithCulturesAndOptingIntoChiseledGetsExtras)}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[Fact]
public void FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtras()
{
var expectedImage = "mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled-extra";
var (project, logger, d) = ProjectInitializer.InitProject(new()
{
["NetCoreSdkVersion"] = "8.0.100",
["TargetFrameworkVersion"] = "v8.0",
[KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64",
[KnownStrings.Properties.ContainerFamily] = "jammy-chiseled",
[KnownStrings.Properties.InvariantGlobalization] = false.ToString(),
}, bonusItems: new()
{
[KnownStrings.Items.FrameworkReference] = KnownFrameworkReferences.WebApp
}, projectName: $"{nameof(FDDAspNetAppWithCulturesAndOptingIntoChiseledGetsExtras)}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}

[InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")]
[InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")]
[Theory]
Expand Down Expand Up @@ -426,7 +538,7 @@ public void AspNetFDDAppsGetAspNetBaseImage()
}, projectName: $"{nameof(AspNetFDDAppsGetAspNetBaseImage)}");
using var _ = d;
var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None);
instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
instance.Build(new[] { ComputeContainerBaseImage }, [logger], null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors));
var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue;
computedBaseImageTag.Should().BeEquivalentTo(expectedImage);
}
Expand Down

0 comments on commit 8ed3fc7

Please sign in to comment.