private AbstractProject GetOrCreateProjectFromArgumentsAndReferences( IWorkspaceProjectContextFactory workspaceProjectContextFactory, IAnalyzerAssemblyLoader analyzerAssemblyLoader, string projectFilename, IReadOnlyDictionary <string, DeferredProjectInformation> allProjectInfos, IReadOnlyDictionary <string, string> targetPathsToProjectPaths) { var languageName = GetLanguageOfProject(projectFilename); if (languageName == null) { return(null); } if (!allProjectInfos.TryGetValue(projectFilename, out var projectInfo)) { // This could happen if we were called recursively about a dangling P2P reference // that isn't actually in the solution. return(null); } var commandLineParser = _workspaceServices.GetLanguageServices(languageName).GetService <ICommandLineParserService>(); var projectDirectory = PathUtilities.GetDirectoryName(projectFilename); var commandLineArguments = commandLineParser.Parse( projectInfo.CommandLineArguments, projectDirectory, isInteractive: false, sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory()); // TODO: Should come from sln file? var projectName = PathUtilities.GetFileName(projectFilename, includeExtension: false); // `AbstractProject` only sets the filename if it actually exists. Since we want // our ids to match, mimic that behavior here. var projectId = File.Exists(projectFilename) ? GetOrCreateProjectIdForPath(projectFilename, projectName) : GetOrCreateProjectIdForPath(projectName, projectName); // See if we've already created this project and we're now in a recursive call to // hook up a P2P ref. if (_projectMap.TryGetValue(projectId, out var project)) { return(project); } OutputToOutputWindow($"\tCreating '{projectName}':\t{commandLineArguments.SourceFiles.Length} source files,\t{commandLineArguments.MetadataReferences.Length} references."); var solution5 = _serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution5; // If the index is stale, it might give us a path that doesn't exist anymore that the // solution doesn't know about - be resilient to that case. Guid projectGuid; try { projectGuid = solution5.GetGuidOfProjectFile(projectFilename); } catch (ArgumentException) { var message = $"Failed to get the project guid for '{projectFilename}' from the solution, using random guid instead."; Debug.Fail(message); OutputToOutputWindow(message); projectGuid = Guid.NewGuid(); } // NOTE: If the indexing service fails for a project, it will give us an *empty* // target path, which we aren't prepared to handle. Instead, convert it to a *null* // value, which we do handle. var outputPath = projectInfo.TargetPath; if (outputPath == string.Empty) { outputPath = null; } var projectContext = workspaceProjectContextFactory.CreateProjectContext( languageName, projectName, projectFilename, projectGuid: projectGuid, hierarchy: null, binOutputPath: outputPath); project = (AbstractProject)projectContext; projectContext.SetOptions(projectInfo.CommandLineArguments.Join(" ")); foreach (var sourceFile in commandLineArguments.SourceFiles) { projectContext.AddSourceFile(sourceFile.Path); } foreach (var sourceFile in commandLineArguments.AdditionalFiles) { projectContext.AddAdditionalFile(sourceFile.Path); } var addedProjectReferences = new HashSet <string>(); foreach (var projectReferencePath in projectInfo.ReferencedProjectFilePaths) { // NOTE: ImmutableProjects might contain projects for other languages like // Xaml, or Typescript where the project file ends up being identical. var referencedProject = ImmutableProjects.SingleOrDefault( p => (p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic) && StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, projectReferencePath)); if (referencedProject == null) { referencedProject = GetOrCreateProjectFromArgumentsAndReferences( workspaceProjectContextFactory, analyzerAssemblyLoader, projectReferencePath, allProjectInfos, targetPathsToProjectPaths); } var referencedProjectContext = referencedProject as IWorkspaceProjectContext; if (referencedProjectContext != null) { // TODO: Can we get the properties from corresponding metadata reference in // commandLineArguments? addedProjectReferences.Add(projectReferencePath); projectContext.AddProjectReference( referencedProjectContext, new MetadataReferenceProperties()); } else if (referencedProject != null) { // This project was already created by the regular project system. See if we // can find the matching project somehow. var existingReferenceOutputPath = referencedProject?.BinOutputPath; if (existingReferenceOutputPath != null) { addedProjectReferences.Add(projectReferencePath); projectContext.AddMetadataReference( existingReferenceOutputPath, new MetadataReferenceProperties()); } } else { // We don't know how to create this project. Another language or something? OutputToOutputWindow($"Failed to create a project for '{projectReferencePath}'."); } } foreach (var reference in commandLineArguments.ResolveMetadataReferences(project.CurrentCompilationOptions.MetadataReferenceResolver)) { // Some references may fail to be resolved - if they are, we'll still pass them // through, in case they come into existence later (they may be built by other // parts of the build system). var unresolvedReference = reference as UnresolvedMetadataReference; var path = unresolvedReference == null ? ((PortableExecutableReference)reference).FilePath : unresolvedReference.Reference; if (targetPathsToProjectPaths.TryGetValue(path, out var possibleProjectReference) && addedProjectReferences.Contains(possibleProjectReference)) { // We already added a P2P reference for this, we don't need to add the file reference too. continue; } projectContext.AddMetadataReference(path, reference.Properties); } foreach (var reference in commandLineArguments.ResolveAnalyzerReferences(analyzerAssemblyLoader)) { var path = reference.FullPath; if (!PathUtilities.IsAbsolute(path)) { path = PathUtilities.CombineAbsoluteAndRelativePaths( projectDirectory, path); } projectContext.AddAnalyzerReference(path); } return((AbstractProject)projectContext); }
private AbstractProject GetOrCreateProjectFromArgumentsAndReferences( IWorkspaceProjectContextFactory workspaceProjectContextFactory, string projectFilename, IReadOnlyDictionary <string, DeferredProjectInformation> allProjectInfos, IReadOnlyDictionary <string, string> targetPathsToProjectPaths) { var languageName = GetLanguageOfProject(projectFilename); if (languageName == null) { return(null); } DeferredProjectInformation projectInfo; if (!allProjectInfos.TryGetValue(projectFilename, out projectInfo)) { // This could happen if we were called recursively about a dangling P2P reference // that isn't actually in the solution. return(null); } var commandLineParser = _workspaceServices.GetLanguageServices(languageName).GetService <ICommandLineParserService>(); var projectDirectory = Path.GetDirectoryName(projectFilename); var commandLineArguments = commandLineParser.Parse( projectInfo.CommandLineArguments, projectDirectory, isInteractive: false, sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory()); // TODO: Should come from sln file? var projectName = Path.GetFileNameWithoutExtension(projectFilename); var projectId = GetOrCreateProjectIdForPath(projectFilename, projectName); // See if we've already created this project and we're now in a recursive call to // hook up a P2P ref. AbstractProject project; if (_projectMap.TryGetValue(projectId, out project)) { return(project); } OutputToOutputWindow($"\tCreating '{projectName}':\t{commandLineArguments.SourceFiles.Length} source files,\t{commandLineArguments.MetadataReferences.Length} references."); var solution5 = _serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution5; var projectGuid = solution5.GetGuidOfProjectFile(projectFilename); var projectContext = workspaceProjectContextFactory.CreateProjectContext( languageName, projectName, projectFilename, projectGuid: projectGuid, hierarchy: null, binOutputPath: projectInfo.TargetPath); projectContext.SetOptions(projectInfo.CommandLineArguments.Join(" ")); foreach (var sourceFile in commandLineArguments.SourceFiles) { projectContext.AddSourceFile(sourceFile.Path); } foreach (var sourceFile in commandLineArguments.AdditionalFiles) { projectContext.AddAdditionalFile(sourceFile.Path); } var addedProjectReferences = new HashSet <string>(); foreach (var projectReferencePath in projectInfo.ReferencedProjectFilePaths) { var referencedProject = ImmutableProjects.SingleOrDefault(p => StringComparer.OrdinalIgnoreCase.Equals(p.ProjectFilePath, projectReferencePath)); if (referencedProject == null) { referencedProject = GetOrCreateProjectFromArgumentsAndReferences( workspaceProjectContextFactory, projectReferencePath, allProjectInfos, targetPathsToProjectPaths); } var referencedProjectContext = referencedProject as IWorkspaceProjectContext; if (referencedProjectContext != null) { // TODO: Can we get the properties from corresponding metadata reference in // commandLineArguments? addedProjectReferences.Add(projectReferencePath); projectContext.AddProjectReference( referencedProjectContext, new MetadataReferenceProperties()); } else if (referencedProject != null) { // This project was already created by the regular project system. See if we // can find the matching project somehow. var existingReferenceOutputPath = referencedProject?.BinOutputPath; if (existingReferenceOutputPath != null) { addedProjectReferences.Add(projectReferencePath); projectContext.AddMetadataReference( existingReferenceOutputPath, new MetadataReferenceProperties()); } } else { // We don't know how to create this project. Another language or something? OutputToOutputWindow($"Failed to create a project for '{projectReferencePath}'."); } } foreach (var reference in commandLineArguments.MetadataReferences) { string possibleProjectReference; if (targetPathsToProjectPaths.TryGetValue(reference.Reference, out possibleProjectReference) && addedProjectReferences.Contains(possibleProjectReference)) { // We already added a P2P reference for this, we don't need to add the file reference too. continue; } projectContext.AddMetadataReference(reference.Reference, reference.Properties); } foreach (var reference in commandLineArguments.AnalyzerReferences) { projectContext.AddAnalyzerReference(reference.FilePath); } return((AbstractProject)projectContext); }