/// <summary> /// Builds a graph of dependencies between solution files, from a list of .csproj and .sln files. /// </summary> /// <param name="inputFiles">Project (.csproj) and solution (.sln) files to start the dependency search from</param> /// <param name="_excludedSLNs">Solution (.sln) files that should be excluded from the final dependency graph - useful for temporarily ignoring cyclic dependencies. /// Note that .sln files may appear in the final graph even if they are not given in the input files list, if something in the input depends on them.</param> /// <param name="maxRecursionLevel">How deep to resolve dependencies of the given inputs. 0 means no dependency resolution is performed. -1 means infinity.</param> public static BuildDependencyInfo GetDependencyInfo(IProjectFinder projectFinder, IEnumerable <string> inputFiles, IEnumerable <string> _excludedSLNs, int maxRecursionLevel) //, bool findAllDependents) { string[] projectFiles; string[] slnFiles; ProcessInputFiles(inputFiles.Select(CanonicalPath), out projectFiles, out slnFiles); var excludedSLNs = _excludedSLNs.Select(CanonicalPath) .Select(x => x.ToLowerInvariant()) .ToArray(); if (excludedSLNs.Any(x => false == SLN_EXTENSION.Equals(System.IO.Path.GetExtension(x)))) { var errorMessage = "excluded files must have extension: " + SLN_EXTENSION; _logger.Error(errorMessage); throw new ArgumentException(errorMessage, "_excludedSLNs"); } var csprojProjects = projectFiles.Select(Project.FromCSProj); var slnProjects = slnFiles.SelectMany(projectFinder.GetProjectsOfSLN); var inputProjects = csprojProjects.Union(slnProjects).ToArray(); PrintInputInfo(excludedSLNs, projectFiles, slnFiles, inputProjects); //Project[] projectsForDependencyGraph = findAllDependents // ? projectFinder.AllProjectsInPath().ToArray() // : inputProjects; return(new BuildDependencyInfo(ProjectDependencyGraph(projectFinder, inputProjects, false, maxRecursionLevel), SolutionDependencyGraph(projectFinder, inputProjects, false, maxRecursionLevel), excludedSLNs)); }
protected static string ProjectsUsingAssemblyReference(IProjectFinder projectFinder, AssemblyReference assemblyReference) { return(StringExtensions.Tabify(projectFinder.AllProjectsInPath() .Where(x => x.AssemblyReferences .Contains(assemblyReference)) .Select(x => x.ToString()))); }
protected static void AddIndirectReferences(IProjectFinder projectFinder, Regex[] assemblyNamePatterns, bool ignoreOnlyMatching, HashSet <string> originalAssemblyReferenceNames, Queue <AssemblyReference> remainingReferences, string targetPath, Project buildingProject, FileInfo[] projectOutputs, string explicitTargetPath, AssemblyReference assemblyReference, List <IndirectReferenceInfo> indirectReferencesOutsideSolution) { var indirectReferences = GetIndirectReferences(originalAssemblyReferenceNames, targetPath, projectOutputs, explicitTargetPath) .Where(x => IncludeAssemblyWhenCopyingDeps(x, assemblyNamePatterns, ignoreOnlyMatching)) .ToArray(); if (false == indirectReferences.Any()) { return; } var buildingSolution = projectFinder.GetSLNFileForProject(buildingProject); // TODO: Print the name of the project that is missing these indirect references instead of letting the user guess. _logger.InfoFormat("Adding indirect references listed below. The direct reference is to {0}.\n{1}\nThis probably means some project is referencing {0} but does not reference all of the listed indirect assemblies above.\nHere's a list of projects referencing {0}:\n{2}", assemblyReference.Name, StringExtensions.Tabify(indirectReferences.Select(x => x.Name)), StringExtensions.Tabify(ProjectsUsingAssemblyReference(projectFinder, assemblyReference))); foreach (var indirectReference in indirectReferences) { var indirectReferenceBuildingProject = projectFinder.FindProjectForAssemblyReference(indirectReference).SingleOrDefault(); var indirectReferenceInfo = new IndirectReferenceInfo(assemblyReference, buildingProject, indirectReference, indirectReferenceBuildingProject); if (null != indirectReferenceBuildingProject) { if (projectFinder.GetSLNFileForProject(indirectReferenceBuildingProject).FullName.Equals(buildingSolution.FullName, StringComparison.InvariantCultureIgnoreCase)) { originalAssemblyReferenceNames.Add(ComparableAssemblyName(indirectReference)); remainingReferences.Enqueue(indirectReference); continue; } } indirectReferencesOutsideSolution.Add(indirectReferenceInfo); } }
protected static void WarnAboutRemainingIndirectReferences(IProjectFinder projectFinder, HashSet <string> originalAssemblyReferenceNames, List <IndirectReferenceInfo> indirectReferencesOutsideSolution) { foreach (var indirectReferenceInfo in indirectReferencesOutsideSolution .Where(x => false == originalAssemblyReferenceNames.Contains(ComparableAssemblyName(x.IndirectReference)))) { Action <string> logAction = _logger.Warn; // Some projects are KNOWN to be executable, which means they will absolutely fail when we try to run them because if this indirect reference that is missing. // Other projects may also be "executable", such as Web Applications, so we still emit a warning. if (KnownExecutableOutputTypes.Contains(indirectReferenceInfo.DirectReferenceProject.OutputType)) { logAction = _logger.Error; } logAction(String.Format(@"Skipped indirect reference from solution other than the direct reference that caused it: Indirect reference: {0} Indirect reference built by: {1} Required by project: {5} - {2} Which builds reference: {3} Which is used directly by projects: {4} ", // TODO: Add description of motivation for this: If the project directly requires an assembly from solution A, which happens to required something from solution B, the builder will not copy assemblies from B and mix them in the same "Components" directory of assemblies from A indirectReferenceInfo.IndirectReference, indirectReferenceInfo.IndirectReferenceProject, indirectReferenceInfo.DirectReferenceProject, indirectReferenceInfo.DirectReference, StringExtensions.Tabify(ProjectsUsingAssemblyReference(projectFinder, indirectReferenceInfo.DirectReference)), indirectReferenceInfo.DirectReferenceProject.OutputType)); } }
protected static void ValidateSolutionReadyForBuild(IProjectFinder projectFinder, string solutionFileName, Regex[] ignoredDependencyAssemblies, bool ignoreOnlyMatching) { foreach (var project in projectFinder.GetProjectsOfSLN(solutionFileName)) { project.ValidateHintPaths(ignoredDependencyAssemblies, ignoreOnlyMatching); project.ValidateAssemblyReferencesAreAvailable(); } }
public static void UpdateComponentsFromBuiltProjects(IProjectFinder projectFinder, string solutionFileName, Regex[] assemblyNamePatterns, bool ignoreOnlyMatching, bool ignoreMissing) { _logger.DebugFormat("Updating components: {0} (copying dependencies required for '{1}')...", Path.GetFileName(solutionFileName), solutionFileName); var assemblyReferences = projectFinder.GetProjectsOfSLN(solutionFileName) .SelectMany(x => x.AssemblyReferences) .Distinct(); Builder.CopyAssemblyReferencesFromBuiltProjects(projectFinder, assemblyNamePatterns, ignoreOnlyMatching, assemblyReferences, ignoreMissing); _logger.InfoFormat("Updated components required by: {0} ('{1}')", Path.GetFileName(solutionFileName), solutionFileName); }
protected static void RunAllUnitTests(IProjectFinder projectFinder, string solutionFileName, bool ignoreFailedTests) { foreach (var project in projectFinder.GetProjectsOfSLN(solutionFileName)) { if (project.AssemblyReferences.Any(x => x.AssemblyNameFromFullName().StartsWith("Microsoft.VisualStudio.QualityTools.UnitTestFramework"))) { _logger.DebugFormat("\tRunning Tests: {0}", project.Name); MSTest(project, ignoreFailedTests); } } }
/// <summary> /// <para>Builds the given solution. Steps:</para> /// <para>1. Update dependencies (components) by copying all referenced assemblies from their building projects' output paths to the HintPath</para> /// <para>2. Clean the solution</para> /// <para>3. Build the solution</para> /// <para>Use <paramref name="ignoredDependencyAssemblies"/> to ignore system and/or third party assemblies that are not part of the build tree (those that will not be found when the <paramref name="projectFinder"/> will search for a project that builds them)</para> /// </summary> /// <param name="projectFinder"></param> /// <param name="solutionFileName"></param> /// <param name="ignoredDependencyAssemblies">Patterns for dependent assemblies to ignore when trying to find a building project to copy from.</param> /// <param name="ignoreOnlyMatching">Flips the meaning of the ignored assemblies so that ALL assemblies will be ignored, EXCEPT the ones matching the given patterns</param> public static void BuildSolution(IProjectFinder projectFinder, string solutionFileName, Regex[] ignoredDependencyAssemblies, bool ignoreOnlyMatching, bool ignoreMissing, bool cleanBeforeBuild, bool runTests, bool ignoreFailedTests) { _logger.InfoFormat("Building Solution: '{0}'", solutionFileName); Builder.UpdateComponentsFromBuiltProjects(projectFinder, solutionFileName, ignoredDependencyAssemblies, ignoreOnlyMatching, ignoreMissing); ValidateSolutionReadyForBuild(projectFinder, solutionFileName, ignoredDependencyAssemblies, ignoreOnlyMatching); if (cleanBeforeBuild) { _logger.DebugFormat("\tCleaning..."); MSBuild(solutionFileName, "/t:clean"); } _logger.DebugFormat("\tBuilding..."); MSBuild(solutionFileName); if (runTests) { _logger.DebugFormat("\tRunning tests is enabled - looking for tests to run..."); RunAllUnitTests(projectFinder, solutionFileName, ignoreFailedTests); } _logger.InfoFormat("Done: {0} ('{1}')\n", Path.GetFileName(solutionFileName), solutionFileName); }
public GitPackEngine(Options options, IGitCommandHelper gitCommandHelper, IChangedFilePreparer projectPreparer, IVisualStudioProjectCompiler projectCompiler, IPathService pathServie, IFilePackService filePackService, IProjectFinder projectFinder, IPackageCompressService compressService, ILogger logger) { this.options = options; this._gitCommandHelper = gitCommandHelper; this.changedFilePreparer = projectPreparer; this.logger = logger; this.pathService = pathServie; this.projectCompiler = projectCompiler; this.projectFinder = projectFinder; this.filePackService = filePackService; this.compressService = compressService; }
protected static IEnumerable <Project> GetAllProjectsInSolutionsOfProject(IProjectFinder projectFinder, Project project) { return(projectFinder.GetProjectsOfSLN(projectFinder.GetSLNFileForProject(project))); }
/// <summary> /// Builds a dependency graph between solutions. The vertices in the graph are the solution full file names. /// </summary> public static AdjacencyGraph <String, SEdge <String> > SolutionDependencyGraph(IProjectFinder projectFinder, IEnumerable <Project> projects, bool reverse, int maxRecursionLevel) { var graph = DeepDependencies(projectFinder, projects, true, maxRecursionLevel) .Where(x => x.Key != x.Value) .Select(x => ProjectEdgeToSLNEdge(projectFinder, x)) .Where(x => false == SolutionNamesEqual(x)) .Distinct() .Select(x => new SEdge <String>(reverse ? x.Key : x.Value, reverse ? x.Value : x.Key)) .ToAdjacencyGraph <String, SEdge <String> >(false); graph.AddVertexRange(projects.Select(x => SLNVertexName(projectFinder, x))); return(graph); }
public static void CopyAssemblyReferencesFromBuiltProjects( IProjectFinder projectFinder, Regex[] assemblyNamePatterns, bool ignoreOnlyMatching, IEnumerable <AssemblyReference> assemblyReferences, bool ignoreMissing) { // TODO: Refactor this mess, and save for each indirect reference the dependency path that caused it to be included in a unified way var originalAssemblyReferenceNames = new HashSet <string>(assemblyReferences.Select(ComparableAssemblyName)); var remainingReferences = new Queue <AssemblyReference>(assemblyReferences); var indirectReferencesOutsideSolution = new List <IndirectReferenceInfo>(); var ignoredAssemblies = new List <AssemblyReference>(); var badHintPathAssemblies = new List <AssemblyReference>(); var missingProjects = new List <AssemblyReference>(); var unbuiltProjects = new List <Project>(); while (remainingReferences.Any()) { var assemblyReference = remainingReferences.Dequeue(); if (false == IncludeAssemblyWhenCopyingDeps(assemblyReference, assemblyNamePatterns, ignoreOnlyMatching)) { _logger.DebugFormat("Not copying ignored assembly: '{0}'", assemblyReference.ToString()); ignoredAssemblies.Add(assemblyReference); continue; } if (String.IsNullOrWhiteSpace(assemblyReference.HintPath)) { _logger.DebugFormat("Can't copy dependency (no target path): Missing HintPath for assembly reference: '{0}', used by projects:\n{1}", assemblyReference, ProjectsUsingAssemblyReference(projectFinder, assemblyReference)); badHintPathAssemblies.Add(assemblyReference); continue; } var targetPath = System.IO.Path.GetDirectoryName(assemblyReference.HintPath); var buildingProject = projectFinder.FindProjectForAssemblyReference(assemblyReference).SingleOrDefault(); if (null == buildingProject) { _logger.DebugFormat("Can't find dependency (no building project): No project builds assembly reference: '{0}', used by projects:\n{1}", assemblyReference, ProjectsUsingAssemblyReference(projectFinder, assemblyReference)); missingProjects.Add(assemblyReference); continue; } if (ignoreMissing && (false == System.IO.Directory.Exists(buildingProject.GetAbsoluteOutputPath()))) { _logger.DebugFormat("Ignoring (not copying) all components from not-built project: {0}", buildingProject.ToString()); unbuiltProjects.Add(buildingProject); continue; } var projectOutputs = buildingProject.GetBuiltProjectOutputs().ToArray(); _logger.InfoFormat("Copy: {0,-40} -> {1}", buildingProject.Name, targetPath); CopyFilesToDirectory(projectOutputs, targetPath, ignoreMissing); // Add sub-references - the indirectly referenced assemblies, the ones used by the current assemblyReference var explicitTargetPath = System.IO.Path.GetDirectoryName(assemblyReference.ExplicitHintPath); // TODO: Refactor AddIndirectReferences(projectFinder, assemblyNamePatterns, ignoreOnlyMatching, originalAssemblyReferenceNames, remainingReferences, targetPath, buildingProject, projectOutputs, explicitTargetPath, assemblyReference, indirectReferencesOutsideSolution); } WarnAboutRemainingIndirectReferences(projectFinder, originalAssemblyReferenceNames, indirectReferencesOutsideSolution); WarnAboutUncopiedAssemblies(assemblyNamePatterns, ignoreOnlyMatching, ignoredAssemblies, badHintPathAssemblies, missingProjects, unbuiltProjects); }
/// <summary> /// Creates a graph representing all the dependencies within the given projects. /// The edges will be from dependent project to dependency, unless <paramref name="reverse"/> is True, /// in which case the edges will be from dependency to dependent (which is more useful for topological sorting - /// which in this way will return the projects in build order) /// </summary> /// <param name="projects"></param> /// <returns></returns> public static AdjacencyGraph <Project, SEdge <Project> > ProjectDependencyGraph(IProjectFinder projectFinder, IEnumerable <Project> projects, bool reverse, int maxRecursionLevel) { // First add all the dependencies (edges) var graph = DeepDependencies(projectFinder, projects, false, maxRecursionLevel) .Distinct() .Select(x => new SEdge <Project>(reverse ? x.Key : x.Value, reverse ? x.Value : x.Key)) .ToAdjacencyGraph <Project, SEdge <Project> >(false); // Then make sure we have vertices for each input "root" project, regardless of if it has dependencies or not graph.AddVertexRange(projects); return(graph); }
public static IEnumerable <Project> BuildOrder(IProjectFinder projectFinder, IEnumerable <Project> projects, int maxRecursionLevel) { return(ProjectDependencyGraph(projectFinder, projects, true, maxRecursionLevel).TopologicalSort()); }
/// <summary> /// Finds all pairs of dependencies source -> target of projects that depend on each other. /// </summary> /// <param name="maxRecursionLevel">How deep to resolve dependencies of the given inputs. 0 means no dependency resolution is performed. -1 means infinity.</param> /// <returns></returns> public static IEnumerable <KeyValuePair <Project, Project> > DeepDependencies(IProjectFinder projectFinder, IEnumerable <Project> projects, bool includeAllProjectsInSolution, int maxRecursionLevel) { var projectsToTraverse = new Queue <ResolvedProjectDependencyInfo>(projects.Select(x => new ResolvedProjectDependencyInfo(0, x, x))); var traversedProjects = new HashSet <Project>(); while (projectsToTraverse.Any()) { var projectPair = projectsToTraverse.Dequeue(); var project = projectPair.Target; if (projectPair.Source != projectPair.Target) { yield return(new KeyValuePair <Project, Project>(projectPair.Source, projectPair.Target)); } if ((0 <= maxRecursionLevel) && (projectPair.RecursionLevel >= maxRecursionLevel)) { continue; } if (traversedProjects.Contains(project)) { continue; } traversedProjects.Add(project); if (includeAllProjectsInSolution) { foreach (var projectInSameSolution in GetAllProjectsInSolutionsOfProject(projectFinder, project) .Where(x => false == traversedProjects.Contains(x))) { projectsToTraverse.Enqueue(new ResolvedProjectDependencyInfo(projectPair.RecursionLevel + 1, projectInSameSolution, projectInSameSolution)); } } foreach (var subProject in project.ProjectReferences) { projectsToTraverse.Enqueue(new ResolvedProjectDependencyInfo(projectPair.RecursionLevel + 1, project, subProject)); } // TODO: Why do we allow a null projectFinder at all here? if (null != projectFinder) { foreach (var assemblySubProject in project.AssemblyReferences.SelectMany(projectFinder.FindProjectForAssemblyReference)) { projectsToTraverse.Enqueue(new ResolvedProjectDependencyInfo(projectPair.RecursionLevel + 1, project, assemblySubProject)); } } } }
private static string SLNVertexName(IProjectFinder projectFinder, Project project) { return(projectFinder.GetSLNFileForProject(project).FullName); }
protected static KeyValuePair <string, string> ProjectEdgeToSLNEdge(IProjectFinder projectFinder, KeyValuePair <Project, Project> x) { return(new KeyValuePair <String, String>(SLNVertexName(projectFinder, x.Key), SLNVertexName(projectFinder, x.Value))); }