From 944d76de449a27457a800f82947db8c1fc570ab9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 14:32:17 +0200 Subject: [PATCH 1/7] C#: Use the build actions IsWindows in the NugetExeWrapper. --- .../NugetExeWrapper.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index e97b0b118c68..e821d166c21d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -30,6 +30,8 @@ internal class NugetExeWrapper : IDisposable /// private readonly DependencyDirectory packageDirectory; + private bool IsWindows => SystemBuildActions.Instance.IsWindows(); + /// /// Create the package manager for a specified source tree. /// @@ -119,7 +121,7 @@ private string ResolveNugetExe() return path; } - var executableName = Win32.IsWindows() ? "nuget.exe" : "nuget"; + var executableName = IsWindows ? "nuget.exe" : "nuget"; var nugetPath = FileUtils.FindProgramOnPath(executableName); if (nugetPath is not null) { @@ -150,7 +152,7 @@ private string DownloadNugetExe(string sourceDir) return nuget; } - private bool RunWithMono => !Win32.IsWindows() && !string.IsNullOrEmpty(Path.GetExtension(nugetExe)); + private bool RunWithMono => !IsWindows && !string.IsNullOrEmpty(Path.GetExtension(nugetExe)); /// /// Restore all packages in the specified packages.config file. @@ -211,7 +213,7 @@ public int InstallPackages() private bool HasNoPackageSource() { - if (Win32.IsWindows()) + if (IsWindows) { return false; } From 9b34cfa3627c93e9dcf394f3881c2d8587625f26 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 14:37:27 +0200 Subject: [PATCH 2/7] C#: Invert logic in HasPackageSource. --- .../NugetExeWrapper.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index e821d166c21d..19d51e694420 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -45,7 +45,7 @@ public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDir { logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore"); nugetExe = ResolveNugetExe(); - if (HasNoPackageSource() && useDefaultFeed()) + if (!HasPackageSource() && useDefaultFeed()) { // We only modify or add a top level nuget.config file nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config"); @@ -211,11 +211,11 @@ public int InstallPackages() return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage); } - private bool HasNoPackageSource() + private bool HasPackageSource() { if (IsWindows) { - return false; + return true; } try @@ -224,15 +224,15 @@ private bool HasNoPackageSource() RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout); if (stdout.All(line => line != "No sources found.")) { - return false; + return true; } - return true; + return false; } catch (Exception e) { logger.LogWarning($"Failed to check if default package source is added: {e}"); - return false; + return true; } } From 21f8caf1534fd536f11c4d6c3e269b61955df765 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 15:26:58 +0200 Subject: [PATCH 3/7] C#: Re-factor the NugetExeWrapper, introduce an interface and a factory method for constructing package config restorers. --- .../NugetExeWrapper.cs | 480 ++++++++++-------- .../NugetPackageRestorer.cs | 8 +- 2 files changed, 260 insertions(+), 228 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index 19d51e694420..877ff1faa1e0 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -7,299 +7,331 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { - /// - /// Manage the downloading of NuGet packages with nuget.exe. - /// Locates packages in a source tree and downloads all of the - /// referenced assemblies to a temp folder. - /// - internal class NugetExeWrapper : IDisposable + internal interface IPackagesConfigRestore : IDisposable { - private readonly string? nugetExe; - private readonly Semmle.Util.Logging.ILogger logger; - - public int PackageCount => fileProvider.PackagesConfigs.Count; - - private readonly string? backupNugetConfig; - private readonly string? nugetConfigPath; - private readonly FileProvider fileProvider; + /// + /// The number of packages.config files found in the source tree. + /// + int PackageCount { get; } /// - /// The packages directory. - /// This will be in the user-specified or computed Temp location - /// so as to not trample the source tree. + /// Download the packages to the temp folder. /// - private readonly DependencyDirectory packageDirectory; + int InstallPackages(); + } - private bool IsWindows => SystemBuildActions.Instance.IsWindows(); + /// + /// Factory for creating a package manager to restore NuGet packages referenced in packages.config files. + /// It is worth noting that for MacOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last images to contain + /// mono are + /// - Ubuntu 22.04 + /// - MacOS 14 + /// + /// It is worth noting that even with the removal of mono, the content of the packages.config files are parsed and added to the packages list in + /// the FileContent implementation. If the packages are not restored in this step, there is a subsequent step that still may succeed in + /// restoring the packages, albeit without the help of nuget.exe. + /// + internal class PackagesConfigRestoreFactory + { + public static IPackagesConfigRestore Create(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func useDefaultFeed) + { + return new NugetExeWrapper(fileProvider, packageDirectory, logger, useDefaultFeed); + } /// - /// Create the package manager for a specified source tree. + /// Manage the downloading of NuGet packages with nuget.exe. + /// Locates packages in a source tree and downloads all of the + /// referenced assemblies to a temp folder. /// - public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func useDefaultFeed) + private class NugetExeWrapper : IPackagesConfigRestore { - this.fileProvider = fileProvider; - this.packageDirectory = packageDirectory; - this.logger = logger; + private readonly string? nugetExe; + private readonly Semmle.Util.Logging.ILogger logger; + + public int PackageCount => fileProvider.PackagesConfigs.Count; + + private readonly string? backupNugetConfig; + private readonly string? nugetConfigPath; + private readonly FileProvider fileProvider; + + /// + /// The packages directory. + /// This will be in the user-specified or computed Temp location + /// so as to not trample the source tree. + /// + private readonly DependencyDirectory packageDirectory; - if (fileProvider.PackagesConfigs.Count > 0) + private bool IsWindows => SystemBuildActions.Instance.IsWindows(); + + /// + /// Create the package manager for a specified source tree. + /// + public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func useDefaultFeed) { - logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore"); - nugetExe = ResolveNugetExe(); - if (!HasPackageSource() && useDefaultFeed()) + this.fileProvider = fileProvider; + this.packageDirectory = packageDirectory; + this.logger = logger; + + if (fileProvider.PackagesConfigs.Count > 0) { - // We only modify or add a top level nuget.config file - nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config"); - try + logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore"); + nugetExe = ResolveNugetExe(); + if (!HasPackageSource() && useDefaultFeed()) { - if (File.Exists(nugetConfigPath)) + // We only modify or add a top level nuget.config file + nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config"); + try { - var tempFolderPath = FileUtils.GetTemporaryWorkingDirectory(out _); - - do + if (File.Exists(nugetConfigPath)) { - backupNugetConfig = Path.Combine(tempFolderPath, Path.GetRandomFileName()); + var tempFolderPath = FileUtils.GetTemporaryWorkingDirectory(out _); + + do + { + backupNugetConfig = Path.Combine(tempFolderPath, Path.GetRandomFileName()); + } + while (File.Exists(backupNugetConfig)); + File.Copy(nugetConfigPath, backupNugetConfig, true); } - while (File.Exists(backupNugetConfig)); - File.Copy(nugetConfigPath, backupNugetConfig, true); - } - else - { - File.WriteAllText(nugetConfigPath, - """ + else + { + File.WriteAllText(nugetConfigPath, + """ """); + } + AddDefaultPackageSource(nugetConfigPath); + } + catch (Exception e) + { + logger.LogError($"Failed to add default package source to {nugetConfigPath}: {e}"); } - AddDefaultPackageSource(nugetConfigPath); - } - catch (Exception e) - { - logger.LogError($"Failed to add default package source to {nugetConfigPath}: {e}"); } } } - } - /// - /// Tries to find the location of `nuget.exe`. It looks for - /// - the environment variable specifying a location, - /// - files in the repository, - /// - tries to resolve nuget from the PATH, or - /// - downloads it if it is not found. - /// - private string ResolveNugetExe() - { - var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath); - if (!string.IsNullOrEmpty(envVarPath)) + /// + /// Tries to find the location of `nuget.exe`. It looks for + /// - the environment variable specifying a location, + /// - files in the repository, + /// - tries to resolve nuget from the PATH, or + /// - downloads it if it is not found. + /// + private string ResolveNugetExe() { - logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'"); - return envVarPath; - } + var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath); + if (!string.IsNullOrEmpty(envVarPath)) + { + logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'"); + return envVarPath; + } - try - { - return DownloadNugetExe(fileProvider.SourceDir.FullName); - } - catch (Exception exc) - { - logger.LogInfo($"Download of nuget.exe failed: {exc.Message}"); - } + try + { + return DownloadNugetExe(fileProvider.SourceDir.FullName); + } + catch (Exception exc) + { + logger.LogInfo($"Download of nuget.exe failed: {exc.Message}"); + } - var nugetExesInRepo = fileProvider.NugetExes; - if (nugetExesInRepo.Count > 1) - { - logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}"); - } + var nugetExesInRepo = fileProvider.NugetExes; + if (nugetExesInRepo.Count > 1) + { + logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}"); + } - if (nugetExesInRepo.Count > 0) - { - var path = nugetExesInRepo.First(); - logger.LogInfo($"Using nuget.exe from path '{path}'"); - return path; - } + if (nugetExesInRepo.Count > 0) + { + var path = nugetExesInRepo.First(); + logger.LogInfo($"Using nuget.exe from path '{path}'"); + return path; + } - var executableName = IsWindows ? "nuget.exe" : "nuget"; - var nugetPath = FileUtils.FindProgramOnPath(executableName); - if (nugetPath is not null) - { - nugetPath = Path.Combine(nugetPath, executableName); - logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}"); - return nugetPath; + var executableName = IsWindows ? "nuget.exe" : "nuget"; + var nugetPath = FileUtils.FindProgramOnPath(executableName); + if (nugetPath is not null) + { + nugetPath = Path.Combine(nugetPath, executableName); + logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}"); + return nugetPath; + } + + throw new Exception("Could not find or download nuget.exe."); } - throw new Exception("Could not find or download nuget.exe."); - } + private string DownloadNugetExe(string sourceDir) + { + var directory = Path.Combine(sourceDir, ".nuget"); + var nuget = Path.Combine(directory, "nuget.exe"); - private string DownloadNugetExe(string sourceDir) - { - var directory = Path.Combine(sourceDir, ".nuget"); - var nuget = Path.Combine(directory, "nuget.exe"); + // Nuget.exe already exists in the .nuget directory. + if (File.Exists(nuget)) + { + logger.LogInfo($"Found nuget.exe at {nuget}"); + return nuget; + } - // Nuget.exe already exists in the .nuget directory. - if (File.Exists(nuget)) - { - logger.LogInfo($"Found nuget.exe at {nuget}"); + Directory.CreateDirectory(directory); + logger.LogInfo("Attempting to download nuget.exe"); + FileUtils.DownloadFile(FileUtils.NugetExeUrl, nuget, logger); + logger.LogInfo($"Downloaded nuget.exe to {nuget}"); return nuget; } - Directory.CreateDirectory(directory); - logger.LogInfo("Attempting to download nuget.exe"); - FileUtils.DownloadFile(FileUtils.NugetExeUrl, nuget, logger); - logger.LogInfo($"Downloaded nuget.exe to {nuget}"); - return nuget; - } - - private bool RunWithMono => !IsWindows && !string.IsNullOrEmpty(Path.GetExtension(nugetExe)); + private bool RunWithMono => !IsWindows && !string.IsNullOrEmpty(Path.GetExtension(nugetExe)); - /// - /// Restore all packages in the specified packages.config file. - /// - /// The packages.config file. - private bool TryRestoreNugetPackage(string packagesConfig) - { - logger.LogInfo($"Restoring file \"{packagesConfig}\"..."); + /// + /// Restore all packages in the specified packages.config file. + /// + /// The packages.config file. + private bool TryRestoreNugetPackage(string packagesConfig) + { + logger.LogInfo($"Restoring file \"{packagesConfig}\"..."); - /* Use nuget.exe to install a package. - * Note that there is a clutch of NuGet assemblies which could be used to - * invoke this directly, which would arguably be nicer. However they are - * really unwieldy and this solution works for now. - */ + /* Use nuget.exe to install a package. + * Note that there is a clutch of NuGet assemblies which could be used to + * invoke this directly, which would arguably be nicer. However they are + * really unwieldy and this solution works for now. + */ - string exe, args; - if (RunWithMono) - { - exe = "mono"; - args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\""; - } - else - { - exe = nugetExe!; - args = $"install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\""; - } + string exe, args; + if (RunWithMono) + { + exe = "mono"; + args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\""; + } + else + { + exe = nugetExe!; + args = $"install -OutputDirectory \"{packageDirectory}\" \"{packagesConfig}\""; + } - var pi = new ProcessStartInfo(exe, args) - { - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }; - - var threadId = Environment.CurrentManagedThreadId; - void onOut(string s) => logger.LogDebug(s, threadId); - void onError(string s) => logger.LogError(s, threadId); - var exitCode = pi.ReadOutput(out _, onOut, onError); - if (exitCode != 0) - { - logger.LogError($"Command {pi.FileName} {pi.Arguments} failed with exit code {exitCode}"); - return false; - } - else - { - logger.LogInfo($"Restored file \"{packagesConfig}\""); - return true; + var pi = new ProcessStartInfo(exe, args) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + var threadId = Environment.CurrentManagedThreadId; + void onOut(string s) => logger.LogDebug(s, threadId); + void onError(string s) => logger.LogError(s, threadId); + var exitCode = pi.ReadOutput(out _, onOut, onError); + if (exitCode != 0) + { + logger.LogError($"Command {pi.FileName} {pi.Arguments} failed with exit code {exitCode}"); + return false; + } + else + { + logger.LogInfo($"Restored file \"{packagesConfig}\""); + return true; + } } - } - - /// - /// Download the packages to the temp folder. - /// - public int InstallPackages() - { - return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage); - } - private bool HasPackageSource() - { - if (IsWindows) + /// + /// Download the packages to the temp folder. + /// + public int InstallPackages() { - return true; + return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage); } - try + private bool HasPackageSource() { - logger.LogInfo("Checking if default package source is available..."); - RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout); - if (stdout.All(line => line != "No sources found.")) + if (IsWindows) { return true; } - return false; - } - catch (Exception e) - { - logger.LogWarning($"Failed to check if default package source is added: {e}"); - return true; - } - } + try + { + logger.LogInfo("Checking if default package source is available..."); + RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout); + if (stdout.All(line => line != "No sources found.")) + { + return true; + } - private void RunMonoNugetCommand(string command, out IList stdout) - { - string exe, args; - if (RunWithMono) - { - exe = "mono"; - args = $"\"{nugetExe}\" {command}"; - } - else - { - exe = nugetExe!; - args = command; + return false; + } + catch (Exception e) + { + logger.LogWarning($"Failed to check if default package source is added: {e}"); + return true; + } } - var pi = new ProcessStartInfo(exe, args) + private void RunMonoNugetCommand(string command, out IList stdout) { - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }; - - var threadId = Environment.CurrentManagedThreadId; - void onOut(string s) => logger.LogDebug(s, threadId); - void onError(string s) => logger.LogError(s, threadId); - pi.ReadOutput(out stdout, onOut, onError); - } + string exe, args; + if (RunWithMono) + { + exe = "mono"; + args = $"\"{nugetExe}\" {command}"; + } + else + { + exe = nugetExe!; + args = command; + } - private void AddDefaultPackageSource(string nugetConfig) - { - logger.LogInfo("Adding default package source..."); - RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {NugetPackageRestorer.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out _); - } + var pi = new ProcessStartInfo(exe, args) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + var threadId = Environment.CurrentManagedThreadId; + void onOut(string s) => logger.LogDebug(s, threadId); + void onError(string s) => logger.LogError(s, threadId); + pi.ReadOutput(out stdout, onOut, onError); + } - public void Dispose() - { - if (nugetConfigPath is null) + private void AddDefaultPackageSource(string nugetConfig) { - return; + logger.LogInfo("Adding default package source..."); + RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {NugetPackageRestorer.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out _); } - try + public void Dispose() { - if (backupNugetConfig is null) + if (nugetConfigPath is null) { - logger.LogInfo("Removing nuget.config file"); - File.Delete(nugetConfigPath); return; } - logger.LogInfo("Reverting nuget.config file content"); - // The content of the original nuget.config file is reverted without changing the file's attributes or casing: - using (var backup = File.OpenRead(backupNugetConfig)) - using (var current = File.OpenWrite(nugetConfigPath)) + try { - current.SetLength(0); // Truncate file - backup.CopyTo(current); // Restore original content - } + if (backupNugetConfig is null) + { + logger.LogInfo("Removing nuget.config file"); + File.Delete(nugetConfigPath); + return; + } - logger.LogInfo("Deleting backup nuget.config file"); - File.Delete(backupNugetConfig); - } - catch (Exception exc) - { - logger.LogError($"Failed to restore original nuget.config file: {exc}"); + logger.LogInfo("Reverting nuget.config file content"); + // The content of the original nuget.config file is reverted without changing the file's attributes or casing: + using (var backup = File.OpenRead(backupNugetConfig)) + using (var current = File.OpenWrite(nugetConfigPath)) + { + current.SetLength(0); // Truncate file + backup.CopyTo(current); // Restore original content + } + + logger.LogInfo("Deleting backup nuget.config file"); + File.Delete(backupNugetConfig); + } + catch (Exception exc) + { + logger.LogError($"Failed to restore original nuget.config file: {exc}"); + } } } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index e042285af11c..7bafbb6f4300 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -161,13 +161,13 @@ public HashSet Restore() reachableFeeds.UnionWith(reachableInheritedFeeds); } - using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger, IsDefaultFeedReachable)) + using (var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, IsDefaultFeedReachable)) { - var count = nuget.InstallPackages(); + var count = packagesConfigRestore.InstallPackages(); - if (nuget.PackageCount > 0) + if (packagesConfigRestore.PackageCount > 0) { - compilationInfoContainer.CompilationInfos.Add(("packages.config files", nuget.PackageCount.ToString())); + compilationInfoContainer.CompilationInfos.Add(("packages.config files", packagesConfigRestore.PackageCount.ToString())); compilationInfoContainer.CompilationInfos.Add(("Successfully restored packages.config files", count.ToString())); } } From 63057db7534833a012f51275d233b07fb83a9e95 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 15:30:21 +0200 Subject: [PATCH 4/7] C#: Only download and use nuget.exe in case of windows or mono is installed. --- .../NugetExeWrapper.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index 877ff1faa1e0..d41917972594 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -22,6 +22,7 @@ internal interface IPackagesConfigRestore : IDisposable /// /// Factory for creating a package manager to restore NuGet packages referenced in packages.config files. + /// If the environment doesn't support using nuget.exe to restore packages from packages.config files, a no-op implementation is returned. /// It is worth noting that for MacOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last images to contain /// mono are /// - Ubuntu 22.04 @@ -35,7 +36,12 @@ internal class PackagesConfigRestoreFactory { public static IPackagesConfigRestore Create(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func useDefaultFeed) { - return new NugetExeWrapper(fileProvider, packageDirectory, logger, useDefaultFeed); + if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMonoInstalled()) + { + return new NugetExeWrapper(fileProvider, packageDirectory, logger, useDefaultFeed); + } + + return new NoOpPackagesConfig(fileProvider, logger); } /// @@ -334,5 +340,30 @@ public void Dispose() } } } + + private class NoOpPackagesConfig : IPackagesConfigRestore + { + private readonly Semmle.Util.Logging.ILogger logger; + private readonly FileProvider fileProvider; + + public NoOpPackagesConfig(FileProvider fileProvider, Semmle.Util.Logging.ILogger logger) + { + this.fileProvider = fileProvider; + this.logger = logger; + } + + public int PackageCount => fileProvider.PackagesConfigs.Count; + + public int InstallPackages() + { + if (PackageCount > 0) + { + logger.LogInfo("Found packages.config files, but nuget.exe cannot be used to restore packages on this platform. Skipping restore of packages.config files."); + } + return 0; + } + + public void Dispose() { } + } } } From dfdd12190e97dbcf7eaedcbfa9e7fab1674d32ad Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 15:47:08 +0200 Subject: [PATCH 5/7] C#: Rename NugetExeWrapper to PackagesConfigRestorer. --- .../{NugetExeWrapper.cs => PackagesConfigRestorer.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/{NugetExeWrapper.cs => PackagesConfigRestorer.cs} (100%) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs similarity index 100% rename from csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs rename to csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs From c747352f411d39740123da8057f7811321e9f81b Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 17 Jun 2026 15:59:44 +0200 Subject: [PATCH 6/7] C#: Fix some code quality issues by replacing Path.Combine with Path.Join. --- .../PackagesConfigRestorer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index d41917972594..9840da4fc653 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -85,7 +85,7 @@ public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDir if (!HasPackageSource() && useDefaultFeed()) { // We only modify or add a top level nuget.config file - nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config"); + nugetConfigPath = Path.Join(fileProvider.SourceDir.FullName, "nuget.config"); try { if (File.Exists(nugetConfigPath)) @@ -94,7 +94,7 @@ public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDir do { - backupNugetConfig = Path.Combine(tempFolderPath, Path.GetRandomFileName()); + backupNugetConfig = Path.Join(tempFolderPath, Path.GetRandomFileName()); } while (File.Exists(backupNugetConfig)); File.Copy(nugetConfigPath, backupNugetConfig, true); @@ -162,7 +162,7 @@ private string ResolveNugetExe() var nugetPath = FileUtils.FindProgramOnPath(executableName); if (nugetPath is not null) { - nugetPath = Path.Combine(nugetPath, executableName); + nugetPath = Path.Join(nugetPath, executableName); logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}"); return nugetPath; } @@ -172,8 +172,8 @@ private string ResolveNugetExe() private string DownloadNugetExe(string sourceDir) { - var directory = Path.Combine(sourceDir, ".nuget"); - var nuget = Path.Combine(directory, "nuget.exe"); + var directory = Path.Join(sourceDir, ".nuget"); + var nuget = Path.Join(directory, "nuget.exe"); // Nuget.exe already exists in the .nuget directory. if (File.Exists(nuget)) From 142a72c77bbe7727508ee2050c8f98606813a7e1 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 18 Jun 2026 12:48:09 +0200 Subject: [PATCH 7/7] C#: Address review comments. --- .../PackagesConfigRestorer.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index 9840da4fc653..68a0a746ca91 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -23,14 +23,13 @@ internal interface IPackagesConfigRestore : IDisposable /// /// Factory for creating a package manager to restore NuGet packages referenced in packages.config files. /// If the environment doesn't support using nuget.exe to restore packages from packages.config files, a no-op implementation is returned. - /// It is worth noting that for MacOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last images to contain - /// mono are + /// It is worth noting that for macOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last GitHub images + /// to contain mono are: /// - Ubuntu 22.04 - /// - MacOS 14 - /// - /// It is worth noting that even with the removal of mono, the content of the packages.config files are parsed and added to the packages list in - /// the FileContent implementation. If the packages are not restored in this step, there is a subsequent step that still may succeed in - /// restoring the packages, albeit without the help of nuget.exe. + /// - macOS 14 + /// + /// If the packages from the packages.config files are not restored with the packages.config restore functionality below, there is a subsequent + /// step that still may succeed in restoring the packages without the help of nuget.exe (by attempting to restore using dotnet). /// internal class PackagesConfigRestoreFactory {