mirror of https://github.com/abpframework/abp.git
16 changed files with 908 additions and 0 deletions
@ -0,0 +1,188 @@ |
|||||
|
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; |
||||
|
|
||||
|
public DefaultChangedProjectFinder(IRepositoryBuildStatusStore repositoryBuildStatusStore) |
||||
|
{ |
||||
|
_repositoryBuildStatusStore = repositoryBuildStatusStore; |
||||
|
} |
||||
|
|
||||
|
public List<DotNetProjectInfo> Find(DotNetProjectBuildConfig buildConfig) |
||||
|
{ |
||||
|
if (buildConfig.SlFilePath.IsNullOrEmpty()) |
||||
|
{ |
||||
|
return FindByRepository(buildConfig.GitRepository); |
||||
|
} |
||||
|
|
||||
|
return FindBySlnFile(buildConfig.GitRepository, buildConfig.SlFilePath); |
||||
|
} |
||||
|
|
||||
|
private List<DotNetProjectInfo> FindByRepository(GitRepository gitRepository) |
||||
|
{ |
||||
|
var changedProjectList = new List<DotNetProjectInfo>(); |
||||
|
var gitRepositoryBuildStatus = _repositoryBuildStatusStore.Get(gitRepository); |
||||
|
|
||||
|
FindChangedFiles(gitRepository, gitRepositoryBuildStatus, changedProjectList); |
||||
|
|
||||
|
return changedProjectList; |
||||
|
} |
||||
|
|
||||
|
private List<DotNetProjectInfo> FindBySlnFile(GitRepository gitRepository, string slnFilePath) |
||||
|
{ |
||||
|
var slFine = new FileInfo(slnFilePath); |
||||
|
var changedProjectList = new List<DotNetProjectInfo>(); |
||||
|
var csProjFiles = slFine.Directory.GetFiles( |
||||
|
"*.csproj", |
||||
|
SearchOption.AllDirectories |
||||
|
).Select(e => e.FullName) |
||||
|
.ToList(); |
||||
|
|
||||
|
foreach (var csProjFile in csProjFiles) |
||||
|
{ |
||||
|
AddDependingProjectsToList(gitRepository, csProjFile, changedProjectList); |
||||
|
} |
||||
|
|
||||
|
return changedProjectList; |
||||
|
} |
||||
|
|
||||
|
private void AddDependingProjectsToList( |
||||
|
GitRepository gitRepository, |
||||
|
string csProjFilePath, |
||||
|
List<DotNetProjectInfo> changedProjectList) |
||||
|
{ |
||||
|
var repositoryName = gitRepository.FindRepositoryOf(csProjFilePath); |
||||
|
var project = new DotNetProjectInfo(repositoryName, csProjFilePath); |
||||
|
if (changedProjectList.Any(e => e.CsProjPath == csProjFilePath)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
changedProjectList.Add(project); |
||||
|
|
||||
|
AddProjectDependencies(gitRepository, project, changedProjectList); |
||||
|
} |
||||
|
|
||||
|
private void AddProjectDependencies(GitRepository gitRepository, DotNetProjectInfo project, List<DotNetProjectInfo> changedProjectList) |
||||
|
{ |
||||
|
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)); |
||||
|
|
||||
|
if (changedProjectList.Any(e => e.CsProjPath == referenceProjectInfo.FullName)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var repositoryName = gitRepository.FindRepositoryOf(referenceProjectInfo.FullName); |
||||
|
var referenceProject = new DotNetProjectInfo(repositoryName, referenceProjectInfo.FullName); |
||||
|
changedProjectList.Add(referenceProject); |
||||
|
|
||||
|
AddProjectDependencies(gitRepository, referenceProject, changedProjectList); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void FindChangedFiles( |
||||
|
GitRepository repository, |
||||
|
GitRepositoryBuildStatus repositoryBuildStatus, |
||||
|
List<DotNetProjectInfo> changedProjectList) |
||||
|
{ |
||||
|
if (repositoryBuildStatus.CommitId.IsNullOrEmpty()) |
||||
|
{ |
||||
|
AddAllCsProjFiles(repository, changedProjectList); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
AddChangedCsProjFiles(repository, changedProjectList, repositoryBuildStatus); |
||||
|
} |
||||
|
|
||||
|
if (repository.DependingRepositories.Any()) |
||||
|
{ |
||||
|
foreach (var dependingRepository in repository.DependingRepositories) |
||||
|
{ |
||||
|
var dependingRepositoryBuildStatus = repositoryBuildStatus.GetChild(dependingRepository.Name); |
||||
|
FindChangedFiles(dependingRepository, dependingRepositoryBuildStatus, changedProjectList); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void AddAllCsProjFiles(GitRepository repository, List<DotNetProjectInfo> changedFiles) |
||||
|
{ |
||||
|
var allCsProjFiles = Directory.GetFiles( |
||||
|
repository.RootPath, |
||||
|
"*.csproj", |
||||
|
SearchOption.AllDirectories |
||||
|
); |
||||
|
|
||||
|
foreach (var file in allCsProjFiles) |
||||
|
{ |
||||
|
changedFiles.Add( |
||||
|
new DotNetProjectInfo(repository.Name, Path.Combine(repository.RootPath, file)) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void AddChangedCsProjFiles( |
||||
|
GitRepository repository, |
||||
|
List<DotNetProjectInfo> changedFiles, |
||||
|
GitRepositoryBuildStatus status) |
||||
|
{ |
||||
|
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 files = repoDifferences |
||||
|
.Where(e => e.Path.EndsWith(".cs") || e.Path.EndsWith(".csproj")) |
||||
|
.Where(e => e.Status != ChangeKind.Deleted) |
||||
|
.Select(e => e) |
||||
|
.ToList(); |
||||
|
|
||||
|
foreach (var file in files) |
||||
|
{ |
||||
|
var csProjPath = Path.Combine(repository.RootPath, file.Path); |
||||
|
if (status.SucceedProjects.Any(p => p.CsProjPath == csProjPath && p.CommitId == "1")) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
changedFiles.Add( |
||||
|
new DotNetProjectInfo(repository.Name, csProjPath) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (!repository.DependingRepositories.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var subRepository in repository.DependingRepositories) |
||||
|
{ |
||||
|
var subRepositoryBuildStatus = status.GetChild(subRepository.Name); |
||||
|
FindChangedFiles(subRepository, subRepositoryBuildStatus, changedFiles); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
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, int maxParallelBuildCount, string arguments) |
||||
|
{ |
||||
|
var builtProjects = new ConcurrentBag<string>(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
foreach (var project in projects) |
||||
|
{ |
||||
|
if (builtProjects.Contains(project.CsProjPath)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
BuildInternal(project, arguments, builtProjects); |
||||
|
|
||||
|
if (!project.Dependencies.Any()) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// TODO: use parallel foreach here.
|
||||
|
foreach (var projectDependency in project.Dependencies) |
||||
|
{ |
||||
|
if (builtProjects.Contains(project.CsProjPath)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
BuildInternal(projectDependency, arguments, builtProjects); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
Console.WriteLine(e); |
||||
|
} |
||||
|
|
||||
|
return builtProjects.ToList(); |
||||
|
} |
||||
|
|
||||
|
private void BuildInternal(DotNetProjectInfo project, string arguments, ConcurrentBag<string> builtProjects) |
||||
|
{ |
||||
|
Console.WriteLine("Building...: dotnet build " + project.CsProjPath + " " + arguments); |
||||
|
|
||||
|
CmdHelper.RunCmdAndGetOutput( |
||||
|
"dotnet build " + project.CsProjPath + " " + arguments, |
||||
|
out int buildStatus |
||||
|
); |
||||
|
|
||||
|
if (buildStatus == 0) |
||||
|
{ |
||||
|
builtProjects.Add(project.CsProjPath); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// TODO: throw exception and stop build process.
|
||||
|
Console.WriteLine("Build failed for :" + project.CsProjPath); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public class DotNetProjectBuildConfig |
||||
|
{ |
||||
|
public string SlFilePath { get; set; } |
||||
|
|
||||
|
public GitRepository GitRepository { 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,93 @@ |
|||||
|
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 ILogger<DotNetProjectDependencyFiller> Logger { get; set; } |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
project.Dependencies.Add(referenceProject); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void GetProjectsOfRepository(GitRepository repository, List<string> projectPaths) |
||||
|
{ |
||||
|
Logger.LogInformation("repo name:" + repository.Name); |
||||
|
Logger.LogInformation("directoryPath:" + repository.RootPath); |
||||
|
|
||||
|
GetProjectsOfADirectory(repository.RootPath, projectPaths, new List<string>()); |
||||
|
|
||||
|
if (!repository.DependingRepositories.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var dependantRepository in repository.DependingRepositories) |
||||
|
{ |
||||
|
GetProjectsOfRepository(dependantRepository, projectPaths); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void GetProjectsOfADirectory( |
||||
|
string directoryPath, |
||||
|
List<string> projectPaths, |
||||
|
List<string> ignoredDirectories) |
||||
|
{ |
||||
|
Logger.LogInformation("directoryPath:" + directoryPath); |
||||
|
|
||||
|
var files = Directory.GetFiles(directoryPath, "*.csproj", SearchOption.AllDirectories) |
||||
|
.Select(f => f) |
||||
|
.ToList(); |
||||
|
|
||||
|
// foreach (var ignoredDirectory in ignoredDirectories)
|
||||
|
// {
|
||||
|
// files = files.Where(e => !e.StartsWith(Path.Combine(directoryPath, ignoredDirectory)))
|
||||
|
// .ToList();
|
||||
|
// }
|
||||
|
|
||||
|
projectPaths.AddRange(files); |
||||
|
|
||||
|
// foreach (var projectFile in files)
|
||||
|
// {
|
||||
|
// var projectNode = XElement.Load(projectFile);
|
||||
|
// if (projectNode.Attribute("Sdk") == null)
|
||||
|
// {
|
||||
|
// continue;
|
||||
|
// }
|
||||
|
//
|
||||
|
// projectPaths.Add(projectFile);
|
||||
|
// }
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
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 List<DotNetProjectInfo> Dependencies { get; set; } |
||||
|
|
||||
|
public DotNetProjectInfo(string repositoryName, string csProjPath) |
||||
|
{ |
||||
|
RepositoryName = repositoryName; |
||||
|
CsProjPath = csProjPath; |
||||
|
Dependencies = new List<DotNetProjectInfo>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
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 GitRepository(string name, string branchName, string rootPath) |
||||
|
{ |
||||
|
Name = name; |
||||
|
BranchName = branchName; |
||||
|
RootPath = rootPath; |
||||
|
DependingRepositories = new List<GitRepository>(); |
||||
|
} |
||||
|
|
||||
|
public string GetUniqueName() |
||||
|
{ |
||||
|
var name = Name + "_" + BranchName; |
||||
|
foreach (var dependingRepository in DependingRepositories) |
||||
|
{ |
||||
|
AddToUniqueName(dependingRepository, name); |
||||
|
} |
||||
|
|
||||
|
return 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,88 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public class GitRepositoryBuildStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Name of the repository
|
||||
|
/// </summary>
|
||||
|
public string Name { 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 name, string branchName) |
||||
|
{ |
||||
|
Name = name; |
||||
|
BranchName = branchName; |
||||
|
SucceedProjects = new List<DotNetProjectBuildStatus>(); |
||||
|
DependingRepositories = new List<GitRepositoryBuildStatus>(); |
||||
|
} |
||||
|
|
||||
|
public GitRepositoryBuildStatus GetChild(string repositoryName) |
||||
|
{ |
||||
|
foreach (var dependingRepository in DependingRepositories) |
||||
|
{ |
||||
|
return GetChildInternal(dependingRepository, repositoryName); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private GitRepositoryBuildStatus GetChildInternal(GitRepositoryBuildStatus repositoryBuildStatus, |
||||
|
string repositoryName) |
||||
|
{ |
||||
|
if (repositoryBuildStatus.Name == repositoryName) |
||||
|
{ |
||||
|
return repositoryBuildStatus; |
||||
|
} |
||||
|
|
||||
|
foreach (var dependingRepository in repositoryBuildStatus.DependingRepositories) |
||||
|
{ |
||||
|
return GetChildInternal(dependingRepository, repositoryName); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public string GetUniqueName() |
||||
|
{ |
||||
|
var name = Name + "_" + BranchName; |
||||
|
foreach (var dependingRepository in DependingRepositories) |
||||
|
{ |
||||
|
AddToUniqueName(dependingRepository, name); |
||||
|
} |
||||
|
|
||||
|
return name.ToMd5(); |
||||
|
} |
||||
|
|
||||
|
private void AddToUniqueName(GitRepositoryBuildStatus gitRepository, string name) |
||||
|
{ |
||||
|
name += "_" + gitRepository.Name + "_" + gitRepository.BranchName; |
||||
|
|
||||
|
foreach (var dependingRepository in gitRepository.DependingRepositories) |
||||
|
{ |
||||
|
AddToUniqueName(dependingRepository, name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using LibGit2Sharp; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public interface IDotNetProjectBuildConfigReader |
||||
|
{ |
||||
|
DotNetProjectBuildConfig Read(string directoryPath); |
||||
|
} |
||||
|
|
||||
|
public class FileSystemDotNetProjectBuildConfigReader : IDotNetProjectBuildConfigReader |
||||
|
{ |
||||
|
private IJsonSerializer _jsonSerializer; |
||||
|
|
||||
|
public FileSystemDotNetProjectBuildConfigReader(IJsonSerializer jsonSerializer) |
||||
|
{ |
||||
|
_jsonSerializer = jsonSerializer; |
||||
|
} |
||||
|
|
||||
|
public DotNetProjectBuildConfig Read(string directoryPath) |
||||
|
{ |
||||
|
var buildArgs = new DotNetProjectBuildConfig(); |
||||
|
var solutionFiles = Directory.GetFiles(directoryPath, "*.sln", SearchOption.TopDirectoryOnly); |
||||
|
if (solutionFiles.Length == 1) |
||||
|
{ |
||||
|
buildArgs.SlFilePath = solutionFiles.First(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Console.WriteLine( |
||||
|
"There are more than 1 solution (*.sln) file in the directory. Searching for build-config.json file." |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
var configFiles = Directory.GetFiles(directoryPath, "abp-build-config.json", SearchOption.TopDirectoryOnly); |
||||
|
if (configFiles.Length == 1) |
||||
|
{ |
||||
|
var configFile = configFiles.First(); |
||||
|
buildArgs.GitRepository = _jsonSerializer.Deserialize<GitRepository>(configFile); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Console.WriteLine( |
||||
|
"There are more than 1 config (abp-build-config.json) file in the directory!" |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
SetBranchNames(buildArgs.GitRepository); |
||||
|
|
||||
|
return buildArgs; |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public interface IChangedProjectFinder |
||||
|
{ |
||||
|
List<DotNetProjectInfo> Find(DotNetProjectBuildConfig buildConfig); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public interface IDotNetProjectBuilder |
||||
|
{ |
||||
|
List<string> Build(List<DotNetProjectInfo> projects, int maxParallelBuildCount, 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,58 @@ |
|||||
|
using System.IO; |
||||
|
using Newtonsoft.Json; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.IO; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Build |
||||
|
{ |
||||
|
public interface IRepositoryBuildStatusStore |
||||
|
{ |
||||
|
GitRepositoryBuildStatus Get(GitRepository repository); |
||||
|
|
||||
|
void Set(GitRepositoryBuildStatus status); |
||||
|
} |
||||
|
|
||||
|
public class FileSystemRepositoryBuildStatusStore : IRepositoryBuildStatusStore, ITransientDependency |
||||
|
{ |
||||
|
// TODO: change this ?
|
||||
|
private string BaseBuildStatusStorePath = @"C:\Users\ismai\.abp\build"; |
||||
|
|
||||
|
public GitRepositoryBuildStatus Get(GitRepository repository) |
||||
|
{ |
||||
|
if (!Directory.Exists(BaseBuildStatusStorePath)) |
||||
|
{ |
||||
|
Directory.CreateDirectory(BaseBuildStatusStorePath); |
||||
|
} |
||||
|
|
||||
|
var buildStatusFile = Path.Combine(BaseBuildStatusStorePath, repository.GetUniqueName()) + ".json"; |
||||
|
|
||||
|
if (!File.Exists(buildStatusFile)) |
||||
|
{ |
||||
|
// TODO: this is wrong
|
||||
|
return new GitRepositoryBuildStatus(repository.Name, repository.BranchName); |
||||
|
} |
||||
|
|
||||
|
var buildStatusText = File.ReadAllText(buildStatusFile); |
||||
|
return JsonConvert.DeserializeObject<GitRepositoryBuildStatus>(buildStatusText); |
||||
|
} |
||||
|
|
||||
|
public void Set(GitRepositoryBuildStatus status) |
||||
|
{ |
||||
|
if (!Directory.Exists(BaseBuildStatusStorePath)) |
||||
|
{ |
||||
|
Directory.CreateDirectory(BaseBuildStatusStorePath); |
||||
|
} |
||||
|
|
||||
|
var buildStatusFile = Path.Combine(BaseBuildStatusStorePath, status.GetUniqueName()) + ".json"; |
||||
|
if (File.Exists(buildStatusFile)) |
||||
|
{ |
||||
|
FileHelper.DeleteIfExists(buildStatusFile); |
||||
|
} |
||||
|
|
||||
|
using (var file = File.CreateText(buildStatusFile)) |
||||
|
{ |
||||
|
new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, status); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using LibGit2Sharp; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
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 ILogger<BuildCommand> Logger { get; set; } |
||||
|
|
||||
|
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 Task ExecuteAsync(CommandLineArgs commandLineArgs) |
||||
|
{ |
||||
|
var sw = new Stopwatch(); |
||||
|
sw.Start(); |
||||
|
|
||||
|
var buildConfig = DotNetProjectBuildConfigReader.Read(Directory.GetCurrentDirectory()); |
||||
|
|
||||
|
var maxParallelBuild = commandLineArgs.Options.GetOrNull( |
||||
|
Options.MaxParallelBuild.Short, |
||||
|
Options.MaxParallelBuild.Long |
||||
|
); |
||||
|
|
||||
|
var dotnetBuildArguments = commandLineArgs.Options.GetOrNull(Options.DotnetBuildArguments.Short, |
||||
|
Options.DotnetBuildArguments.Long); |
||||
|
|
||||
|
var changedProjectFiles = ChangedProjectFinder.Find(buildConfig); |
||||
|
|
||||
|
DotNetProjectDependencyFiller.Fill(changedProjectFiles); |
||||
|
|
||||
|
var sortedProjects = changedProjectFiles.SortByDependencies(p => p.Dependencies); |
||||
|
|
||||
|
var buildSucceededProjects = DotNetProjectBuilder.Build( |
||||
|
sortedProjects, |
||||
|
string.IsNullOrEmpty(maxParallelBuild) ? 1 : Convert.ToInt32(maxParallelBuild), |
||||
|
dotnetBuildArguments ?? "" |
||||
|
); |
||||
|
|
||||
|
var buildStatus = GenerateBuildStatus(buildConfig.GitRepository, changedProjectFiles, buildSucceededProjects); |
||||
|
RepositoryBuildStatusStore.Set(buildStatus); |
||||
|
|
||||
|
sw.Stop(); |
||||
|
Console.WriteLine("Build operation is completed in " + sw.ElapsedMilliseconds + " (ms)"); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
private GitRepositoryBuildStatus GenerateBuildStatus( |
||||
|
GitRepository gitRepository, |
||||
|
List<DotNetProjectInfo> changedProjects, |
||||
|
List<string> buildSucceededProjects) |
||||
|
{ |
||||
|
using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) |
||||
|
{ |
||||
|
var lastCommitId = repo.Head.Tip.Id.ToString(); |
||||
|
var status = new GitRepositoryBuildStatus( |
||||
|
gitRepository.Name, |
||||
|
repo.Head.FriendlyName |
||||
|
) |
||||
|
{ |
||||
|
CommitId = lastCommitId |
||||
|
}; |
||||
|
|
||||
|
status.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(dependingRepository, changedProjects, buildSucceededProjects, status); |
||||
|
} |
||||
|
|
||||
|
return status; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void GenerateBuildStatusInternal( |
||||
|
GitRepository gitRepository, |
||||
|
List<DotNetProjectInfo> changedProjects, |
||||
|
List<string> buildSucceededProjects, |
||||
|
GitRepositoryBuildStatus status) |
||||
|
{ |
||||
|
using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) |
||||
|
{ |
||||
|
var lastCommitId = repo.Head.Tip.Id.ToString(); |
||||
|
var dependingRepositoryStatus = new GitRepositoryBuildStatus( |
||||
|
gitRepository.Name, |
||||
|
repo.Head.FriendlyName |
||||
|
) |
||||
|
{ |
||||
|
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( |
||||
|
dependingRepository, |
||||
|
changedProjects, |
||||
|
buildSucceededProjects, |
||||
|
dependingRepositoryStatus |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
status.DependingRepositories.Add(dependingRepositoryStatus); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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(""); |
||||
|
// TODO: Explain extra parameters
|
||||
|
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 MaxParallelBuild |
||||
|
{ |
||||
|
public const string Short = "m"; |
||||
|
public const string Long = "max-paralel-builds"; |
||||
|
} |
||||
|
|
||||
|
public static class DotnetBuildArguments |
||||
|
{ |
||||
|
public const string Short = "d"; |
||||
|
public const string Long = "dotnet-build-arguments"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue