Skip to content

Commit be0eb9a

Browse files
committed
Fix #820: Refactor C# parser to extract target DLLs from .csproj and populate build graph
1 parent ea6ddfb commit be0eb9a

3 files changed

Lines changed: 112 additions & 90 deletions

File tree

plugins/csharp/parser/include/csharpparser/csharpparser.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ class CsharpParser : public AbstractParser
3232

3333
bool acceptProjectBuildPath(const std::string& buildDir_);
3434
bool parseProjectBuildPath(
35-
const std::vector<std::string>& path_,
36-
const std::string& buildPath_);
37-
void addSource(const std::string& filepath_, bool error_);
35+
const std::vector<std::string>& path_ //,
36+
); //const std::string& buildPath_
37+
//void addSource(const std::string& filepath_, bool error_);
38+
void addSource(const std::string& filepath_, const std::string& targetDll_, bool error_);
3839
};
3940

4041
} // parser

plugins/csharp/parser/src/csharpparser.cpp

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ CsharpParser::CsharpParser(ParserContext& ctx_): AbstractParser(ctx_)
2727
{
2828
_threadNum = _ctx.options["jobs"].as<int>();
2929
}
30-
/*
31-
bool CsharpParser::acceptProjectBuildPath(const std::vector<std::string>& path_)
32-
{
33-
return path_.size() >= 2 && fs::is_directory(path_[0]) && fs::is_directory(path_[1]);
34-
}*/
30+
3531
bool CsharpParser::acceptProjectBuildPath(const std::string& buildPath_)
3632
{
3733
return fs::is_directory(buildPath_);
@@ -42,26 +38,24 @@ bool CsharpParser::parse()
4238
bool success = true;
4339

4440
std::vector<std::string> paths = _ctx.options["input"].as<std::vector<std::string>>();
45-
std::string buildPath = _ctx.options["build-dir"].as<std::string>();
4641

47-
if (acceptProjectBuildPath(buildPath))
42+
if (!paths.empty())
4843
{
49-
LOG(debug) << "C# parser parse path: " << paths[0];
50-
LOG(debug) << "Parsed csharp project build path: " << buildPath;
51-
success = success && parseProjectBuildPath(paths, buildPath);
44+
LOG(debug) << "C# parser parse path: " << paths.size();
45+
success = success && parseProjectBuildPath(paths);
5246
}
5347
else
5448
{
55-
LOG(error) << "Build path must be a directory!";
49+
LOG(error) << "No input directories provided for C# parser!";
5650
success = false;
5751
}
5852

5953
return success;
6054
}
6155

6256
bool CsharpParser::parseProjectBuildPath(
63-
const std::vector<std::string>& paths_,
64-
const std::string& buildPath_)
57+
const std::vector<std::string>& paths_ //,
58+
)
6559
{
6660
namespace ch = std::chrono;
6761
std::future<std::string> log;
@@ -80,8 +74,6 @@ bool CsharpParser::parseProjectBuildPath(
8074
command.append("'");
8175
command.append(_ctx.options["database"].as<std::string>());
8276
command.append("' '");
83-
command.append(buildPath_);
84-
command.append("' '");
8577
command.append(csharp_path.string());
8678
command.append("' ");
8779
command.append(std::to_string(_ctx.options["jobs"].as<int>()));
@@ -108,22 +100,35 @@ bool CsharpParser::parseProjectBuildPath(
108100

109101
std::string line;
110102
std::stringstream log_str(log.get());
111-
//LOG(warning) << log_str.str();
112103
int countFull = 0, countPart = 0;
113-
104+
114105
while(std::getline(log_str, line, '\n'))
115106
{
116107
if (line[0] == '+' || line[0] == '-')
117108
{
118-
addSource(line.substr(1), line[0] == '-');
119-
if (line[0] == '+')
120-
{
121-
countFull++;
109+
std::string content = line.substr(1); // We cut off the +/- sign
110+
111+
// Find the line (|) that separates the file and the DLL
112+
size_t separatorPos = content.find('|');
113+
114+
if (separatorPos != std::string::npos) {
115+
// If it exists, we split the text along |
116+
std::string filepath = content.substr(0, separatorPos);
117+
std::string targetDll = content.substr(separatorPos + 1);
118+
119+
// We clean up the spaces from the beginning
120+
filepath.erase(0, filepath.find_first_not_of(" \t"));
121+
122+
addSource(filepath, targetDll, line[0] == '-');
122123
}
123-
else
124-
{
125-
countPart++;
124+
else {
125+
// Fallback if for some reason the DLL name was not sent by C#
126+
content.erase(0, content.find_first_not_of(" \t"));
127+
addSource(content, "Unknown.dll", line[0] == '-');
126128
}
129+
130+
if (line[0] == '+') { countFull++; }
131+
else { countPart++; }
127132
}
128133
}
129134

@@ -138,12 +143,12 @@ bool CsharpParser::parseProjectBuildPath(
138143
return result == 0;
139144
}
140145

141-
void CsharpParser::addSource(const std::string& filepath_, bool error_)
146+
void CsharpParser::addSource(const std::string& filepath_, const std::string& targetDll_, bool error_)
142147
{
143148
util::OdbTransaction transaction(_ctx.db);
144149

145150
model::BuildActionPtr buildAction(new model::BuildAction);
146-
buildAction->command = " ";
151+
buildAction->command = "dotnet build " + targetDll_; //buildAction->command = " ";
147152
buildAction->type = model::BuildAction::Compile;
148153

149154
model::BuildSource buildSource;
@@ -154,12 +159,21 @@ void CsharpParser::addSource(const std::string& filepath_, bool error_)
154159
buildSource.file->type = "CS";
155160
buildSource.action = buildAction;
156161

162+
163+
model::BuildTarget buildTarget;
164+
buildTarget.action = buildAction;
165+
166+
buildTarget.file = _ctx.srcMgr.getFile(targetDll_);
167+
buildTarget.file->type = "CS_DLL";
168+
157169
_ctx.srcMgr.updateFile(*buildSource.file);
170+
_ctx.srcMgr.updateFile(*buildTarget.file);
158171
_ctx.srcMgr.persistFiles();
159172

160173
transaction([&, this] {
161174
_ctx.db->persist(buildAction);
162175
_ctx.db->persist(buildSource);
176+
_ctx.db->persist(buildTarget); //new!!
163177
});
164178
}
165179

@@ -176,10 +190,6 @@ extern "C"
176190
{
177191
boost::program_options::options_description description("C# Plugin");
178192

179-
description.add_options()
180-
("build-dir,b", po::value<std::string>()->default_value("Build directory"),
181-
"The build directory of the parsed project.");
182-
183193
return description;
184194
}
185195

plugins/csharp/parser/src_csharp/Program.cs

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
using System.Collections.Generic;
1010
using System.Threading.Tasks;
1111
using CSharpParser.model;
12+
using System.Xml.Linq;
1213

1314
namespace CSharpParser
1415
{
1516
class Program
1617
{
17-
//private readonly CsharpDbContext _context;
1818
private static List<string> _rootDir;
19-
private static string _buildDir = "";
2019
private static string _buildDirBase = "";
2120
private static string _connectionString = "";
2221

@@ -28,11 +27,10 @@ static int Main(string[] args)
2827
try
2928
{
3029
_connectionString = args[0].Replace("'", "");
31-
_buildDir = args[1].Replace("'", "");
32-
_buildDirBase = args[2].Replace("'", "");
33-
threadNum = int.Parse(args[3]);
30+
_buildDirBase = args[1].Replace("'", ""); //indexes
31+
threadNum = int.Parse(args[2]);
3432

35-
for (int i = 4; i < args.Length; ++i)
33+
for (int i = 3; i < args.Length; ++i)
3634
{
3735
_rootDir.Add(args[i].Replace("'", ""));
3836
}
@@ -42,44 +40,6 @@ static int Main(string[] args)
4240
WriteLine("Error in parsing command!");
4341
return 1;
4442
}
45-
/*if (args.Length < 3)
46-
{
47-
WriteLine("Missing command-line arguments in CSharpParser!");
48-
return 1;
49-
}
50-
else if (args.Length == 3)
51-
{
52-
_connectionString = args[0].Replace("'", "");
53-
_rootDir = args[1].Replace("'", "");
54-
_buildDir = args[2].Replace("'", "");
55-
}
56-
else if (args.Length == 4)
57-
{
58-
_connectionString = args[0].Replace("'", "");
59-
_rootDir = args[1].Replace("'", "");
60-
_buildDir = args[2].Replace("'", "");
61-
bool success = int.TryParse(args[3], out threadNum);
62-
if (!success){
63-
WriteLine("Invalid threadnumber argument! Multithreaded parsing disabled!");
64-
}
65-
}
66-
else if (args.Length == 5)
67-
{
68-
_connectionString = args[0].Replace("'", "");
69-
_rootDir = args[1].Replace("'", "");
70-
_buildDir = args[2].Replace("'", "");
71-
_buildDirBase = args[3].Replace("'", "");
72-
bool success = int.TryParse(args[4], out threadNum);
73-
if (!success)
74-
{
75-
WriteLine("Invalid threadnumber argument! Multithreaded parsing disabled!");
76-
}
77-
}
78-
else if (args.Length > 5)
79-
{
80-
WriteLine("Too many command-line arguments in CSharpParser!");
81-
return 1;
82-
}*/
8343

8444
//Converting the connectionstring into entiy framwork style connectionstring
8545
string csharpConnectionString = transformConnectionString();
@@ -91,21 +51,67 @@ static int Main(string[] args)
9151
CsharpDbContext _context = new CsharpDbContext(options);
9252
_context.Database.Migrate();
9353

54+
9455
List<string> allFiles = new List<string>();
56+
// This dictionary will remember which file belongs to which DLL
57+
Dictionary<string, string> fileToTargetDll = new Dictionary<string, string>();
58+
9559
foreach (var p in _rootDir)
9660
{
97-
Console.WriteLine(p);
98-
allFiles.AddRange(GetSourceFilesFromDir(p, ".cs"));
61+
// We find all .csproj files
62+
var csprojFiles = Directory.GetFiles(p, "*.csproj", SearchOption.AllDirectories);
63+
foreach (var csproj in csprojFiles)
64+
{
65+
string projectDir = Path.GetDirectoryName(csproj);
66+
// Default DLL name based on project file name
67+
string targetDll = Path.GetFileNameWithoutExtension(csproj) + ".dll";
68+
69+
// we try to read the real AssemblyName from the XML
70+
try {
71+
XDocument doc = XDocument.Load(csproj);
72+
var assemblyNameNode = doc.Descendants("AssemblyName").FirstOrDefault();
73+
if (assemblyNameNode != null && !string.IsNullOrWhiteSpace(assemblyNameNode.Value))
74+
{
75+
targetDll = assemblyNameNode.Value + ".dll";
76+
}
77+
} catch { /* If we cannot read the XML, the default name will remain.*/ }
78+
79+
// search for C# files belonging to the project (filtering out the garbage)
80+
var csFiles = Directory.GetFiles(projectDir, "*.cs", SearchOption.AllDirectories)
81+
.Where(f => !f.Contains("/obj/") && !f.Contains("\\obj\\") &&
82+
!f.Contains("/bin/") && !f.Contains("\\bin\\"));
83+
84+
foreach (var cs in csFiles)
85+
{
86+
// if a file is not already in it (to avoid duplication)
87+
if (!fileToTargetDll.ContainsKey(cs))
88+
{
89+
fileToTargetDll[cs] = targetDll;
90+
allFiles.Add(cs);
91+
}
92+
}
93+
}
9994
}
95+
allFiles = allFiles.Distinct().ToList();
10096

10197
foreach (var f in allFiles)
10298
{
10399
WriteLine(f);
104100
}
105-
IEnumerable<string> assemblies = GetSourceFilesFromDir(_buildDir, ".dll");
106-
IEnumerable<string> assemblies_base = assemblies;
107-
if (args.Length == 5)
108-
assemblies_base = GetSourceFilesFromDir(_buildDirBase, ".dll");
101+
102+
103+
IEnumerable<string> assemblies_base = GetSourceFilesFromDir(_buildDirBase, ".dll"); //loading basic dlls
104+
105+
List<string> assemblies = new List<string>();
106+
foreach (var p in _rootDir)
107+
{
108+
// We search for all .dll files in all input directories
109+
assemblies.AddRange(GetSourceFilesFromDir(p, ".dll"));
110+
}
111+
// Let's keep only one of each DLL based on the file name!
112+
assemblies = assemblies.GroupBy(x => System.IO.Path.GetFileName(x))
113+
.Select(g => g.First())
114+
.ToList();
109115

110116
List<SyntaxTree> trees = new List<SyntaxTree>();
111117
foreach (string file in allFiles)
@@ -129,14 +135,14 @@ static int Main(string[] args)
129135
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(file));
130136
}
131137

132-
var runtask = ParalellRun(csharpConnectionString, threadNum, trees, compilation);
138+
var runtask = ParalellRun(csharpConnectionString, threadNum, trees, compilation, fileToTargetDll);
133139
int ret = runtask.Result;
134140

135141
return 0;
136142
}
137143

138144
private static async Task<int> ParalellRun(string csharpConnectionString, int threadNum,
139-
List<SyntaxTree> trees, CSharpCompilation compilation)
145+
List<SyntaxTree> trees, CSharpCompilation compilation, Dictionary<string, string> fileToTargetDll)
140146
{
141147
var options = new DbContextOptionsBuilder<CsharpDbContext>()
142148
.UseNpgsql(csharpConnectionString)
@@ -156,7 +162,7 @@ private static async Task<int> ParalellRun(string csharpConnectionString, int th
156162
WriteLine(threadNum);
157163
for (int i = 0; i < maxThread; i++)
158164
{
159-
ParsingTasks.Add(ParseTree(contextList[i],trees[i],compilation,i));
165+
ParsingTasks.Add(ParseTree(contextList[i],trees[i],compilation,i,fileToTargetDll));
160166
}
161167

162168
int nextTreeIndex = maxThread;
@@ -169,7 +175,7 @@ private static async Task<int> ParalellRun(string csharpConnectionString, int th
169175
if (nextTreeIndex < trees.Count)
170176
{
171177
ParsingTasks.Add(ParseTree(contextList[nextContextIndex],
172-
trees[nextTreeIndex],compilation,nextContextIndex));
178+
trees[nextTreeIndex],compilation,nextContextIndex, fileToTargetDll));
173179
++nextTreeIndex;
174180
}
175181
}
@@ -183,15 +189,20 @@ private static async Task<int> ParalellRun(string csharpConnectionString, int th
183189
}
184190

185191
private static async Task<int> ParseTree(CsharpDbContext context,
186-
SyntaxTree tree, CSharpCompilation compilation, int index)
192+
SyntaxTree tree, CSharpCompilation compilation, int index,
193+
Dictionary<string, string> fileToTargetDll)
187194
{
188195
var ParsingTask = Task.Run(() =>
189196
{
190197
WriteLine("ParallelRun " + tree.FilePath);
191198
SemanticModel model = compilation.GetSemanticModel(tree);
192199
var visitor = new AstVisitor(context, model, tree);
193-
visitor.Visit(tree.GetCompilationUnitRoot());
194-
WriteLine((visitor.FullyParsed ? "+" : "-") + tree.FilePath);
200+
visitor.Visit(tree.GetCompilationUnitRoot());
201+
202+
// Find the DLL name and append a | to the filename.
203+
string target = fileToTargetDll.ContainsKey(tree.FilePath) ? fileToTargetDll[tree.FilePath] : "Unknown.dll";
204+
WriteLine((visitor.FullyParsed ? "+" : "-") + tree.FilePath + "|" + target);
205+
195206
return index;
196207
});
197208
return await ParsingTask;

0 commit comments

Comments
 (0)