0

I'm writing a metadata extractor for C# projects. It works by creating SyntaxTrees from source files via CSharpSyntaxTree.ParseText(), compiling them via CSharpCompilation.Create() and then analyzing symbols in the SemanticModel.

I'm running into an issue where various assembly references aren't being handled correctly.

I generate assembly references by parsing project.assets.json and extracting the compile-time package references (my test project doesn't include any project references). The targets section of project.assets.json looks like this:

"targets": {
".NETStandard,Version=v2.1": {
  "Autofac/5.1.2": {
    "type": "package",
    "compile": {
      "lib/netstandard2.1/Autofac.dll": {}
    },
    "runtime": {
      "lib/netstandard2.1/Autofac.dll": {}
    }
  },
  "Microsoft.NETCore.Platforms/1.1.0": {
    "type": "package",
    "compile": {
      "lib/netstandard1.0/_._": {}
    },
    "runtime": {
      "lib/netstandard1.0/_._": {}
    }
  },
  "Microsoft.NETCore.Targets/1.1.0": {
    "type": "package",
    "compile": {
      "lib/netstandard1.0/_._": {}
    },
    "runtime": {
      "lib/netstandard1.0/_._": {}
    }
  },
  "Serilog/2.9.0": {
    "type": "package",
    "compile": {
      "lib/netstandard2.0/Serilog.dll": {}
    },
    "runtime": {
      "lib/netstandard2.0/Serilog.dll": {}
    }
  },
  "Serilog.Sinks.Console/3.1.1": {
    "type": "package",
    "dependencies": {
      "Serilog": "2.5.0",
      "System.Console": "4.3.0"
    },
    "compile": {
      "lib/netstandard1.3/Serilog.Sinks.Console.dll": {}
    },
    "runtime": {
      "lib/netstandard1.3/Serilog.Sinks.Console.dll": {}
    }
  },
  "Serilog.Sinks.File/4.1.0": {
    "type": "package",
    "dependencies": {
      "Serilog": "2.5.0",
      "System.IO.FileSystem": "4.0.1",
      "System.Text.Encoding.Extensions": "4.0.11",
      "System.Threading.Timer": "4.0.1"
    },
    "compile": {
      "lib/netstandard2.0/Serilog.Sinks.File.dll": {}
    },
    "runtime": {
      "lib/netstandard2.0/Serilog.Sinks.File.dll": {}
    }
  },
  "System.Console/4.3.0": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.1.0",
      "Microsoft.NETCore.Targets": "1.1.0",
      "System.IO": "4.3.0",
      "System.Runtime": "4.3.0",
      "System.Text.Encoding": "4.3.0"
    },
    "compile": {
      "ref/netstandard1.3/System.Console.dll": {}
    }
  },
  "System.IO/4.3.0": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.1.0",
      "Microsoft.NETCore.Targets": "1.1.0",
      "System.Runtime": "4.3.0",
      "System.Text.Encoding": "4.3.0",
      "System.Threading.Tasks": "4.3.0"
    },
    "compile": {
      "ref/netstandard1.5/System.IO.dll": {}
    }
  },
  "System.IO.FileSystem/4.0.1": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.0.1",
      "Microsoft.NETCore.Targets": "1.0.1",
      "System.IO": "4.1.0",
      "System.IO.FileSystem.Primitives": "4.0.1",
      "System.Runtime": "4.1.0",
      "System.Runtime.Handles": "4.0.1",
      "System.Text.Encoding": "4.0.11",
      "System.Threading.Tasks": "4.0.11"
    },
    "compile": {
      "ref/netstandard1.3/System.IO.FileSystem.dll": {}
    }
  },
  "System.IO.FileSystem.Primitives/4.0.1": {
    "type": "package",
    "dependencies": {
      "System.Runtime": "4.1.0"
    },
    "compile": {
      "ref/netstandard1.3/System.IO.FileSystem.Primitives.dll": {}
    },
    "runtime": {
      "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {}
    }
  },
  "System.Runtime/4.3.0": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.1.0",
      "Microsoft.NETCore.Targets": "1.1.0"
    },
    "compile": {
      "ref/netstandard1.5/System.Runtime.dll": {}
    }
  },
  "System.Runtime.Handles/4.0.1": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.0.1",
      "Microsoft.NETCore.Targets": "1.0.1",
      "System.Runtime": "4.1.0"
    },
    "compile": {
      "ref/netstandard1.3/System.Runtime.Handles.dll": {}
    }
  },
  "System.Text.Encoding/4.3.0": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.1.0",
      "Microsoft.NETCore.Targets": "1.1.0",
      "System.Runtime": "4.3.0"
    },
    "compile": {
      "ref/netstandard1.3/System.Text.Encoding.dll": {}
    }
  },
  "System.Text.Encoding.Extensions/4.0.11": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.0.1",
      "Microsoft.NETCore.Targets": "1.0.1",
      "System.Runtime": "4.1.0",
      "System.Text.Encoding": "4.0.11"
    },
    "compile": {
      "ref/netstandard1.3/System.Text.Encoding.Extensions.dll": {}
    }
  },
  "System.Threading.Tasks/4.3.0": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.1.0",
      "Microsoft.NETCore.Targets": "1.1.0",
      "System.Runtime": "4.3.0"
    },
    "compile": {
      "ref/netstandard1.3/System.Threading.Tasks.dll": {}
    }
  },
  "System.Threading.Timer/4.0.1": {
    "type": "package",
    "dependencies": {
      "Microsoft.NETCore.Platforms": "1.0.1",
      "Microsoft.NETCore.Targets": "1.0.1",
      "System.Runtime": "4.1.0"
    },
    "compile": {
      "ref/netstandard1.2/System.Threading.Timer.dll": {}
    }
  }
}
}

The resulting list of references is this:

lib/netstandard2.1/Autofac.dll
lib/netstandard1.0/_._
lib/netstandard1.0/_._
lib/netstandard2.0/Serilog.dll
lib/netstandard1.3/Serilog.Sinks.Console.dll
lib/netstandard2.0/Serilog.Sinks.File.dll
ref/netstandard1.3/System.Console.dll
ref/netstandard1.5/System.IO.dll
ref/netstandard1.3/System.IO.FileSystem.dll
ref/netstandard1.3/System.IO.FileSystem.Primitives.dll
ref/netstandard1.5/System.Runtime.dll
ref/netstandard1.3/System.Runtime.Handles.dll
ref/netstandard1.3/System.Text.Encoding.dll
ref/netstandard1.3/System.Text.Encoding.Extensions.dll
ref/netstandard1.3/System.Threading.Tasks.dll
ref/netstandard1.2/System.Threading.Timer.dll

I ignore the two oddly-formatted netstandard1.0 ones, replacing them with a reference created by MetadataReference.CreateFromFile( Assembly.Load( "netstandard" ).Location ) ), and resolve the remaining paths against the local nuget repositories, creating references from the dlls.

This mostly works fine...but certain things don't resolve. Here are the diagnostic messages I'm getting after compiling with CSharpCompilation.Create():

CS0103 - The name 'Assembly' does not exist in the current context

CS0103 - The name 'Path' does not exist in the current context

CS1069 - The type name 'ApplicationException' could not be found in the namespace 'System'. This type has been forwarded to assembly 'System.Runtime, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' Consider adding a reference to that assembly. SymbolExtractor::AnalyzeProject

CS0103 - The name 'Environment' does not exist in the current context

Interestingly, all these errors relate to one method in a static class in the project I'm trying to compile. I'm not sure if it's relevant but here's the code in question:

public static string DefineLocalAppDataLogPath( string fileStub, string folder = null )
{
    fileStub = IsFileNameValid( fileStub ) ? fileStub : "log.txt";

    if( string.IsNullOrEmpty( folder ) )
    {
        var assLoc = Assembly.GetExecutingAssembly().GetType().Assembly.Location;
            folder = Path.GetFileNameWithoutExtension( assLoc );
    }

    DirectoryInfo logDir = null;

    if( ( logDir = CreateLogFileDirectory( folder ) ) == null )
    {
        if( ( logDir = CreateLogFileDirectory( "LogFiles" ) ) == null )
            throw new ApplicationException(
                $"Couldn't create log file directory {folder} or the backup/default directory 'LogFiles'" );
    }

    return logDir.FullName;
}

What am I missing about resolving assembly dependencies? Do I need to also include references to the assemblies required at runtime as well?

Community
  • 1
  • 1
Mark Olbert
  • 6,584
  • 9
  • 35
  • 69
  • Those are compiler errors, not run-time errors. If you refer to it by name in your code, you must have a reference to it in your project. – jwdonahue Feb 25 '20 at 23:13
  • @jwdonahue, not sure what you mean. I realize they’re compiler errors — all I’m doing is compiling the code not running anything — but if there’s a needed assembly why isn’t it showing up in the targets section of project.assets.json? – Mark Olbert Feb 25 '20 at 23:50
  • "but if there’s a needed assembly why isn’t it showing up in the targets section of project.assets.json" seems like the question you should ask in your OP. This search result looks interesting: https://improveandrepeat.com/2019/10/how-to-generate-a-missing-project-assets-json-in-azure-devops/ – jwdonahue Feb 25 '20 at 23:57
  • If dotnet restore doesn't solve your problem, you may have to derive the additional information from your AST. – jwdonahue Feb 25 '20 at 23:59

0 Answers0