diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
index 61b3ad39d..48675a2f1 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
@@ -76,11 +76,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
targetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase) && !a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase));
string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase));
- if (frameworkVersion != null)
+ if (frameworkVersion != null && Version.TryParse(frameworkVersion.Substring(VersionToken.Length).Replace("v", ""), out var version))
{
- versionNumber = int.Parse(frameworkVersion.Substring(VersionToken.Length).Replace("v", "").Replace(".", ""));
- if (versionNumber < 100)
- versionNumber *= 10;
+ versionNumber = version.Major * 100 + version.Minor * 10;
+ if (version.Build > 0)
+ versionNumber += version.Build;
}
string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase));
diff --git a/ICSharpCode.Decompiler/Solution/SolutionCreator.cs b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
index 1619ea8f1..2b11a712f 100644
--- a/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
+++ b/ICSharpCode.Decompiler/Solution/SolutionCreator.cs
@@ -29,10 +29,11 @@ namespace ICSharpCode.Decompiler.Solution
///
public static class SolutionCreator
{
- private static readonly XNamespace ProjectFileNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
+ static readonly XNamespace NonSDKProjectFileNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
///
/// Writes a solution file to the specified .
+ /// Also fixes intra-solution project references in the project files.
///
/// The full path of the file to write.
/// The projects contained in this solution.
@@ -40,7 +41,7 @@ namespace ICSharpCode.Decompiler.Solution
/// Thrown when is null or empty.
/// Thrown when is null.
/// Thrown when contains no items.
- public static void WriteSolutionFile(string targetFile, IEnumerable projects)
+ public static void WriteSolutionFile(string targetFile, List projects)
{
if (string.IsNullOrWhiteSpace(targetFile))
{
@@ -62,10 +63,10 @@ namespace ICSharpCode.Decompiler.Solution
WriteSolutionFile(writer, projects, targetFile);
}
- FixProjectReferences(projects);
+ FixAllProjectReferences(projects);
}
- private static void WriteSolutionFile(TextWriter writer, IEnumerable projects, string solutionFilePath)
+ static void WriteSolutionFile(TextWriter writer, List projects, string solutionFilePath)
{
WriteHeader(writer);
WriteProjects(writer, projects, solutionFilePath);
@@ -90,7 +91,7 @@ namespace ICSharpCode.Decompiler.Solution
writer.WriteLine("MinimumVisualStudioVersion = 10.0.40219.1");
}
- private static void WriteProjects(TextWriter writer, IEnumerable projects, string solutionFilePath)
+ static void WriteProjects(TextWriter writer, List projects, string solutionFilePath)
{
foreach (var project in projects)
{
@@ -103,7 +104,7 @@ namespace ICSharpCode.Decompiler.Solution
}
}
- private static IEnumerable WriteSolutionConfigurations(TextWriter writer, IEnumerable projects)
+ static List WriteSolutionConfigurations(TextWriter writer, List projects)
{
var platforms = projects.GroupBy(p => p.PlatformName).Select(g => g.Key).ToList();
@@ -125,10 +126,10 @@ namespace ICSharpCode.Decompiler.Solution
return platforms;
}
- private static void WriteProjectConfigurations(
+ static void WriteProjectConfigurations(
TextWriter writer,
- IEnumerable projects,
- IEnumerable solutionPlatforms)
+ List projects,
+ List solutionPlatforms)
{
writer.WriteLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
@@ -152,47 +153,80 @@ namespace ICSharpCode.Decompiler.Solution
writer.WriteLine("\tEndGlobalSection");
}
- private static void FixProjectReferences(IEnumerable projects)
+ static void FixAllProjectReferences(List projects)
{
- var projectsMap = projects.ToDictionary(p => p.ProjectName, p => p);
+ var projectsMap = projects.ToDictionary(
+ p => p.ProjectName,
+ p => p);
foreach (var project in projects)
{
XDocument projectDoc = XDocument.Load(project.FilePath);
+ if (projectDoc.Root?.Name.LocalName != "Project")
+ {
+ throw new InvalidOperationException(
+ $"The file {project.FilePath} is not a valid project file, " +
+ $"no at the root; could not fix project references.");
+ }
+
+ // sdk style projects don't use a namespace for the elements,
+ // but we still need to use the namespace for non-sdk style projects.
+ var sdkStyle = projectDoc.Root.Attribute("Sdk") != null;
+ var itemGroupTagName = sdkStyle ? "ItemGroup" : NonSDKProjectFileNamespace + "ItemGroup";
+ var referenceTagName = sdkStyle ? "Reference" : NonSDKProjectFileNamespace + "Reference";
+
var referencesItemGroups = projectDoc.Root
- .Elements(ProjectFileNamespace + "ItemGroup")
- .Where(e => e.Elements(ProjectFileNamespace + "Reference").Any());
+ .Elements(itemGroupTagName)
+ .Where(e => e.Elements(referenceTagName).Any())
+ .ToList();
foreach (var itemGroup in referencesItemGroups)
{
- FixProjectReferences(project.FilePath, itemGroup, projectsMap);
+ FixProjectReferences(project.FilePath, itemGroup, projectsMap, sdkStyle);
}
projectDoc.Save(project.FilePath);
}
}
- private static void FixProjectReferences(string projectFilePath, XElement itemGroup, IDictionary projects)
+ static void FixProjectReferences(string projectFilePath, XElement itemGroup,
+ Dictionary projects, bool sdkStyle)
{
- foreach (var item in itemGroup.Elements(ProjectFileNamespace + "Reference").ToList())
+
+ XName GetElementName(string name) => sdkStyle ? name : NonSDKProjectFileNamespace + name;
+
+ var referenceTagName = GetElementName("Reference");
+ var projectReferenceTagName = GetElementName("ProjectReference");
+
+ foreach (var item in itemGroup.Elements(referenceTagName).ToList())
{
var assemblyName = item.Attribute("Include")?.Value;
if (assemblyName != null && projects.TryGetValue(assemblyName, out var referencedProject))
{
item.Remove();
- var projectReference = new XElement(ProjectFileNamespace + "ProjectReference",
- new XElement(ProjectFileNamespace + "Project", referencedProject.Guid.ToString("B").ToUpperInvariant()),
- new XElement(ProjectFileNamespace + "Name", referencedProject.ProjectName));
- projectReference.SetAttributeValue("Include", GetRelativePath(projectFilePath, referencedProject.FilePath));
+ var projectReference = new XElement(
+ projectReferenceTagName,
+ new XAttribute("Include", GetRelativePath(projectFilePath, referencedProject.FilePath)));
+
+ // SDK-style projects do not use the and elements for project references.
+ // (Instead, those get read from the .csproj file in "Include".)
+ if (!sdkStyle)
+ {
+ projectReference.Add(
+ // no ToUpper() for uuids, most Microsoft tools seem to emit them in lowercase
+ // (no .ToLower() as .ToString("B") already outputs lowercase)
+ new XElement(NonSDKProjectFileNamespace + "Project", referencedProject.Guid.ToString("B")),
+ new XElement(NonSDKProjectFileNamespace + "Name", referencedProject.ProjectName));
+ }
itemGroup.Add(projectReference);
}
}
}
- private static string GetRelativePath(string fromFilePath, string toFilePath)
+ static string GetRelativePath(string fromFilePath, string toFilePath)
{
Uri fromUri = new Uri(fromFilePath);
Uri toUri = new Uri(toFilePath);
diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
index 071f29599..2950a3200 100644
--- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs
+++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs
@@ -78,7 +78,7 @@ namespace ICSharpCode.ILSpy.TextView
{
var assemblies = selectedNodes.OfType()
.Select(n => n.LoadedAssembly)
- .Where(a => a.IsLoadedAsValidAssembly).ToArray();
+ .Where(a => a.IsLoadedAsValidAssembly).ToList();
SolutionWriter.CreateSolution(tabPage, textView, selectedPath, currentLanguage, assemblies);
}
return;
diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs
index 29e8db8b0..62f6ef6c2 100644
--- a/ILSpy/SolutionWriter.cs
+++ b/ILSpy/SolutionWriter.cs
@@ -56,7 +56,7 @@ namespace ICSharpCode.ILSpy
/// Thrown when > or
/// is null.
public static void CreateSolution(TabPageModel tabPage, DecompilerTextView textView, string solutionFilePath,
- Language language, IEnumerable assemblies)
+ Language language, List assemblies)
{
if (textView == null)
{
@@ -77,7 +77,7 @@ namespace ICSharpCode.ILSpy
textView
.RunWithCancellation(ct => writer.CreateSolution(tabPage, assemblies, language, ct))
- .Then(output => textView.ShowText(output))
+ .Then(textView.ShowText)
.HandleExceptions();
}
@@ -94,41 +94,29 @@ namespace ICSharpCode.ILSpy
projects = new ConcurrentBag();
}
- async Task CreateSolution(TabPageModel tabPage, IEnumerable assemblies, Language language, CancellationToken ct)
+ async Task CreateSolution(TabPageModel tabPage, List allAssemblies, Language language, CancellationToken ct)
{
var result = new AvalonEditTextOutput();
- var assembliesByShortName = assemblies.ToLookup(_ => _.ShortName);
+ var assembliesByShortName = allAssemblies.GroupBy(_ => _.ShortName).ToDictionary(_ => _.Key, _ => _.ToList());
bool first = true;
bool abort = false;
- foreach (var item in assembliesByShortName)
+ foreach (var (shortName, assemblies) in assembliesByShortName)
{
- var enumerator = item.GetEnumerator();
- if (!enumerator.MoveNext())
- continue;
- var firstAssembly = enumerator.Current;
- if (!enumerator.MoveNext())
+ if (assemblies.Count == 1)
+ {
continue;
+ }
+
if (first)
{
result.WriteLine("Duplicate assembly names selected, cannot generate a solution:");
abort = true;
+ first = false;
}
- result.Write("- " + firstAssembly.Text + " conflicts with ");
-
- first = true;
- do
- {
- var asm = enumerator.Current;
- if (!first)
- result.Write(", ");
- result.Write(asm.Text);
- first = false;
- } while (enumerator.MoveNext());
- result.WriteLine();
- first = false;
+ result.WriteLine("- " + assemblies[0].Text + " conflicts with " + string.Join(", ", assemblies.Skip(1)));
}
if (abort)
@@ -141,7 +129,7 @@ namespace ICSharpCode.ILSpy
// Explicitly create an enumerable partitioner here to avoid Parallel.ForEach's special cases for lists,
// as those seem to use static partitioning which is inefficient if assemblies take differently
// long to decompile.
- await Task.Run(() => Parallel.ForEach(Partitioner.Create(assemblies),
+ await Task.Run(() => Parallel.ForEach(Partitioner.Create(allAssemblies),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct },
item => WriteProject(tabPage, item, language, solutionDirectory, ct)))
.ConfigureAwait(false);
@@ -153,8 +141,8 @@ namespace ICSharpCode.ILSpy
}
else
{
- await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects))
- .ConfigureAwait(false);
+ await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects.ToList()), ct)
+ .ConfigureAwait(false);
}
}
catch (AggregateException ae)
@@ -184,14 +172,14 @@ namespace ICSharpCode.ILSpy
if (statusOutput.Count == 0)
{
result.WriteLine("Successfully decompiled the following assemblies into Visual Studio projects:");
- foreach (var item in assemblies.Select(n => n.Text.ToString()))
+ foreach (var n in allAssemblies)
{
- result.WriteLine(item);
+ result.WriteLine(n.Text.ToString());
}
result.WriteLine();
- if (assemblies.Count() == projects.Count)
+ if (allAssemblies.Count == projects.Count)
{
result.WriteLine("Created the Visual Studio Solution file.");
}