Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Detect TargetFrameworks and install them if there is no global.json #14821

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

mbg
Copy link
Member

@mbg mbg commented Nov 16, 2023

Summary

For .NET projects, our builds may fail if a .NET version that is specified as a TargetFramework is not available on the host. We have seen this recently in cases where e.g. a .csproj specifies .NET 8 but .NET 8 was not available on a GHA runner yet by default.

This PR:

  • Retrieves TargetFramework values from the project files, where available.
  • If there's no global.json, we look at the TargetFramework values and install those SDKs. (Should we do this even if there is a global.json?)

@mbg mbg added the C# label Nov 16, 2023
@mbg mbg self-assigned this Nov 16, 2023
@mbg mbg force-pushed the mbg/csharp/detect-target-framework branch from 09fc91e to 9ab7c96 Compare November 21, 2023 13:32
@mbg mbg changed the title C#: Detect highest TargetFramework and install it if there is no global.json C#: Detect TargetFrameworks and install them if there is no global.json Nov 21, 2023
@mbg mbg force-pushed the mbg/csharp/detect-target-framework branch from 9ab7c96 to 2bce387 Compare November 21, 2023 13:51
@mbg mbg marked this pull request as ready for review November 21, 2023 13:51
@mbg mbg requested a review from a team as a code owner November 21, 2023 13:51
Comment on lines +174 to +175
// TargetFramework values in the project files and install the latest versions
// from the corresponding channels
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we only install the latest? Why not all? If there are multiple projects with different target framework settings in the repo, some of them will fail the build, won't it?

Copy link
Member Author

Choose a reason for hiding this comment

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

We do install all! Maybe you had a look at the initial draft version of this PR, which only installed the latest, but now the review-ready version does install all the ones we find (in theory at least - pending changes we may have to make to DownloadDotNetVersion)

Copy link
Member Author

@mbg mbg Nov 21, 2023

Choose a reason for hiding this comment

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

If there are multiple projects with different target framework settings in the repo, some of them will fail the build, won't it?

This is an interesting question. I would assume that to be the case yes, but if so that's been a pretty big oversight on our part with respect to C# projects generally.

Also relates to my question whether we should do this even if there is a global.json.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could add an integration test to check this.

Copy link
Contributor

@tamasvajk tamasvajk Nov 21, 2023

Choose a reason for hiding this comment

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

I think it's not possible to pin multiple versions of dotnet in global.json. So maybe whoever needs this would add a build script which takes care of the installation.

Copy link
Member Author

Choose a reason for hiding this comment

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

According to the docs for global.json, the version specified there just controls which version of the CLI is used and is independent of the SDK versions that the projects target.

@@ -51,7 +53,8 @@ public Project(Autobuilder<TAutobuildOptions> builder, string path) : base(build
{
if (root.HasAttribute("Sdk"))
{
DotNetProject = true;
this.DotNetProject = true;
this.TargetFramework = root.SelectSingleNode("//Project/PropertyGroup/TargetFramework/text()")?.Value;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also include the values from <TargetFrameworks> (plural).

.OfType<Project<CSharpAutobuildOptions>>()
.Select(p => p.TargetFramework)
.OfType<string>()
.Where(v => v.StartsWith("net"));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could be more specific about the versions. There's no point in issuing a web request for things that will obviously fail, such as netstandard.*, net48 and below, and maybe netcoreapp3.1 and below also doesn't work.

Copy link
Member Author

Choose a reason for hiding this comment

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

netstandard and netcoreapp should already get filtered out by the code further down (see the comment there). There was a unit test which initially failed for me (because it specified netcoreapp2.1 or something), but now passes since we no longer attempt to install those SDKs.

I can also filter out older net versions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's as simple as filtering out anything that has a major version number below 5.

Version? version = null;
if (Version.TryParse(targetFrameworkVersion[3..], out version))
{
installScript &= DownloadDotNetVersion(builder, installDir, "latest", channel: version.ToString());
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the download logic work for both net7.0 and net70?

Copy link
Member Author

@mbg mbg Nov 21, 2023

Choose a reason for hiding this comment

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

I think the Version parsing here would interpret the latter as 70.0 essentially, so I doubt that the dotnet-install.sh script would be happy with that.

// Download versions mentioned in `global.json` files
// See https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
var installScript = BuildScript.Success;
var validGlobalJson = false;
foreach (var path in builder.Paths.Select(p => p.Item1).Where(p => p.EndsWith("global.json", StringComparison.Ordinal)))
{
string version;
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to declare version explicitly. You can just return directly within the try block.

try
{
    var o = JObject.Parse(File.ReadAllText(path));
    return (string)(o?["sdk"]?["version"]!);
}
catch  // lgtm[cs/catch-of-all-exceptions]
{
    // not a valid global.json file
    continue;
}

{
// For simplicity, we only accept target frameworks of the form "netX.Y"
Version? version = null;
if (Version.TryParse(targetFrameworkVersion[3..], out version))
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using out var version and avoid declaring version above.
What will happen in case the target framework is net70 and not net7.0 (I think it will be parsed as version 70, which probably will cause problems downstream).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants