Skip to content

Conversation

@jpobst
Copy link
Contributor

@jpobst jpobst commented May 1, 2025

Enable NRT for tasks in Xamarin.Android.Build.Tasks that only needed trivial changes.

The following conventions were used:

When the task property is guarded by [Register], MSBuild will ensure the property is not null, thus we set these values to a non-allocating default:

[Required]
public ITaskItem Manifest { get; set; } = null!;  // NRT - guarded by [Required]

[Required]
public ITaskItem[] AssetDirectories { get; set; } = [];

[Required]
public string PackageName { get; set; } = "";

For non-[Required] properties and fields, set the type to nullable.

public ITaskItem? Manifest { get; set; }

public ITaskItem[]? AssetDirectories { get; set; }

public string? PackageName { get; set; }

Additionally, string.IsNullOrEmpty (string) and friends are not properly annotated for NRT in netstandard2.0. Create string extension methods like foo.IsNullOrEmpty () that are properly annotated and use them where needed.

A few of our unit tests were not filling in [Required] task properties and thus failing due to getting different results. Update the tests to pass the [Required] properties.

@jpobst jpobst force-pushed the dev/jpobst/xabt-nrt-1 branch from 28d1aee to e2761df Compare May 2, 2025 17:57
@jpobst jpobst marked this pull request as ready for review May 2, 2025 19:03
Comment on lines -130 to +128
if (!string.IsNullOrEmpty (ExtraArgs))
if (!ExtraArgs.IsNullOrEmpty ())
Copy link
Member

Choose a reason for hiding this comment

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

So, you can also use pattern matching instead:

If you see a `string.IsNullOrEmpty()` check:
```csharp
if (!string.IsNullOrEmpty (NonRequiredProperty)) {
// Code here
}
```
Convert this to:
```csharp
if (NonRequiredProperty is { Length: > 0 }) {
// Code here
}
```

Should we do this instead of an extension method?

GH Copilot was also able to make the change as mentioned here. I'm not sure it will be able to know about an extension method we made unless the file was somehow always added in its context.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we need a poll, but I find string.IsNullOrEmpty() or value.IsNullOrEmpty() to be easier to read than value is { Length: > 0 }. I want proper semantic names, not expressions!

How does everyone else feel?

Copy link
Contributor

Choose a reason for hiding this comment

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

I find IsNullOrEmpty() to be easier to read as well, it's documenting the intent and the effect very clearly.


[Required]
public ITaskItem[] MappingFiles { get; set; }
public ITaskItem[] MappingFiles { get; set; } = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this also be = null!? It would prevent a GC allocation, and it'll be set to a different value by MSBuild anyway

Copy link
Member

Choose a reason for hiding this comment

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

They are the same as Array.Empty<ITaskItem>(), it shouldn't allocate.


[Required]
public ITaskItem[] AssetDirectories { get; set; }
public ITaskItem[] AssetDirectories { get; set; } = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto here.

@jonpryor
Copy link
Contributor

jonpryor commented May 3, 2025

Array initializers:

public ITaskItem[] AssetDirectories { get; set; } = [];

result in use of Array.Empty<T>(). This is a caching "non-allocating" method, i.e. there is a single GC allocation for each type T. If you wouldn't otherwise use Array.Empty<ITaskItem>(), then this is a (single) extra GC allocation.

Given MSBuild semantics here, I think we should use = null! for arrays, not = [].

@jpobst
Copy link
Contributor Author

jpobst commented May 3, 2025

Maybe we need a poll, but I find string.IsNullOrEmpty() or value.IsNullOrEmpty() to be easier to read than value is { Length: > 0 }. I want proper semantic names, not expressions!

I find IsNullOrEmpty to be easier to read as well.

there is a single GC allocation for each type T

As the only 2 types of T here are ITaskItem and string, we're talking about allocating 2 empty arrays for the whole process, which seems fine.

I think we should use = null! for arrays

In general my preference is to use the null forgiveness operator as sparingly as possible if there's a cheap/easy alternative.

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

If we are good with the other syntax in here, I think I am.

Maybe the last thing is to update this example to use IsNullOrEmpty() extension method:

If you see a `string.IsNullOrEmpty()` check:
```csharp
if (!string.IsNullOrEmpty (NonRequiredProperty)) {
// Code here
}
```
Convert this to:
```csharp
if (NonRequiredProperty is { Length: > 0 }) {
// Code here
}
```

If someone ever asked copilot to opt into NRT, it could have a chance at doing the right thing.

I guess we'd also put a path to where the new extension method lives?

@jpobst
Copy link
Contributor Author

jpobst commented May 5, 2025

Maybe the last thing is to update this example to use IsNullOrEmpty() extension method:

Is there a way to scope that example solely to the Xamarin.Android.Build.Tasks project? For example, we wouldn't want it to make that change in anything targeting .NET 10 because string.IsNullOrEmpty is properly annotated.

I also noticed those instructions specify using "" instead of string.Empty. Do we want to change this PR to use ""? I used string.Empty but I don't really have a preference here.

@jonathanpeppers
Copy link
Member

It says to only do it for "For MSBuild task properties like:", but we could put a stronger suggestion in there. Could mention netstandard2.0?

@jpobst jpobst force-pushed the dev/jpobst/xabt-nrt-1 branch from e2761df to 105d65b Compare May 5, 2025 19:40
@jpobst jpobst force-pushed the dev/jpobst/xabt-nrt-1 branch from 105d65b to 4652b88 Compare May 5, 2025 19:42
@jpobst
Copy link
Contributor Author

jpobst commented May 5, 2025

Updated copilot-instructions.md and switched from string.Empty to "".

@jpobst jpobst merged commit 65430b1 into main May 6, 2025
59 checks passed
@jpobst jpobst deleted the dev/jpobst/xabt-nrt-1 branch May 6, 2025 02:50
jonathanpeppers pushed a commit that referenced this pull request May 6, 2025
Context: #10099
Context: #9918

#10099 enabled NRT checks for a number of XABT Tasks and was merged
because all the tests passed.  However, #10099 was not rebased on the
tip of `main` which, in the meantime, contained code from the recently
merged #9918 PR. #9918 introduced a couple of `string` properties to
the `AndroidComputeResPaths` and `CreateAar` tasks, which were not
nullable annotated.

This resulted in the following build errors once #10099 was merged:

    src/Xamarin.Android.Build.Tasks/Tasks/AndroidComputeResPaths.cs(52,17): error CS8618: Non-nullable property 'PrefixProperty' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
    
    src/Xamarin.Android.Build.Tasks/Tasks/CreateAar.cs(37,17): error CS8618: Non-nullable property 'PrefixProperty' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

This PR makes the `PrefixProperty` properties `[Required]` (because of
the way they are used in the tasks) and assigns default values to
them, as per convention introduced in #10099
jpobst added a commit that referenced this pull request May 9, 2025
Enable NRT for some "utility" classes in `Xamarin.Android.Build.Tasks` that only needed trivial changes.

The conventions established in #10099 were used.
@github-actions github-actions bot locked and limited conversation to collaborators Jun 5, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants