-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Thread TaskEnvironment through StaticWebAsset path helpers (foundation for dotnet/msbuild#14036) #54708
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
Merged
OvesN
merged 5 commits into
dotnet:main
from
jankratochvilcz:mt/swa-thread-task-environment
Jun 12, 2026
Merged
Thread TaskEnvironment through StaticWebAsset path helpers (foundation for dotnet/msbuild#14036) #54708
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0161592
Thread TaskEnvironment through StaticWebAsset path-resolution helpers…
e4879ee
remove unnecesary comments, fix MaterializeFrameworkAsset
OvesN 15283d5
fixes
OvesN 3a88a83
Add RelatedAsset cross-boundary tests for package manifest pipeline
jankratochvilcz 95018c3
Merge branch 'main' into mt/swa-thread-task-environment
jankratochvilcz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -589,10 +589,19 @@ public DateTimeOffset LastWriteTime | |
| } | ||
|
|
||
| public static StaticWebAsset FromTaskItem(ITaskItem item, bool validate = false) | ||
| => FromTaskItem(item, TaskEnvironment.Fallback, validate); | ||
|
|
||
| /// <summary> | ||
| /// Builds a <see cref="StaticWebAsset"/> from an MSBuild item, absolutizing path-bearing | ||
| /// metadata (ContentRoot, RelatedAsset) against the supplied <paramref name="env"/>'s | ||
| /// project directory rather than the process current directory. Required for multithreaded | ||
| /// MSBuild execution. | ||
| /// </summary> | ||
| public static StaticWebAsset FromTaskItem(ITaskItem item, TaskEnvironment env, bool validate = false) | ||
| { | ||
| var result = FromTaskItemCore(item); | ||
|
|
||
| result.Normalize(); | ||
| result.Normalize(env); | ||
| if (validate) | ||
| { | ||
| result.Validate(); | ||
|
|
@@ -803,12 +812,15 @@ private bool HasKind(string assetKind) => | |
| AssetKinds.IsKind(AssetKind, assetKind); | ||
|
|
||
| public static StaticWebAsset FromV1TaskItem(ITaskItem item) | ||
| => FromV1TaskItem(item, TaskEnvironment.Fallback); | ||
|
|
||
| public static StaticWebAsset FromV1TaskItem(ITaskItem item, TaskEnvironment env) | ||
| { | ||
| var result = FromTaskItemCore(item); | ||
| result.ApplyDefaults(); | ||
| result.ApplyDefaults(env); | ||
| result.OriginalItemSpec = string.IsNullOrEmpty(result.OriginalItemSpec) ? item.GetMetadata("FullPath") : result.OriginalItemSpec; | ||
|
|
||
| result.Normalize(); | ||
| result.Normalize(env); | ||
| result.Validate(); | ||
|
|
||
| return result; | ||
|
|
@@ -822,7 +834,9 @@ private static StaticWebAsset FromTaskItemCore(ITaskItem item) | |
| }; | ||
| } | ||
|
|
||
| public void ApplyDefaults() | ||
| public void ApplyDefaults() => ApplyDefaults(TaskEnvironment.Fallback); | ||
|
|
||
| public void ApplyDefaults(TaskEnvironment env) | ||
| { | ||
| CopyToOutputDirectory = string.IsNullOrEmpty(CopyToOutputDirectory) ? AssetCopyOptions.Never : CopyToOutputDirectory; | ||
| CopyToPublishDirectory = string.IsNullOrEmpty(CopyToPublishDirectory) ? AssetCopyOptions.PreserveNewest : CopyToPublishDirectory; | ||
|
|
@@ -831,7 +845,7 @@ public void ApplyDefaults() | |
| AssetRole = string.IsNullOrEmpty(AssetRole) ? AssetRoles.Primary : AssetRole; | ||
| if (string.IsNullOrEmpty(Fingerprint) || string.IsNullOrEmpty(Integrity) || FileLength == -1 || LastWriteTime == DateTimeOffset.MinValue) | ||
| { | ||
| var file = ResolveFile(Identity, OriginalItemSpec); | ||
| var file = ResolveFile(Identity, OriginalItemSpec, env); | ||
| (Fingerprint, Integrity) = string.IsNullOrEmpty(Fingerprint) || string.IsNullOrEmpty(Integrity) ? | ||
| ComputeFingerprintAndIntegrityIfNeeded(file) : (Fingerprint, Integrity); | ||
| FileLength = FileLength == -1 ? file.Length : FileLength; | ||
|
|
@@ -861,8 +875,11 @@ internal static (string fingerprint, string integrity) ComputeFingerprintAndInte | |
| } | ||
|
|
||
| internal static string ComputeIntegrity(string identity, string originalItemSpec) | ||
| => ComputeIntegrity(identity, originalItemSpec, TaskEnvironment.Fallback); | ||
|
|
||
| internal static string ComputeIntegrity(string identity, string originalItemSpec, TaskEnvironment env) | ||
| { | ||
| var fileInfo = ResolveFile(identity, originalItemSpec); | ||
| var fileInfo = ResolveFile(identity, originalItemSpec, env); | ||
| return ComputeIntegrity(fileInfo); | ||
| } | ||
|
|
||
|
|
@@ -1064,20 +1081,24 @@ internal static StaticWebAsset FromProperties( | |
| internal bool HasSourceId(string source) => | ||
| HasSourceId(SourceId, source); | ||
|
|
||
| public void Normalize() | ||
| public void Normalize() => Normalize(TaskEnvironment.Fallback); | ||
|
|
||
| public void Normalize(TaskEnvironment env) | ||
| { | ||
| ContentRoot = !string.IsNullOrEmpty(ContentRoot) ? NormalizeContentRootPath(ContentRoot) : ContentRoot; | ||
| ContentRoot = !string.IsNullOrEmpty(ContentRoot) ? NormalizeContentRootPath(ContentRoot, env) : ContentRoot; | ||
| BasePath = Normalize(BasePath); | ||
| RelativePath = Normalize(RelativePath, allowEmpyPath: true); | ||
| RelatedAsset = !string.IsNullOrEmpty(RelatedAsset) ? Path.GetFullPath(RelatedAsset) : RelatedAsset; | ||
| RelatedAsset = !string.IsNullOrEmpty(RelatedAsset) ? Path.GetFullPath(env.GetAbsolutePath(RelatedAsset)) : RelatedAsset; | ||
| } | ||
|
|
||
| // Normalizes the given path to a content root path in the way we expect it: | ||
| // * Converts the path to absolute with Path.GetFullPath(path) which takes care of normalizing | ||
| // the directory separators to use Path.DirectorySeparator | ||
| // * Appends a trailing directory separator at the end. | ||
| public static string NormalizeContentRootPath(string path) | ||
| => Path.GetFullPath(path) + | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the thing the PR is fundamentally trying to fix |
||
| public static string NormalizeContentRootPath(string path) => NormalizeContentRootPath(path, TaskEnvironment.Fallback); | ||
|
|
||
| public static string NormalizeContentRootPath(string path, TaskEnvironment env) | ||
| => Path.GetFullPath(string.IsNullOrEmpty(path) ? path : env.GetAbsolutePath(path)) + | ||
| // We need to do .ToString because there is no EndsWith overload for chars in .NET Framework | ||
| (path.EndsWith(Path.DirectorySeparatorChar.ToString()), path.EndsWith(Path.AltDirectorySeparatorChar.ToString())) switch | ||
| { | ||
|
|
@@ -1134,8 +1155,10 @@ public bool ShouldCopyToOutputDirectory() | |
| public bool ShouldCopyToPublishDirectory() | ||
| => !string.Equals(CopyToPublishDirectory, AssetCopyOptions.Never, StringComparison.Ordinal); | ||
|
|
||
| public bool HasContentRoot(string path) => | ||
| string.Equals(ContentRoot, NormalizeContentRootPath(path), StringComparison.Ordinal); | ||
| public bool HasContentRoot(string path) => HasContentRoot(path, TaskEnvironment.Fallback); | ||
|
|
||
| public bool HasContentRoot(string path, TaskEnvironment env) => | ||
| string.Equals(ContentRoot, NormalizeContentRootPath(path, env), StringComparison.Ordinal); | ||
|
|
||
| /// <summary> | ||
| /// Materializes a framework asset by copying it to the consuming project's intermediate directory | ||
|
|
@@ -1149,6 +1172,15 @@ public static (StaticWebAsset Asset, string OldIdentity, string OldBasePath) Mat | |
| string projectPackageId, | ||
| string projectBasePath, | ||
| TaskLoggingHelper log) | ||
| => MaterializeFrameworkAsset(asset, intermediateOutputPath, projectPackageId, projectBasePath, log, TaskEnvironment.Fallback); | ||
|
|
||
| public static (StaticWebAsset Asset, string OldIdentity, string OldBasePath) MaterializeFrameworkAsset( | ||
| StaticWebAsset asset, | ||
| string intermediateOutputPath, | ||
| string projectPackageId, | ||
| string projectBasePath, | ||
| TaskLoggingHelper log, | ||
| TaskEnvironment env) | ||
| { | ||
| var originalSourceId = asset.SourceId; | ||
| var oldBasePath = asset.BasePath; | ||
|
|
@@ -1158,12 +1190,18 @@ public static (StaticWebAsset Asset, string OldIdentity, string OldBasePath) Mat | |
| var fxDir = Path.Combine(intermediateOutputPath, "fx", originalSourceId); | ||
| var fileSystemRelativePath = asset.ComputePathWithoutTokens(relativePath); | ||
| var destPath = Path.Combine(fxDir, Normalize(fileSystemRelativePath)); | ||
| destPath = Path.GetFullPath(destPath); | ||
|
|
||
| destPath = Path.GetFullPath(env.GetAbsolutePath(destPath)); | ||
| if (string.IsNullOrEmpty(asset.Identity)) | ||
| { | ||
| log.LogError("Source file '{0}' does not exist for framework asset materialization.", asset.Identity); | ||
| return (null, null, null); | ||
| } | ||
|
|
||
| var sourceFile = asset.Identity; | ||
| var sourceFile = env.GetAbsolutePath(asset.Identity); | ||
| if (!File.Exists(sourceFile)) | ||
| { | ||
| log.LogError("Source file '{0}' does not exist for framework asset materialization.", sourceFile); | ||
| log.LogError("Source file '{0}' does not exist for framework asset materialization.", sourceFile.OriginalValue); | ||
| return (null, null, null); | ||
| } | ||
|
|
||
|
|
@@ -1173,22 +1211,22 @@ public static (StaticWebAsset Asset, string OldIdentity, string OldBasePath) Mat | |
| if (!File.Exists(destPath) || File.GetLastWriteTimeUtc(sourceFile) > File.GetLastWriteTimeUtc(destPath)) | ||
| { | ||
| File.Copy(sourceFile, destPath, overwrite: true); | ||
| log.LogMessage(MessageImportance.Low, "Materialized framework asset '{0}' to '{1}'.", sourceFile, destPath); | ||
| log.LogMessage(MessageImportance.Low, "Materialized framework asset '{0}' to '{1}'.", sourceFile.OriginalValue, destPath); | ||
| } | ||
| else | ||
| { | ||
| log.LogMessage(MessageImportance.Low, "Framework asset '{0}' already up to date at '{1}'.", sourceFile, destPath); | ||
| log.LogMessage(MessageImportance.Low, "Framework asset '{0}' already up to date at '{1}'.", sourceFile.OriginalValue, destPath); | ||
| } | ||
|
|
||
| asset.Identity = destPath; | ||
| asset.OriginalItemSpec = destPath; | ||
| asset.ContentRoot = NormalizeContentRootPath(fxDir); | ||
| asset.ContentRoot = NormalizeContentRootPath(fxDir, env); | ||
| asset.SourceType = SourceTypes.Discovered; | ||
| asset.SourceId = projectPackageId; | ||
| asset.BasePath = projectBasePath; | ||
| asset.AssetMode = AssetModes.CurrentProject; | ||
| asset.AssetGroups = ""; | ||
| asset.Normalize(); | ||
| asset.Normalize(env); | ||
|
|
||
| return (asset, oldIdentity, oldBasePath); | ||
| } | ||
|
|
@@ -1597,16 +1635,21 @@ internal string EmbedTokens(string relativePath) | |
| return pattern.RawPattern.ToString(); | ||
| } | ||
|
|
||
| internal FileInfo ResolveFile() => ResolveFile(Identity, OriginalItemSpec); | ||
| internal FileInfo ResolveFile() => ResolveFile(Identity, OriginalItemSpec, TaskEnvironment.Fallback); | ||
|
|
||
| internal FileInfo ResolveFile(TaskEnvironment env) => ResolveFile(Identity, OriginalItemSpec, env); | ||
|
|
||
| internal static FileInfo ResolveFile(string identity, string originalItemSpec) | ||
| => ResolveFile(identity, originalItemSpec, TaskEnvironment.Fallback); | ||
|
|
||
| internal static FileInfo ResolveFile(string identity, string originalItemSpec, TaskEnvironment env) | ||
| { | ||
| var fileInfo = new FileInfo(identity); | ||
| var fileInfo = new FileInfo(string.IsNullOrEmpty(identity) ? identity: env.GetAbsolutePath(identity)); | ||
| if (fileInfo.Exists) | ||
| { | ||
| return fileInfo; | ||
| } | ||
| fileInfo = new FileInfo(originalItemSpec); | ||
| fileInfo = new FileInfo(string.IsNullOrEmpty(originalItemSpec) ? originalItemSpec : env.GetAbsolutePath(originalItemSpec)); | ||
| if (fileInfo.Exists) | ||
| { | ||
| return fileInfo; | ||
|
|
@@ -1616,23 +1659,29 @@ internal static FileInfo ResolveFile(string identity, string originalItemSpec) | |
| } | ||
|
|
||
| internal static Dictionary<string, StaticWebAsset> ToAssetDictionary(ITaskItem[] candidateAssets, bool validate = false) | ||
| => ToAssetDictionary(candidateAssets, TaskEnvironment.Fallback, validate); | ||
|
|
||
| internal static Dictionary<string, StaticWebAsset> ToAssetDictionary(ITaskItem[] candidateAssets, TaskEnvironment env, bool validate = false) | ||
| { | ||
| var dictionary = new Dictionary<string, StaticWebAsset>(candidateAssets.Length); | ||
| for (var i = 0; i < candidateAssets.Length; i++) | ||
| { | ||
| var candidateAsset = FromTaskItem(candidateAssets[i], validate); | ||
| var candidateAsset = FromTaskItem(candidateAssets[i], env, validate); | ||
| dictionary.Add(candidateAsset.Identity, candidateAsset); | ||
| } | ||
|
|
||
| return dictionary; | ||
| } | ||
|
|
||
| internal static StaticWebAsset[] FromTaskItemGroup(ITaskItem[] candidateAssets, bool validate = false) | ||
| => FromTaskItemGroup(candidateAssets, TaskEnvironment.Fallback, validate); | ||
|
|
||
| internal static StaticWebAsset[] FromTaskItemGroup(ITaskItem[] candidateAssets, TaskEnvironment env, bool validate = false) | ||
| { | ||
| var result = new StaticWebAsset[candidateAssets.Length]; | ||
| for (var i = 0; i != result.Length; i++) | ||
| { | ||
| var candidateAsset = FromTaskItem(candidateAssets[i], validate); | ||
| var candidateAsset = FromTaskItem(candidateAssets[i], env, validate); | ||
| result[i] = candidateAsset; | ||
| } | ||
| return result; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the entry point if you're looking at the PR