Browse Source

Merge pull request #26109 from abpframework/issue-switch-to-nightly-cpm-support

Add CPM include support to switch-to-nightly
pull/25822/merge
Yağmur Çelik 1 day ago
committed by GitHub
parent
commit
76888e5688
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs
  2. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  3. 103
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs
  4. 103
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs

@ -32,6 +32,8 @@ public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("-d|--directory");
sb.AppendLine("-i|--include (optional) comma-separated list of Directory.Packages.props-style files to also update for Central Package Management");
sb.AppendLine("-ep|--exclude-packages (optional) comma-separated list of package ids to never touch in --include files");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://abp.io/docs/latest/cli");

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -132,7 +132,7 @@ public class NpmPackagesUpdater : ITransientDependency
if (!fileContent.Contains(volosoftRegistry))
{
fileContent += volosoftRegistry;
fileContent += Environment.NewLine + volosoftRegistry;
}
File.WriteAllText(fileName, fileContent);

103
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -66,13 +67,13 @@ public class PackagePreviewSwitcher : ITransientDependency
if (solutionPaths.Any())
{
await SwitchSolutionsToNightlyPreview(solutionPaths);
await SwitchSolutionsToNightlyPreview(solutionPaths, commandLineArgs);
}
else
{
var projectPaths = GetProjectPaths(commandLineArgs);
await SwitchProjectsToNightlyPreview(projectPaths);
await SwitchProjectsToNightlyPreview(projectPaths, commandLineArgs);
}
}
@ -185,13 +186,16 @@ public class PackagePreviewSwitcher : ITransientDependency
}
}
private async Task SwitchProjectsToNightlyPreview(List<string> projects)
private async Task SwitchProjectsToNightlyPreview(List<string> projects, CommandLineArgs commandLineArgs)
{
var (includeFiles, excludedPackages, latestVersionFromMyGet) = await ResolveNightlyIncludeContextAsync(commandLineArgs);
foreach (var project in projects)
{
var folder = Path.GetDirectoryName(project);
var projectFolder = FindSolutionFolder(project) ?? folder;
_packageSourceManager.Add(FindSolutionFolder(project) ?? folder, "ABP Nightly",
_packageSourceManager.Add(projectFolder, "ABP Nightly",
"https://www.myget.org/F/abp-nightly/api/v3/index.json", "Volo.*");
await _nugetPackagesVersionUpdater.UpdateSolutionAsync(
@ -201,11 +205,17 @@ public class PackagePreviewSwitcher : ITransientDependency
await _npmPackagesUpdater.Update(
folder,
true);
// See SwitchSolutionsToNightlyPreview for the race-avoidance rationale: this
// sequential pass always runs after the per-project UpdateSolutionAsync above.
await UpdateIncludedCentralPackageFilesAsync(includeFiles, excludedPackages, latestVersionFromMyGet, projectFolder);
}
}
private async Task SwitchSolutionsToNightlyPreview(List<string> solutionPaths)
private async Task SwitchSolutionsToNightlyPreview(List<string> solutionPaths, CommandLineArgs commandLineArgs)
{
var (includeFiles, excludedPackages, latestVersionFromMyGet) = await ResolveNightlyIncludeContextAsync(commandLineArgs);
foreach (var solutionPath in solutionPaths)
{
var solutionFolder = Path.GetDirectoryName(solutionPath);
@ -232,6 +242,67 @@ public class PackagePreviewSwitcher : ITransientDependency
solutionAngularFolder,
true);
}
// Optional Central Package Management support: only runs when --include is
// explicitly passed, and only after UpdateSolutionAsync's internal parallel
// (Task.WaitAll) per-project update has fully completed, so no --include file
// is ever touched concurrently with anything else.
await UpdateIncludedCentralPackageFilesAsync(includeFiles, excludedPackages, latestVersionFromMyGet, solutionFolder);
}
}
private async Task<(List<string> IncludeFiles, List<string> ExcludedPackages, string LatestVersionFromMyGet)> ResolveNightlyIncludeContextAsync(
CommandLineArgs commandLineArgs)
{
var includeFiles = GetCommaSeparatedOption(commandLineArgs, Options.Include.Short, Options.Include.Long);
var excludedPackages = GetCommaSeparatedOption(commandLineArgs, Options.Exclude.Short, Options.Exclude.Long);
if (!includeFiles.Any())
{
return (includeFiles, excludedPackages, null);
}
string latestVersionFromMyGet;
try
{
latestVersionFromMyGet = await _nugetPackagesVersionUpdater.GetLatestVersionFromMyGet("Volo.Abp.Core");
}
catch (Exception ex)
{
// Don't let a transient MyGet failure abort the whole switch-to-nightly run
// (source registration / regular PackageReference updates below must still
// proceed for every solution/project) - just skip the --include pass.
Logger.LogWarning(ex, "Could not resolve the latest Volo.Abp.Core nightly version; --include files will be skipped for this run.");
return (includeFiles, excludedPackages, null);
}
if (latestVersionFromMyGet.IsNullOrWhiteSpace())
{
// No exception was thrown, but MyGet simply has no version for this package yet
// (e.g. not indexed there) - warn so users aren't left wondering why --include did nothing.
Logger.LogWarning("Could not resolve the latest Volo.Abp.Core nightly version; --include files will be skipped for this run.");
return (includeFiles, excludedPackages, null);
}
return (includeFiles, excludedPackages, latestVersionFromMyGet);
}
private async Task UpdateIncludedCentralPackageFilesAsync(
List<string> includeFiles,
List<string> excludedPackages,
string latestVersionFromMyGet,
string baseFolder)
{
foreach (var includeFile in includeFiles)
{
var resolvedPath = Path.IsPathRooted(includeFile)
? includeFile
: Path.Combine(baseFolder, includeFile);
await _nugetPackagesVersionUpdater.UpdateCentralPackageVersionsAsync(
resolvedPath,
latestVersionFromMyGet,
excludedPackages);
}
}
@ -285,6 +356,14 @@ public class PackagePreviewSwitcher : ITransientDependency
?? Directory.GetCurrentDirectory();
}
private List<string> GetCommaSeparatedOption(CommandLineArgs commandLineArgs, string shortName, string longName)
{
var raw = commandLineArgs.Options.GetOrNull(shortName, longName);
return raw.IsNullOrWhiteSpace()
? new List<string>()
: raw.Split(',').Select(s => s.Trim()).Where(s => !s.IsNullOrWhiteSpace()).ToList();
}
private string GetSolutionAngularFolder(string solutionFolder)
{
var upperAngularPath = Path.Combine(Directory.GetParent(solutionFolder)?.FullName ?? "", "angular");
@ -340,5 +419,15 @@ public class PackagePreviewSwitcher : ITransientDependency
public const string Short = "d";
public const string Long = "directory";
}
public static class Include
{
public const string Short = "i";
public const string Long = "include";
}
public static class Exclude
{
public const string Short = "ep";
public const string Long = "exclude-packages";
}
}
}

103
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs

@ -217,8 +217,8 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
}
var currentVersion = versionAttribute.Value;
var isLeptonXPackage = packageId.Contains("LeptonX");
var isStudioPackage = packageId.StartsWith("Volo.Abp.Studio.");
var isLeptonXPackage = IsLeptonXPackage(packageId);
var isStudioPackage = IsStudioPackage(packageId);
if(isLeptonXPackage)
{
//'SemanticVersion.TryParse' can not parse the version if the version contains floating version resolution, such as '*-*'
@ -366,10 +366,107 @@ public class VoloNugetPackagesVersionUpdater : ITransientDependency
return await Task.FromResult(content);
}
private async Task<string> GetLatestVersionFromMyGet(string packageId)
private static bool IsLeptonXPackage(string packageId) => packageId.Contains("LeptonX");
private static bool IsStudioPackage(string packageId) => packageId.StartsWith("Volo.Abp.Studio.");
internal async Task<string> GetLatestVersionFromMyGet(string packageId)
{
var myGetPack = await _myGetPackageListFinder.GetPackagesAsync();
return myGetPack.Packages.FirstOrDefault(p => p.Id == packageId)?.Versions.LastOrDefault();
}
/// <summary>
/// Updates &lt;PackageVersion Include="Volo.*"&gt; entries in a Central Package Management
/// props file (e.g. Directory.Packages.props) to <paramref name="latestVersionFromMyGet"/>.
/// Regular PackageReference-based updates (UpdateSolutionAsync/UpdateProjectAsync) already
/// skip any PackageReference with no Version attribute (i.e. CPM-managed packages) - this
/// method is the explicit, opt-in counterpart for callers that also want those central
/// versions kept in sync. Not invoked unless a caller (e.g. the switch-to-nightly --include
/// option) explicitly requests it.
/// </summary>
public async Task UpdateCentralPackageVersionsAsync(
string filePath,
string latestVersionFromMyGet,
IEnumerable<string> excludedPackageIds = null)
{
if (!File.Exists(filePath))
{
Logger.LogWarning("--include file not found, skipped: {FilePath}", filePath);
return;
}
if (latestVersionFromMyGet == null)
{
return;
}
var excluded = new HashSet<string>(excludedPackageIds ?? Enumerable.Empty<string>(), StringComparer.OrdinalIgnoreCase);
try
{
using (var fs = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
using (var sr = new StreamReader(fs, DefaultEncoding, true))
{
var fileContent = await sr.ReadToEndAsync();
var doc = new XmlDocument { PreserveWhitespace = true };
doc.LoadXml(fileContent);
var packageNodeList = doc.SelectNodes("//PackageVersion[starts-with(@Include, 'Volo.')]");
if (packageNodeList != null)
{
foreach (XmlNode package in packageNodeList)
{
var packageId = package.Attributes?["Include"]?.Value;
if (packageId == null || excluded.Contains(packageId))
{
continue;
}
// LeptonX and Studio packages follow their own, independent version
// stream (see IsLeptonXPackage/IsStudioPackage, also used by
// UpdateVoloPackagesAsync above) - never stamp them with the
// Volo.Abp.Core anchor version, regardless of --exclude-packages.
if (IsLeptonXPackage(packageId) || IsStudioPackage(packageId))
{
continue;
}
var versionAttribute = package.Attributes["Version"];
if (versionAttribute == null)
{
continue;
}
if (versionAttribute.Value != latestVersionFromMyGet)
{
Logger.LogInformation("Updating central package \"{PackageId}\" from v{CurrentVersion} to v{LatestVersion}", packageId, versionAttribute.Value, latestVersionFromMyGet);
versionAttribute.Value = latestVersionFromMyGet;
}
}
}
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);
using (var sw = new StreamWriter(fs, DefaultEncoding))
{
await sw.WriteAsync(doc.OuterXml);
await sw.FlushAsync();
}
}
}
}
catch (Exception ex)
{
// The file is truncated before the updated XML is written back, so a failure here
// (disk full, process killed, file locked mid-write) can leave it empty/partially
// written on disk. Logged as an error (not a warning) so this isn't missed - the
// rest of the switch-to-nightly run still continues for other solutions/files.
Logger.LogError(ex, "Failed to update central package versions in \"{FilePath}\". The file may now be empty or partially written - please check it manually.", filePath);
}
}
}

Loading…
Cancel
Save