mirror of https://github.com/abpframework/abp.git
Browse Source
Introduced "abp build" command to optimize building for big mono repositoriespull/5443/head
committed by
GitHub
35 changed files with 2071 additions and 4 deletions
@ -0,0 +1,67 @@ |
|||
# Build Command |
|||
|
|||
Building a .NET project is hard when the project references a project reference outside of the solution or even from a different GIT repository. This command builds a GIT repository and it's depending repositories or a single .NET solution File. In order ```build``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain one of; |
|||
|
|||
* A .NET solution file (*.sln) |
|||
* abp-build-config.json |
|||
|
|||
When the executing directory (or ```--working-directory``` parameter's directory) contains a .NET solution file, ```build``` command builds all the projects in the related solution file and all project references on building project files recursively. |
|||
|
|||
When the executing directory (or ```--working-directory``` parameter's directory) contains a ```abp-build-config.json```, ```build``` command builds all changed projects form its last build and all project references on building project files recursively. |
|||
|
|||
# Build Command Config |
|||
|
|||
```abp-build-config.json``` contains properties below; |
|||
|
|||
* ```Name```: Name of the GIT repository. This can be friendly name of your GIT repository or any other unique string for the repository. |
|||
* ```RootPath```: Root path of the repository which contains ```.git``` folder. |
|||
* ```DependingRepositories```: Depending repository list of a repository. Each depending repository item contains same fields as a repository. |
|||
* ```IgnoredDirectories```: Relative directory paths to ignore while building a GIT repository. |
|||
|
|||
A sample ```abp-build-config.json``` looks like for a Windows OS; |
|||
|
|||
````json |
|||
{ |
|||
"Name": "main-repository", |
|||
"RootPath": "D:\\GitHub\\main-repository", |
|||
"DependingRepositories": [{ |
|||
"Name": "module-repository", |
|||
"RootPath": "D:\\GitHub\\module-repository" |
|||
}], |
|||
"IgnoredDirectories": [ |
|||
"utils" |
|||
] |
|||
} |
|||
```` |
|||
|
|||
# Build Status |
|||
|
|||
ABP CLI stores a build status file for builds using the repository friendly names and current branch names in the; |
|||
|
|||
* ```%USERPROFILE%\.abp\build\``` for Windows. |
|||
* ```$HOME/.abp/build/``` for Linux/macOS. |
|||
|
|||
and uses this file when building same repository next time and only builds affected projects and decreases the total build time. A sample build status file content looks like; |
|||
|
|||
````json |
|||
{ |
|||
"RepositoryName": "main-repository", |
|||
"BranchName": "dev", |
|||
"CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba", |
|||
"SucceedProjects": [{ |
|||
"CsProjPath": "D:\\GitHub\\main-repository\\BookStore\\BookStore.Web.csproj", |
|||
"CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba" |
|||
}], |
|||
"DependingRepositories": [{ |
|||
"RepositoryName": "module-repository", |
|||
"BranchName": "dev", |
|||
"CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04", |
|||
"SucceedProjects": [{ |
|||
"CsProjPath": "D:\\GitHub\\module-repository\\identity-module\Identity\\Identity.Web.csproj", |
|||
"CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04" |
|||
}], |
|||
"DependingRepositories": [] |
|||
}] |
|||
} |
|||
```` |
|||
|
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DefaultBuildProjectListSorter : IBuildProjectListSorter, ITransientDependency |
|||
{ |
|||
public List<DotNetProjectInfo> SortByDependencies( |
|||
List<DotNetProjectInfo> source, |
|||
IEqualityComparer<DotNetProjectInfo> comparer = null) |
|||
{ |
|||
/* See: http://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp
|
|||
* http://en.wikipedia.org/wiki/Topological_sorting
|
|||
*/ |
|||
|
|||
var sorted = new List<DotNetProjectInfo>(); |
|||
var visited = new Dictionary<DotNetProjectInfo, bool>(comparer); |
|||
|
|||
foreach (var item in source) |
|||
{ |
|||
SortByDependenciesVisit(source, item, sorted, visited); |
|||
} |
|||
|
|||
return sorted; |
|||
} |
|||
|
|||
private void SortByDependenciesVisit( |
|||
List<DotNetProjectInfo> source, |
|||
DotNetProjectInfo item, |
|||
List<DotNetProjectInfo> sorted, |
|||
Dictionary<DotNetProjectInfo, bool> visited) |
|||
{ |
|||
bool inProcess; |
|||
var alreadyVisited = visited.TryGetValue(item, out inProcess); |
|||
|
|||
if (alreadyVisited) |
|||
{ |
|||
if (inProcess) |
|||
{ |
|||
throw new ArgumentException("Cyclic dependency found! Item: " + item); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
visited[item] = true; |
|||
|
|||
var dependencies = item.Dependencies; |
|||
if (dependencies != null) |
|||
{ |
|||
foreach (var dependency in dependencies) |
|||
{ |
|||
var dependencyItem = source.FirstOrDefault(e => e.CsProjPath == dependency.CsProjPath); |
|||
if (dependencyItem != null) |
|||
{ |
|||
SortByDependenciesVisit(source, dependencyItem, sorted, visited); |
|||
} |
|||
} |
|||
} |
|||
|
|||
visited[item] = false; |
|||
sorted.Add(item); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using LibGit2Sharp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DefaultBuildStatusGenerator : IBuildStatusGenerator, ITransientDependency |
|||
{ |
|||
private readonly IGitRepositoryHelper _gitRepositoryHelper; |
|||
|
|||
public DefaultBuildStatusGenerator(IGitRepositoryHelper gitRepositoryHelper) |
|||
{ |
|||
_gitRepositoryHelper = gitRepositoryHelper; |
|||
} |
|||
|
|||
public GitRepositoryBuildStatus Generate( |
|||
DotNetProjectBuildConfig buildConfig, |
|||
List<DotNetProjectInfo> changedProjects, |
|||
List<string> buildSucceededProjects) |
|||
{ |
|||
var lastCommitId = _gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository); |
|||
var repoFriendlyName = _gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository); |
|||
|
|||
var status = new GitRepositoryBuildStatus( |
|||
buildConfig.GitRepository.Name, |
|||
repoFriendlyName |
|||
); |
|||
|
|||
if (ShouldUpdateRepositoryCommitId(buildConfig, changedProjects, buildSucceededProjects)) |
|||
{ |
|||
status.CommitId = lastCommitId; |
|||
} |
|||
|
|||
status.SucceedProjects = changedProjects.Where(p => |
|||
p.RepositoryName == buildConfig.GitRepository.Name && |
|||
buildSucceededProjects.Contains(p.CsProjPath) |
|||
) |
|||
.Select(e => new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = e.CsProjPath, |
|||
CommitId = lastCommitId |
|||
}).ToList(); |
|||
|
|||
foreach (var dependingRepository in buildConfig.GitRepository.DependingRepositories) |
|||
{ |
|||
GenerateBuildStatusInternal( |
|||
buildConfig, |
|||
dependingRepository, |
|||
changedProjects, |
|||
buildSucceededProjects, |
|||
status |
|||
); |
|||
} |
|||
|
|||
return status; |
|||
} |
|||
|
|||
private bool ShouldUpdateRepositoryCommitId( |
|||
DotNetProjectBuildConfig buildConfig, |
|||
List<DotNetProjectInfo> changedProjects, |
|||
List<string> buildSucceededProjects) |
|||
{ |
|||
if (!buildConfig.SlFilePath.IsNullOrEmpty()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (changedProjects.Count == 0 || buildSucceededProjects.Count == 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return changedProjects.Count == buildSucceededProjects.Count; |
|||
} |
|||
|
|||
private void GenerateBuildStatusInternal( |
|||
DotNetProjectBuildConfig buildConfig, |
|||
GitRepository gitRepository, |
|||
List<DotNetProjectInfo> changedProjects, |
|||
List<string> buildSucceededProjects, |
|||
GitRepositoryBuildStatus status) |
|||
{ |
|||
var lastCommitId = _gitRepositoryHelper.GetLastCommitId(gitRepository); |
|||
var repoFriendlyName = _gitRepositoryHelper.GetFriendlyName(gitRepository); |
|||
|
|||
var dependingRepositoryStatus = new GitRepositoryBuildStatus( |
|||
gitRepository.Name, |
|||
repoFriendlyName |
|||
); |
|||
|
|||
if (ShouldUpdateRepositoryCommitId(buildConfig, changedProjects, buildSucceededProjects)) |
|||
{ |
|||
dependingRepositoryStatus.CommitId = lastCommitId; |
|||
} |
|||
|
|||
dependingRepositoryStatus.SucceedProjects = changedProjects.Where(p => |
|||
p.RepositoryName == gitRepository.Name && |
|||
buildSucceededProjects.Contains(p.CsProjPath) |
|||
) |
|||
.Select(e => new DotNetProjectBuildStatus() |
|||
{ |
|||
CsProjPath = e.CsProjPath, |
|||
CommitId = lastCommitId |
|||
}).ToList(); |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
GenerateBuildStatusInternal( |
|||
buildConfig, |
|||
dependingRepository, |
|||
changedProjects, |
|||
buildSucceededProjects, |
|||
dependingRepositoryStatus |
|||
); |
|||
} |
|||
|
|||
status.DependingRepositories.Add(dependingRepositoryStatus); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,352 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Xml.Linq; |
|||
using LibGit2Sharp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DefaultChangedProjectFinder : IChangedProjectFinder, ITransientDependency |
|||
{ |
|||
private readonly IRepositoryBuildStatusStore _repositoryBuildStatusStore; |
|||
private readonly IGitRepositoryHelper _gitRepositoryHelper; |
|||
|
|||
private readonly IDotNetProjectDependencyFiller _dotNetProjectDependencyFiller; |
|||
private readonly IBuildProjectListSorter _buildProjectListSorter; |
|||
|
|||
private readonly List<string> _changeDetectionFileExtensions = new List<string> |
|||
{ |
|||
".cs", |
|||
".csproj", |
|||
".cshtml" |
|||
}; |
|||
|
|||
public DefaultChangedProjectFinder( |
|||
IRepositoryBuildStatusStore repositoryBuildStatusStore, |
|||
IGitRepositoryHelper gitRepositoryHelper, |
|||
IDotNetProjectDependencyFiller dotNetProjectDependencyFiller, |
|||
IBuildProjectListSorter buildProjectListSorter) |
|||
{ |
|||
_repositoryBuildStatusStore = repositoryBuildStatusStore; |
|||
_gitRepositoryHelper = gitRepositoryHelper; |
|||
_dotNetProjectDependencyFiller = dotNetProjectDependencyFiller; |
|||
_buildProjectListSorter = buildProjectListSorter; |
|||
} |
|||
|
|||
public List<DotNetProjectInfo> Find(DotNetProjectBuildConfig buildConfig) |
|||
{ |
|||
if (!buildConfig.SlFilePath.IsNullOrEmpty()) |
|||
{ |
|||
return FindBySlnFile(buildConfig); |
|||
} |
|||
|
|||
return FindByRepository(buildConfig); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns list of projects in a repository and its depending repositories sorted by dependencies
|
|||
/// </summary>
|
|||
/// <param name="buildConfig"></param>
|
|||
/// <returns></returns>
|
|||
private List<DotNetProjectInfo> FindAllProjects(DotNetProjectBuildConfig buildConfig) |
|||
{ |
|||
var projects = new List<DotNetProjectInfo>(); |
|||
|
|||
AddProjectsOfRepository(buildConfig.GitRepository, projects); |
|||
|
|||
_dotNetProjectDependencyFiller.Fill(projects); |
|||
|
|||
var allSortedProjectList = _buildProjectListSorter.SortByDependencies( |
|||
projects, |
|||
new DotNetProjectInfoEqualityComparer() |
|||
); |
|||
|
|||
FilterIgnoredDirectories(allSortedProjectList, buildConfig.GitRepository); |
|||
|
|||
return allSortedProjectList; |
|||
} |
|||
|
|||
private void FilterIgnoredDirectories(List<DotNetProjectInfo> projects, GitRepository gitRepository) |
|||
{ |
|||
foreach (var ignoredDirectory in gitRepository.IgnoredDirectories) |
|||
{ |
|||
projects = projects.Where(e => |
|||
!e.CsProjPath.StartsWith(Path.Combine(gitRepository.RootPath, ignoredDirectory))) |
|||
.ToList(); |
|||
} |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
FilterIgnoredDirectories(projects, dependingRepository); |
|||
} |
|||
} |
|||
|
|||
private void AddProjectsOfRepository(GitRepository gitRepository, List<DotNetProjectInfo> projects) |
|||
{ |
|||
var allCsProjFiles = Directory.GetFiles( |
|||
gitRepository.RootPath, |
|||
"*.csproj", |
|||
SearchOption.AllDirectories |
|||
).ToList(); |
|||
|
|||
projects.AddRange( |
|||
allCsProjFiles.Select(csProjPath => new DotNetProjectInfo(gitRepository.Name, csProjPath, false)) |
|||
); |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
AddProjectsOfRepository(dependingRepository, projects); |
|||
} |
|||
} |
|||
|
|||
private List<DotNetProjectInfo> FindByRepository(DotNetProjectBuildConfig buildConfig) |
|||
{ |
|||
var gitRepositoryBuildStatus = _repositoryBuildStatusStore.Get( |
|||
buildConfig.BuildName, |
|||
buildConfig.GitRepository |
|||
); |
|||
|
|||
var allSortedProjectList = FindAllProjects(buildConfig); |
|||
|
|||
MarkProjectsForBuild( |
|||
buildConfig.GitRepository, |
|||
gitRepositoryBuildStatus, |
|||
buildConfig.ForceBuild, |
|||
allSortedProjectList |
|||
); |
|||
|
|||
return allSortedProjectList.Where(e => e.ShouldBuild).ToList(); |
|||
} |
|||
|
|||
private void MarkProjectsForBuild( |
|||
GitRepository repository, |
|||
GitRepositoryBuildStatus repositoryBuildStatus, |
|||
bool forceBuild, |
|||
List<DotNetProjectInfo> allProjectList) |
|||
{ |
|||
if (forceBuild || repositoryBuildStatus == null || repositoryBuildStatus.CommitId.IsNullOrEmpty()) |
|||
{ |
|||
// Mark all projects for build
|
|||
allProjectList.ForEach(e => e.ShouldBuild = true); |
|||
} |
|||
else |
|||
{ |
|||
MarkChangedProjectsForBuild( |
|||
repository, |
|||
repositoryBuildStatus, |
|||
allProjectList |
|||
); |
|||
} |
|||
|
|||
if (!repository.DependingRepositories.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var dependingRepository in repository.DependingRepositories) |
|||
{ |
|||
var dependingRepositoryBuildStatus = repositoryBuildStatus?.GetChild(dependingRepository.Name); |
|||
MarkProjectsForBuild( |
|||
dependingRepository, |
|||
dependingRepositoryBuildStatus, |
|||
forceBuild, |
|||
allProjectList |
|||
); |
|||
} |
|||
} |
|||
|
|||
private void MarkChangedProjectsForBuild( |
|||
GitRepository repository, |
|||
GitRepositoryBuildStatus status, |
|||
List<DotNetProjectInfo> allProjectList) |
|||
{ |
|||
using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) |
|||
{ |
|||
var firstCommit = status.CommitId.IsNullOrEmpty() |
|||
? null |
|||
: repo.Lookup<Commit>(status.CommitId); |
|||
|
|||
var repoDifferences = repo.Diff.Compare<Patch>(firstCommit?.Tree, repo.Head.Tip.Tree); |
|||
|
|||
var fileExtensionPredicate = PredicateBuilder.New<PatchEntryChanges>(true); |
|||
|
|||
foreach (var changeDetectionFileExtension in _changeDetectionFileExtensions) |
|||
{ |
|||
fileExtensionPredicate = fileExtensionPredicate.Or( |
|||
e => e.Path.EndsWith(changeDetectionFileExtension) |
|||
); |
|||
} |
|||
|
|||
var files = repoDifferences |
|||
.Where(fileExtensionPredicate) |
|||
.Where(e => e.Status != ChangeKind.Deleted) |
|||
.Select(e => e) |
|||
.ToList(); |
|||
|
|||
var affectedCsProjFiles = FindAffectedCsProjFiles(repository.RootPath, files); |
|||
var lastCommitId = _gitRepositoryHelper.GetLastCommitId(repository); |
|||
|
|||
foreach (var file in affectedCsProjFiles) |
|||
{ |
|||
var csProjPath = Path.Combine(repository.RootPath, file); |
|||
if (status.SucceedProjects.Any(p => p.CsProjPath == csProjPath && p.CommitId == lastCommitId)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Filter ignored directories
|
|||
var isIgnored = repository.IgnoredDirectories.Any(ignoredDirectory => |
|||
csProjPath.StartsWith(Path.Combine(repository.RootPath, ignoredDirectory))); |
|||
if (isIgnored) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
allProjectList.MarkForBuild(repository.Name, csProjPath); |
|||
AddDependingProjectsToList(repository.Name, csProjPath, allProjectList); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private List<DotNetProjectInfo> FindBySlnFile(DotNetProjectBuildConfig buildConfig) |
|||
{ |
|||
var allProjectList = FindAllProjects(buildConfig); |
|||
|
|||
var slFine = new FileInfo(buildConfig.SlFilePath); |
|||
var csProjFiles = slFine.Directory.GetFiles( |
|||
"*.csproj", |
|||
SearchOption.AllDirectories |
|||
).Select(e => e.FullName) |
|||
.ToList(); |
|||
|
|||
foreach (var csProjFile in csProjFiles) |
|||
{ |
|||
MarkDependantProjectsForBuild(buildConfig.GitRepository, csProjFile, allProjectList); |
|||
} |
|||
|
|||
return _buildProjectListSorter.SortByDependencies( |
|||
allProjectList, |
|||
new DotNetProjectInfoEqualityComparer() |
|||
).Where(e => e.ShouldBuild).ToList(); |
|||
} |
|||
|
|||
private void MarkDependantProjectsForBuild( |
|||
GitRepository gitRepository, |
|||
string csProjFilePath, |
|||
List<DotNetProjectInfo> allProjectList) |
|||
{ |
|||
var repositoryName = gitRepository.FindRepositoryOf(csProjFilePath); |
|||
var project = new DotNetProjectInfo(repositoryName, csProjFilePath, true); |
|||
|
|||
if (allProjectList.IsMarkedForBuild(repositoryName, csProjFilePath)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
allProjectList.MarkForBuild(project); |
|||
AddProjectDependencies(gitRepository, project, allProjectList); |
|||
} |
|||
|
|||
private void AddProjectDependencies( |
|||
GitRepository gitRepository, |
|||
DotNetProjectInfo project, |
|||
List<DotNetProjectInfo> allProjectList) |
|||
{ |
|||
var projectInfo = allProjectList.FirstOrDefault(e => e.CsProjPath == project.CsProjPath); |
|||
if (projectInfo == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var dependencies = projectInfo.Dependencies; |
|||
|
|||
foreach (var dependency in dependencies) |
|||
{ |
|||
if (allProjectList.IsMarkedForBuild(dependency.RepositoryName, dependency.CsProjPath)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
allProjectList.MarkForBuild(dependency.RepositoryName, dependency.CsProjPath); |
|||
|
|||
AddProjectDependencies(gitRepository, dependency, allProjectList); |
|||
} |
|||
} |
|||
|
|||
private void AddDependingProjectsToList( |
|||
string repositoryName, |
|||
string csProjPath, |
|||
List<DotNetProjectInfo> allProjectList) |
|||
{ |
|||
var dependingProjects = allProjectList.Where( |
|||
e => e.Dependencies.Any(d => d.RepositoryName == repositoryName && d.CsProjPath == csProjPath) |
|||
).Select(e => new DotNetProjectInfo(e.RepositoryName, e.CsProjPath, true)).ToList(); |
|||
|
|||
if (!dependingProjects.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (var dependingProject in dependingProjects) |
|||
{ |
|||
if (allProjectList.IsMarkedForBuild(dependingProject)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
allProjectList.MarkForBuild(dependingProject); |
|||
AddDependingProjectsToList(dependingProject.RepositoryName, dependingProject.CsProjPath, |
|||
allProjectList); |
|||
} |
|||
} |
|||
|
|||
private List<string> FindAffectedCsProjFiles(string repositoryPath, List<PatchEntryChanges> files) |
|||
{ |
|||
var affectedProjectFiles = new List<string>(); |
|||
foreach (var file in files) |
|||
{ |
|||
var filePath = Path.Combine(repositoryPath, file.Path); |
|||
if (filePath.EndsWith(".csproj")) |
|||
{ |
|||
affectedProjectFiles.Add(filePath); |
|||
} |
|||
|
|||
if (!filePath.EndsWith(".cs")) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var classFile = new FileInfo(filePath); |
|||
var csProjPath = FindBelongingProjectPathOfClass(classFile.Directory?.FullName); |
|||
if (csProjPath.IsNullOrEmpty() || affectedProjectFiles.Contains(csProjPath)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
affectedProjectFiles.Add(csProjPath); |
|||
} |
|||
|
|||
return affectedProjectFiles; |
|||
} |
|||
|
|||
private string FindBelongingProjectPathOfClass(string directoryPath) |
|||
{ |
|||
var files = Directory.GetFiles(directoryPath, "*.csproj", SearchOption.TopDirectoryOnly); |
|||
if (files.Length == 1) |
|||
{ |
|||
return files.First(); |
|||
} |
|||
|
|||
var directoryInfo = new DirectoryInfo(directoryPath); |
|||
|
|||
if (directoryInfo.Parent == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return FindBelongingProjectPathOfClass(directoryInfo.Parent.FullName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.Cli.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DefaultDotNetProjectBuilder : IDotNetProjectBuilder, ITransientDependency |
|||
{ |
|||
public List<string> Build(List<DotNetProjectInfo> projects, string arguments) |
|||
{ |
|||
var builtProjects = new ConcurrentBag<string>(); |
|||
var totalProjectCountToBuild = projects.Count; |
|||
var buildingProjectIndex = 0; |
|||
|
|||
try |
|||
{ |
|||
foreach (var project in projects) |
|||
{ |
|||
if (builtProjects.Contains(project.CsProjPath)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
buildingProjectIndex++; |
|||
|
|||
Console.WriteLine( |
|||
"Building....: " + " (" + buildingProjectIndex + "/" + |
|||
totalProjectCountToBuild + ")" + project.CsProjPath |
|||
); |
|||
|
|||
BuildInternal(project, arguments, builtProjects); |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e); |
|||
} |
|||
|
|||
return builtProjects.ToList(); |
|||
} |
|||
|
|||
private void BuildInternal(DotNetProjectInfo project, string arguments, ConcurrentBag<string> builtProjects) |
|||
{ |
|||
var buildArguments = arguments.TrimStart('"').TrimEnd('"'); |
|||
Console.WriteLine("Executing...: dotnet build " + project.CsProjPath + " " + buildArguments); |
|||
|
|||
var output = CmdHelper.RunCmdAndGetOutput( |
|||
"dotnet build " + project.CsProjPath + " " + buildArguments, |
|||
out int buildStatus |
|||
); |
|||
|
|||
if (buildStatus == 0) |
|||
{ |
|||
builtProjects.Add(project.CsProjPath); |
|||
WriteOutput(output, ConsoleColor.Green); |
|||
} |
|||
else |
|||
{ |
|||
WriteOutput(output, ConsoleColor.Red); |
|||
Console.WriteLine("Build failed for :" + project.CsProjPath); |
|||
throw new Exception("Build failed!"); |
|||
} |
|||
} |
|||
|
|||
private void WriteOutput(string text, ConsoleColor color) |
|||
{ |
|||
var currentConsoleColor = Console.ForegroundColor; |
|||
Console.ForegroundColor = color; |
|||
Console.WriteLine(text); |
|||
Console.ForegroundColor = currentConsoleColor; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DotNetProjectBuildConfig |
|||
{ |
|||
public string BuildName { get; set; } |
|||
|
|||
public string SlFilePath { get; set; } |
|||
|
|||
public GitRepository GitRepository { get; set; } |
|||
|
|||
public bool ForceBuild { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DotNetProjectBuildStatus |
|||
{ |
|||
public string CsProjPath { get; set; } |
|||
|
|||
public string CommitId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Xml.Linq; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DotNetProjectDependencyFiller : IDotNetProjectDependencyFiller, ITransientDependency |
|||
{ |
|||
public void Fill(List<DotNetProjectInfo> projects) |
|||
{ |
|||
foreach (var project in projects) |
|||
{ |
|||
FillProjectDependencies(project); |
|||
} |
|||
} |
|||
|
|||
private void FillProjectDependencies(DotNetProjectInfo project) |
|||
{ |
|||
var projectNode = XElement.Load(project.CsProjPath); |
|||
var referenceNodes = projectNode.Descendants("ItemGroup").Descendants("ProjectReference"); |
|||
|
|||
foreach (var referenceNode in referenceNodes) |
|||
{ |
|||
if (referenceNode.Attribute("Include") == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var relativePath = referenceNode.Attribute("Include").Value; |
|||
var file = new FileInfo(project.CsProjPath); |
|||
var referenceProjectInfo = new FileInfo(Path.Combine(file.Directory.FullName, relativePath)); |
|||
|
|||
var referenceProject = new DotNetProjectInfo(project.RepositoryName, referenceProjectInfo.FullName, false); |
|||
project.Dependencies.Add(referenceProject); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
[Serializable] |
|||
public class DotNetProjectInfo |
|||
{ |
|||
public string RepositoryName { get; set; } |
|||
|
|||
public string CsProjPath { get; set; } |
|||
|
|||
public bool ShouldBuild { get; set; } |
|||
|
|||
public List<DotNetProjectInfo> Dependencies { get; set; } |
|||
|
|||
public DotNetProjectInfo(string repositoryName, string csProjPath, bool shouldBuild) |
|||
{ |
|||
RepositoryName = repositoryName; |
|||
CsProjPath = csProjPath; |
|||
ShouldBuild = shouldBuild; |
|||
Dependencies = new List<DotNetProjectInfo>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class DotNetProjectInfoEqualityComparer : EqualityComparer<DotNetProjectInfo> |
|||
{ |
|||
public override bool Equals(DotNetProjectInfo x, DotNetProjectInfo y) |
|||
{ |
|||
return (x == null && y == null) || (x != null && y != null && x.CsProjPath == y.CsProjPath); |
|||
} |
|||
|
|||
public override int GetHashCode(DotNetProjectInfo obj) |
|||
{ |
|||
return obj == null ? 0 : obj.CsProjPath.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public static class DotNetProjectInfoExtensions |
|||
{ |
|||
public static void MarkForBuild(this List<DotNetProjectInfo> projects, string repositoryName, string csProjPath) |
|||
{ |
|||
var project = projects.FirstOrDefault(e => |
|||
e.RepositoryName == repositoryName && e.CsProjPath == csProjPath |
|||
); |
|||
|
|||
if (project == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
project.ShouldBuild = true; |
|||
} |
|||
|
|||
public static void MarkForBuild(this List<DotNetProjectInfo> projects, DotNetProjectInfo project) |
|||
{ |
|||
projects.MarkForBuild(project.RepositoryName, project.CsProjPath); |
|||
} |
|||
|
|||
public static bool IsMarkedForBuild(this List<DotNetProjectInfo> projects, string repositoryName, |
|||
string csProjPath) |
|||
{ |
|||
var project = projects.FirstOrDefault(e => |
|||
e.RepositoryName == repositoryName && e.CsProjPath == csProjPath |
|||
); |
|||
|
|||
if (project == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return project.ShouldBuild; |
|||
} |
|||
|
|||
public static bool IsMarkedForBuild(this List<DotNetProjectInfo> projects, DotNetProjectInfo project) |
|||
{ |
|||
return projects.IsMarkedForBuild(project.RepositoryName, project.CsProjPath); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using LibGit2Sharp; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class FileSystemDotNetProjectBuildConfigReader : IDotNetProjectBuildConfigReader, ITransientDependency |
|||
{ |
|||
private readonly IJsonSerializer _jsonSerializer; |
|||
private string _buildConfigName = "abp-build-config.json"; |
|||
|
|||
public FileSystemDotNetProjectBuildConfigReader(IJsonSerializer jsonSerializer) |
|||
{ |
|||
_jsonSerializer = jsonSerializer; |
|||
} |
|||
|
|||
public DotNetProjectBuildConfig Read(string directoryPath) |
|||
{ |
|||
var buildConfig = new DotNetProjectBuildConfig(); |
|||
var solutionFiles = Directory.GetFiles(directoryPath, "*.sln", SearchOption.TopDirectoryOnly); |
|||
if (solutionFiles.Length == 1) |
|||
{ |
|||
buildConfig.SlFilePath = solutionFiles.First(); |
|||
var configFile = GetClosestFile(directoryPath, _buildConfigName); |
|||
var configFileContent = File.ReadAllText(configFile); |
|||
buildConfig.GitRepository = _jsonSerializer.Deserialize<GitRepository>(configFileContent); |
|||
|
|||
SetBranchNames(buildConfig.GitRepository); |
|||
|
|||
return buildConfig; |
|||
} |
|||
|
|||
var configFiles = Directory.GetFiles(directoryPath, _buildConfigName, SearchOption.TopDirectoryOnly); |
|||
if (configFiles.Length == 1) |
|||
{ |
|||
var configFile = configFiles.First(); |
|||
var configFileContent = File.ReadAllText(configFile); |
|||
buildConfig.GitRepository = _jsonSerializer.Deserialize<GitRepository>(configFileContent); |
|||
|
|||
SetBranchNames(buildConfig.GitRepository); |
|||
|
|||
return buildConfig; |
|||
} |
|||
|
|||
Console.WriteLine( |
|||
"There are more than 1 config (abp-build-config.json) file in the directory!" |
|||
); |
|||
|
|||
throw new Exception("There is no solution file (*.sln) or " + _buildConfigName + " in the working directory !"); |
|||
} |
|||
|
|||
private void SetBranchNames(GitRepository gitRepository) |
|||
{ |
|||
using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) |
|||
{ |
|||
gitRepository.BranchName = repo.Head.FriendlyName; |
|||
} |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
SetBranchNames(dependingRepository); |
|||
} |
|||
} |
|||
|
|||
private string GetClosestFile(string directoryPath, string fileName) |
|||
{ |
|||
var directory = new DirectoryInfo(directoryPath); |
|||
var files = directory.GetFiles(fileName, SearchOption.TopDirectoryOnly); |
|||
if (files.Any() && files.Length == 1) |
|||
{ |
|||
return files.First().FullName; |
|||
} |
|||
|
|||
do |
|||
{ |
|||
directory = directory.Parent; |
|||
if (directory == null) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
files = directory.GetFiles(fileName, SearchOption.TopDirectoryOnly); |
|||
if (files.Any() && files.Length == 1) |
|||
{ |
|||
return files.First().FullName; |
|||
} |
|||
} while (true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System.IO; |
|||
using Newtonsoft.Json; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.IO; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class FileSystemRepositoryBuildStatusStore : IRepositoryBuildStatusStore, ITransientDependency |
|||
{ |
|||
public GitRepositoryBuildStatus Get(string buildNamePrefix, GitRepository repository) |
|||
{ |
|||
if (!Directory.Exists(CliPaths.Build)) |
|||
{ |
|||
Directory.CreateDirectory(CliPaths.Build); |
|||
} |
|||
|
|||
var buildStatusFile = Path.Combine(CliPaths.Build, repository.GetUniqueName(buildNamePrefix)) + |
|||
".json"; |
|||
|
|||
if (!File.Exists(buildStatusFile)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var buildStatusText = File.ReadAllText(buildStatusFile); |
|||
return JsonConvert.DeserializeObject<GitRepositoryBuildStatus>(buildStatusText); |
|||
} |
|||
|
|||
public void Set(string buildNamePrefix, GitRepository repository, GitRepositoryBuildStatus status) |
|||
{ |
|||
var existingRepositoryStatus = Get(buildNamePrefix, repository); |
|||
|
|||
var buildStatusFile = Path.Combine( |
|||
CliPaths.Build, |
|||
status.GetUniqueName(buildNamePrefix) |
|||
) + ".json"; |
|||
|
|||
if (File.Exists(buildStatusFile)) |
|||
{ |
|||
FileHelper.DeleteIfExists(buildStatusFile); |
|||
} |
|||
|
|||
if (existingRepositoryStatus != null) |
|||
{ |
|||
existingRepositoryStatus.MergeWith(status); |
|||
|
|||
using (var file = File.CreateText(buildStatusFile)) |
|||
{ |
|||
new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, existingRepositoryStatus); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
using (var file = File.CreateText(buildStatusFile)) |
|||
{ |
|||
new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, status); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a source code repository
|
|||
/// </summary>
|
|||
public class GitRepository |
|||
{ |
|||
/// <summary>
|
|||
/// Name of the repository
|
|||
/// </summary>
|
|||
public string Name { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Branch of the repository
|
|||
/// </summary>
|
|||
public string BranchName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Root path of the repository which contains .git folder
|
|||
/// </summary>
|
|||
public string RootPath { get; set; } |
|||
|
|||
public List<GitRepository> DependingRepositories { get; set; } |
|||
|
|||
public List<string> IgnoredDirectories { get; set; } |
|||
|
|||
public GitRepository(string name, string branchName, string rootPath) |
|||
{ |
|||
Name = name; |
|||
BranchName = branchName; |
|||
RootPath = rootPath; |
|||
DependingRepositories = new List<GitRepository>(); |
|||
IgnoredDirectories = new List<string>(); |
|||
} |
|||
|
|||
public string GetUniqueName(string prefix) |
|||
{ |
|||
var name = Name + "_" + BranchName; |
|||
foreach (var dependingRepository in DependingRepositories) |
|||
{ |
|||
AddToUniqueName(dependingRepository, name); |
|||
} |
|||
|
|||
return (prefix.IsNullOrEmpty() ? "" : prefix + "_") + name.ToMd5(); |
|||
} |
|||
|
|||
private void AddToUniqueName(GitRepository gitRepository, string name) |
|||
{ |
|||
name += "_" + gitRepository.Name + "_" + gitRepository.BranchName; |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
AddToUniqueName(dependingRepository, name); |
|||
} |
|||
} |
|||
|
|||
public string FindRepositoryOf(string csProjFilePath) |
|||
{ |
|||
if (csProjFilePath.StartsWith(RootPath)) |
|||
{ |
|||
return Name; |
|||
} |
|||
|
|||
foreach (var dependingRepository in DependingRepositories) |
|||
{ |
|||
var name = FindRepositoryOfInternal(dependingRepository, csProjFilePath); |
|||
if (!string.IsNullOrEmpty(name)) |
|||
{ |
|||
return name; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private string FindRepositoryOfInternal(GitRepository repository, string csProjFilePath) |
|||
{ |
|||
if (csProjFilePath.StartsWith(repository.RootPath)) |
|||
{ |
|||
return repository.Name; |
|||
} |
|||
|
|||
foreach (var dependingRepository in repository.DependingRepositories) |
|||
{ |
|||
return FindRepositoryOfInternal(dependingRepository, csProjFilePath); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class GitRepositoryBuildStatus |
|||
{ |
|||
/// <summary>
|
|||
/// Name of the repository
|
|||
/// </summary>
|
|||
public string RepositoryName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Branch of the repository
|
|||
/// </summary>
|
|||
public string BranchName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Last succeeded commitId of the repository
|
|||
/// </summary>
|
|||
public string CommitId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Build succeeded projects of this repository
|
|||
/// </summary>
|
|||
public List<DotNetProjectBuildStatus> SucceedProjects { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Build status of depending repositories
|
|||
/// </summary>
|
|||
public List<GitRepositoryBuildStatus> DependingRepositories { get; set; } |
|||
|
|||
public GitRepositoryBuildStatus(string repositoryName, string branchName) |
|||
{ |
|||
RepositoryName = repositoryName; |
|||
BranchName = branchName; |
|||
SucceedProjects = new List<DotNetProjectBuildStatus>(); |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>(); |
|||
} |
|||
|
|||
public GitRepositoryBuildStatus GetSelfOrChild(string repositoryName) |
|||
{ |
|||
if (RepositoryName == repositoryName) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
return GetChild(repositoryName); |
|||
} |
|||
|
|||
public GitRepositoryBuildStatus GetChild(string repositoryName) |
|||
{ |
|||
foreach (var dependingRepository in DependingRepositories) |
|||
{ |
|||
var child = GetChildInternal(dependingRepository, repositoryName); |
|||
if (child != null) |
|||
{ |
|||
return child; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public string GetUniqueName(string prefix) |
|||
{ |
|||
var name = RepositoryName + "_" + BranchName; |
|||
foreach (var dependingRepository in DependingRepositories) |
|||
{ |
|||
AddToUniqueName(dependingRepository, name); |
|||
} |
|||
|
|||
return (prefix.IsNullOrEmpty() ? "" : prefix + "_") + name.ToMd5(); |
|||
} |
|||
|
|||
public void AddOrUpdateProjectStatus(DotNetProjectBuildStatus status) |
|||
{ |
|||
var existingProjectStatus = SucceedProjects.FirstOrDefault(p => p.CsProjPath == status.CsProjPath); |
|||
if (existingProjectStatus != null) |
|||
{ |
|||
existingProjectStatus.CommitId = status.CommitId; |
|||
} |
|||
else |
|||
{ |
|||
SucceedProjects.Add(status); |
|||
} |
|||
} |
|||
|
|||
public void MergeWith(GitRepositoryBuildStatus newBuildStatus) |
|||
{ |
|||
if (!newBuildStatus.CommitId.IsNullOrEmpty()) |
|||
{ |
|||
CommitId = newBuildStatus.CommitId; |
|||
} |
|||
|
|||
foreach (var succeedProject in newBuildStatus.SucceedProjects) |
|||
{ |
|||
AddOrUpdateProjectStatus(succeedProject); |
|||
} |
|||
|
|||
foreach (var dependingRepositoryBuildStatus in newBuildStatus.DependingRepositories) |
|||
{ |
|||
var existingDependingRepositoryBuildStatus = GetChild(dependingRepositoryBuildStatus.RepositoryName); |
|||
var newDependingRepositoryBuildStatus = newBuildStatus.GetChild( |
|||
dependingRepositoryBuildStatus.RepositoryName |
|||
); |
|||
|
|||
existingDependingRepositoryBuildStatus.MergeWith(newDependingRepositoryBuildStatus); |
|||
} |
|||
} |
|||
|
|||
private GitRepositoryBuildStatus GetChildInternal(GitRepositoryBuildStatus repositoryBuildStatus, |
|||
string repositoryName) |
|||
{ |
|||
if (repositoryBuildStatus.RepositoryName == repositoryName) |
|||
{ |
|||
return repositoryBuildStatus; |
|||
} |
|||
|
|||
foreach (var dependingRepository in repositoryBuildStatus.DependingRepositories) |
|||
{ |
|||
var child = GetChildInternal(dependingRepository, repositoryName); |
|||
if (child != null) |
|||
{ |
|||
return child; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private void AddToUniqueName(GitRepositoryBuildStatus gitRepository, string name) |
|||
{ |
|||
name += "_" + gitRepository.RepositoryName + "_" + gitRepository.BranchName; |
|||
|
|||
foreach (var dependingRepository in gitRepository.DependingRepositories) |
|||
{ |
|||
AddToUniqueName(dependingRepository, name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using LibGit2Sharp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class GitRepositoryHelper : IGitRepositoryHelper, ITransientDependency |
|||
{ |
|||
public string GetLastCommitId(GitRepository repository) |
|||
{ |
|||
using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) |
|||
{ |
|||
return repo.Head.Tip.Id.ToString(); |
|||
} |
|||
} |
|||
|
|||
public string GetFriendlyName(GitRepository repository) |
|||
{ |
|||
using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) |
|||
{ |
|||
return repo.Head.FriendlyName; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IBuildProjectListSorter |
|||
{ |
|||
List<DotNetProjectInfo> SortByDependencies( |
|||
List<DotNetProjectInfo> source, |
|||
IEqualityComparer<DotNetProjectInfo> comparer = null); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IBuildStatusGenerator |
|||
{ |
|||
GitRepositoryBuildStatus Generate( |
|||
DotNetProjectBuildConfig buildConfig, |
|||
List<DotNetProjectInfo> changedProjects, |
|||
List<string> buildSucceededProjects |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IChangedProjectFinder |
|||
{ |
|||
List<DotNetProjectInfo> Find(DotNetProjectBuildConfig buildConfig); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IDotNetProjectBuildConfigReader |
|||
{ |
|||
DotNetProjectBuildConfig Read(string directoryPath); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IDotNetProjectBuilder |
|||
{ |
|||
List<string> Build(List<DotNetProjectInfo> projects, string arguments); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IDotNetProjectDependencyFiller |
|||
{ |
|||
void Fill(List<DotNetProjectInfo> projects); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IGitRepositoryHelper |
|||
{ |
|||
string GetLastCommitId(GitRepository repository); |
|||
|
|||
string GetFriendlyName(GitRepository repository); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public interface IRepositoryBuildStatusStore |
|||
{ |
|||
GitRepositoryBuildStatus Get(string buildNamePrefix, GitRepository repository); |
|||
|
|||
void Set(string buildNamePrefix, GitRepository repository, GitRepositoryBuildStatus status); |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.Build; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class BuildCommand : IConsoleCommand, ITransientDependency |
|||
{ |
|||
public IDotNetProjectDependencyFiller DotNetProjectDependencyFiller { get; set; } |
|||
|
|||
public IChangedProjectFinder ChangedProjectFinder { get; set; } |
|||
|
|||
public IDotNetProjectBuilder DotNetProjectBuilder { get; set; } |
|||
|
|||
public IRepositoryBuildStatusStore RepositoryBuildStatusStore { get; set; } |
|||
|
|||
public IDotNetProjectBuildConfigReader DotNetProjectBuildConfigReader { get; set; } |
|||
|
|||
public IBuildStatusGenerator BuildStatusGenerator { get; set; } |
|||
|
|||
public IBuildProjectListSorter BuildProjectListSorter { get; set; } |
|||
|
|||
public Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var sw = new Stopwatch(); |
|||
sw.Start(); |
|||
|
|||
var workingDirectory = commandLineArgs.Options.GetOrNull( |
|||
Options.WorkingDirectory.Short, |
|||
Options.WorkingDirectory.Long |
|||
); |
|||
|
|||
var dotnetBuildArguments = commandLineArgs.Options.GetOrNull( |
|||
Options.DotnetBuildArguments.Short, |
|||
Options.DotnetBuildArguments.Long |
|||
); |
|||
|
|||
var buildName = commandLineArgs.Options.GetOrNull( |
|||
Options.BuildName.Short, |
|||
Options.BuildName.Long |
|||
); |
|||
|
|||
var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) || |
|||
commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long); |
|||
|
|||
var buildConfig = DotNetProjectBuildConfigReader.Read(workingDirectory ?? Directory.GetCurrentDirectory()); |
|||
buildConfig.BuildName = buildName; |
|||
buildConfig.ForceBuild = forceBuild; |
|||
|
|||
Console.WriteLine("Finding changed projects..."); |
|||
|
|||
var changedProjectFiles = ChangedProjectFinder.Find(buildConfig); |
|||
|
|||
var buildSucceededProjects = DotNetProjectBuilder.Build( |
|||
changedProjectFiles, |
|||
dotnetBuildArguments ?? "" |
|||
); |
|||
|
|||
var buildStatus = BuildStatusGenerator.Generate( |
|||
buildConfig, |
|||
changedProjectFiles, |
|||
buildSucceededProjects |
|||
); |
|||
|
|||
RepositoryBuildStatusStore.Set(buildName, buildConfig.GitRepository, buildStatus); |
|||
|
|||
sw.Stop(); |
|||
Console.WriteLine("Build operation is completed in " + sw.ElapsedMilliseconds + " (ms)"); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public string GetUsageInfo() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Usage:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine(" abp build [options]"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Options:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("-wd|--working-directory <directory-path> (default: empty)"); |
|||
sb.AppendLine("-m |--max-parallel-builds <parallel-build-count> (default: 1)"); |
|||
sb.AppendLine("-a |--dotnet-build-arguments <arguments> (default: empty)"); |
|||
sb.AppendLine("-n |--build-name <name> (default: empty)"); |
|||
sb.AppendLine("-f | --force (default: false)"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
public string GetShortDescription() |
|||
{ |
|||
return "Builds a dotnet repository and dependent repositories or a solution."; |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class WorkingDirectory |
|||
{ |
|||
public const string Short = "wd"; |
|||
public const string Long = "working-directory"; |
|||
} |
|||
|
|||
public static class DotnetBuildArguments |
|||
{ |
|||
public const string Short = "a"; |
|||
public const string Long = "dotnet-build-arguments"; |
|||
} |
|||
|
|||
public static class BuildName |
|||
{ |
|||
public const string Short = "n"; |
|||
public const string Long = "build-name"; |
|||
} |
|||
|
|||
public static class ForceBuild |
|||
{ |
|||
public const string Short = "f"; |
|||
public const string Long = "force"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
using System.Collections.Generic; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class BuildProjectListSorter_Tests : AbpCliTestBase |
|||
{ |
|||
private IBuildProjectListSorter _buildProjectListSorter; |
|||
|
|||
public BuildProjectListSorter_Tests() |
|||
{ |
|||
_buildProjectListSorter = GetRequiredService<IBuildProjectListSorter>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SortByDependencies_Test() |
|||
{ |
|||
// A -> B, C
|
|||
// B -> D
|
|||
// D -> F
|
|||
// F -> C
|
|||
// C -> G
|
|||
// Final build order must be: G,
|
|||
|
|||
var repositoryName = "volo"; |
|||
var source = new List<DotNetProjectInfo> |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "A", true) |
|||
{ |
|||
Dependencies = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "B", true), |
|||
new DotNetProjectInfo(repositoryName, "C", true) |
|||
} |
|||
}, |
|||
new DotNetProjectInfo(repositoryName, "B", true) |
|||
{ |
|||
Dependencies = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "D", true) |
|||
} |
|||
}, |
|||
new DotNetProjectInfo(repositoryName, "D", true) |
|||
{ |
|||
Dependencies = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "F", true) |
|||
} |
|||
}, |
|||
new DotNetProjectInfo(repositoryName, "F", true) |
|||
{ |
|||
Dependencies = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "C", true) |
|||
} |
|||
}, |
|||
new DotNetProjectInfo(repositoryName, "C", true) |
|||
{ |
|||
Dependencies = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo(repositoryName, "G", true) |
|||
} |
|||
}, |
|||
new DotNetProjectInfo(repositoryName, "G", true) |
|||
}; |
|||
|
|||
var sortedDependencies = |
|||
_buildProjectListSorter.SortByDependencies(source, new DotNetProjectInfoEqualityComparer()); |
|||
sortedDependencies.Count.ShouldBe(6); |
|||
sortedDependencies[0].CsProjPath.ShouldBe("G"); |
|||
sortedDependencies[1].CsProjPath.ShouldBe("C"); |
|||
sortedDependencies[2].CsProjPath.ShouldBe("F"); |
|||
sortedDependencies[3].CsProjPath.ShouldBe("D"); |
|||
sortedDependencies[4].CsProjPath.ShouldBe("B"); |
|||
sortedDependencies[5].CsProjPath.ShouldBe("A"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class BuildStatusGenerator_Tests : AbpCliTestBase |
|||
{ |
|||
private readonly IBuildStatusGenerator _buildStatusGenerator; |
|||
private IGitRepositoryHelper _gitRepositoryHelper; |
|||
|
|||
public BuildStatusGenerator_Tests() |
|||
{ |
|||
_buildStatusGenerator = GetRequiredService<IBuildStatusGenerator>(); |
|||
} |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
_gitRepositoryHelper = Substitute.For<IGitRepositoryHelper>(); |
|||
services.AddTransient(provider => _gitRepositoryHelper); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Set_Repository_CommitId_When_All_Projects_Built() |
|||
{ |
|||
var buildConfig = new DotNetProjectBuildConfig |
|||
{ |
|||
GitRepository = new GitRepository("volo", "dev", "") |
|||
{ |
|||
DependingRepositories = new List<GitRepository>() |
|||
{ |
|||
new GitRepository("abp", "dev", "") |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var changedProjects = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo("volo", "project1.csproj", true) |
|||
}; |
|||
|
|||
var builtProjects = new List<string>() |
|||
{ |
|||
"project1.csproj" |
|||
}; |
|||
|
|||
var lastCommitId = "1"; |
|||
_gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); |
|||
_gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("volo"); |
|||
|
|||
var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); |
|||
status.CommitId.ShouldBe(lastCommitId); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void Should_Set_Repository_CommitId_When_All_Projects_Built_For_Child_Repository() |
|||
{ |
|||
var buildConfig = new DotNetProjectBuildConfig |
|||
{ |
|||
GitRepository = new GitRepository("volo", "dev", "") |
|||
{ |
|||
DependingRepositories = new List<GitRepository>() |
|||
{ |
|||
new GitRepository("abp", "dev", "") |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var changedProjects = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo("abp", "project1.csproj", true) |
|||
}; |
|||
|
|||
var builtProjects = new List<string>() |
|||
{ |
|||
"project1.csproj" |
|||
}; |
|||
|
|||
var lastCommitId = "1"; |
|||
_gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); |
|||
_gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("abp"); |
|||
|
|||
var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); |
|||
status.CommitId.ShouldBe(lastCommitId); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Set_Repository_CommitId_When_Building_Single_Solution() |
|||
{ |
|||
var buildConfig = new DotNetProjectBuildConfig |
|||
{ |
|||
GitRepository = new GitRepository("volo", "dev", "") |
|||
{ |
|||
DependingRepositories = new List<GitRepository>() |
|||
{ |
|||
new GitRepository("abp", "dev", "") |
|||
} |
|||
}, |
|||
SlFilePath = "test.sln" |
|||
}; |
|||
|
|||
var changedProjects = new List<DotNetProjectInfo>() |
|||
{ |
|||
new DotNetProjectInfo("volo", "project1.csproj", true) |
|||
}; |
|||
|
|||
var builtProjects = new List<string>() |
|||
{ |
|||
"project1.csproj" |
|||
}; |
|||
|
|||
var lastCommitId = "1"; |
|||
_gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); |
|||
_gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("volo"); |
|||
|
|||
var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); |
|||
status.CommitId.ShouldBeNull(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,318 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using NSubstitute; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class GitRepositoryBuildStatus_Tests : AbpCliTestBase |
|||
{ |
|||
private IGitRepositoryHelper _gitRepositoryHelper; |
|||
|
|||
protected override void AfterAddApplication(IServiceCollection services) |
|||
{ |
|||
_gitRepositoryHelper = Substitute.For<IGitRepositoryHelper>(); |
|||
services.AddTransient(provider => _gitRepositoryHelper); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Add_New_Build_Status_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project1.csproj", |
|||
CommitId = "1" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var newBuildStatus = new GitRepositoryBuildStatus( |
|||
existingBuildStatus.RepositoryName, |
|||
existingBuildStatus.BranchName |
|||
) |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project2.csproj", |
|||
CommitId = "2" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.MergeWith(newBuildStatus); |
|||
|
|||
existingBuildStatus.SucceedProjects.Count.ShouldBe(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Update_Existing_Build_Status_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project1.csproj", |
|||
CommitId = "1" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var newBuildStatus = new GitRepositoryBuildStatus( |
|||
existingBuildStatus.RepositoryName, |
|||
existingBuildStatus.BranchName |
|||
) |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project1.csproj", |
|||
CommitId = "2" |
|||
}, |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project2.csproj", |
|||
CommitId = "2" |
|||
} |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.MergeWith(newBuildStatus); |
|||
existingBuildStatus.SucceedProjects.Count.ShouldBe(2); |
|||
existingBuildStatus.GetSelfOrChild("volo").SucceedProjects.First(p => p.CsProjPath == "project1.csproj") |
|||
.CommitId.ShouldBe("2"); |
|||
existingBuildStatus.GetSelfOrChild("volo").SucceedProjects.First(p => p.CsProjPath == "project2.csproj") |
|||
.CommitId.ShouldBe("2"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Add_New_Build_Status_For_Child_Repository_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("abp", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project1.csproj", |
|||
CommitId = "1" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var newBuildStatus = new GitRepositoryBuildStatus( |
|||
existingBuildStatus.RepositoryName, |
|||
existingBuildStatus.BranchName |
|||
) |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("abp", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "project2.csproj", |
|||
CommitId = "2" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.MergeWith(newBuildStatus); |
|||
existingBuildStatus.GetChild("abp").SucceedProjects.Count.ShouldBe(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Update_Repository_CommitId_When_New_CommitId_Is_Not_Empty() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev"); |
|||
|
|||
var newBuildStatus = new GitRepositoryBuildStatus( |
|||
existingBuildStatus.RepositoryName, |
|||
existingBuildStatus.BranchName |
|||
) |
|||
{ |
|||
CommitId = "42" |
|||
}; |
|||
|
|||
existingBuildStatus.MergeWith(newBuildStatus); |
|||
|
|||
existingBuildStatus.CommitId.ShouldBe("42"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Update_Repository_CommitId_When_New_CommitId_Is_Empty() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") |
|||
{ |
|||
CommitId = "21" |
|||
}; |
|||
|
|||
var newBuildStatus = new GitRepositoryBuildStatus( |
|||
existingBuildStatus.RepositoryName, |
|||
existingBuildStatus.BranchName |
|||
) |
|||
{ |
|||
CommitId = "" |
|||
}; |
|||
|
|||
existingBuildStatus.MergeWith(newBuildStatus); |
|||
|
|||
existingBuildStatus.CommitId.ShouldBe("21"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetChild_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-2", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-3", "dev") |
|||
} |
|||
}, |
|||
new GitRepositoryBuildStatus("repo-4", "dev") |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.GetChild("repo-3").RepositoryName.ShouldBe("repo-3"); |
|||
existingBuildStatus.GetChild("repo-4").RepositoryName.ShouldBe("repo-4"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetUniqueName_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-2", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-3", "dev") |
|||
} |
|||
}, |
|||
new GitRepositoryBuildStatus("repo-4", "dev") |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.GetUniqueName("").ShouldBe("B25C935F97D7B3375530A96B392B7644"); |
|||
existingBuildStatus.GetUniqueName("production").ShouldBe("production_B25C935F97D7B3375530A96B392B7644"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void GetSelfOrChild_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-2", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-3", "dev") |
|||
} |
|||
}, |
|||
new GitRepositoryBuildStatus("repo-4", "dev") |
|||
} |
|||
}; |
|||
|
|||
existingBuildStatus.GetSelfOrChild("repo-1").RepositoryName.ShouldBe("repo-1"); |
|||
existingBuildStatus.GetSelfOrChild("repo-2").RepositoryName.ShouldBe("repo-2"); |
|||
existingBuildStatus.GetSelfOrChild("repo-3").RepositoryName.ShouldBe("repo-3"); |
|||
existingBuildStatus.GetSelfOrChild("repo-4").RepositoryName.ShouldBe("repo-4"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddOrUpdateProjectStatus_Test() |
|||
{ |
|||
var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") |
|||
{ |
|||
DependingRepositories = new List<GitRepositoryBuildStatus>() |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-2", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "A.csproj", |
|||
CommitId = "42" |
|||
} |
|||
}, |
|||
DependingRepositories = new List<GitRepositoryBuildStatus> |
|||
{ |
|||
new GitRepositoryBuildStatus("repo-3", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus> |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "B.csproj", |
|||
CommitId = "42" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
new GitRepositoryBuildStatus("repo-4", "dev") |
|||
{ |
|||
SucceedProjects = new List<DotNetProjectBuildStatus>() |
|||
{ |
|||
new DotNetProjectBuildStatus |
|||
{ |
|||
CsProjPath = "C.csproj", |
|||
CommitId = "42" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var repo2 = existingBuildStatus.GetChild("repo-2"); |
|||
repo2.AddOrUpdateProjectStatus(new DotNetProjectBuildStatus |
|||
{ |
|||
CommitId = "21", |
|||
CsProjPath = "A.csproj" |
|||
}); |
|||
|
|||
var repo3 = existingBuildStatus.GetChild("repo-3"); |
|||
repo3.AddOrUpdateProjectStatus(new DotNetProjectBuildStatus |
|||
{ |
|||
CommitId = "21", |
|||
CsProjPath = "X.csproj" |
|||
}); |
|||
|
|||
repo2.SucceedProjects.Count.ShouldBe(1); |
|||
repo2.SucceedProjects.ShouldContain(e=> e.CsProjPath == "A.csproj" && e.CommitId == "21"); |
|||
|
|||
repo3.SucceedProjects.Count.ShouldBe(2); |
|||
repo3.SucceedProjects.ShouldContain(e=> e.CsProjPath == "X.csproj" && e.CommitId == "21"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System.Collections.Generic; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Cli.Build |
|||
{ |
|||
public class GitRepository_Tests : AbpCliTestBase |
|||
{ |
|||
[Fact] |
|||
public void GetUniqueName_Test() |
|||
{ |
|||
var gitRepository = new GitRepository("repo-1", "dev", "") |
|||
{ |
|||
DependingRepositories = new List<GitRepository> |
|||
{ |
|||
new GitRepository("repo-2", "dev", ""), |
|||
new GitRepository("repo-3", "dev", "") |
|||
{ |
|||
DependingRepositories = new List<GitRepository>() |
|||
{ |
|||
new GitRepository("repo-4", "dev", "") |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
gitRepository.GetUniqueName("").ShouldBe("B25C935F97D7B3375530A96B392B7644"); |
|||
gitRepository.GetUniqueName("production").ShouldBe("production_B25C935F97D7B3375530A96B392B7644"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindRepositoryOf_Test() |
|||
{ |
|||
var gitRepository = new GitRepository("repo-1", "dev", "/repo-1/dev/") |
|||
{ |
|||
DependingRepositories = new List<GitRepository> |
|||
{ |
|||
new GitRepository("repo-2", "dev", "/repo-2/dev/"), |
|||
new GitRepository("repo-3", "dev", "/repo-3/dev/") |
|||
{ |
|||
DependingRepositories = new List<GitRepository>() |
|||
{ |
|||
new GitRepository("repo-4", "dev", "/repo-4/dev/") |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
gitRepository.FindRepositoryOf("/repo-4/dev/A.csproj").ShouldBe("repo-4"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue