| File: UserFolderCachePath.cs | Web Access |
| Project: Microsoft.NET.ProjectData.csproj (Microsoft.NET.ProjectData) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; namespace Microsoft.NET.ProjectData; /// <summary> /// Computes the user-folder location for a project's <c>.lscache</c> file. /// /// <para>This file is the single source of truth for the layout. The MSBuild /// task (writer/merger) and the runtime reader both call <see cref="Compute"/> /// so the path the writer produces is exactly the path the reader looks for. /// The Tasks project links this source file into its own assembly via a /// <c><Compile Include=...></c> reference rather than taking a project /// dependency on the cache assembly.</para> /// </summary> public static class UserFolderCachePath { private const string CacheFileExtension = ".lscache"; private const string CacheBaseDirectoryEnvVar = "DOTNET_PROJECTDATA_CACHE_DIR"; private const string XdgCacheHomeEnvVar = "XDG_CACHE_HOME"; private const string CacheDirectoryName = "dotnet-projectdata"; /// <summary> /// Returns the absolute path of the user-folder cache file for /// <paramref name="projectFilePath"/>. Layout (matches the v1 reader): /// <c><base>/<sha1[0..2]>/<sha1[2..]></c> where the SHA-1 /// hash is taken over the lower-cased project path on case-insensitive /// filesystems (Windows / macOS) and the original casing on Linux. /// </summary> public static string Compute(string projectFilePath) { if (string.IsNullOrEmpty(projectFilePath)) throw new ArgumentException("Project file path must be non-empty.", nameof(projectFilePath)); bool caseSensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); string hashInput = caseSensitive ? projectFilePath : projectFilePath.ToLowerInvariant(); byte[] inputBytes = Encoding.UTF8.GetBytes(hashInput); byte[] hashBytes; #if NET5_0_OR_GREATER hashBytes = SHA1.HashData(inputBytes); #else using (var sha1 = SHA1.Create()) { hashBytes = sha1.ComputeHash(inputBytes); } #endif string hex = HexEncoder.ToLowerHex(hashBytes); string baseDir = GetCacheBaseDirectory(); return Path.Combine(baseDir, hex.Substring(0, 2), hex.Substring(2)); } internal static bool TryCompute(string projectFilePath, out string cacheFilePath) { try { cacheFilePath = Compute(projectFilePath); return true; } catch (InvalidOperationException) { cacheFilePath = string.Empty; return false; } } /// <summary> /// Returns the cache root directory used by <see cref="Compute"/>. /// /// <para>Set <c>DOTNET_PROJECTDATA_CACHE_DIR</c> to override the cache root /// exactly. Otherwise the cache is stored under the platform's user-local /// cache directory.</para> /// </summary> public static string GetCacheBaseDirectory() { string? envOverride = Environment.GetEnvironmentVariable(CacheBaseDirectoryEnvVar); if (!string.IsNullOrWhiteSpace(envOverride)) { return envOverride!; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); if (!string.IsNullOrWhiteSpace(localAppData)) { return Path.Combine(localAppData, "Microsoft", CacheDirectoryName); } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); if (!string.IsNullOrWhiteSpace(home)) { return Path.Combine(home, "Library", "Caches", CacheDirectoryName); } } else { string? xdgCacheHome = Environment.GetEnvironmentVariable(XdgCacheHomeEnvVar); if (!string.IsNullOrWhiteSpace(xdgCacheHome)) { return Path.Combine(xdgCacheHome!, CacheDirectoryName); } string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); if (!string.IsNullOrWhiteSpace(home)) { return Path.Combine(home, ".cache", CacheDirectoryName); } } throw new InvalidOperationException($"Unable to determine the ProjectData cache root. Set {CacheBaseDirectoryEnvVar}."); } /// <summary>The cache file extension (currently <c>.lscache</c>).</summary> public static string FileExtension => CacheFileExtension; }