File: src\GenerateSharedFrameworkDepsFile.cs
Web Access
Project: Microsoft.DotNet.SharedFramework.Sdk.csproj (Microsoft.DotNet.SharedFramework.Sdk)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Extensions.DependencyModel;
using NuGet.RuntimeModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Microsoft.DotNet.SharedFramework.Sdk;

[MSBuildMultiThreadableTask]
public class GenerateSharedFrameworkDepsFile : Task, IMultiThreadableTask
{
    /// <summary>Injected by MSBuild so paths resolve against the project directory in multithreaded builds.</summary>
    public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback;

    [Required]
    public string TargetFrameworkMoniker { get; set; }

    [Required]
    public string RuntimeIdentifier { get; set; }

    [Required]
    public string SharedFrameworkName { get; set; }

    [Required]
    public string SharedFrameworkPackName { get; set; }

    [Required]
    public string Version { get; set; }

    [Required]
    public ITaskItem[] Files { get; set; }

    [Required]
    public string IntermediateOutputPath { get; set; }

    public string SharedFrameworkDepsNameOverride { get; set; }

    public string RuntimeIdentifierGraph { get; set; }

    public bool IncludeFallbacksInDepsFile { get; set; }

    [Output]
    public ITaskItem GeneratedDepsFile { get; set; }

    public override bool Execute()
    {
        var target = new TargetInfo(TargetFrameworkMoniker, RuntimeIdentifier, string.Empty, isPortable: false);
        var runtimeFiles = new List<RuntimeFile>();
        var nativeFiles = new List<RuntimeFile>();
        var resourceAssemblies = new List<ResourceAssembly>();

        foreach (var file in Files)
        {
            if (!string.IsNullOrEmpty(file.GetMetadata("GeneratedBuildFile")))
            {
                continue;
            }
            string filePath = file.ItemSpec;
            string fileName = Path.GetFileName(filePath);
            FileInfo absoluteFilePath = new(TaskEnvironment.GetAbsolutePath(filePath));
            string fileVersion = FileUtilities.GetFileVersion(absoluteFilePath)?.ToString() ?? string.Empty;
            Version assemblyVersion = FileUtilities.GetAssemblyName(absoluteFilePath)?.Version;
            string cultureMaybe = file.GetMetadata("Culture");
            if (!string.IsNullOrEmpty(cultureMaybe))
            {
                resourceAssemblies.Add(new ResourceAssembly(Path.Combine(cultureMaybe, fileName), cultureMaybe));
            }
            else if (assemblyVersion == null)
            {
                var nativeFile = new RuntimeFile(fileName, null, fileVersion);
                nativeFiles.Add(nativeFile);
            }
            else
            {
                var runtimeFile = new RuntimeFile(fileName,
                    fileVersion: fileVersion,
                    assemblyVersion: assemblyVersion.ToString());
                runtimeFiles.Add(runtimeFile);
            }
        }

        var runtimeLibrary = new RuntimeLibrary("package",
           SharedFrameworkPackName,
           Version,
           hash: string.Empty,
           runtimeAssemblyGroups: new[] { new RuntimeAssetGroup(string.Empty, runtimeFiles) },
           nativeLibraryGroups: new[] { new RuntimeAssetGroup(string.Empty, nativeFiles) },
           resourceAssemblies,
           Array.Empty<Dependency>(),
           hashPath: null,
           path: $"{SharedFrameworkPackName.ToLowerInvariant()}/{Version}",
           serviceable: true);

        IEnumerable<RuntimeFallbacks> runtimeFallbackGraph = Array.Empty<RuntimeFallbacks>();

        if (IncludeFallbacksInDepsFile)
        {
            // RuntimeIdentifierGraph is optional; only resolve it when set so an unset value keeps
            // producing the existing error from ReadRuntimeGraph rather than throwing from GetAbsolutePath.
            string runtimeIdentifierGraphPath = string.IsNullOrEmpty(RuntimeIdentifierGraph)
                ? RuntimeIdentifierGraph
                : TaskEnvironment.GetAbsolutePath(RuntimeIdentifierGraph);
            RuntimeGraph runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(runtimeIdentifierGraphPath);
            runtimeFallbackGraph = runtimeGraph.Runtimes
                    .Select(runtimeDict => runtimeGraph.ExpandRuntime(runtimeDict.Key))
                    .Where(expansion => expansion.Contains(RuntimeIdentifier))
                    .Select(expansion => new RuntimeFallbacks(expansion.First(), expansion.Skip(1))); // ExpandRuntime return runtime itself as first item.
        }

        var context = new DependencyContext(target,
            CompilationOptions.Default,
            Enumerable.Empty<CompilationLibrary>(),
            new[] { runtimeLibrary },
            runtimeFallbackGraph);

        var depsFileName = string.IsNullOrEmpty(SharedFrameworkDepsNameOverride) ? $"{SharedFrameworkName}.deps.json" : $"{SharedFrameworkDepsNameOverride}.deps.json";

        var depsFilePath = Path.Combine(IntermediateOutputPath, depsFileName);
        try
        {
            using var depsStream = File.Create(TaskEnvironment.GetAbsolutePath(depsFilePath));
            new DependencyContextWriter().Write(context, depsStream);
            GeneratedDepsFile = new TaskItem(depsFilePath);
        }
        catch (Exception ex)
        {
            // If there is a problem, ensure we don't write a partially complete version to disk.
            AbsolutePath absoluteDepsFilePath = TaskEnvironment.GetAbsolutePath(depsFilePath);
            if (File.Exists(absoluteDepsFilePath))
            {
                File.Delete(absoluteDepsFilePath);
            }
            Log.LogErrorFromException(ex, false);
            return false;
        }
        return true;
    }
}