File: WasmLoadAssembliesAndReferences.cs
Web Access
Project: src\src\tasks\WasmAppBuilder\WasmAppBuilder.csproj (WasmAppBuilder)
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
 
namespace Microsoft.WebAssembly.Build.Tasks;
 
public class WasmLoadAssembliesAndReferences : Task
{
    [Required]
    [NotNull]
    public string[]? Assemblies { get; set; }
 
    [Required]
    [NotNull]
    public string[]? AssemblySearchPaths { get; set; }
 
    // If true, continue when a referenced assembly cannot be found.
    public bool SkipMissingAssemblies { get; set; }
 
    // The set of assemblies the app will use
    [Output]
    public string[]? ReferencedAssemblies { get; private set; }
 
    private SortedDictionary<string, Assembly> _assemblies = new();
 
    public override bool Execute ()
    {
        string? badPath = AssemblySearchPaths.FirstOrDefault(path => !Directory.Exists(path));
        if (badPath != null)
        {
            Log.LogError($"Directory '{badPath}' in AssemblySearchPaths does not exist or is not a directory.");
            return false;
        }
 
        SearchPathsAssemblyResolver resolver = new(AssemblySearchPaths);
        MetadataLoadContext mlc = new(resolver, "System.Private.CoreLib");
        foreach (var asm in Assemblies)
        {
            var asmFullPath = Path.GetFullPath(asm);
            if (!File.Exists(asmFullPath))
            {
                Log.LogError($"Could not find assembly '{asmFullPath}'");
                return false;
            }
 
            var refAssembly = mlc.LoadFromAssemblyPath(asmFullPath);
            if (!AddAssemblyAndReferences(mlc, refAssembly))
                return !Log.HasLoggedErrors;
        }
 
        ReferencedAssemblies = _assemblies.Values.Select(asm => asm.Location).ToArray();
        return !Log.HasLoggedErrors;
    }
 
    private bool AddAssemblyAndReferences(MetadataLoadContext mlc, Assembly assembly)
    {
        if (_assemblies.ContainsKey(assembly.GetName().Name!))
            return true;
 
        _assemblies[assembly.GetName().Name!] = assembly;
        foreach (var aname in assembly.GetReferencedAssemblies())
        {
            try
            {
                Assembly refAssembly = mlc.LoadFromAssemblyName(aname);
                if (!AddAssemblyAndReferences(mlc, refAssembly))
                    return false;
            }
            catch (Exception ex) when (ex is FileLoadException || ex is BadImageFormatException || ex is FileNotFoundException)
            {
                if (SkipMissingAssemblies)
                {
                    Log.LogWarning(null, "WASM0004", "", "", 0, 0, 0, 0, $"Loading assembly reference '{aname}' for '{assembly.GetName()}' failed: {ex.Message} Skipping.");
                }
                else
                {
                    Log.LogError($"Failed to load assembly reference '{aname}' for '{assembly.GetName()}': {ex.Message}");
                    return false;
                }
            }
        }
 
        return true;
    }
}
 
internal sealed class SearchPathsAssemblyResolver : MetadataAssemblyResolver
{
    private readonly string[] _searchPaths;
 
    public SearchPathsAssemblyResolver(string[] searchPaths) => _searchPaths = searchPaths;
 
    public override Assembly? Resolve(MetadataLoadContext context, AssemblyName assemblyName)
    {
        string? name = assemblyName.Name;
        foreach (var dir in _searchPaths)
        {
            string path = Path.Combine(dir, name + ".dll");
            if (File.Exists(path))
                return context.LoadFromAssemblyPath(path);
        }
        return null;
    }
}