示例#1
0
 /// <summary>
 ///     Create an MSBuild project collection.
 /// </summary>
 /// <param name="solutionDirectory">
 ///     The base (i.e. solution) directory.
 /// </param>
 /// <param name="globalPropertyOverrides">
 ///     An optional dictionary containing property values to override.
 /// </param>
 /// <returns>
 ///     The project collection.
 /// </returns>
 public static ProjectCollection CreateProjectCollection(string solutionDirectory, Dictionary <string, string> globalPropertyOverrides = null)
 {
     return(CreateProjectCollection(solutionDirectory,
                                    DotNetRuntimeInfo.GetCurrent(solutionDirectory),
                                    globalPropertyOverrides
                                    ));
 }
示例#2
0
        /// <summary>
        ///     Create global properties for MSBuild.
        /// </summary>
        /// <param name="runtimeInfo">
        ///     Information about the current .NET Core runtime.
        /// </param>
        /// <param name="solutionDirectory">
        ///     The base (i.e. solution) directory.
        /// </param>
        /// <returns>
        ///     A dictionary containing the global properties.
        /// </returns>
        public static Dictionary <string, string> CreateGlobalMSBuildProperties(DotNetRuntimeInfo runtimeInfo, string solutionDirectory)
        {
            if (runtimeInfo == null)
            {
                throw new ArgumentNullException(nameof(runtimeInfo));
            }

            if (String.IsNullOrWhiteSpace(solutionDirectory))
            {
                throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'solutionDirectory'.", nameof(solutionDirectory));
            }

            if (solutionDirectory.Length > 0 && solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                solutionDirectory += Path.DirectorySeparatorChar;
            }

            return(new Dictionary <string, string>
            {
                [WellKnownPropertyNames.DesignTimeBuild] = "true",
                [WellKnownPropertyNames.BuildProjectReferences] = "false",
                [WellKnownPropertyNames.ResolveReferenceDependencies] = "true",
                [WellKnownPropertyNames.SolutionDir] = solutionDirectory,
                [WellKnownPropertyNames.MSBuildExtensionsPath] = runtimeInfo.BaseDirectory,
                [WellKnownPropertyNames.MSBuildSDKsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Sdks"),
                [WellKnownPropertyNames.RoslynTargetsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Roslyn")
            });
        }
示例#3
0
        /// <summary>
        ///     Create an MSBuild project collection.
        /// </summary>
        /// <param name="solutionDirectory">
        ///     The base (i.e. solution) directory.
        /// </param>
        /// <param name="globalPropertyOverrides">
        ///     An optional dictionary containing property values to override.
        /// </param>
        /// <param name="logger">
        ///     An optional <see cref="ILogger"/> to use for diagnostic purposes (if not specified, the static <see cref="Log.Logger"/> will be used).
        /// </param>
        /// <returns>
        ///     The project collection.
        /// </returns>
        public static ProjectCollection CreateProjectCollection(string solutionDirectory, Dictionary <string, string> globalPropertyOverrides = null, ILogger logger = null)
        {
            if (logger == null)
            {
                logger = Log.Logger;
            }

            return(CreateProjectCollection(solutionDirectory,
                                           DotNetRuntimeInfo.GetCurrent(solutionDirectory, logger),
                                           globalPropertyOverrides
                                           ));
        }
示例#4
0
        /// <summary>
        ///     Create global properties for MSBuild.
        /// </summary>
        /// <param name="runtimeInfo">
        ///     Information about the current .NET Core runtime.
        /// </param>
        /// <param name="solutionDirectory">
        ///     The base (i.e. solution) directory.
        /// </param>
        /// <param name="globalPropertyOverrides">
        ///     An optional dictionary containing property values to override.
        /// </param>
        /// <returns>
        ///     A dictionary containing the global properties.
        /// </returns>
        public static Dictionary <string, string> CreateGlobalMSBuildProperties(DotNetRuntimeInfo runtimeInfo, string solutionDirectory, Dictionary <string, string> globalPropertyOverrides = null)
        {
            if (runtimeInfo == null)
            {
                throw new ArgumentNullException(nameof(runtimeInfo));
            }

            if (String.IsNullOrWhiteSpace(solutionDirectory))
            {
                throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'solutionDirectory'.", nameof(solutionDirectory));
            }

            if (solutionDirectory.Length > 0 && solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
            {
                solutionDirectory += Path.DirectorySeparatorChar;
            }

            // Support overriding of SDKs path.
            string sdksPath = Environment.GetEnvironmentVariable("MSBuildSDKsPath");

            if (String.IsNullOrWhiteSpace(sdksPath))
            {
                sdksPath = Path.Combine(runtimeInfo.BaseDirectory, "Sdks");
            }

            var globalProperties = new Dictionary <string, string>
            {
                [WellKnownPropertyNames.DesignTimeBuild]              = "true",
                [WellKnownPropertyNames.BuildProjectReferences]       = "false",
                [WellKnownPropertyNames.ResolveReferenceDependencies] = "true",
                [WellKnownPropertyNames.SolutionDir]           = solutionDirectory,
                [WellKnownPropertyNames.MSBuildExtensionsPath] = runtimeInfo.BaseDirectory,
                [WellKnownPropertyNames.MSBuildSDKsPath]       = sdksPath,
                [WellKnownPropertyNames.RoslynTargetsPath]     = Path.Combine(runtimeInfo.BaseDirectory, "Roslyn")
            };

            if (globalPropertyOverrides != null)
            {
                foreach (string propertyName in globalPropertyOverrides.Keys)
                {
                    globalProperties[propertyName] = globalPropertyOverrides[propertyName];
                }
            }

            return(globalProperties);
        }
示例#5
0
        /// <summary>
        ///     Create an MSBuild project collection.
        /// </summary>
        /// <param name="solutionDirectory">
        ///     The base (i.e. solution) directory.
        /// </param>
        /// <param name="runtimeInfo">
        ///     Information about the current .NET Core runtime.
        /// </param>
        /// <returns>
        ///     The project collection.
        /// </returns>
        public static ProjectCollection CreateProjectCollection(string solutionDirectory, DotNetRuntimeInfo runtimeInfo)
        {
            if (String.IsNullOrWhiteSpace(solutionDirectory))
            {
                throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'baseDir'.", nameof(solutionDirectory));
            }

            if (runtimeInfo == null)
            {
                throw new ArgumentNullException(nameof(runtimeInfo));
            }

            if (String.IsNullOrWhiteSpace(runtimeInfo.BaseDirectory))
            {
                throw new InvalidOperationException("Cannot determine base directory for .NET Core.");
            }

            Dictionary <string, string> globalProperties = CreateGlobalMSBuildProperties(runtimeInfo, solutionDirectory);

            EnsureMSBuildEnvironment(globalProperties);

            ProjectCollection projectCollection = new ProjectCollection(globalProperties)
            {
                IsBuildEnabled = false
            };

            // Override toolset paths (for some reason these point to the main directory where the dotnet executable lives).
            Toolset toolset = projectCollection.GetToolset("15.0");

            toolset = new Toolset(
                toolsVersion: "15.0",
                toolsPath: globalProperties["MSBuildExtensionsPath"],
                projectCollection: projectCollection,
                msbuildOverrideTasksPath: ""
                );
            projectCollection.AddToolset(toolset);

            return(projectCollection);
        }
示例#6
0
 /// <summary>
 ///     Create an MSBuild project collection.
 /// </summary>
 /// <param name="solutionDirectory">
 ///     The base (i.e. solution) directory.
 /// </param>
 /// <returns>
 ///     The project collection.
 /// </returns>
 public static ProjectCollection CreateProjectCollection(string solutionDirectory)
 {
     return(CreateProjectCollection(solutionDirectory,
                                    DotNetRuntimeInfo.GetCurrent(solutionDirectory)
                                    ));
 }
示例#7
0
        /// <summary>
        ///     Create an MSBuild project collection.
        /// </summary>
        /// <param name="solutionDirectory">
        ///     The base (i.e. solution) directory.
        /// </param>
        /// <param name="runtimeInfo">
        ///     Information about the current .NET Core runtime.
        /// </param>
        /// <param name="globalPropertyOverrides">
        ///     An optional dictionary containing property values to override.
        /// </param>
        /// <returns>
        ///     The project collection.
        /// </returns>
        public static ProjectCollection CreateProjectCollection(string solutionDirectory, DotNetRuntimeInfo runtimeInfo, Dictionary <string, string> globalPropertyOverrides = null)
        {
            if (String.IsNullOrWhiteSpace(solutionDirectory))
            {
                throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'baseDir'.", nameof(solutionDirectory));
            }

            if (runtimeInfo == null)
            {
                throw new ArgumentNullException(nameof(runtimeInfo));
            }

            if (String.IsNullOrWhiteSpace(runtimeInfo.BaseDirectory))
            {
                throw new InvalidOperationException("Cannot determine base directory for .NET Core (check the output of 'dotnet --info').");
            }

            Dictionary <string, string> globalProperties = CreateGlobalMSBuildProperties(runtimeInfo, solutionDirectory, globalPropertyOverrides);

            EnsureMSBuildEnvironment(globalProperties);

            ProjectCollection projectCollection = new ProjectCollection(globalProperties)
            {
                IsBuildEnabled = false
            };

            SemanticVersion netcoreVersion;

            if (!SemanticVersion.TryParse(runtimeInfo.Version, out netcoreVersion))
            {
                throw new FormatException($"Cannot parse .NET Core version '{runtimeInfo.Version}' (does not appear to be a valid semantic version).");
            }

            // For .NET Core 3.0 and newer, toolset version is simply "Current" instead of "15.0" (tintoy/msbuild-project-tools-vscode#46).
            string toolsVersion = netcoreVersion.Major < 3 ? "15.0" : "Current";

            // Override toolset paths (for some reason these point to the main directory where the dotnet executable lives).
            Toolset toolset = projectCollection.GetToolset(toolsVersion);

            toolset = new Toolset(toolsVersion,
                                  toolsPath: runtimeInfo.BaseDirectory,
                                  projectCollection: projectCollection,
                                  msbuildOverrideTasksPath: ""
                                  );

            // Other toolset versions won't be supported by the .NET Core SDK
            projectCollection.RemoveAllToolsets();

            // TODO: Add configuration setting that enables user to configure custom toolsets.

            projectCollection.AddToolset(toolset);
            projectCollection.DefaultToolsVersion = toolsVersion;

            return(projectCollection);
        }
示例#8
0
        /// <summary>
        ///     Get information about the current .NET Core runtime.
        /// </summary>
        /// <param name="baseDirectory">
        ///     An optional base directory where dotnet.exe should be run (this may affect the version it reports due to global.json).
        /// </param>
        /// <returns>
        ///     A <see cref="DotNetRuntimeInfo"/> containing the runtime information.
        /// </returns>
        public static DotNetRuntimeInfo GetCurrent(string baseDirectory = null)
        {
            DotNetRuntimeInfo runtimeInfo = new DotNetRuntimeInfo();

            Process dotnetInfoProcess = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName               = "dotnet",
                    WorkingDirectory       = baseDirectory,
                    Arguments              = "--info",
                    UseShellExecute        = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true
                },
                EnableRaisingEvents = true
            };

            using (dotnetInfoProcess)
            {
                // For logging purposes.
                string command = $"{dotnetInfoProcess.StartInfo.FileName} {dotnetInfoProcess.StartInfo.Arguments}";

                // Buffer the output locally (otherwise, the process may hang if it fills up its STDOUT / STDERR buffer).
                StringBuilder localOutputBuffer = new StringBuilder();
                dotnetInfoProcess.OutputDataReceived += (sender, args) =>
                {
                    lock (localOutputBuffer)
                    {
                        localOutputBuffer.AppendLine(args.Data);
                    }
                };
                dotnetInfoProcess.ErrorDataReceived += (sender, args) =>
                {
                    lock (localOutputBuffer)
                    {
                        localOutputBuffer.AppendLine(args.Data);
                    }
                };

                Log.Debug("Launching {Command}...", command);

                dotnetInfoProcess.Start();

                Log.Debug("Launched {Command}. Waiting for process {TargetProcessId} to terminate...", command, dotnetInfoProcess.Id);

                // Asynchronously start reading from STDOUT / STDERR.
                dotnetInfoProcess.BeginOutputReadLine();
                dotnetInfoProcess.BeginErrorReadLine();

                try
                {
                    dotnetInfoProcess.WaitForExit(milliseconds: 5000);
                }
                catch (TimeoutException exitTimedOut)
                {
                    Log.Error(exitTimedOut, "Timed out after waiting 5 seconds for {Command} to exit.", command);

                    throw new TimeoutException($"Timed out after waiting 5 seconds for '{command}' to exit.", exitTimedOut);
                }

                Log.Debug("{Command} terminated with exit code {ExitCode}.", command, dotnetInfoProcess.ExitCode);

                string processOutput;
                lock (localOutputBuffer)
                {
                    processOutput = localOutputBuffer.ToString();
                }

                // Only log output if there's a problem.
                if (dotnetInfoProcess.ExitCode != 0)
                {
                    if (!String.IsNullOrWhiteSpace(processOutput))
                    {
                        Log.Debug("{Command} returned the following text on STDOUT / STDERR.\n\n{DotNetInfoOutput:l}", command, processOutput);
                    }
                    else
                    {
                        Log.Debug("{Command} returned no output on STDOUT / STDERR.");
                    }
                }

                using (StringReader bufferReader = new StringReader(processOutput))
                {
                    return(ParseDotNetInfoOutput(bufferReader));
                }
            }
        }
示例#9
0
        /// <summary>
        ///     Parse the output of "dotnet --info" into a <see cref="DotNetRuntimeInfo"/>.
        /// </summary>
        /// <param name="dotnetInfoOutput">
        ///     A <see cref="TextReader"/> containing the output of "dotnet --info".
        /// </param>
        /// <returns>
        ///     The <see cref="DotNetRuntimeInfo"/>.
        /// </returns>
        public static DotNetRuntimeInfo ParseDotNetInfoOutput(TextReader dotnetInfoOutput)
        {
            if (dotnetInfoOutput == null)
            {
                throw new ArgumentNullException(nameof(dotnetInfoOutput));
            }

            DotNetRuntimeInfo runtimeInfo = new DotNetRuntimeInfo();

            DotnetInfoSection currentSection = DotnetInfoSection.Start;

            string currentLine;

            while ((currentLine = dotnetInfoOutput.ReadLine()) != null)
            {
                if (String.IsNullOrWhiteSpace(currentLine))
                {
                    continue;
                }

                if (!currentLine.StartsWith(" ") && currentLine.EndsWith(":"))
                {
                    currentSection++;

                    if (currentSection > DotnetInfoSection.RuntimeEnvironment)
                    {
                        break;
                    }

                    continue;
                }

                string[] property = currentLine.Split(new char[] { ':' }, count: 2);
                if (property.Length != 2)
                {
                    continue;
                }

                property[0] = property[0].Trim();
                property[1] = property[1].Trim();

                switch (currentSection)
                {
                case DotnetInfoSection.ProductInformation:
                {
                    switch (property[0])
                    {
                    case "Version":
                    {
                        runtimeInfo.Version = property[1];

                        break;
                    }
                    }

                    break;
                }

                case DotnetInfoSection.RuntimeEnvironment:
                {
                    switch (property[0])
                    {
                    case "Base Path":
                    {
                        runtimeInfo.BaseDirectory = property[1];

                        break;
                    }

                    case "RID":
                    {
                        runtimeInfo.RID = property[1];

                        break;
                    }
                    }

                    break;
                }
                }
            }

            return(runtimeInfo);
        }
        /// <summary>
        ///     Find and use the latest version of the MSBuild engine compatible with the current SDK.
        /// </summary>
        /// <param name="baseDirectory">
        ///     An optional base directory where dotnet.exe should be run (this may affect the version it reports due to global.json).
        /// </param>
        /// <param name="logger">
        ///     An optional <see cref="ILogger"/> to use for diagnostic purposes (if not specified, the static <see cref="Log.Logger"/> will be used).
        /// </param>
        public static void DiscoverMSBuildEngine(string baseDirectory = null, ILogger logger = null)
        {
            if (MSBuildLocator.IsRegistered)
            {
                MSBuildLocator.Unregister();
            }

            _registeredMSBuildInstance = null;

            // Assume working directory is VS code's current working directory (i.e. the workspace root).
            //
            // Really, until we figure out a way to change the version of MSBuild we're using after the server has started,
            // we're still going to have problems here.
            //
            // In the end we will probably wind up having to move all the MSBuild stuff out to a separate process, and use something like GRPC (or even Akka.NET's remoting) to communicate with it.
            // It can be stopped and restarted by the language server (even having different instances for different SDK / MSBuild versions).
            //
            // This will also ensure that the language server's model doesn't expose any MSBuild objects anywhere.
            //
            // For now, though, let's choose the dumb option.
            DotNetRuntimeInfo runtimeInfo = DotNetRuntimeInfo.GetCurrent(baseDirectory, logger);

            // SDK versions are in SemVer format...
            SemanticVersion targetSdkSemanticVersion;

            if (!SemanticVersion.TryParse(runtimeInfo.SdkVersion, out targetSdkSemanticVersion))
            {
                throw new Exception($"Cannot determine SDK version information for current .NET SDK (located at '{runtimeInfo.BaseDirectory}').");
            }

            // ...which MSBuildLocator does not understand.
            Version targetSdkVersion = new Version(
                major: targetSdkSemanticVersion.Major,
                minor: targetSdkSemanticVersion.Minor,
                build: targetSdkSemanticVersion.Patch
                );

            var queryOptions = new VisualStudioInstanceQueryOptions
            {
                // We can only load the .NET Core MSBuild engine
                DiscoveryTypes = DiscoveryType.DotNetSdk
            };

            VisualStudioInstance[] allInstances = MSBuildLocator
                                                  .QueryVisualStudioInstances(queryOptions)
                                                  .ToArray();

            VisualStudioInstance latestInstance = allInstances
                                                  .OrderByDescending(instance => instance.Version)
                                                  .FirstOrDefault(instance =>
                                                                  // We need a version of MSBuild for the currently-supported SDK
                                                                  instance.Version == targetSdkVersion
                                                                  );

            if (latestInstance == null)
            {
                string foundVersions = String.Join(", ", allInstances.Select(instance => instance.Version));

                throw new Exception($"Cannot locate MSBuild engine for .NET SDK v{targetSdkVersion}. This probably means that MSBuild Project Tools cannot find the MSBuild for the current project instance. It did find the following version(s), though: [{foundVersions}].");
            }

            MSBuildLocator.RegisterInstance(latestInstance);

            _registeredMSBuildInstance = latestInstance;
        }
示例#11
0
        /// <summary>
        ///     Get information about the current .NET Core runtime.
        /// </summary>
        /// <param name="baseDirectory">
        ///     An optional base directory where dotnet.exe should be run (this may affect the version it reports due to global.json).
        /// </param>
        /// <returns>
        ///     A <see cref="DotNetRuntimeInfo"/> containing the runtime information.
        /// </returns>
        public static DotNetRuntimeInfo GetCurrent(string baseDirectory = null)
        {
            DotNetRuntimeInfo runtimeInfo = new DotNetRuntimeInfo();

            Process dotnetInfoProcess = Process.Start(new ProcessStartInfo
            {
                FileName               = "dotnet",
                WorkingDirectory       = baseDirectory,
                Arguments              = "--info",
                UseShellExecute        = false,
                RedirectStandardOutput = true
            });

            using (dotnetInfoProcess)
            {
                dotnetInfoProcess.WaitForExit();

                string currentSection = null;
                string currentLine;
                while ((currentLine = dotnetInfoProcess.StandardOutput.ReadLine()) != null)
                {
                    if (String.IsNullOrWhiteSpace(currentLine))
                    {
                        continue;
                    }

                    if (!currentLine.StartsWith(" "))
                    {
                        currentSection = currentLine;

                        continue;
                    }

                    string[] property = currentLine.Split(new char[] { ':' }, count: 2);
                    if (property.Length != 2)
                    {
                        continue;
                    }

                    property[0] = property[0].Trim();
                    property[1] = property[1].Trim();

                    switch (currentSection)
                    {
                    case "Product Information:":
                    {
                        switch (property[0])
                        {
                        case "Version":
                        {
                            runtimeInfo.Version = property[1];

                            break;
                        }
                        }

                        break;
                    }

                    case "Runtime Environment:":
                    {
                        switch (property[0])
                        {
                        case "Base Path":
                        {
                            runtimeInfo.BaseDirectory = property[1];

                            break;
                        }

                        case "RID":
                        {
                            runtimeInfo.RID = property[1];

                            break;
                        }
                        }

                        break;
                    }
                    }
                }
            }

            return(runtimeInfo);
        }