diff --git a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/CSProjectInfo.cs b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/CSProjectInfo.cs index 39f3400..b7c4689 100644 --- a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/CSProjectInfo.cs +++ b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/CSProjectInfo.cs @@ -45,6 +45,7 @@ public class CSProjectInfo : ReferenceItemInfo { private readonly List> csProjectDependencies = new List>(); private readonly List> pluginAssemblyDependencies = new List>(); + private readonly List> winmdDependencies = new List>(); /// /// The associated Assembly-Definition info if available. @@ -63,6 +64,8 @@ public class CSProjectInfo : ReferenceItemInfo public IReadOnlyCollection> PluginDependencies { get; } + public IReadOnlyCollection> WinMDDependencies { get; } + /// /// Creates a new instance of the CSProject info. /// @@ -87,6 +90,7 @@ internal CSProjectInfo(UnityProjectInfo unityProjectInfo, AssemblyDefinitionInfo ProjectDependencies = new ReadOnlyCollection>(csProjectDependencies); PluginDependencies = new ReadOnlyCollection>(pluginAssemblyDependencies); + WinMDDependencies = new ReadOnlyCollection>(winmdDependencies); } private ProjectType GetProjectType(AssemblyDefinitionInfo assemblyDefinitionInfo) @@ -161,6 +165,15 @@ internal void AddDependency(PluginAssemblyInfo pluginAssemblyInfo) AddDependency(pluginAssemblyDependencies, pluginAssemblyInfo); } + /// + /// Adds a dependency to the project. + /// + /// The winmd dependency. + internal void AddDependency(WinMDInfo winmdInfo) + { + AddDependency(winmdDependencies, winmdInfo); + } + private void AddDependency(List> items, T referenceInfo) where T : ReferenceItemInfo { items.Add(new CSProjectDependency(referenceInfo, diff --git a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/Exporters/TemplatedProjectExporter.cs b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/Exporters/TemplatedProjectExporter.cs index 66d55f8..5831a61 100644 --- a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/Exporters/TemplatedProjectExporter.cs +++ b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/Exporters/TemplatedProjectExporter.cs @@ -438,6 +438,18 @@ private void CreateProjectReferencesSet(CSProjectInfo projectInfo, ITemplatePart additionalSearchPaths.Add(Path.GetDirectoryName(dependency.Dependency.ReferencePath.LocalPath)); } + foreach (CSProjectDependency dependency in projectInfo.WinMDDependencies) + { + List platformConditions = GetPlatformConditions(inEditor ? projectInfo.InEditorPlatforms : projectInfo.PlayerPlatforms, inEditor ? dependency.InEditorSupportedPlatforms : dependency.PlayerSupportedPlatforms); + + TemplateReplacementSet replacementSet = pluginReferenceTemplatePart.CreateReplacementSet(templateReplacementSet); + pluginReferenceTemplatePart.Tokens["REFERENCE"].AssignValue(replacementSet, dependency.Dependency.Name); + pluginReferenceTemplatePart.Tokens["HINT_PATH"].AssignValue(replacementSet, dependency.Dependency.ReferencePath.LocalPath); + pluginReferenceTemplatePart.Tokens["CONDITION"].AssignValue(replacementSet, platformConditions.Count == 0 ? "false" : string.Join(" OR ", platformConditions)); + + additionalSearchPaths.Add(Path.GetDirectoryName(dependency.Dependency.ReferencePath.LocalPath)); + } + templatePart.Tokens["REFERENCE_CONFIGURATION"].AssignValue(templateReplacementSet, inEditor ? "InEditor" : "Player"); } diff --git a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/UnityProjectInfo.cs b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/UnityProjectInfo.cs index 345bd1f..af4ea3b 100644 --- a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/UnityProjectInfo.cs +++ b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/UnityProjectInfo.cs @@ -56,6 +56,11 @@ public class UnityProjectInfo : IDisposable /// public IReadOnlyCollection Plugins { get; private set; } + /// + /// Gets all the parsed winmds for this Unity project. + /// + public IReadOnlyCollection WinMDs { get; private set; } + public UnityProjectInfo(HashSet supportedBuildTargets) { AvailablePlatforms = new ReadOnlyCollection(CompilationPipeline.GetAssemblyDefinitionPlatforms() @@ -86,7 +91,8 @@ public void Dispose() public void RefreshPlugins() { - Plugins = new ReadOnlyCollection(ScanForPluginDLLs()); + ScanForReferences(out List plugins, out List winmds); + Plugins = new ReadOnlyCollection(plugins); foreach (PluginAssemblyInfo plugin in Plugins) { @@ -97,6 +103,8 @@ public void RefreshPlugins() } } + WinMDs = new ReadOnlyCollection(winmds); + RefreshProjects(); } @@ -270,6 +278,21 @@ private CSProjectInfo GetProjectInfo(Dictionary projectsM toReturn.AddDependency(plugin); } } + + foreach (WinMDInfo winmd in WinMDs) + { + if (!dependencies.IsBaseOf(winmd.ReferencePath)) + { + if (winmd.IsValid) + { + toReturn.AddDependency(winmd); + } + else + { + Debug.LogError($"References to {winmd} were excluded because the winmd is configured incorrectly. Make sure this winmd is setup to only support WSAPlayer in the Unity inspector."); + } + } + } } foreach (string reference in toReturn.AssemblyDefinitionInfo.References) @@ -296,13 +319,16 @@ private CSProjectInfo GetProjectInfo(Dictionary projectsM return toReturn; } - private List ScanForPluginDLLs() + private void ScanForReferences(out List plugins, out List winmds) { - List toReturn = new List(); + plugins = new List(); + winmds = new List(); - foreach (string assetAssemblyPath in Directory.GetFiles(Utilities.AssetPath, "*.dll", SearchOption.AllDirectories)) + IEnumerable assetReferences = Directory.EnumerateFiles(Utilities.AssetPath, "*.*", SearchOption.AllDirectories) + .Where(file => file.ToLower().EndsWith(".dll") || file.ToLower().EndsWith(".winmd")); + foreach (string assetPath in assetReferences) { - string assetRelativePath = Utilities.GetAssetsRelativePathFrom(assetAssemblyPath); + string assetRelativePath = Utilities.GetAssetsRelativePathFrom(assetPath); PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath(assetRelativePath); if (importer == null) { @@ -310,17 +336,27 @@ private List ScanForPluginDLLs() continue; } - PluginAssemblyInfo toAdd = new PluginAssemblyInfo(this, Guid.Parse(AssetDatabase.AssetPathToGUID(assetRelativePath)), assetAssemblyPath, importer.isNativePlugin ? PluginType.Native : PluginType.Managed); - toReturn.Add(toAdd); + if (assetRelativePath.EndsWith(".dll")) + { + PluginAssemblyInfo toAdd = new PluginAssemblyInfo(this, Guid.Parse(AssetDatabase.AssetPathToGUID(assetRelativePath)), assetPath, importer.isNativePlugin ? PluginType.Native : PluginType.Managed); + plugins.Add(toAdd); + } + else if (assetRelativePath.EndsWith(".winmd")) + { + WinMDInfo toAdd = new WinMDInfo(this, Guid.Parse(AssetDatabase.AssetPathToGUID(assetRelativePath)), assetPath); + winmds.Add(toAdd); + } } - foreach (string packageDllPath in Directory.GetFiles(Utilities.PackageLibraryCachePath, "*.dll", SearchOption.AllDirectories)) + IEnumerable packageReferences = Directory.EnumerateFiles(Utilities.PackageLibraryCachePath, "*.*", SearchOption.AllDirectories) + .Where(file => file.ToLower().EndsWith(".dll") || file.ToLower().EndsWith(".winmd")); + foreach (string packagePath in packageReferences) { - string metaPath = packageDllPath + ".meta"; + string metaPath = packagePath + ".meta"; if (!File.Exists(metaPath)) { - Debug.LogWarning($"Skipping a packages DLL that didn't have an associated meta: '{packageDllPath}'"); + Debug.LogWarning($"Skipping a packages reference that didn't have an associated meta: '{packagePath}'"); continue; } Guid guid; @@ -329,18 +365,24 @@ private List ScanForPluginDLLs() string guidLine = reader.ReadUntil("guid"); if (!Guid.TryParse(guidLine.Split(':')[1].Trim(), out guid)) { - Debug.LogWarning($"Skipping a packages DLL that didn't have a valid guid in the .meta file: '{packageDllPath}'"); + Debug.LogWarning($"Skipping a packages reference that didn't have a valid guid in the .meta file: '{packagePath}'"); continue; } } - bool isManaged = Utilities.IsManagedAssembly(packageDllPath); - PluginAssemblyInfo toAdd = new PluginAssemblyInfo(this, guid, packageDllPath, isManaged ? PluginType.Managed : PluginType.Native); - toReturn.Add(toAdd); + if (packagePath.EndsWith(".dll")) + { + bool isManaged = Utilities.IsManagedAssembly(packagePath); + PluginAssemblyInfo toAdd = new PluginAssemblyInfo(this, guid, packagePath, isManaged ? PluginType.Managed : PluginType.Native); + plugins.Add(toAdd); + } + else if (packagePath.EndsWith(".winmd")) + { + WinMDInfo toAdd = new WinMDInfo(this, guid, packagePath); + winmds.Add(toAdd); + } } - - return toReturn; } } } -#endif +#endif \ No newline at end of file diff --git a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs new file mode 100644 index 0000000..e524003 --- /dev/null +++ b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Microsoft.Build.Unity.ProjectGeneration +{ + /// + /// This is the information for the winmds in the Unity project. + /// + public class WinMDInfo : ReferenceItemInfo + { + /// + /// Gets the output path to the reference. + /// + public Uri ReferencePath { get; } + + /// + /// If the plugin has define constraints, then it will only be referenced if the platform/project defines at least one of these constraints. + /// ! operator means that the specified plugin must not be included + /// https://docs.unity3d.com/ScriptReference/PluginImporter.DefineConstraints.html + /// + public HashSet DefineConstraints { get; private set; } + + public bool IsValid { get; private set; } + + /// + /// Creates a new instance of the . + /// + public WinMDInfo(UnityProjectInfo unityProjectInfo, Guid guid, string fullPath) + : base(unityProjectInfo, guid, Path.GetFileNameWithoutExtension(fullPath)) + { + ReferencePath = new Uri(fullPath); + ParseYAMLFile(); + } + + private void ParseYAMLFile() + { + Dictionary enabledPlatforms = new Dictionary(); + using (StreamReader reader = new StreamReader(ReferencePath.LocalPath + ".meta")) + { + DefineConstraints = new HashSet(); + + // Parse define constraints + string defineConstraints = reader.ReadUntil("defineConstraints:", "platformData:"); + string platformData; + if (defineConstraints.Contains("defineConstraints:")) + { + if (!defineConstraints.Contains("[]")) + { + reader.ReadWhile(line => + { + line = line.Trim(); + if (line.StartsWith("-")) + { + string define = line.Substring(1).Trim(); + + if (define.StartsWith("'") && define.EndsWith("'")) + { + define = define.Substring(1, define.Length - 2); + } + + DefineConstraints.Add(define); + return true; + } + // else + return false; + }); + } + + // Since succeded, read until platformData + platformData = reader.ReadUntil("platformData:"); + } + else + { + // If it's not defineConstraints, check next for platformData + platformData = defineConstraints; + } + + if (!platformData.Contains("platformData:")) + { + // Read until platform data + reader.ReadUntil("platformData:"); + } + + ParsePlatformData(reader, enabledPlatforms); + } + + Dictionary inEditorPlatforms = new Dictionary(); + if (enabledPlatforms.TryGetValue("Editor", out bool platformEnabled) && platformEnabled) + { + foreach (CompilationPlatformInfo platform in UnityProjectInfo.AvailablePlatforms) + { + inEditorPlatforms.Add(platform.BuildTarget, platform); + } + } + + Dictionary playerPlatforms = new Dictionary(); + + IsValid = VerifyEnabledPlatforms(enabledPlatforms); + TryAddEnabledPlatform(playerPlatforms, enabledPlatforms, "WindowsStoreApps", BuildTarget.WSAPlayer); + + FilterPlatformsBasedOnDefineConstraints(inEditorPlatforms, true); + FilterPlatformsBasedOnDefineConstraints(playerPlatforms, false); + + InEditorPlatforms = new ReadOnlyDictionary(inEditorPlatforms); + PlayerPlatforms = new ReadOnlyDictionary(playerPlatforms); + } + + private void ParsePlatformData(StreamReader reader, Dictionary enabledPlatforms) + { + if (reader.ReadUntil("first:", "userData:").Contains("userData:") || reader.EndOfStream) + { + // We reached the end + return; + } + + if (reader.ReadLine().Contains("'': Any")) // Try use exclude method + { + string settingsLine = reader.ReadUntil("settings:", "userData:"); + if (settingsLine.Contains("userData:")) + { + return; + } + + // We are fine to use exclude method if we have a set of settings + if (!settingsLine.Contains("settings: {}")) + { + reader.ReadWhile(l => + { + if (l.Contains("Exclude")) + { + string[] parts = l.Trim().Replace("Exclude ", string.Empty).Split(':'); + enabledPlatforms.Add(parts[0], parts[1].Trim() == "0"); // These are exclude, so check for 0 if to include + return true; + } + + return false; + }); + + return; + } + } + // else fall through to use -first method + + string line; + while ((line = reader.ReadUntil("first:", "userData:")).Contains("first:") && !reader.EndOfStream) + { + string[] platformLineParts = reader.ReadLine().Split(':'); + string platform = platformLineParts[1].Trim(); + + if (platformLineParts[0].Contains("Facebook")) + { + platform = $"Facebook{platform}"; + } + string enabledLine = reader.ReadUntil("enabled:"); + + enabledPlatforms.Add(platform, enabledLine.Split(':')[1].Trim() == "1"); + } + } + + private bool ContainsDefineHelper(string define, bool inEditor, CompilationPlatformInfo platform) + { + return platform.CommonPlatformDefines.Contains(define) + || (inEditor ? platform.AdditionalInEditorDefines.Contains(define) : platform.AdditionalPlayerDefines.Contains(define)); + } + + private void FilterPlatformsBasedOnDefineConstraints(IDictionary platformDictionary, bool inEditor) + { + if (DefineConstraints.Count == 0) + { + // No exclusions + return; + } + + bool defaultExcludeValue = DefineConstraints.Any(t => !t.StartsWith("!")); + HashSet toExclude = new HashSet(); + foreach (KeyValuePair platformPair in platformDictionary) + { + // We presume exclude, then check + bool exclude = defaultExcludeValue; + foreach (string define in DefineConstraints) + { + // Does this define exclude + if (define.StartsWith("!")) + { + if (ContainsDefineHelper(define.Substring(1), inEditor, platformPair.Value)) + { + exclude = true; + break; + } + } + else if (ContainsDefineHelper(define, inEditor, platformPair.Value)) + { + // This platform is supported, but still search for !defineconstraitns that may force exclusion + exclude = false; + } + } + + if (exclude) + { + toExclude.Add(platformPair.Key); + } + } + + foreach (BuildTarget buildTarget in toExclude) + { + platformDictionary.Remove(buildTarget); + } + } + + private void TryAddEnabledPlatform(Dictionary playerPlatforms, Dictionary enabledPlatforms, string platformName, BuildTarget platformTarget) + { + if (enabledPlatforms.TryGetValue(platformName, out bool platformEnabled) && platformEnabled) + { + CompilationPlatformInfo platform = UnityProjectInfo.AvailablePlatforms.FirstOrDefault(t => t.BuildTarget == platformTarget); + if (platform != null) + { + playerPlatforms.Add(platformTarget, platform); + } + else + { + Debug.LogError($"Platform '{platformName}' was specified as enabled by '{ReferencePath.LocalPath}' plugin, but not available in processed compilation settings."); + } + } + } + + private bool VerifyEnabledPlatforms(Dictionary enabledPlatforms) + { + bool valid = true; + foreach (KeyValuePair platform in enabledPlatforms) + { + if (platform.Value && + (platform.Key == "Win" || + platform.Key == "Win64" || + platform.Key == "iOS" || + platform.Key == "Android" || + platform.Key == "Editor")) + { + Debug.LogError($"WinMDs should only be enabled for the WSA Player; however, {ReferencePath} was configured to support {platform.Key}."); + valid = false; + break; + } + } + + return valid; + } + } +} +#endif \ No newline at end of file diff --git a/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs.meta b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs.meta new file mode 100644 index 0000000..4d180eb --- /dev/null +++ b/Source/MSBuildTools.Unity/Packages/com.microsoft.msbuildforunity/Editor/ProjectGenerator/Scripts/WinMDInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 769306ff23e177f4abf9ce36414f8f44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: