Skip to content

Commit

Permalink
Merge pull request GlitchEnzo#393 from nowsprinting/roslyn_analyzer_s…
Browse files Browse the repository at this point in the history
…upport

Add Roslyn analyzer package support
Allow System.Text.Json to be imported correctly
Change back NuGetForUnity.dll name to NugetForUnity.dll (need to be same as namespace)
  • Loading branch information
JoC0de authored Jan 7, 2023
2 parents bda44cc + 2556bfc commit 1a6b486
Show file tree
Hide file tree
Showing 49 changed files with 1,252 additions and 849 deletions.
14 changes: 14 additions & 0 deletions Assets/NuGet/Editor/NuGetForUnity.asmdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "NuGetForUnity",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
7 changes: 7 additions & 0 deletions Assets/NuGet/Editor/NuGetForUnity.asmdef.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions Assets/NuGet/Editor/NugetConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,10 @@ public static NugetConfigFile Load(string filePath)

if (!Path.IsPathRooted(configFile.RepositoryPath))
{
string repositoryPath = Path.Combine(UnityEngine.Application.dataPath, configFile.RepositoryPath);
repositoryPath = Path.GetFullPath(repositoryPath);

configFile.RepositoryPath = repositoryPath;
configFile.RepositoryPath = Path.Combine(UnityEngine.Application.dataPath, configFile.RepositoryPath);
}

configFile.RepositoryPath = Path.GetFullPath(configFile.RepositoryPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
}
else if (String.Equals(key, "DefaultPushSource", StringComparison.OrdinalIgnoreCase))
{
Expand Down
45 changes: 42 additions & 3 deletions Assets/NuGet/Editor/NugetHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
namespace NugetForUnity
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("NuGetForUnity.Editor.Tests")]

namespace NugetForUnity
{
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -56,6 +60,11 @@ public static class NugetHelper
/// </summary>
private static PackagesConfigFile packagesConfigFile;

/// <summary>
/// Gets the absolute path to the Unity-Project root directory.
/// </summary>
internal static string AbsoluteProjectPath { get; }

/// <summary>
/// Gets the loaded packages.config file that hold the dependencies for the project.
/// </summary>
Expand Down Expand Up @@ -107,6 +116,7 @@ private static ApiCompatibilityLevel DotNetVersion
/// </summary>
static NugetHelper()
{
AbsoluteProjectPath = Path.GetFullPath(Path.GetDirectoryName(Application.dataPath));
insideInitializeOnLoad = true;
try
{
Expand Down Expand Up @@ -569,6 +579,9 @@ private static HashSet<string> GetAlreadyImportedLibs()
.Select(Path.GetFileName)
.Select(p => Path.ChangeExtension(p, null));
alreadyImportedLibs = new HashSet<string>(libNames);

// workaround not sure why but it reported as missing when not added as NuGet package
alreadyImportedLibs.Remove("System.Runtime.CompilerServices.Unsafe");
LogVerbose("Already imported libs: {0}", string.Join(", ", alreadyImportedLibs));
}

Expand Down Expand Up @@ -1461,9 +1474,36 @@ public static bool Install(NugetPackage package, bool refreshAssets = true)
return installSuccess;
}

/// <summary>
/// Makes a given path relative to the current Unity-Projects directory.
/// </summary>
/// <param name="path">The path to make relative.</param>
/// <returns>The relative path.</returns>
internal static string GetProjectRelativePath(string path)
{
return GetRelativePath(AbsoluteProjectPath, path);
}

private static string GetRelativePath(string relativeTo, string path)
{
// Path.GetRelativePath is only available in newer .net versions so we need to implement it our self
if (path == null)
{
return null;
}

path = Path.GetFullPath(path);
relativeTo = Path.GetFullPath(relativeTo).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (!path.StartsWith(relativeTo, StringComparison.OrdinalIgnoreCase))
{
return path;
}

return path.Substring(relativeTo.Length);
}

private static void WarnIfDotNetAuthenticationIssue(Exception e)
{
#if !NET_4_6
WebException webException = e as WebException;
HttpWebResponse webResponse = webException != null ? webException.Response as HttpWebResponse : null;
if (webResponse != null && webResponse.StatusCode == HttpStatusCode.BadRequest && webException.Message.Contains("Authentication information is not given in the correct format"))
Expand All @@ -1472,7 +1512,6 @@ private static void WarnIfDotNetAuthenticationIssue(Exception e)
// Inform users when this occurs.
Debug.LogError("Authentication failed. This can occur due to a known issue in .NET 3.5. This can be fixed by changing Scripting Runtime to Experimental (.NET 4.6 Equivalent) in Player Settings.");
}
#endif
}

private struct AuthenticatedFeed
Expand Down
194 changes: 194 additions & 0 deletions Assets/NuGet/Editor/NugetPackageAssetPostprocessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace NugetForUnity
{
/// <summary>
/// Hook into the import pipeline to change NuGet package related assets after / before they are imported.
/// </summary>
public class NugetPackageAssetPostprocessor : AssetPostprocessor
{
/// <summary>
/// Folder used to store Roslyn-Analyzers inside NuGet packages.
/// </summary>
private const string AnalyzersFolderName = "analyzers";

/// <summary>
/// Used to mark an asset as already processed by this class.
/// </summary>
private const string ProcessedLabel = "NuGetForUnity";

/// <summary>
/// Used to let unity know an asset is a Roslyn-Analyzer.
/// </summary>
private const string RoslynAnalyzerLabel = "RoslynAnalyzer";

/// <summary>
/// Get informed about a new asset that is added.
/// This is called before unity tried to import a asset but the <see cref="AssetImporter"/> is already created
/// so we can change the import settings before unity throws errors about incompatibility etc..
/// Currently we change the import settings of:
/// Roslyn-Analyzers: are marked so unity knows that the *.dll's are analyzers and treats them accordingly.
/// </summary>
private void OnPreprocessAsset()
{
var absoluteRepositoryPath = GetNuGetRepositoryPath();
var result = HandleAsset(assetPath, absoluteRepositoryPath, false);
if (result.HasValue)
{
LogResults(new[] { result.Value });
}
}

private static readonly List<BuildTarget> NonObsoleteBuildTargets = typeof(BuildTarget)
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(fieldInfo => fieldInfo.GetCustomAttribute(typeof(ObsoleteAttribute)) == null)
.Select(fieldInfo => (BuildTarget)fieldInfo.GetValue(null))
.ToList();

private enum ResultStatus
{
Success,
Failure,
AlreadyProcessed
}

private static (string AssetType, string AssetPath, ResultStatus Status)? HandleAsset(string projectRelativeAssetPath, string absoluteRepositoryPath, bool reimport)
{
var absoluteAssetPath = Path.GetFullPath(Path.Combine(NugetHelper.AbsoluteProjectPath, projectRelativeAssetPath));
if (!AssetIsDllInsideNuGetRepository(absoluteAssetPath, absoluteRepositoryPath))
{
return null;
}

var assetPathRelativeToRepository = absoluteAssetPath.Substring(absoluteRepositoryPath.Length);
// the first component is the package name
var assetPathComponents = GetPathComponents(assetPathRelativeToRepository);
if (assetPathComponents.Length > 1 && assetPathComponents[1].Equals(AnalyzersFolderName, StringComparison.OrdinalIgnoreCase))
{
var result = ModifyImportSettingsOfRoslynAnalyzer(projectRelativeAssetPath, reimport);
return ("RoslynAnalyzer", projectRelativeAssetPath, result);
}

return null;
}

private static string[] GetPathComponents(string path)
{
return path.Split(Path.DirectorySeparatorChar);
}

private static bool AssetIsDllInsideNuGetRepository(string absoluteAssetPath, string absoluteRepositoryPath)
{
return absoluteAssetPath.StartsWith(absoluteRepositoryPath, StringComparison.OrdinalIgnoreCase)
&& absoluteAssetPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
&& File.Exists(absoluteAssetPath);
}

/// <summary>
/// Gets the absolute path where NuGetForUnity restores NuGet packages, with trailing directory separator.
/// </summary>
/// <returns>The absolute path where NuGetForUnity restores NuGet packages, with trailing directory separator.</returns>
private static string GetNuGetRepositoryPath()
{
if (NugetHelper.NugetConfigFile == null)
{
NugetHelper.LoadNugetConfigFile();
}

return NugetHelper.NugetConfigFile.RepositoryPath + Path.DirectorySeparatorChar;
}

private static ResultStatus ModifyImportSettingsOfRoslynAnalyzer(string analyzerAssetPath, bool reimport)
{
if (!GetPluginImporter(analyzerAssetPath, out var plugin))
{
return ResultStatus.Failure;
}

if (AlreadyProcessed(plugin))
{
return ResultStatus.AlreadyProcessed;
}

plugin.SetCompatibleWithAnyPlatform(false);
plugin.SetCompatibleWithEditor(false);
foreach (var platform in NonObsoleteBuildTargets)
{
plugin.SetExcludeFromAnyPlatform(platform, false);
}

AssetDatabase.SetLabels(plugin, new[] { RoslynAnalyzerLabel, ProcessedLabel });

if (reimport)
{
// Persist and reload the change to the meta file
plugin.SaveAndReimport();
}

NugetHelper.LogVerbose("Configured asset '{0}' as a Roslyn-Analyzer.", analyzerAssetPath);
return ResultStatus.Success;
}

/// <summary>
/// Logs the aggregated result of the processing of the assets.
/// </summary>
/// <param name="results">The aggregated result status.</param>
private static void LogResults(IEnumerable<(string AssetType, string AssetPath, ResultStatus Status)> results)
{
var grouped = results.GroupBy(result => result.Status);
foreach (var groupEntry in grouped)
{
if (groupEntry.Key == ResultStatus.Success && NugetHelper.NugetConfigFile.Verbose)
{
NugetHelper.LogVerbose("NuGetForUnity: successfully processed: {0}", string.Join(",", groupEntry.Select(asset => $"'{asset.AssetPath}' ({asset.AssetType})")));
}

if (groupEntry.Key == ResultStatus.Failure)
{
Debug.LogError($"NuGetForUnity: failed to process: {string.Join(",", groupEntry.Select(asset => $"'{asset.AssetPath}' ({asset.AssetType})"))}");
}
}
}

/// <summary>
/// Given the <paramref name="assetFilePath" /> load the <see cref="PluginImporter" /> for the asset it's associated
/// with.
/// </summary>
/// <param name="assetFilePath">Path to the assets.</param>
/// <param name="plugin">The loaded PluginImporter.</param>
/// <returns>True if the plug-in importer for the asset could be loaded.</returns>
private static bool GetPluginImporter(string assetFilePath, out PluginImporter plugin)
{
var assetPath = assetFilePath;
plugin = AssetImporter.GetAtPath(assetPath) as PluginImporter;

if (plugin == null)
{
Debug.LogWarning($"Failed to import plug-in at {assetPath}");
return false;
}

NugetHelper.LogVerbose("Plug-in loaded for file: {0}", assetPath);

return true;
}

/// <summary>
/// Check the labels on the asset to determine if we've already processed it. Calls to
/// <see cref="AssetImporter.SaveAndReimport"/> trigger call backs to this class to occur so not checking
/// if an asset has already been processed triggers an infinite loop that Unity detects and logs as an error.
/// </summary>
/// <param name="asset">Asset to check.</param>
/// <returns>True if already processed.</returns>
private static bool AlreadyProcessed(UnityEngine.Object asset)
{
return AssetDatabase.GetLabels(asset).Contains(ProcessedLabel);
}
}
}
11 changes: 11 additions & 0 deletions Assets/NuGet/Editor/NugetPackageAssetPostprocessor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/NuGet/Editor/NugetWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ private void DrawPackage(NugetPackage package, GUIStyle packageStyle, GUIStyle c
{
GUI.DrawTexture(rect, package.Icon, ScaleMode.StretchToFill);
}
else
else if (defaultIcon != null)
{
GUI.DrawTexture(rect, defaultIcon, ScaleMode.StretchToFill);
}
Expand Down
18 changes: 18 additions & 0 deletions Assets/Tests/Editor/NuGetForUnity.Editor.Tests.asmdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "NuGetForUnity.Editor.Tests",
"references": [
"NuGetForUnity"
],
"optionalUnityReferences": [
"TestAssemblies"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
7 changes: 7 additions & 0 deletions Assets/Tests/Editor/NuGetForUnity.Editor.Tests.asmdef.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1a6b486

Please sign in to comment.