| File: LockFileCache.cs | Web Access |
| Project: src\sdk\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using Microsoft.Build.Framework; using NuGet.Common; using NuGet.ProjectModel; namespace Microsoft.NET.Build.Tasks { internal class LockFileCache { private IBuildEngine4 _buildEngine; private Logger _log; public LockFileCache(TaskBase task) { _buildEngine = task.BuildEngine4; _log = task.Log; } public LockFile GetLockFile(string path) { if (string.IsNullOrEmpty(path)) { throw new BuildErrorException(Strings.AssetsFileNotSet); } if (!Path.IsPathRooted(path)) { throw new BuildErrorException(Strings.AssetsFilePathNotRooted, path); } string lockFileKey = GetTaskObjectKey(path); LockFile result; object existingLockFileTaskObject = _buildEngine?.GetRegisteredTaskObject(lockFileKey, RegisteredTaskObjectLifetime.Build); if (existingLockFileTaskObject == null) { result = LoadLockFile(path); _buildEngine?.RegisterTaskObject(lockFileKey, result, RegisteredTaskObjectLifetime.Build, true); } else { result = (LockFile)existingLockFileTaskObject; } return result; } private static string GetTaskObjectKey(string lockFilePath) { return $"{nameof(LockFileCache)}:{lockFilePath}"; } private LockFile LoadLockFile(string path) { // https://github.com/NuGet/Home/issues/6732 // // LockFileUtilties.GetLockFile has odd error handling: // // 1. Exceptions creating TextReader from path (after up to 3 tries) will // bubble out. // // 2. There's an up-front File.Exists that returns null without logging // anything. // // 3. Any other exception whatsoever is logged by its Message property // alone, and an empty, non-null lock file is returned. // // This wrapper will never return null or empty lock file and instead throw // if the assets file is not found or cannot be read for any other reason. LockFile lockFile; try { lockFile = LockFileUtilities.GetLockFile( path, new ThrowOnLockFileLoadError(_log)); } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { // Case 1 throw new BuildErrorException( string.Format(Strings.ErrorReadingAssetsFile, ex.Message), ex); } if (lockFile == null) { // Case 2 // NB: Cannot be moved to our own up-front File.Exists check or else there would be // a race where we still need to handle null for delete between our check and // NuGet's. throw new BuildErrorException(Strings.AssetsFileNotFound, path); } return lockFile; } // Case 3 // Force an exception on errors reading the lock file // Non-errors are not logged today, but push them to the build log in case they are in the future. private sealed class ThrowOnLockFileLoadError : LoggerBase { private Logger _log; public ThrowOnLockFileLoadError(Logger log) { _log = log; } public override void Log(ILogMessage message) { if (message.Level == LogLevel.Error) { throw new BuildErrorException( string.Format(Strings.ErrorReadingAssetsFile, message.Message)); } _log.Log( new Message( level: message.Level == LogLevel.Warning ? MessageLevel.Warning : MessageLevel.NormalImportance, code: message.Code == NuGetLogCode.Undefined ? default : message.Code.ToString(), file: message.ProjectPath, text: message.Message)); } public override System.Threading.Tasks.Task LogAsync(ILogMessage message) { Log(message); return Task.CompletedTask; } } } }