| File: StateFileBase.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; #nullable disable namespace Microsoft.Build.Tasks { /// <remarks> /// Base class for task state files. /// </remarks> internal abstract class StateFileBase { // Current version for serialization. This should be changed when breaking changes // are made to this class. // Note: Consider that changes can break VS2015 RTM which did not have a version check. // Version 4/5 - VS2017.7: // Unify .NET Core + Full Framework. Custom serialization on some types that are no // longer [Serializable]. internal const byte CurrentSerializationVersion = 6; // Version this instance is serialized with. private byte _serializedVersion = CurrentSerializationVersion; /// <summary> /// True if <see cref="SerializeCache(string, TaskLoggingHelper, bool)"/> should create the state file and serialize ourselves, false otherwise. /// </summary> internal virtual bool HasStateToSave => true; /// <summary> /// Writes the contents of this object out to the specified file. /// </summary> /// <remarks> /// Prioritize using the AbsolutePath overload of this method. This method is still used by unenlightened tasks, but new code should use the AbsolutePath overload. /// Delete this method once all tasks have been migrated to the AbsolutePath overload. /// </remarks> internal virtual void SerializeCache(string stateFile, TaskLoggingHelper log, bool serializeEmptyState = false) { if (string.IsNullOrEmpty(stateFile)) { return; } SerializeCache(new AbsolutePath(stateFile, ignoreRootedCheck: true), log, serializeEmptyState); } /// <summary> /// Writes the contents of this object out to the specified file. /// </summary> internal virtual void SerializeCache(AbsolutePath stateFile, TaskLoggingHelper log, bool serializeEmptyState = false) { try { if (!string.IsNullOrEmpty(stateFile)) { if (FileSystems.Default.FileExists(stateFile)) { File.Delete(stateFile); } if (serializeEmptyState || HasStateToSave) { using (var s = new FileStream(stateFile, FileMode.CreateNew)) { var translator = BinaryTranslator.GetWriteTranslator(s); translator.Translate(ref _serializedVersion); Translate(translator); } } } } // If there was a problem writing the file (like it's read-only or locked on disk, for // example), then eat the exception and log a warning. Otherwise, rethrow. catch (Exception e) when (!ExceptionHandling.NotExpectedSerializationException(e)) { // Not being able to serialize the cache is not an error, but we let the user know anyway. // Don't want to hold up processing just because we couldn't read the file. log.LogWarningWithCodeFromResources("General.CouldNotWriteStateFile", stateFile.OriginalValue, e.Message); } } public abstract void Translate(ITranslator translator); /// <summary> /// Reads the specified file from disk into a StateFileBase derived object. /// stateFile should be absolute path to the file on disk. /// </summary> /// <remarks> /// Prioritize using the AbsolutePath overload of this method. This method is still used by unenlightened tasks, but new code should use the AbsolutePath overload. /// </remarks> internal static T DeserializeCache<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string stateFile, TaskLoggingHelper log) where T : StateFileBase { if (string.IsNullOrEmpty(stateFile)) { return null; } return DeserializeCache<T>(new AbsolutePath(stateFile, ignoreRootedCheck: true), log); } /// <summary> /// Reads the specified file from disk into a StateFileBase derived object. /// </summary> internal static T DeserializeCache<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(AbsolutePath stateFile, TaskLoggingHelper log) where T : StateFileBase { T retVal = null; // First, we read the cache from disk if one exists, or if one does not exist, we create one. try { if (!string.IsNullOrEmpty(stateFile) && FileSystems.Default.FileExists(stateFile)) { using (FileStream s = File.OpenRead(stateFile)) { using var translator = BinaryTranslator.GetReadTranslator(s, InterningBinaryReader.PoolingBuffer); byte version = 0; translator.Translate(ref version); // If the version is wrong, log a message not a warning. This could be a valid cache with the wrong version preventing correct deserialization. // For the latter case, internals may be unexpectedly null. if (version != CurrentSerializationVersion) { log.LogMessageFromResources("General.CouldNotReadStateFileMessage", stateFile.OriginalValue, log.FormatResourceString("General.IncompatibleStateFileType")); return null; } var constructors = typeof(T).GetConstructors(); foreach (var constructor in constructors) { var parameters = constructor.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ITranslator)) { retVal = constructor.Invoke([translator]) as T; } } if (retVal == null) { log.LogMessageFromResources("General.CouldNotReadStateFileMessage", stateFile.OriginalValue, log.FormatResourceString("General.IncompatibleStateFileType")); } } } } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { // The deserialization process seems like it can throw just about // any exception imaginable. Catch them all here. // Not being able to deserialize the cache is not an error, but we let the user know anyway. // Don't want to hold up processing just because we couldn't read the file. log.LogMessageFromResources("General.CouldNotReadStateFileMessage", stateFile.OriginalValue, e.Message); } return retVal; } /// <summary> /// Deletes the state file from disk /// </summary> /// <param name="stateFile"></param> /// <param name="log"></param> internal static void DeleteFile(string stateFile, TaskLoggingHelper log) { try { if (!string.IsNullOrEmpty(stateFile)) { if (FileSystems.Default.FileExists(stateFile)) { File.Delete(stateFile); } } } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { log.LogWarningWithCodeFromResources("General.CouldNotDeleteStateFile", stateFile, e.Message); } } } }