Skip to content

Commit

Permalink
Extract version logic to main program + add tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
torbacz committed Sep 7, 2024
1 parent d7ed2d8 commit 30c6bdd
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 121 deletions.
7 changes: 7 additions & 0 deletions DependencyUpdated.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyUpdated.Projects.
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D271967-C5D2-425F-883E-FFCDC51A62A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyUpdated.Core.UnitTests", "tests\DependencyUpdated.Core.UnitTests\DependencyUpdated.Core.UnitTests.csproj", "{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -50,12 +52,17 @@ Global
{0F4B408D-9B4A-4621-BD67-AB80EB74E14B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F4B408D-9B4A-4621-BD67-AB80EB74E14B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F4B408D-9B4A-4621-BD67-AB80EB74E14B}.Release|Any CPU.Build.0 = Release|Any CPU
{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0F4B408D-9B4A-4621-BD67-AB80EB74E14B} = {35DCECB9-4FF1-4277-8C84-B905A5D1E693}
{8F92D3D1-EB23-4CA3-BCE9-ACC6327993E1} = {0D271967-C5D2-425F-883E-FFCDC51A62A2}
{F346E159-924F-4206-B2BA-D5B34F91B9C0} = {0D271967-C5D2-425F-883E-FFCDC51A62A2}
{D10799F8-DCBB-442E-85C0-2F473C716D01} = {0D271967-C5D2-425F-883E-FFCDC51A62A2}
{A9181ADC-801D-45BE-B091-CF6C14E6F702} = {0D271967-C5D2-425F-883E-FFCDC51A62A2}
{A6ACB1EA-5F1B-4C85-AE9E-D376A1A80744} = {35DCECB9-4FF1-4277-8C84-B905A5D1E693}
EndGlobalSection
EndGlobal
8 changes: 8 additions & 0 deletions src/DependencyUpdated.Core/Config/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ public IEnumerable<ValidationResult> Validate(ValidationContext validationContex
yield return new ValidationResult($"{nameof(Directories)} cannot be empty");
}

foreach (var directory in Directories)
{
if (!Path.Exists(directory))
{
yield return new ValidationResult($"Path {directory} not found");
}
}

if (!EachDirectoryAsSeparate && string.IsNullOrEmpty(Name))
{
yield return new ValidationResult($"{nameof(Name)} must be provided when {nameof(EachDirectoryAsSeparate)} is not set");
Expand Down
6 changes: 4 additions & 2 deletions src/DependencyUpdated.Core/Interfaces/IProjectUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ namespace DependencyUpdated.Core.Interfaces;

public interface IProjectUpdater
{
Task<ICollection<DependencyDetails>> ExtractAllPackagesThatNeedToBeUpdated(IReadOnlyCollection<string> fullPath,
Project projectConfiguration);
Task<ICollection<DependencyDetails>> ExtractAllPackages(IReadOnlyCollection<string> fullPath);

IReadOnlyCollection<string> GetAllProjectFiles(string searchPath);

IReadOnlyCollection<UpdateResult> HandleProjectUpdate(IReadOnlyCollection<string> fullPath,
ICollection<DependencyDetails> dependenciesToUpdate);

Task<IReadOnlyCollection<DependencyDetails>> GetVersions(DependencyDetails package,
Project projectConfiguration);
}
77 changes: 63 additions & 14 deletions src/DependencyUpdated.Core/Updater.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using DependencyUpdated.Core.Config;
using DependencyUpdated.Core.Interfaces;
using DependencyUpdated.Core.Models;
using DependencyUpdated.Core.Models.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Serilog;
using System.IO.Enumeration;

namespace DependencyUpdated.Core;

public sealed class Updater(IServiceProvider serviceProvider, IOptions<UpdaterConfig> config)
public sealed class Updater(IServiceProvider serviceProvider, IOptions<UpdaterConfig> config, ILogger logger)
{
public async Task DoUpdate()
{
Expand All @@ -21,20 +24,14 @@ public async Task DoUpdate()

foreach (var directory in configEntry.Directories)
{
if (!Path.Exists(directory))
{
throw new FileNotFoundException("Search path not found", directory);
}

var projectFiles = updater.GetAllProjectFiles(directory);
var allDependenciesToUpdate =
await updater.ExtractAllPackagesThatNeedToBeUpdated(projectFiles, configEntry);

if (allDependenciesToUpdate.Count == 0)
var allProjectDependencies = await updater.ExtractAllPackages(projectFiles);
if (allProjectDependencies.Count == 0)
{
continue;
}

var allDependenciesToUpdate = await GetLatestVersions(allProjectDependencies, updater, configEntry);
var uniqueListOfDependencies = allDependenciesToUpdate.DistinctBy(x => x.Name).ToList();
var projectName = ResolveProjectName(configEntry, directory);
foreach (var group in configEntry.Groups)
Expand All @@ -46,18 +43,16 @@ public async Task DoUpdate()
continue;
}

uniqueListOfDependencies.RemoveAll(x => FileSystemName.MatchesSimpleExpression(group, x.Name));
repositoryProvider.SwitchToUpdateBranch(repositoryPath, projectName, group);

uniqueListOfDependencies.RemoveAll(x => FileSystemName.MatchesSimpleExpression(group, x.Name));
var allUpdates = updater.HandleProjectUpdate(projectFiles, matchesForGroup);
if (allUpdates.Count == 0)
{
continue;
}

repositoryProvider.CommitChanges(repositoryPath, projectName, group);
await repositoryProvider.SubmitPullRequest(allUpdates.DistinctBy(x => x.PackageName).ToArray(),
projectName, group);
await repositoryProvider.SubmitPullRequest(allUpdates, projectName, group);
repositoryProvider.SwitchToDefaultBranch(repositoryPath);
}
}
Expand All @@ -73,4 +68,58 @@ private static string ResolveProjectName(Project project, string directory)

return Path.GetFileName(directory);
}

private static DependencyDetails? GetMaxVersion(IReadOnlyCollection<DependencyDetails> versions,
Version currentVersion,
Project projectConfiguration)
{
if (versions.Count == 0)
{
return null;
}

if (projectConfiguration.Version == VersionUpdateType.Major)
{
return versions.MaxBy(x => x.Version);
}

if (projectConfiguration.Version == VersionUpdateType.Minor)
{
return versions.Where(x =>
x.Version.Major == currentVersion.Major && x.Version.Minor > currentVersion.Minor).Max();
}

if (projectConfiguration.Version == VersionUpdateType.Patch)
{
return versions.Where(x =>
x.Version.Major == currentVersion.Major && x.Version.Minor == currentVersion.Minor &&
x.Version.Build > currentVersion.Build).Max();
}

throw new NotSupportedException($"Version configuration {projectConfiguration.Version} is not supported");
}

private async Task<HashSet<DependencyDetails>> GetLatestVersions(
ICollection<DependencyDetails> allDependenciesToCheck,
IProjectUpdater projectUpdater, Project projectConfiguration)
{
var returnList = new HashSet<DependencyDetails>();
foreach (var dependencyDetails in allDependenciesToCheck)
{
logger.Verbose("Processing {PackageName}:{PackageVersion}", dependencyDetails.Name,
dependencyDetails.Version);
var allVersions = await projectUpdater.GetVersions(dependencyDetails, projectConfiguration);
var latestVersion = GetMaxVersion(allVersions, dependencyDetails.Version, projectConfiguration);
if (latestVersion is null)
{
logger.Warning("{PacakgeName} unable to find in sources", dependencyDetails.Name);
continue;
}

logger.Information("{PacakgeName} new version {Version} available", dependencyDetails.Name, latestVersion);
returnList.Add(dependencyDetails with { Version = latestVersion.Version });
}

return returnList;
}
}
73 changes: 16 additions & 57 deletions src/DependencyUpdated.Projects.DotNet/DotNetUpdater.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using DependencyUpdated.Core.Config;
using DependencyUpdated.Core.Interfaces;
using DependencyUpdated.Core.Models;
using DependencyUpdated.Core.Models.Enums;
using Microsoft.Extensions.Caching.Memory;
using NuGet.Common;
using NuGet.Configuration;
Expand Down Expand Up @@ -34,51 +33,9 @@ public IReadOnlyCollection<UpdateResult> HandleProjectUpdate(IReadOnlyCollection
return UpdateCsProj(fullPath, dependenciesToUpdate);
}

public async Task<ICollection<DependencyDetails>> ExtractAllPackagesThatNeedToBeUpdated(IReadOnlyCollection<string> fullPath, Project projectConfiguration)
public async Task<ICollection<DependencyDetails>> ExtractAllPackages(IReadOnlyCollection<string> fullPath)
{
var nugets = ParseCsproj(fullPath);

var returnList = new List<DependencyDetails>();
foreach (var nuget in nugets)
{
logger.Verbose("Processing {PackageName}:{PackageVersion}", nuget.Name, nuget.Version);
var latestVersion = await GetLatestVersion(nuget, projectConfiguration);
if (latestVersion is null)
{
logger.Warning("{PacakgeName} unable to find in sources", nuget.Name);
continue;
}

logger.Information("{PacakgeName} new version {Version} available", nuget.Name, latestVersion);
returnList.Add(nuget with { Version = latestVersion.Version });
}

return returnList;
}

private static NuGetVersion? GetMaxVersion(IEnumerable<NuGetVersion> versions, Version currentVersion,
Project projectConfiguration)
{
var baseQuery = versions.Where(x => !x.IsPrerelease);
if (projectConfiguration.Version == VersionUpdateType.Major)
{
return baseQuery.Max();
}

if (projectConfiguration.Version == VersionUpdateType.Minor)
{
return baseQuery.Where(x =>
x.Version.Major == currentVersion.Major && x.Version.Minor > currentVersion.Minor).Max();
}

if (projectConfiguration.Version == VersionUpdateType.Patch)
{
return baseQuery.Where(x =>
x.Version.Major == currentVersion.Major && x.Version.Minor == currentVersion.Minor &&
x.Version.Build > currentVersion.Build).Max();
}

throw new NotSupportedException($"Version configuration {projectConfiguration.Version} is not supported");
return await Task.FromResult(ParseCsproj(fullPath));
}

private static HashSet<DependencyDetails> ParseCsproj(IReadOnlyCollection<string> paths)
Expand Down Expand Up @@ -116,10 +73,12 @@ private static HashSet<DependencyDetails> ParseCsproj(string path)
return nugets;
}

private async Task<NuGetVersion?> GetLatestVersion(DependencyDetails package, Project projectConfiguration)
public async Task<IReadOnlyCollection<DependencyDetails>> GetVersions(DependencyDetails package,

Check failure on line 76 in src/DependencyUpdated.Projects.DotNet/DotNetUpdater.cs

View workflow job for this annotation

GitHub Actions / build

Check failure on line 76 in src/DependencyUpdated.Projects.DotNet/DotNetUpdater.cs

View workflow job for this annotation

GitHub Actions / build

Project projectConfiguration)
{
var existsInCache = memoryCache.TryGetValue<NuGetVersion?>(package.Name, out var cachedVersion);
if (existsInCache)
var existsInCache =
memoryCache.TryGetValue<IReadOnlyCollection<DependencyDetails>>(package.Name, out var cachedVersion);
if (existsInCache && cachedVersion is not null)
{
return cachedVersion;
}
Expand All @@ -138,7 +97,7 @@ private static HashSet<DependencyDetails> ParseCsproj(string path)
packageSources.Add(new PackageSource(projectConfigurationPath));
continue;
}

var setting = Settings.LoadSpecificSettings(Path.GetDirectoryName(projectConfigurationPath)!,
Path.GetFileName(projectConfigurationPath));
var packageSourceProvider = new PackageSourceProvider(setting);
Expand All @@ -150,7 +109,7 @@ private static HashSet<DependencyDetails> ParseCsproj(string path)
var sourceRepositoryProvider =
new SourceRepositoryProvider(new PackageSourceProvider(NullSettings.Instance, packageSources), providers);
var repositories = sourceRepositoryProvider.GetRepositories();
var version = default(NuGetVersion?);
var allVersions = new List<NuGetVersion>();
foreach (var repository in repositories)
{
var findPackageByIdResource = await repository.GetResourceAsync<FindPackageByIdResource>();
Expand All @@ -159,15 +118,15 @@ private static HashSet<DependencyDetails> ParseCsproj(string path)
new SourceCacheContext(),
NullLogger.Instance,
CancellationToken.None);
var maxVersion = GetMaxVersion(versions, package.Version, projectConfiguration);
if (version is null || (maxVersion is not null && maxVersion >= version))
{
version = maxVersion;
}
allVersions.AddRange(versions.Where(x => !x.IsPrerelease));
}

memoryCache.Set(package.Name, version);
return version;
var result = allVersions
.DistinctBy(x => x.Version)
.Select(x => package with { Version = x.Version })
.ToHashSet();
memoryCache.Set(package.Name, result);
return result;
}

private IReadOnlyCollection<UpdateResult> UpdateCsProj(IReadOnlyCollection<string> fullPaths,
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyUpdated/DependencyUpdated.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\..\README.MD" Pack="true" PackagePath="\"/>
<None Include="..\..\LICENSE" Pack="true" PackagePath="\"/>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DependencyUpdated.Core\DependencyUpdated.Core.csproj" />
</ItemGroup>
</Project>
Loading

0 comments on commit 30c6bdd

Please sign in to comment.