diff --git a/TopModel.Generator/ModgenDependency.cs b/TopModel.Generator/ModgenDependency.cs index 068f5949..c5beaf24 100644 --- a/TopModel.Generator/ModgenDependency.cs +++ b/TopModel.Generator/ModgenDependency.cs @@ -2,7 +2,7 @@ namespace TopModel.Generator; -public record ModgenDependency(string ConfigKey, string Version) +public record ModgenDependency(string ConfigKey, TopModelLockModule Version) { public string FullName => $"TopModel.Generator.{ConfigKey.ToFirstUpper()}"; } diff --git a/TopModel.Generator/Program.cs b/TopModel.Generator/Program.cs index 46ca4c66..22b515f7 100644 --- a/TopModel.Generator/Program.cs +++ b/TopModel.Generator/Program.cs @@ -1,5 +1,7 @@ using System.CommandLine; using System.Reflection; +using System.Security.Cryptography; +using System.Text; using System.Text.Encodings.Web; using System.Text.Json.Nodes; using System.Xml; @@ -90,7 +92,7 @@ void HandleFile(FileInfo file) { var dir = Directory.GetCurrentDirectory(); var pattern = "topmodel*.config"; - foreach (var fileName in Directory.GetFiles(dir, pattern, SearchOption.AllDirectories)) + foreach (var fileName in Directory.EnumerateFiles(dir, pattern, SearchOption.AllDirectories)) { HandleFile(new FileInfo(fileName)); } @@ -103,7 +105,7 @@ void HandleFile(FileInfo file) dir = Directory.GetParent(dir)?.FullName; if (dir != null) { - foreach (var fileName in Directory.GetFiles(dir, pattern)) + foreach (var fileName in Directory.EnumerateFiles(dir, pattern)) { HandleFile(new FileInfo(fileName)); found = true; @@ -277,9 +279,9 @@ void HandleFile(FileInfo file) var configKey = dep.Key.Split('.').Last().ToLower(); if (!topModelLock.Modules.TryGetValue(configKey, out var ev)) { - topModelLock.Modules.Add(configKey, dep.Value); + topModelLock.Modules.Add(configKey, new() { Version = dep.Value }); } - else if (ev != dep.Value) + else if (ev.Version != dep.Value) { logger.LogError($"Le module personalisé '{cg}' référence le module '{configKey}' en version '{dep.Value}', ce qui n'est pas la version du lockfile ('{ev}')."); returnCode = 1; @@ -339,7 +341,7 @@ void HandleFile(FileInfo file) } var nugetVersion = moduleVersions.Last().Version; - moduleVersion = $"{nugetVersion.Major}.{nugetVersion.Minor}.{nugetVersion.Build}"; + moduleVersion = new() { Version = $"{nugetVersion.Major}.{nugetVersion.Minor}.{nugetVersion.Build}" }; topModelLock.Modules.Add(configKey, moduleVersion); } @@ -367,15 +369,24 @@ void HandleFile(FileInfo file) foreach (var dep in deps) { - var moduleFolder = Path.Combine(modgenRoot, $"{dep.ConfigKey}.{dep.Version}"); + var depVersion = dep.Version.Version; + var moduleFolder = Path.Combine(modgenRoot, $"{dep.ConfigKey}.{depVersion}"); - if (!Directory.Exists(moduleFolder)) + var depHash = GetFolderHash(moduleFolder); + + if (depHash == null || depHash != dep.Version.Hash) { - logger.LogInformation($"({dep.ConfigKey}) Installation de {dep.FullName}@{dep.Version} en cours..."); + if (Directory.Exists(moduleFolder)) + { + logger.LogInformation($"({dep.ConfigKey}) Module corrompu, réinstallation..."); + Directory.Delete(moduleFolder, true); + } - if (!await nugetResource.DoesPackageExistAsync(dep.FullName, new NuGetVersion(dep.Version), nugetCache, NullLogger.Instance, ct)) + logger.LogInformation($"({dep.ConfigKey}) Installation de {dep.FullName}@{depVersion} en cours..."); + + if (!await nugetResource.DoesPackageExistAsync(dep.FullName, new NuGetVersion(depVersion), nugetCache, NullLogger.Instance, ct)) { - logger.LogError($"({dep.ConfigKey}) Le package {dep.FullName}@{dep.Version} est introuvable."); + logger.LogError($"({dep.ConfigKey}) Le package {dep.FullName}@{depVersion} est introuvable."); returnCode = 1; continue; } @@ -383,7 +394,7 @@ void HandleFile(FileInfo file) Directory.CreateDirectory(moduleFolder); using var packageStream = new MemoryStream(); - await nugetResource.CopyNupkgToStreamAsync(dep.FullName, new NuGetVersion(dep.Version), packageStream, nugetCache, NullLogger.Instance, ct); + await nugetResource.CopyNupkgToStreamAsync(dep.FullName, new NuGetVersion(depVersion), packageStream, nugetCache, NullLogger.Instance, ct); using var packageReader = new PackageArchiveReader(packageStream); var nuspecReader = await packageReader.GetNuspecReaderAsync(ct); @@ -391,9 +402,7 @@ void HandleFile(FileInfo file) .Single(dg => dg.TargetFramework.ToString() == framework) .Packages; - var minVersion = dependencies.Single(d => d.Id == "TopModel.Generator.Core").VersionRange.MinVersion!.ToString(); - - File.WriteAllText(Path.Combine(moduleFolder, "min-version"), minVersion); + File.WriteAllText(Path.Combine(moduleFolder, "min-version"), dependencies.Single(d => d.Id == "TopModel.Generator.Core").VersionRange.MinVersion!.ToString()); foreach (var file in packageReader.GetFiles().Where(f => f == $"lib/{framework}/{dep.FullName}.dll" || f.EndsWith("config.json"))) { @@ -434,26 +443,27 @@ void HandleFile(FileInfo file) } hasInstalled = true; - logger.LogInformation($"({dep.ConfigKey}) Installation de {dep.FullName}@{dep.Version} terminée avec succès."); + logger.LogInformation($"({dep.ConfigKey}) Installation de {dep.FullName}@{depVersion} terminée avec succès."); + dep.Version.Hash = GetFolderHash(moduleFolder); } - var depVersionText = File.ReadAllText(Path.Combine(moduleFolder, "min-version")); - var depVersion = depVersionText.Split('.').Select(int.Parse).ToArray(); - if (depVersion[0] != fullVersion.Major) + var minVersionText = File.ReadAllText(Path.Combine(moduleFolder, "min-version")); + var minVersion = minVersionText.Split('.').Select(int.Parse).ToArray(); + if (minVersion[0] != fullVersion.Major) { - logger.LogError($"Le module '{dep.ConfigKey}' ne référence pas la bonne version majeure de TopModel ({dep.Version} < {version})."); + logger.LogError($"Le module '{dep.ConfigKey}' ne référence pas la bonne version majeure de TopModel ({depVersion} < {version})."); returnCode = 1; continue; } - else if (depVersion[1] > fullVersion.Minor) + else if (minVersion[1] > fullVersion.Minor) { - logger.LogError($"Le module '{dep.ConfigKey}' référence une version plus récente de TopModel ({depVersionText} > {version})."); + logger.LogError($"Le module '{dep.ConfigKey}' référence une version plus récente de TopModel ({minVersionText} > {version})."); returnCode = 1; continue; } generators.AddRange(Directory.GetFiles(moduleFolder, "*.dll").SelectMany(a => Assembly.LoadFrom(a).GetExportedTypes().Where(t => GetIGenRegInterface(t) != null))); - resolvedConfigKeys.Add(dep.ConfigKey, dep.Version); + resolvedConfigKeys.Add(dep.ConfigKey, depVersion); } } @@ -613,3 +623,30 @@ void HandleFile(FileInfo file) } return returnCode; + +static string? GetFolderHash(string path) +{ + var md5 = MD5.Create(); + + var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories).OrderBy(p => p).ToList(); + foreach (var file in files) + { + var relativePath = file.Substring(path.Length + 1); + var pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower()); + md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); + + var contentBytes = File.ReadAllBytes(file); + if (files.IndexOf(file) == files.Count - 1) + { + md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length); + } + else + { + md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); + } + } + + return md5.Hash != null + ? BitConverter.ToString(md5.Hash).Replace("-", string.Empty).ToLower() + : null; +} \ No newline at end of file diff --git a/TopModel.Utils/TopModelLock.cs b/TopModel.Utils/TopModelLock.cs index 95c95b34..0c01fe8e 100644 --- a/TopModel.Utils/TopModelLock.cs +++ b/TopModel.Utils/TopModelLock.cs @@ -17,6 +17,11 @@ public class TopModelLock : TopModelLockFile private readonly ILogger _logger; private readonly string _modelRoot; + private readonly ISerializer _serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .Build(); + [SetsRequiredMembers] public TopModelLock(ILogger logger, string modelRoot, string lockFileName) { @@ -90,25 +95,7 @@ public void Write() StartCommentToken = "#" }; - fw.WriteLine($"version: {Version}"); - - if (Modules.Count > 0) - { - fw.WriteLine("modules:"); - foreach (var module in Modules) - { - fw.WriteLine($" {module.Key}: {module.Value}"); - } - } - - if (GeneratedFiles.Count > 0) - { - fw.WriteLine("generatedFiles:"); - foreach (var genFile in GeneratedFiles) - { - fw.WriteLine($" - {genFile}"); - } - } + fw.Write(_serializer.Serialize(this)); } } } \ No newline at end of file diff --git a/TopModel.Utils/TopModelLockFile.cs b/TopModel.Utils/TopModelLockFile.cs index 6ce34723..728083f5 100644 --- a/TopModel.Utils/TopModelLockFile.cs +++ b/TopModel.Utils/TopModelLockFile.cs @@ -4,7 +4,7 @@ public class TopModelLockFile { public required string Version { get; set; } - public Dictionary Modules { get; set; } = []; + public Dictionary Modules { get; set; } = []; public List GeneratedFiles { get; set; } = []; } diff --git a/TopModel.Utils/TopModelLockModule.cs b/TopModel.Utils/TopModelLockModule.cs new file mode 100644 index 00000000..0e1cb982 --- /dev/null +++ b/TopModel.Utils/TopModelLockModule.cs @@ -0,0 +1,44 @@ +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace TopModel.Utils; + +public class TopModelLockModule : TopModelLockModuleBase, IYamlConvertible +{ + /// + public void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) + { + if (parser.TryConsume(out var version)) + { + Version = version.Value; + } + else if (parser.Current is MappingStart) + { + var lol = (TopModelLockModuleBase)nestedObjectDeserializer(typeof(TopModelLockModuleBase))!; + Version = lol.Version; + Hash = lol.Hash; + } + } + + /// + public void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) + { + if (Hash != null) + { + nestedObjectSerializer(new TopModelLockModuleBase { Version = Version, Hash = Hash }); + } + else + { + emitter.Emit(new Scalar(Version)); + } + } +} + +#pragma warning disable SA1402 +public class TopModelLockModuleBase +{ + public required string Version { get; set; } + + public string? Hash { get; set; } +} \ No newline at end of file