Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project assembly references #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ If you want to extend Blazor itself, the setup is a bit more involved.
#### Instructions

* Clone this repo
* In a command prompt at the root of the repo, run `dotnet restore Blazor.Core.sln`
* In a command prompt at the root of the repo, run `dotnet build src\BuildUtils\ILStrip\ILStrip.sln & dotnet restore Blazor.Core.sln`
* Now open `Blazor.Core.sln` in VS2017.3 or later
* Expand `samples`, then `ClientServerApp`, then right click on `ClientServerApp.Server` and choose "*Set as StartUp Project*".
* Launch the application using Ctrl+F5
Expand Down
144 changes: 97 additions & 47 deletions src/Blazor.Compiler/ProjectReferenceUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,86 +10,136 @@ namespace Blazor.Compiler
{
internal static class ProjectReferenceUtil
{

/// <summary>
/// Inspects the project.assets.json file in a .NET Core project and attempts to determine
/// Inspects the project.assets.json file and .csproj files in a .NET Core project and attempts to determine
/// which .dll files are referenced in its compilation
/// </summary>
public static string[] FindReferencedAssemblies(string copyFromProjectRoot, OptimizationLevel optimizationLevel)
{
var assetsJsonPath = Path.Combine(copyFromProjectRoot, "obj", "project.assets.json");
if (!File.Exists(assetsJsonPath))
{
public static string[] FindReferencedAssemblies(string projectRoot, OptimizationLevel optimizationLevel) {
return assembliesFromProjectAssets(projectRoot, optimizationLevel)
.Concat(assembliesFromCSProj(projectRoot, optimizationLevel))
.Select(path => path.Replace('/', Path.DirectorySeparatorChar))
.Distinct(new FilenameComparer()) // TODO: Make sure you pick the most recent version of each assembly, not just an arbitrary one
.ToArray();
}

private static IEnumerable<string> assembliesFromProjectAssets(string projectRoot, OptimizationLevel optimizationLevel) {
var assetsJsonPath = Path.Combine(projectRoot, "obj", "project.assets.json");
if(!File.Exists(assetsJsonPath)) {
throw new FileNotFoundException($"Cannot local project assets file. Maybe the referenced project hasn't been restored yet. Searched for: {assetsJsonPath}");
}

var assetsInfo = (JsonDict)Json.Deserialize(File.ReadAllText(assetsJsonPath));

var packageFolders = ((JsonDict)assetsInfo["packageFolders"]).Keys.ToList();
if (!packageFolders.Any())
{
if(!packageFolders.Any()) {
throw new InvalidDataException($"Expected to find package folders, but found none.");
}

var targets = ((JsonDict)assetsInfo["targets"]);
if (targets.Count != 1)
{
if(targets.Count != 1) {
throw new InvalidDataException($"Expected to find exactly 1 target, but found {targets.Count}.");
}
var target = (Dictionary<string, object>)targets.Values.Single();

var project = (JsonDict)assetsInfo["project"];
var frameworks = ((JsonDict)project["frameworks"]);
if (frameworks.Count != 1)
{
if(frameworks.Count != 1) {
throw new InvalidDataException($"Expected to find exactly 1 frameworks, but found {frameworks.Count}.");
}
var framework = frameworks.Single();
var binDir = Path.Combine(copyFromProjectRoot, "bin", optimizationLevel.ToString(), framework.Key);
var binDir = Path.Combine(projectRoot, "bin", optimizationLevel.ToString(), framework.Key);

var referenceAssemblies = target.SelectMany(referenceKvp =>
{
var reference = (JsonDict)referenceKvp.Value;
if (reference.TryGetValue("compile", out var compileInfo))
{
var compileItems = ((JsonDict)compileInfo).Keys.Where(item => item.EndsWith(".dll"));
var referenceType = (string)reference["type"];
if (referenceType == "package")
{
var packageNameAndVersion = referenceKvp.Key;

return compileItems.Select(item =>
{
var partialPath = Path.Combine(packageNameAndVersion, item);
foreach (var packageFolder in packageFolders)
{
var candidateFilename = Path.Combine(packageFolder, partialPath);
if (File.Exists(candidateFilename))
{
return candidateFilename;
}
return compiledReferences(target, packageFolders, binDir);
}



private static IEnumerable<string> compiledReferences(IDictionary<string, object> references, IList<string> packageFolders, string binDir) {
foreach(var (packageNameAndVersion, reference) in references) {
var referenceInfo = (JsonDict)reference;
if(referenceInfo.TryGetValue("compile", out var compileInfo)) {
var compileItems = ((JsonDict)compileInfo).Keys.Where(item => Path.GetExtension(item) == ".dll");
switch(referenceInfo["type"]) {
case "package":
foreach(var item in compileItems) {
var partialPath = Path.Combine(packageNameAndVersion, item);
yield return packageFolders
.Select(getCandidate)
.FirstOrDefault(File.Exists)
?? throw new InvalidDataException($"Could not find {partialPath} in any of the package folders:\n" +
$"{String.Join('\n', packageFolders)}");

string getCandidate(string packageFolder) => Path.Combine(packageFolder, partialPath);
}
break;
case "project":
foreach(var item in compileItems) {
yield return item.Replace("bin/placeholder", binDir);
}
break;
}
}
}
}

private static IEnumerable<string> assembliesFromCSProj(string projectRoot, OptimizationLevel optimizationLevel) {
string projectName = Path.GetFileName(projectRoot.TrimEnd(Path.DirectorySeparatorChar));
var csProjPath = Path.Combine(projectRoot, projectName + ".csproj");
if(!File.Exists(csProjPath)) {
throw new FileNotFoundException($"Could not find csproj for {projectRoot}");
}

var projInfo = new AngleSharp.Parser.Xml.XmlParser().Parse(File.ReadAllText(csProjPath));

var projectNode = elementsOf(projInfo, "Project").Single();

string binDir;
{
var propertyGroup = elementsOf(projectNode, "PropertyGroup").Single();
var frameworkNode = elementsOf(propertyGroup, "TargetFramework").Single();
var targetFramework = frameworkNode.TextContent;
binDir = Path.Combine(projectRoot, "bin", optimizationLevel.ToString(), targetFramework);
}

throw new InvalidDataException($"Could not find {partialPath} in any of the package folders:\n{string.Join('\n', packageFolders)}");
});
foreach(var itemGroup in elementsOf(projectNode, "ItemGroup")) {
foreach(var reference in elementsOf(itemGroup, "Reference")) {
var includeAttr = reference.Attributes.Single(x => x.Name == "Include");

//Look in bin dir first, else try looking for the hint
var assemblyPath = Path.Combine(binDir, includeAttr.Value + ".dll"); //TODO is it possible to reference others eg. .exe
if(File.Exists(assemblyPath)) {
yield return assemblyPath;
continue;
}
else if (referenceType == "project")
{
return compileItems.Select(item => item.Replace("bin/placeholder", binDir));
else {
var hint = elementsOf(reference, "HintPath")
.SingleOrDefault()?.TextContent;
if(!String.IsNullOrWhiteSpace(hint)) {
assemblyPath = Path.Combine(projectRoot, hint);
if(File.Exists(assemblyPath)) {
yield return assemblyPath;
continue;
}
}
}
throw new InvalidDataException($"Could not find {includeAttr.Value} in the bin directory \"{binDir}\"");
}

return Enumerable.Empty<string>();
});
//foreach(var projReference in elementsOf(itemGroup, "ProjectReference")) {
// The project references should be picked up from the project assets. The csproj is only needed for direct dll references.
//}
}

return referenceAssemblies
.Select(path => path.Replace('/', Path.DirectorySeparatorChar))
.Distinct(new FilenameComparer()) // TODO: Make sure you pick the most recent version of each assembly, not just an arbitrary one
.ToArray();

IEnumerable<AngleSharp.Dom.Xml.XmlElement> elementsOf(AngleSharp.Dom.INode node, string nodeName) {
return node.ChildNodes.OfType<AngleSharp.Dom.Xml.XmlElement>().Where(x => x.NodeName == nodeName);
}
}


private class FilenameComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y) => string.Equals(
public bool Equals(string x, string y) => String.Equals(
Path.GetFileName(x),
Path.GetFileName(y),
StringComparison.OrdinalIgnoreCase);
Expand Down