Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS port of the upgrade-assistant #1409

Merged
merged 35 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6964c75
Use a hard-coded PackageId for Extensions.Default.Analyzers
jstedfast Jan 25, 2023
f878190
Do not require the ENABLE_CROSS_PLATFORM feature flag to run on macOS
jstedfast Jan 26, 2023
d3aa104
Don't crash in VisualStudioFinder.Configure() due to COM exceptions
jstedfast Jan 26, 2023
8fba4a0
Use sudo on macOS when running `dotnet workload install maui`
jstedfast Jan 26, 2023
5abdea9
Trim trailing whitespace (CRLF) from the using directive when searchi…
jstedfast Jan 26, 2023
a79e9af
A bunch of fixes to the unit tests
jstedfast Jan 26, 2023
8c5a0be
Fixed MappedSubTextTests (and found a legit bug in MappedSubText rega…
jstedfast Jan 27, 2023
ecb4401
Fixed RazorHelperUpdaterTests
jstedfast Jan 27, 2023
9e3d927
Fixed RazorMappedTextReplacerTests and RazorSourceUpdaterTests
jstedfast Jan 27, 2023
a3b77be
Fixed WCFUpdaterTests
jstedfast Jan 27, 2023
7fa6165
Fixed pruning of duplicate Compile/None items on macOS
jstedfast Feb 6, 2023
2296f43
More path directory separator fixes
jstedfast Feb 6, 2023
e79a06e
Need to compare item.Include/EvaluatedInclude using canonical paths
jstedfast Feb 6, 2023
37e017d
Moved GetProjectName() call out of the inner loop
jstedfast Feb 6, 2023
0d95545
More canonicalization of paths when used in comparisons
jstedfast Feb 6, 2023
09186e4
More canonicalization of paths
jstedfast Feb 6, 2023
ba923b9
Fixed up paths in <ItemType Update=...> and <ItemType Remove=...>
jstedfast Feb 6, 2023
8de8ec7
Needed to add more .ReplaceLineEndings() in the unit tests for Razor
jstedfast Feb 6, 2023
2496964
Only use the VisualStudioPath/Version on the Windows platform
jstedfast Feb 7, 2023
182e245
Create a temp MSBuildExtensionsPath on macOS
jstedfast Feb 7, 2023
f3ac6c0
Optimized ProjectRootElementExtensionsForConversion.GetProjectName()
jstedfast Feb 7, 2023
043d731
Add a start-up warning for MacOS
jstedfast Feb 8, 2023
a285de5
Use "dotnet-upgrade-assistant" in the temp directory path
jstedfast Feb 8, 2023
e45cc1f
Use a predictable ~/.dotnet-upgrade-assistant/dotnet-sdk/{version} di…
jstedfast Feb 14, 2023
755dcb0
Use !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) when decidin…
jstedfast Feb 14, 2023
2b09b11
Disable the PCL test on non-Windows
jstedfast Feb 14, 2023
7afc277
Disable some DependencyInjections Options tests on non-Windows platforms
jstedfast Feb 14, 2023
5b0a814
Disable WinUI and WPF migrations on non-Windows platforms.
jstedfast Feb 16, 2023
3e274d2
Disable Razor UpgradeSteps on non-Windows platforms
jstedfast Feb 16, 2023
7072800
Disable VisualBasic and WCF UpgradeSteps on non-Windows platforms
jstedfast Feb 16, 2023
aaa418f
Updated warning message for MacOS
jstedfast Feb 24, 2023
a30fa77
Reduce code duplication in conversion between file path separators
jstedfast Feb 24, 2023
fef944a
Don't swallow exceptions thrown while creating MSBuildExtensionsPath …
jstedfast Feb 24, 2023
3043993
Removed FIXME that is no longer necessary
jstedfast Feb 24, 2023
3f30d7e
File.Exists() returns false for symlinks to directories
jstedfast Mar 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@
<data name="ListExtensionItem" xml:space="preserve">
<value>{Name}: {Source}</value>
</data>
<data name="MacOSWarning" xml:space="preserve">
<value>MacOS support for this tool is limited to migrating Xamarin.Forms to MAUI. Other migration paths are not supported and may or may not work correctly.</value>
</data>
jstedfast marked this conversation as resolved.
Show resolved Hide resolved
<data name="NonWindowsWarning" xml:space="preserve">
<value>This tool is not supported on non-Windows platforms due to dependencies on Visual Studio.</value>
</data>
Expand Down
6 changes: 5 additions & 1 deletion src/cli/Microsoft.DotNet.UpgradeAssistant.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ public static class Program
{
public static Task<int> Main(string[] args)
{
if (FeatureFlags.IsWindowsCheckEnabled && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Console.WriteLine(LocalizedStrings.MacOSWarning);
}
else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Console.WriteLine(LocalizedStrings.NonWindowsWarning);
return Task.FromResult(ErrorCodes.PlatformNotSupported);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ public static class FeatureFlags
{
private const string AnalyzeBinaries = "ANALYZE_BINARIES";
private const string SolutionWideSdkConversion = "SOLUTION_WIDE_SDK_CONVERSION";
private const string EnableCrossPlatform = "ENABLE_CROSS_PLATFORM";

public static readonly IReadOnlyCollection<string> RegisteredFeatures = new[]
{
SolutionWideSdkConversion,
EnableCrossPlatform,
AnalyzeBinaries
};

Expand All @@ -38,8 +36,6 @@ private static ICollection<string> CreateFeatures()

public static bool IsRegistered(string name) => _features.Contains(name);

public static bool IsWindowsCheckEnabled => !_features.Contains(EnableCrossPlatform);

public static bool IsAnalyzeBinariesEnabled => _features.Contains(AnalyzeBinaries);

public static bool IsSolutionWideSdkConversionEnabled => _features.Contains(SolutionWideSdkConversion);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace Microsoft.DotNet.UpgradeAssistant
{
public static class PathHelpers
{
public static string GetNativePath(string path)
{
if (Path.DirectorySeparatorChar == '/')
{
return path.Replace('\\', '/');
}

return path;
}

public static string GetIncludePath(string path)
{
if (Path.DirectorySeparatorChar == '/')
{
return path.Replace('/', '\\');
}

return path;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,17 @@ public void AddItem(ProjectItemDescriptor projectItem)
var item = ProjectRoot.CreateItemElement(projectItem.ItemType.Name);
if (projectItem.Include is not null)
{
item.Include = projectItem.Include;
item.Include = PathHelpers.GetIncludePath(projectItem.Include);
}

if (projectItem.Exclude is not null)
{
item.Exclude = projectItem.Remove;
item.Exclude = PathHelpers.GetIncludePath(projectItem.Exclude);
}

if (projectItem.Remove is not null)
{
item.Remove = projectItem.Remove;
item.Remove = PathHelpers.GetIncludePath(projectItem.Remove);
}

itemGroup.AppendChild(item);
Expand Down Expand Up @@ -230,9 +230,11 @@ public void RemoveProperty(string propertyName)
}
}

private static string GetPathRelativeToProject(string path, string projectDir) =>
Path.IsPathFullyQualified(path)
? path
: Path.Combine(projectDir, path);
private static string GetPathRelativeToProject(string path, string projectDir)
{
path = PathHelpers.GetNativePath(path);

return Path.IsPathFullyQualified(path) ? path : Path.Combine(projectDir, path);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Evaluation;
Expand All @@ -18,6 +19,8 @@ namespace Microsoft.DotNet.UpgradeAssistant.MSBuild
{
internal sealed class MSBuildWorkspaceUpgradeContext : IUpgradeContext, IDisposable
{
private const string MacOSMonoFrameworkMSBuildExtensionsDir = "/Library/Frameworks/Mono.framework/External/xbuild";

private readonly ILogger<MSBuildWorkspaceUpgradeContext> _logger;
private readonly Dictionary<string, IProject> _projectCache;
private readonly IOptions<WorkspaceOptions> _options;
Expand Down Expand Up @@ -140,20 +143,119 @@ public IEnumerable<IProject> Projects
}
}

private static void CreateSymbolicLinks(string targetDir, string sourceDir)
{
foreach (var entry in Directory.EnumerateFileSystemEntries(sourceDir))
{
var target = Path.Combine(targetDir, Path.GetFileName(entry));

var fileInfo = new FileInfo(target);
if (fileInfo.Exists)
{
if (fileInfo.LinkTarget is not null && fileInfo.LinkTarget.Equals(entry, StringComparison.Ordinal))
{
continue;
}

File.Delete(target);
}
else
{
var dirInfo = new DirectoryInfo(target);
if (dirInfo.Exists)
{
if (dirInfo.LinkTarget is not null && dirInfo.LinkTarget.Equals(entry, StringComparison.Ordinal))
{
continue;
}

Directory.Delete(target);
}
}

File.CreateSymbolicLink(target, entry);
}
}

private static string? GetMacOSMSBuildExtensionsPath(WorkspaceOptions options)
{
const string DefaultDotnetSdkLocation = "/usr/local/share/dotnet/sdk/";

if (options.MSBuildPath == null || !options.MSBuildPath.StartsWith(DefaultDotnetSdkLocation, StringComparison.Ordinal))
{
return null;
}

string? msbuildExtensionsPath = null;

if (Directory.Exists(MacOSMonoFrameworkMSBuildExtensionsDir))
{
// Check to see if the specified MSBuildPath contains the Mono.framework build extensions.
var monoExtensionDirectories = Directory.GetDirectories(MacOSMonoFrameworkMSBuildExtensionsDir);
var createTempExtensionsDir = false;

foreach (var monoExtensionDir in monoExtensionDirectories)
{
var dotnetExtensionDir = Path.Combine(options.MSBuildPath, Path.GetFileName(monoExtensionDir));
if (!Directory.Exists(dotnetExtensionDir))
{
createTempExtensionsDir = true;
break;
}
}

// If the specified MSBuildPath does not contain the Mono.framework build extensions, create a temp
// directory that we'll use to symlink everything.
if (createTempExtensionsDir)
{
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var versionDir = Path.GetFileName(options.MSBuildPath.TrimEnd('/'));

msbuildExtensionsPath = Path.Combine(homeDir, ".dotnet-upgrade-assistant", "dotnet-sdk", versionDir);

if (!Directory.Exists(msbuildExtensionsPath))
{
Directory.CreateDirectory(msbuildExtensionsPath);
}

// First, create symbolic links to all of the dotnet MSBuild file system entries.
CreateSymbolicLinks(msbuildExtensionsPath, options.MSBuildPath);

// Then create the symbolic links to the Mono.framework/External/xbuild system entries.
CreateSymbolicLinks(msbuildExtensionsPath, MacOSMonoFrameworkMSBuildExtensionsDir);
}
}

return msbuildExtensionsPath;
}

private static Dictionary<string, string> CreateProperties(WorkspaceOptions options)
{
var properties = new Dictionary<string, string>();

if (options.VisualStudioPath is string vsPath)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
properties.Add("VSINSTALLDIR", vsPath);
properties.Add("MSBuildExtensionsPath32", Path.Combine(vsPath, "MSBuild"));
properties.Add("MSBuildExtensionsPath", Path.Combine(vsPath, "MSBuild"));
}
if (options.VisualStudioPath is string vsPath)
{
properties.Add("VSINSTALLDIR", vsPath);
properties.Add("MSBuildExtensionsPath32", Path.Combine(vsPath, "MSBuild"));
properties.Add("MSBuildExtensionsPath", Path.Combine(vsPath, "MSBuild"));
}

if (options.VisualStudioVersion is int version)
if (options.VisualStudioVersion is int version)
{
properties.Add("VisualStudioVersion", $"{version}.0");
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
properties.Add("VisualStudioVersion", $"{version}.0");
var msbuildExtensionsPath = GetMacOSMSBuildExtensionsPath(options);

if (msbuildExtensionsPath != null)
{
properties.Add("MSBuildExtensionsPath32", msbuildExtensionsPath);
properties.Add("MSBuildExtensionsPath", msbuildExtensionsPath);
}
}

return properties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
Expand Down Expand Up @@ -78,7 +79,8 @@ internal static void WorkAroundRoslynIssue36781(this ProjectRootElement rootElem
}

// Skip items that are only included once
if (project.Items.Count(i2 => i2.EvaluatedInclude.Equals(i.EvaluatedInclude, StringComparison.Ordinal)) <= 1)
var path = PathHelpers.GetIncludePath(i.EvaluatedInclude);
if (project.Items.Count(i2 => PathHelpers.GetIncludePath(i2.EvaluatedInclude).Equals(path, StringComparison.OrdinalIgnoreCase)) <= 1)
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ public VisualStudioFinder(ILogger<VisualStudioFinder> logger)

public void Configure(WorkspaceOptions options)
{
(options.VisualStudioPath, options.VisualStudioVersion) = GetLatestVisualStudioPath(options.VisualStudioPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
(options.VisualStudioPath, options.VisualStudioVersion) = GetLatestVisualStudioPath(options.VisualStudioPath);
}
else
{
// MSBuildWorkspaceUpgradeContext.CreateProperties() uses the VS path to set the MSBuildExtensionsPath[32]
// environment variables and there is some logging in UpgraderMsBuildExtensions.AddMsBuild().
_logger.LogInformation("Visual Studio path not required on macOS");
jstedfast marked this conversation as resolved.
Show resolved Hide resolved
}
}

private (string? Path, int? Version) GetLatestVisualStudioPath(string? suppliedPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,20 @@ private static IEnumerable<Operation<NuGetReference>> UpdatePackageAddition(IDep
var containsService = false;
foreach (var f in files)
{
var root = CSharpSyntaxTree.ParseText(File.ReadAllText(f)).GetRoot();
if (ContainsIdentifier(root, "ChannelFactory") || ContainsIdentifier(root, "ClientBase"))
{
return packages.Additions;
}
var path = PathHelpers.GetNativePath(f);

jstedfast marked this conversation as resolved.
Show resolved Hide resolved
if (!containsService && ContainsIdentifier(root, "ServiceHost"))
if (File.Exists(path))
{
containsService = true;
var root = CSharpSyntaxTree.ParseText(File.ReadAllText(path)).GetRoot();
if (ContainsIdentifier(root, "ChannelFactory") || ContainsIdentifier(root, "ClientBase"))
{
return packages.Additions;
}

if (!containsService && ContainsIdentifier(root, "ServiceHost"))
{
containsService = true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<!-- Avoid ID conflicts with the package project. -->
<PackageId>*$(MSBuildProjectFullPath)*</PackageId>
<PackageId>Real.Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers</PackageId>
jstedfast marked this conversation as resolved.
Show resolved Hide resolved
</PropertyGroup>
<ItemGroup>
<None Remove="DefaultApiAlerts.apitargets" />
Expand Down
Loading