From a10689e3bb089aa73572f2bf5574550aa97916b3 Mon Sep 17 00:00:00 2001 From: Max Kopjev Date: Thu, 21 Dec 2023 17:48:16 +0100 Subject: [PATCH] Add auto syncing references (#4) * Add extraction of project file path. * Add field for reference syncing in config file. * Add new dependencies. * Add sync algorithm for references. * Update version * Fix ups * Rename ReferenceHeader * Fix message * Check arguments count. * Fix * Revert "Update version" This reverts commit e24d75d18a86139ba48445d59d0a965564fbb98e. * Change error logic - no return * Add adding new references. * Add more true NuGet support. * Fix braces. * Fix First call. --------- Co-authored-by: Ell --- Contentless/Config.cs | 5 +- Contentless/Contentless.csproj | 7 ++ Contentless/Contentless.targets | 2 +- Contentless/NuGetHelper.cs | 15 +++++ Contentless/Program.cs | 116 +++++++++++++++++++++++++++++--- Test/Content/Contentless.json | 1 + Test/NuGet.Config | 7 ++ 7 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 Contentless/NuGetHelper.cs create mode 100644 Test/NuGet.Config diff --git a/Contentless/Config.cs b/Contentless/Config.cs index beacabd..d1dd626 100644 --- a/Contentless/Config.cs +++ b/Contentless/Config.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -13,7 +14,9 @@ public class Config { [JsonProperty("overrides")] public Dictionary Overrides = new(); - + + [JsonProperty("references")] + public string[] References = Array.Empty(); } public class Override { diff --git a/Contentless/Contentless.csproj b/Contentless/Contentless.csproj index e93b373..0c56164 100644 --- a/Contentless/Contentless.csproj +++ b/Contentless/Contentless.csproj @@ -4,6 +4,7 @@ Exe net6.0 Major + true @@ -20,6 +21,12 @@ All + + All + + + All + diff --git a/Contentless/Contentless.targets b/Contentless/Contentless.targets index 6724487..b5103d9 100644 --- a/Contentless/Contentless.targets +++ b/Contentless/Contentless.targets @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/Contentless/NuGetHelper.cs b/Contentless/NuGetHelper.cs new file mode 100644 index 0000000..f31a940 --- /dev/null +++ b/Contentless/NuGetHelper.cs @@ -0,0 +1,15 @@ +using NuGet.Configuration; + +namespace Contentless; + +public class NuGetHelper +{ + private readonly ISettings _settings; + + public NuGetHelper(string projectFolder) + { + _settings = Settings.LoadDefaultSettings(projectFolder); + } + + public string PackageFolder => SettingsUtility.GetGlobalPackagesFolder(_settings); +} \ No newline at end of file diff --git a/Contentless/Program.cs b/Contentless/Program.cs index c6ad56d..dfd7e82 100644 --- a/Contentless/Program.cs +++ b/Contentless/Program.cs @@ -4,15 +4,15 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using Microsoft.Build.Construction; using Microsoft.Xna.Framework.Content.Pipeline; using Newtonsoft.Json; namespace Contentless; public static class Program { - public static void Main(string[] args) { - if (args.Length != 1) { + if (args.Length < 1) { Console.WriteLine("Please specify the location of the content file you want to use"); return; } @@ -43,26 +43,81 @@ public static class Program { var excluded = config.ExcludedFiles.Select(Program.MakeFileRegex).ToArray(); var overrides = Program.GetOverrides(config.Overrides).ToArray(); + var referencesVersions = config.References.ToDictionary(x => x, x => (string)null, StringComparer.OrdinalIgnoreCase); + if (config.References.Length > 0) { + if (args.Length > 1) { + ExtractVersions(args[1], referencesVersions); + _nuGetHelper = new NuGetHelper(Path.GetDirectoryName(args[1])); + } + else + Console.Error.WriteLine("You supplied references but there is no project file, this isn't compatible. Please specify the full path of project file, if you want to sync references"); + } + + const string ReferenceHeader = "/reference:"; + var changed = false; + var referencesSyncs = new HashSet(StringComparer.OrdinalIgnoreCase); // load any references to be able to include custom content types as well - foreach (var line in content) { - if (!line.StartsWith("/reference:")) + for (int i = 0; i < content.Count; i++) { + var line = content[i]; + if (!line.StartsWith(ReferenceHeader)) continue; - var reference = line.Substring(11); + var reference = line.Substring(ReferenceHeader.Length); + var libraryName = Path.GetFileName(reference)[..^4]; + + if (referencesVersions.TryGetValue(libraryName, out var version) && version is not null) { + var fullLibraryPath = CalculateFullPathToLibrary(libraryName, version); + if (reference != fullLibraryPath) { + Console.WriteLine($"Changing library reference from {reference} to {fullLibraryPath}"); + reference = fullLibraryPath; + content[i] = ReferenceHeader + fullLibraryPath; + changed = true; + } + else + Console.WriteLine($"Skipping library reference {fullLibraryPath} (success sync)"); + + referencesSyncs.Add(libraryName); + } + var refPath = Path.GetFullPath(Path.Combine(contentFile.DirectoryName, reference)); - try { - Assembly.LoadFrom(refPath); - Console.WriteLine($"Using reference {refPath}"); - } catch (Exception e) { - Console.WriteLine($"Error loading reference {refPath}: {e}"); + SafeAssemblyLoad(refPath); + } + + // check references not in .mgcb now + var referencesLastIndex = 0; + // find place where I can add new reference + for (int i = 0; i < content.Count; i++) + { + var line = content[i]; + if (line.StartsWith(ReferenceHeader)) + referencesLastIndex = i + 1; + else if (line.StartsWith("/importer:") || line.StartsWith("/processor:") || line.StartsWith("/build:") || + line.Contains("-- Content --")) { + if (referencesLastIndex == 0) + referencesLastIndex = i; + break; } } + foreach (var reference in referencesVersions) + if (!referencesSyncs.Contains(reference.Key) && reference.Value is not null) { + try { + var path = CalculateFullPathToLibrary(reference.Key, reference.Value); + content.Insert(referencesLastIndex++, ReferenceHeader + path); + changed = true; + SafeAssemblyLoad(path); + Console.WriteLine($"Adding reference for {path} in .mgcb"); + } + catch (Exception e) + { + Console.Error.WriteLine($"Error adding library {reference.Key} in .mgcb: {e}"); + } + } + // load content importers var (importers, processors) = Program.GetContentData(); Console.WriteLine($"Found possible importer types {string.Join(", ", importers)}"); Console.WriteLine($"Found possible processor types {string.Join(", ", processors)}"); - var changed = false; foreach (var file in contentFile.Directory.EnumerateFiles("*", SearchOption.AllDirectories)) { // is the file the content or config file? if (file.Name == contentFile.Name || file.Name == configFile.Name) @@ -142,6 +197,45 @@ public static class Program { Console.Write("Done"); } + private static void SafeAssemblyLoad(string refPath) + { + try { + Assembly.LoadFrom(refPath); + Console.WriteLine($"Using reference {refPath}"); + } catch (Exception e) { + Console.WriteLine($"Error loading reference {refPath}: {e}"); + } + } + + private static void ExtractVersions(string csprojPath, Dictionary referencesVersions) + { + Console.WriteLine($"Using project file {csprojPath}"); + var projectRootElement = ProjectRootElement.Open(csprojPath); + foreach (var property in projectRootElement.AllChildren.Where(x => x.ElementName == "PackageReference").Select(x => x as ProjectItemElement)) + { + var libraryName = property.Include; + if (property.Children.FirstOrDefault(x => x.ElementName == "Version") is not ProjectMetadataElement versionElement) + continue; + var version = versionElement.Value; + if (referencesVersions.Keys.Contains(libraryName)) + { + referencesVersions[libraryName] = version; + Console.WriteLine($"Found library version for sync: {libraryName}, {version}"); + } + } + + foreach (var library in referencesVersions) + if (library.Value is null) + Console.Error.WriteLine($"Unable to find library {library.Key} in .csproj"); + } + + private static NuGetHelper _nuGetHelper; + + private static string CalculateFullPathToLibrary(string libraryName, string referencesVersion) + { + return Path.Combine(_nuGetHelper.PackageFolder, libraryName.ToLower(), referencesVersion, "tools", libraryName + ".dll"); + } + private static (List, List) GetContentData() { var importers = new List(); var processors = new List(); diff --git a/Test/Content/Contentless.json b/Test/Content/Contentless.json index 4154711..81af4a5 100644 --- a/Test/Content/Contentless.json +++ b/Test/Content/Contentless.json @@ -5,6 +5,7 @@ "Ex*.png" ], "logSkipped": true, + "references": ["monogame.extended.content.pipeline"], "overrides": { "Copy.*": { "copy": true diff --git a/Test/NuGet.Config b/Test/NuGet.Config new file mode 100644 index 0000000..767af86 --- /dev/null +++ b/Test/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file