MonoDevelop 编译用户应用程序时调用的是哪个编译器
分析对象:本工作区自编译的 MonoDevelop 7.8.4(
monodevelop/main/build/bin)+ Mono 6.12 + MSBuild 16.10.1。目标:回答"在 IDE 里构建自己写的 C# 应用时,最终由哪个编译器、以什么路径、经什么链路被调用"。
一、结论速览
| 场景 | 实际使用的编译器 | 说明 |
|---|---|---|
经典 C# 项目(.csproj + Microsoft.CSharp.targets,含 Gtk# 项目) |
Roslyn C# 编译器 csc | 由 MSBuild 的 Csc 任务驱动,来自 $(RoslynTargetsPath) |
| .NET Core / .NET Standard SDK 风格项目 | Roslyn C# 编译器 csc | 仍是同一套 MSBuild + Roslyn,只是把 MSBuildSDKsPath 指向 .NET SDK 的 Sdks |
| VB.NET 项目 | Roslyn VB 编译器 vbc | 与 C# 完全对称,Vbc 任务 |
| 编辑器内的语法/语义分析、IntelliSense、错误波浪线 | 进程内 Roslyn 库 (Microsoft.CodeAnalysis.CSharp) |
不产出程序集,与构建编译器是两套使用方式 |
旧的 mcs.exe(Mono C# 编译器) |
默认不使用 | 仅当显式设置 CscToolExe=mcs.exe 才会被选中 |
一句话:MonoDevelop 本身不直接调用编译器;它启动 MSBuild 构建器,由 MSBuild 的 Csc/Vbc 任务调用 Roslyn 编译器(csc/vbc 或 Roslyn 编译服务器 VBCSCompiler)。
二、完整调用链
用户在 IDE 中点击"生成"
│
▼
MonoDevelop.Ide (Build 命令 / DotNetProject.DoBuild)
│ 选择项目对应的 TargetRuntime(决定用哪一个 Mono)
▼
MonoDevelop.Projects.MSBuild(MonoDevelop.Core)
│ RemoteBuildEngineManager.GetRemoteProjectBuilder(...)
│ 1) 把 <runtime>/lib/mono/msbuild/15.0/bin 整个目录复制到
│ ~/.cache/MonoDevelop/7.0/MSBuild/<pid>_<runtimeId>/
│ 2) 并把 MonoDevelop.MSBuildBuilder.exe 复制过去作为构建器
▼
MonoDevelop.MSBuildBuilder.exe (由目标运行时 <runtime>/bin/mono 启动)
│ 进程内 Microsoft.Build.Execution.BuildManager
│ 以全局属性初始化:
│ BuildingInsideVisualStudio = true
│ UseHostCompilerIfAvailable = false
│ RoslynTargetsPath = <binDir>/Roslyn
▼
加载项目 → 导入 $(MSBuildBinPath)/Microsoft.CSharp.targets
│ └─ Microsoft.CSharp.CurrentVersion.targets
│ └─ $(RoslynTargetsPath)/Microsoft.CSharp.Core.targets
▼
CoreCompile 目标中的 <Csc ... /> 任务
│ UsingTask 声明在 Microsoft.Common.tasks:
│ AssemblyFile="$(RoslynTargetsPath)\Microsoft.Build.Tasks.CodeAnalysis.dll"
▼
Microsoft.CodeAnalysis.BuildTasks.Csc(ToolTask)
│ UseSharedCompilation 默认为 true
├── 复用/启动 Roslyn 编译服务器 VBCSCompiler.exe(常驻进程)
└── 或直接执行 $(RoslynTargetsPath)/csc.exe
▼
Roslyn 编译器产出 obj/.../<Assembly>.exe|dll → 复制到 bin/...
关键点:MonoDevelop 不做任何"编译器选择"决策 。它只负责"用哪个 MSBuild 目录 + 设好全局属性",真正的编译器由 MSBuild targets 里的 Csc/Vbc 任务决定,而这个任务指向 Roslyn。
三、决定"用哪个编译器"的四个关键属性
| 属性 | 取值来源 | 作用 |
|---|---|---|
ToolsVersion |
写死为 "15.0"(MSBuildProjectService.cs:60) |
决定使用 <runtime>/lib/mono/msbuild/15.0/bin,与 csproj 里的 ToolsVersion="4.0" 无关 |
MSBuildBinPath |
MonoTargetRuntime.GetMSBuildBinPath("15.0")(MonoTargetRuntime.cs:180-198)→ <mono前缀>/lib/mono/msbuild/15.0/bin |
决定构建器复制哪一套 MSBuild,以及 $(MSBuildBinPath) 的解析结果 |
RoslynTargetsPath |
由 IDE 作为全局属性 传入:Path.Combine(binDir, "Roslyn")(RemoteBuildEngineManager.cs:404-434) |
决定 Microsoft.CSharp.Core.targets 与 Csc 任务程序集从哪个 Roslyn 目录加载 |
BuildingInsideVisualStudio / UseHostCompilerIfAvailable |
IDE 固定传入 true / false |
让 targets 按 IDE 构建语义运行,且不使用宿主内编译器 |
补充:csproj 中 ToolsVersion="4.0" 只是项目文件格式版本,注释在 MSBuildFileFormat.cs 中已说明------"构建器总是使用最新的 tools version"。
用哪个 Mono,取决于 IDE 自己的运行时
TargetRuntime 由 MonoDevelop 探测得到,默认就是它自己正在运行的那个 Mono:
| 启动方式 | 目标运行时 | MSBuild 目录 | 编译器实体 |
|---|---|---|---|
双击 MonoDevelop.exe(系统 mono) |
系统 Mono 6.12.0.200 | /usr/lib/mono/msbuild/15.0/bin |
/usr/lib/mono/4.5/csc.exe(Roslyn 3.9.0-6.21124.20) |
./build-from-source.sh run(自编译 mono) |
mono-install 的 Mono 6.12.0 |
mono-install/lib/mono/msbuild/15.0/bin |
mono-install/lib/mono/4.5/csc.exe(Roslyn 3.9.0-5.21112.8) |
msbuild/15.0 在 Mono 安装里是 Current 的符号链接,所以日志中两种路径都可能出现。
四、编译器本体与语言版本
4.1 Roslyn csc / vbc
-
$(RoslynTargetsPath)/csc.exe在 Mono 中是符号链接:
.../msbuild/15.0/bin/Roslyn/csc.exe -> ../../../../4.5/csc.exe -
版本实测:
$ mono /usr/lib/mono/4.5/csc.exe -version
3.9.0-6.21124.20 (db94f4cc)$ mono
/lib/mono/4.5/csc.exe -version
3.9.0-5.21112.8 (f3ff0437) -
mcs.exe仍在<mono>/lib/mono/4.5/中,但Microsoft.CSharp.Mono.targets只在
CscToolExe == mcs.exe时才把CscToolPath指向框架目录;MonoDevelop 未设置该属性,
因此默认不会走 mcs。
4.2 编译方式:共享编译(VBCSCompiler)
Microsoft.Managed.Core.targets 中 UseSharedCompilation 默认 true,Csc 任务会把编译请求
交给常驻的 Roslyn 编译服务器:
mono <cache>/MSBuild/<pid>_1/Roslyn/VBCSCompiler.exe -pipename:...
首次构建后该进程会存活,后续编译直接复用以加速。若设置 UseSharedCompilation=false,
则退化为每次直接启动 csc.exe。
4.3 语言版本
Microsoft.CSharp.Core.targets 按目标框架推导 _MaxSupportedLangVersion:
| 目标框架 | 默认 LangVersion |
|---|---|
| .NET Framework / .NET Standard < 2.1 | 7.3 |
| .NET Core < 3.0 | 7.3 |
| .NET Core < 5.0 或 .NET Standard 2.1 | 8.0 |
| .NET Core 5.0 | 9.0 |
实测 net47 项目的命令行带 /langversion:7.3。需要更高版本时在 csproj 中显式设置
<LangVersion>latest</LangVersion>。
五、实测证据
用与 IDE 相同的构建栈(mdtool → MonoDevelop.MSBuildBuilder.exe)构建一个 Gtk# 项目,
日志中 CoreCompile 目标下直接打印出编译器命令行(节选):
目标 CoreCompile:
/usr/lib/mono/msbuild/Current/bin/Roslyn/csc.exe /noconfig /nowarn:1701,1702,2008
/fullpaths /nostdlib+ /platform:x86 /define:DEBUG /debug+ /debug:full /optimize-
/reference:/usr/lib/mono/4.7-api/mscorlib.dll
/reference:.../gtk-sharp.dll ...
/out:"obj/x86/Debug/123.exe" /target:winexe /langversion:7.3
Program.cs MainWindow.cs Properties/AssemblyInfo.cs ...
可确认:
- 编译器可执行文件来自
$(RoslynTargetsPath),即 MSBuild 的 Roslyn 目录; - 引用程序集来自目标框架的引用程序集目录(
4.7-api)与项目引用; - 编译器由
Microsoft.Build.Tasks.CodeAnalysis.dll中的Csc任务加载。
六、SDK 风格项目(.NET Core / .NET Standard)
- 不是 调用
dotnet build。仍然走上面同一条链路:Mono 的 MSBuild 15 + 构建器进程。 - IDE 通过全局属性
MSBuildSDKsPath指向已安装 .NET SDK 的Sdks目录
(MSBuildSdksPathGlobalPropertyProvider/DotNetCoreSdk.MSBuildSDKsPath);
若 SDK 自带MSBuildSDKsPath也找不到,则回退到<runtime>/lib/mono/msbuild/15.0/bin/Sdks。 - 编译器仍然是 Roslyn csc/vbc,只是 targets 由 SDK 的
Microsoft.NET.Sdk提供。 dotnetCLI 在 IDE 里只用于"运行/迁移"等场景,不参与构建。
七、编辑器内的编译器(容易混淆,需区分)
| 用途 | 实现 | 是否产出程序集 |
|---|---|---|
| 构建/生成 | MSBuild Csc 任务 → Roslyn csc / VBCSCompiler |
是 |
| 编辑器语义分析、补全、重构、错误波浪线 | 进程内 Roslyn Workspaces(Microsoft.CodeAnalysis 2.10 系列,见 main/packages) |
否 |
所以"IDE 里没报错但编译失败"或反过来,往往是这两套编译环境(引用集合、语言版本、条件编译符号)不一致造成的,而不是"调用了另一个编译器"。
另外,MonoDevelop 对项目属性的读取(设计时求值)使用的是它自带的托管求值器
(DefaultMSBuildEngine,MSBuildEngineManager.cs:37-59;MSBuildEngineV12 相关代码已注释停用),
它不启动编译器;真正的编译只发生在构建阶段。
八、平台差异:Windows 上的 .NET Framework 项目
MsNetTargetRuntime 会把 MSBuild 目录解析到 Visual Studio 的 MSBuild\15.0\bin,
因此编译器来自 VS 自带的 Roslyn;链接方式与 Mono 完全一致(同样是 Csc 任务)。
本工作区是 Linux,实际走的是 Mono 分支。
九、本工作区的已知坑(与"编译器从哪加载"直接相关)
-
$(RoslynTargetsPath)为空导致 MSB4062症状:
未能从程序集 /Microsoft.Build.Tasks.CodeAnalysis.dll 加载任务 Csc。原因:构建器工具集配置里的
$(MSBuildToolsPath)\Roslyn未被展开,而Microsoft.Common.tasks中
UsingTask的注册早于全局属性生效。规避:在
~/Projects/Directory.Build.targets中用$(RoslynTargetsPath)重新声明Csc/Vbc。 -
GAC 引用解析不到导致 MSB3245
症状:
未能找到程序集"gtk-sharp"(命令行msbuild同项目却正常)。规避:同样在
Directory.Build.targets里把main/build/bin追加进AssemblySearchPaths。 -
Gtk# 原生 glue 缺失导致运行时
DllNotFoundException: gtksharpglue-2这是运行期问题(非编译器问题),需在各
*-sharp.dll.config中为 glue 添加dllmap指向
mono-install/lib/lib*sharpglue-2.so;build_ide已内置该补写。
十、如何自行验证
bash
# 1) 用与 IDE 相同的构建栈构建(会打印 csc.exe 命令行)
cd <解决方案目录>
mono /media/deepseek2008/data/monodev/monodevelop/main/build/bin/mdtool.exe build xxx.sln
# 2) 查看编译器版本
mono /usr/lib/mono/4.5/csc.exe -version
mono /media/deepseek2008/data/monodev/mono-install/lib/mono/4.5/csc.exe -version
# 3) 观察构建器与编译服务器进程
ls ~/.cache/MonoDevelop/7.0/MSBuild/ # 每个 <pid>_<runtimeId> 是一套构建器+MSBuild+Roslyn
ps -ef | grep -E "MSBuildBuilder|VBCSCompiler" # 构建器 / Roslyn 共享编译服务器
# 4) 在构建日志里确认 RoslynTargetsPath(可临时加诊断 Target)
# <Message Importance="high" Text="ROSLYN=$(RoslynTargetsPath)" />
十一、代码位置索引
| 内容 | 位置 |
|---|---|
MSBuild 工具版本常量 15.0 |
monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProjectService.cs:60 |
| Mono 的 MSBuild 目录解析 | monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntime.cs:180-198 |
| 构建器定位与缓存复制 | monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs:440-518 |
传给 MSBuild 的全局属性(含 RoslynTargetsPath) |
同上 :404-434 |
| 项目求值引擎(不调用编译器) | monodevelop/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildEngineManager.cs:37-59 |
Csc 任务的 UsingTask 声明 |
<mono>/lib/mono/msbuild/15.0/bin/Microsoft.Common.tasks:182 |
Csc 任务调用点(ToolPath/ToolExe/UseSharedCompilation) |
<mono>/lib/mono/msbuild/15.0/bin/Roslyn/Microsoft.CSharp.Core.targets:71,137-140 |
| Roslyn targets 引入点 | <mono>/lib/mono/msbuild/15.0/bin/Microsoft.CSharp.CurrentVersion.targets:320-326 |
| 共享编译默认值 | <mono>/lib/mono/msbuild/15.0/bin/Roslyn/Microsoft.Managed.Core.targets:48-49 |
十二、一句话总结
MonoDevelop 只把项目交给"目标运行时的 MSBuild 构建器",由 MSBuild 的 Csc/Vbc 任务
加载 $(RoslynTargetsPath) 下的 Roslyn(csc/vbc,或共享编译服务器 VBCSCompiler)完成编译;
mcs 已是历史遗留、默认不参与;编辑器里的 IntelliSense 使用同一家 Roslyn 但走进程内库,
不产出程序集。