能否使用.NET Core Roslyn编译器编译单个C#代码文件?

76

在旧版.NET中,我们可以运行编译器来编译单个.cs文件或多个文件。

使用.NET Core,我们有,它坚持拥有适当的项目文件。是否有一个独立的命令行编译器,可以在没有项目的情况下编译源代码文件(并在同一命令行上列出引用的依赖项)?

在Linux上,当我安装了旧的和新的.NET Core时,我得到以下时间:

[root@li1742-80 test]# time dotnet build
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  test -> /root/test/bin/Debug/netcoreapp2.0/test.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:03.94

real    0m7.027s
user    0m5.714s
sys     0m0.838s

[root@li1742-80 test]# time csc Program.cs
Microsoft (R) Visual C# Compiler version 2.3.0.61801 (3722bb71)
Copyright (C) Microsoft Corporation. All rights reserved.


real    0m0.613s
user    0m0.522s
sys     0m0.071s
[root@li1742-80 test]#

使用.NET Core编译同样的文件Program.cs,需要7秒,而旧版本的csc只需要几百毫秒。

我希望能够像以前使用csc一样快速地编译.NET Core。


略微相关:https://github.com/dotnet/corefx/blob/master/Documentation/building/advanced-inner-loop-testing.md - Andrew Savinykh
我在Windows、Ubuntu和macOS上使用命令行编译没有任何问题 - 请查看我的回答。 - Jacek Blaszczynski
https://github.com/oleg-shilo/cs-script - Robert Harvey
@RobertHarvey 我认为这并不太相关,因为CS-Script需要Mono v5.0.1或更高版本。而要求安装Mono来编译Roslyn在Linux上是不合理的。 - Andrew Savinykh
5个回答

53
是的,在.NET Core中可以使用csc或vbc编译器编译单个文件。
要直接调用Roslyn编译器,需要使用命令行驱动程序。与旧版csc.exe不同,Roslyn没有隐式引用mscorlib.dll,因此需要传递对所需依赖项(即System.Runtime和System.Private.CoreLib库以及任何其他所需引用)的引用。以下清单展示了如何编译以下Hello, World!程序。
using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

在已安装Ubuntu 16.04(Xenial Xerus)和dotnet-sdk-2.0.0的情况下使用WSL:

time dotnet /usr/share/dotnet/sdk/2.0.0/Roslyn/csc.exe -r:/usr/share/dotnet/shared/Microsoft.NETCore.App/2.0.0/System.Private.CoreLib.dll -r:/usr/share/dotnet/shared/Microsoft.NETCore.App/2.0.0/System.Console.dll -r:/usr/share/dotnet/shared/Microsoft.NETCore.App/2.0.0/System.Runtime.dll HelloWorld.cs
Microsoft (R) Visual C# Compiler version 2.3.2.61921 (ad0efbb6)
Copyright (C) Microsoft Corporation. All rights reserved.

real    0m0.890s
user    0m0.641s
sys     0m0.250s

ls -li
total 4
 4785074604720852 -rw-rw-rw- 1 developer developer  178 Dec  7 15:07 HelloWorld.cs
11821949022487213 -rw-rw-rw- 1 developer developer 4096 Dec  7 15:13 HelloWorld.exe

编译器需要的依赖项在不同平台上是不同的,例如在Windows上只需传递System.Runtime.dllSystem.Console.dll,而在Ubuntu 16.04上还需要传递System.Private.CoreLib.dll。不同的SDK版本会将Roslyn和命令行驱动程序放置在不同的位置 - SDK布局在版本之间会发生变化 - 最新的2.2.2 SDK使用csc.dllvbc.dll代替csc.exevbc.exe。因此,在使用此方法之前,必须检查您的SDK布局。

详细说明

Roslyn编译器与以前使用的csc.exevbc.exe编译器有所不同。首先,Roslyn使用C#和VB.NET编写,并且是托管的.NET应用程序。在Windows上,它主要用作在服务器进程VBCSCompiler.exe(.dll)中运行的常见服务。但是,Roslyn附带了托管的命令行驱动程序csc.exevbc.exe(最新的.NET SDK版本附带csc.dllvbc.dll),可用于直接从命令行编译源文件。无论如何,这正是.NET中的构建系统所做的,通过命令行调用Roslyn。运行简单的dotnet csc.exe -help命令将打印使用信息,指导您直接从命令行使用编译器(请参见最后一项清单)。

旧本地编译器和Roslyn之间的主要区别在于后者是托管应用程序,因此启动时间会有所不同。即使将Roslyn编译为R2R本机程序集(Ready To Run),它仍需要通过加载整个.NET框架、初始化并加载Roslyn程序集以及启动编译过程来启动。它总是比运行本地编译器慢一点,但正如上面的时间表所示,差别并不大。 corefx存储库中添加了一个新的文档文章,描述了高级场景-使用csc/vbc和CoreRun构建和运行应用程序代码。任何有兴趣的人都可以将其用作指南,以了解.NET Core的低级工作方式。
    Microsoft (R) Visual C# Compiler version 2.3.2.61921 (ad0efbb6)
Copyright (C) Microsoft Corporation. All rights reserved.


                              Visual C# Compiler Options

                        - OUTPUT FILES -
 /out:<file>                   Specify output file name (default: base name of
                               file with main class or first file)
 /target:exe                   Build a console executable (default) (Short
                               form: /t:exe)
 /target:winexe                Build a Windows executable (Short form:
                               /t:winexe)
 /target:library               Build a library (Short form: /t:library)
 /target:module                Build a module that can be added to another
                               assembly (Short form: /t:module)
 /target:appcontainerexe       Build an Appcontainer executable (Short form:
                               /t:appcontainerexe)
 /target:winmdobj              Build a Windows Runtime intermediate file that
                               is consumed by WinMDExp (Short form: /t:winmdobj)
 /doc:<file>                   XML Documentation file to generate
 /refout:<file>                Reference assembly output to generate
 /platform:<string>            Limit which platforms this code can run on: x86,
                               Itanium, x64, arm, anycpu32bitpreferred, or
                               anycpu. The default is anycpu.

                        - INPUT FILES -
 /recurse:<wildcard>           Include all files in the current directory and
                               subdirectories according to the wildcard
                               specifications
 /reference:<alias>=<file>     Reference metadata from the specified assembly
                               file using the given alias (Short form: /r)
 /reference:<file list>        Reference metadata from the specified assembly
                               files (Short form: /r)
 /addmodule:<file list>        Link the specified modules into this assembly
 /link:<file list>             Embed metadata from the specified interop
                               assembly files (Short form: /l)
 /analyzer:<file list>         Run the analyzers from this assembly
                               (Short form: /a)
 /additionalfile:<file list>   Additional files that don't directly affect code
                               generation but may be used by analyzers for producing
                               errors or warnings.
 /embed                        Embed all source files in the PDB.
 /embed:<file list>            Embed specific files in the PDB

                        - RESOURCES -
 /win32res:<file>              Specify a Win32 resource file (.res)
 /win32icon:<file>             Use this icon for the output
 /win32manifest:<file>         Specify a Win32 manifest file (.xml)
 /nowin32manifest              Do not include the default Win32 manifest
 /resource:<resinfo>           Embed the specified resource (Short form: /res)
 /linkresource:<resinfo>       Link the specified resource to this assembly
                               (Short form: /linkres) Where the resinfo format
                               is <file>[,<string name>[,public|private]]

                        - CODE GENERATION -
 /debug[+|-]                   Emit debugging information
 /debug:{full|pdbonly|portable|embedded}
                               Specify debugging type ('full' is default,
                               'portable' is a cross-platform format,
                               'embedded' is a cross-platform format embedded into
                               the target .dll or .exe)
 /optimize[+|-]                Enable optimizations (Short form: /o)
 /deterministic                Produce a deterministic assembly
                               (including module version GUID and timestamp)
 /refonly                      Produce a reference assembly in place of the main output
 /instrument:TestCoverage      Produce an assembly instrumented to collect
                               coverage information
 /sourcelink:<file>            Source link info to embed into PDB.

                        - ERRORS AND WARNINGS -
 /warnaserror[+|-]             Report all warnings as errors
 /warnaserror[+|-]:<warn list> Report specific warnings as errors
 /warn:<n>                     Set warning level (0-4) (Short form: /w)
 /nowarn:<warn list>           Disable specific warning messages
 /ruleset:<file>               Specify a ruleset file that disables specific
                               diagnostics.
 /errorlog:<file>              Specify a file to log all compiler and analyzer
                               diagnostics.
 /reportanalyzer               Report additional analyzer information, such as
                               execution time.

                        - LANGUAGE -
 /checked[+|-]                 Generate overflow checks
 /unsafe[+|-]                  Allow 'unsafe' code
 /define:<symbol list>         Define conditional compilation symbol(s) (Short
                               form: /d)
 /langversion:<string>         Specify language version mode: ISO-1, ISO-2, 3,
                               4, 5, 6, 7, 7.1, Default, or Latest

                        - SECURITY -
 /delaysign[+|-]               Delay-sign the assembly using only the public
                               portion of the strong name key
 /publicsign[+|-]              Public-sign the assembly using only the public
                               portion of the strong name key
 /keyfile:<file>               Specify a strong name key file
 /keycontainer:<string>        Specify a strong name key container
 /highentropyva[+|-]           Enable high-entropy ASLR

                        - MISCELLANEOUS -
 @<file>                       Read response file for more options
 /help                         Display this usage message (Short form: /?)
 /nologo                       Suppress compiler copyright message
 /noconfig                     Do not auto include CSC.RSP file
 /parallel[+|-]                Concurrent build.
 /version                      Display the compiler version number and exit.

                        - ADVANCED -
 /baseaddress:<address>        Base address for the library to be built
 /checksumalgorithm:<alg>      Specify algorithm for calculating source file
                               checksum stored in PDB. Supported values are:
                               SHA1 (default) or SHA256.
 /codepage:<n>                 Specify the codepage to use when opening source
                               files
 /utf8output                   Output compiler messages in UTF-8 encoding
 /main:<type>                  Specify the type that contains the entry point
                               (ignore all other possible entry points) (Short
                               form: /m)
 /fullpaths                    Compiler generates fully qualified paths
 /filealign:<n>                Specify the alignment used for output file
                               sections
 /pathmap:<K1>=<V1>,<K2>=<V2>,...
                               Specify a mapping for source path names output by
                               the compiler.
 /pdb:<file>                   Specify debug information file name (default:
                               output file name with .pdb extension)
 /errorendlocation             Output line and column of the end location of
                               each error
 /preferreduilang              Specify the preferred output language name.
 /nostdlib[+|-]                Do not reference standard library (mscorlib.dll)
 /subsystemversion:<string>    Specify subsystem version of this assembly
 /lib:<file list>              Specify additional directories to search in for
                               references
 /errorreport:<string>         Specify how to handle internal compiler errors:
                               prompt, send, queue, or none. The default is
                               queue.
 /appconfig:<file>             Specify an application configuration file
                               containing assembly binding settings
 /moduleassemblyname:<string>  Name of the assembly which this module will be
                               a part of
 /modulename:<string>          Specify the name of the source module

嗨Jack,但如果我不想得到dll或exe文件,而是想要像*.o(C ++)或*.class(Java)这样的东西 - 编译文件而不是打包在dll或exe中,这是否可能?例如,我需要将未来的几个编译文件打包在我的特殊文件格式中...是否可能? - Denis Kotov
1
dotnet csc.exe -help 命令是需要安装的工具的一部分吗? - FilBot3

31
接受的答案提到了使用System.Private.CoreLib.dll,这是一个运行时程序集,不建议使用。C#编译器开发人员的评论

尝试使用运行时程序集作为编译引用不受支持,并且由于运行时程序集的结构而经常会出现错误。

相反,应该使用引用程序集。在dotnet build期间从NuGet获取引用程序集,并且在使用增加的详细信息(dotnet build --verbosity normal)运行dotnet CLI时可以看到完整的csc调用。可能会看到来自microsoft.netcore.app NuGet包的程序集引用,例如System.Runtime.dllSystem.Console.dll

但是,对于一个简单的单文件Hello, World!编译,可以引用netstandard.dll,对于.NET Core 2.2,它存在于<installation-directory>/sdk/2.2.203/ref/netstandard.dll下。

请注意,为了使用dotnet HelloWorld.exe运行生成的可执行文件,必须创建一个相应的HelloWorld.runtimeconfig.json,其中包含目标.NET Core运行时版本。我们将通过创建控制台(NETCoreApp)应用程序的通用运行时配置和一个相应的别名csc_run来简化它。

在您的~/.profile中添加以下内容:

#!/usr/bin/env sh

# IMPORTANT: make sure dotnet is present in PATH before the next lines

# prepare csc alias

DOTNETDIR=$(dirname $(dirname $(dotnet --info | grep "Base Path" | cut -d' ' -f 6)))
CSCPATH=$(find $DOTNETDIR -name csc.dll -print | sort | tail -n1)
NETSTANDARDPATH=$(find $DOTNETDIR -path *sdk/*/ref/netstandard.dll ! -path *NuGetFallback* -print | sort | tail -n1)

alias csc='dotnet $CSCPATH /r:$NETSTANDARDPATH '

# prepare csc_run alias

if [ ! -w "$DOTNETDIR" ]; then
  mkdir -p $HOME/.dotnet
  DOTNETDIR=$HOME/.dotnet
fi

DOTNETCSCRUNTIMECONFIG=$DOTNETDIR/csc-console-apps.runtimeconfig.json

alias csc_run='dotnet exec --runtimeconfig $DOTNETCSCRUNTIMECONFIG '

if [ ! -f $DOTNETCSCRUNTIMECONFIG ]; then
  DOTNETRUNTIMEVERSION=$(dotnet --list-runtimes |
    grep Microsoft\.NETCore\.App | tail -1 | cut -d' ' -f2)

  cat << EOF > $DOTNETCSCRUNTIMECONFIG
{
  "runtimeOptions": {
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "$DOTNETRUNTIMEVERSION"
    }
  }
}
EOF
fi

退出并启动shell以重新加载配置文件(或者在不想离开当前会话的情况下使用. ~/.profile命令来源化它)。

用法:

cat << EOF > ./Program.cs
class Program
{
  static void Main() => System.Console.WriteLine("Hello World!");
}
EOF

csc     -out:hwapp.exe Program.cs
csc_run hwapp.exe

# Hello World!

2
这很有帮助,尽管 SDK 的版本不一定是 2.2.203(或者框架是 2.2.4)。我认为以下代码可以解决问题:DOTNETSDKVER=$(ls $DOTNETDIR/sdk | grep "^[0-9]" | sort -rn | head -1) 尽管我承认这有点 hacky。 - Peter Taylor
1
可以随时运行 dotnet --info 命令,并查找 .NET Core SDK 下的 Version,以及 .NET Core runtimes installed 下的 Microsoft.NETCore.App 版本。 - Ivan
知道了,谢谢。我认为在脚本中从输出中解析信息甚至更加不可靠,所以我会坚持使用基于“ls”的方法。 - Peter Taylor
1
更新了此答案,包括灵活的别名:csccsc_run别名。它适用于控制台应用程序。有关命令行引用,请参见编译器选项,可与csc别名一起使用。 - vulcan raven

6
编译器可以直接使用以下命令调用:
$ /usr/local/share/dotnet/sdk/2.0.0/Roslyn/RunCsc.sh

然而,如果没有支持的项目基础设施,这个特定的命令可能并不是非常有帮助,因为你需要手动传入所有的.NET Core或.NET Standard参考程序集,而这通常由SDK和NuGet处理。否则会出现以下错误:

$ /usr/local/share/dotnet/sdk/2.0.0/Roslyn/RunCsc.sh Program.cs
Microsoft (R) Visual C# Compiler version 2.3.2.61921 (ad0efbb6)
Copyright (C) Microsoft Corporation. All rights reserved.

Program.cs(1,7): error CS0246: The type or namespace name 'System' could not be found (are you missing a using directive or an assembly reference?)
Program.cs(5,11): error CS0518: Predefined type 'System.Object' is not defined or imported
Program.cs(7,26): error CS0518: Predefined type 'System.String' is not defined or imported
Program.cs(7,16): error CS0518: Predefined type 'System.Void' is not defined or imported

13
很遗憾,我希望有一种更简单的方法来编译单个文件。 - Ghasan غسان
没有办法告诉编译器在哪里找到所引用的程序集,除了“告诉”它以外,还有什么其他简化的方法吗? - VMAtm
1
@VMAtm 他可能是指我们在以前使用的传统csc中没有遇到这个问题。 - Andrew Savinykh
解决您提到的错误提示:https://daveaglick.com/posts/running-a-design-time-build-with-msbuild-apis - Glenn Slayden
2
脚本在2.2.301中已不存在。 - Peter Taylor
在尝试用C#编写一些代码来回答stackoverflow上的问题时,我注意到我的ls ~/.dotnet/sdk/3.1.401/Roslyn目录树中没有这样的脚本或任何csc.exe。哦,好吧。 - BitTickler

1
This is the scripts:



#!/bin/bash

#dotnethome=`dirname "$0"`
dotnethome=`dirname \`which dotnet\``
sdkver=$(dotnet --version)
fwkver=$(dotnet --list-runtimes | grep Microsoft.NETCore.App | awk '{printf("%s", $2)}')
dotnetlib=$dotnethome/shared/Microsoft.NETCore.App/$fwkver

if [ "$#" -lt 1 ]; then
    dotnet $dotnethome/sdk/$sdkver/Roslyn/bincore/csc.dll -help
    echo dotnethome=$dotnethome
    echo sdkver=$sdkver
    echo fwkver=$fwkver
    echo dotnetlib=$dotnetlib
    exit 1
fi

progfile=$1
prog="${progfile%.*}"
echo -r:$dotnetlib/netstandard.dll > /tmp/$prog.rsp
echo -r:$dotnetlib/System.dll >> /tmp/$prog.rsp
echo -r:$dotnetlib/Microsoft.CSharp.dll >> /tmp/$prog.rsp
for f in  $dotnetlib/System.*.dll; do
    echo -r:$f >> /tmp/$prog.rsp
done

dotnet $dotnethome/sdk/$sdkver/Roslyn/bincore/csc.dll -out:$prog.dll -nologo @/tmp/$prog.rsp $* 
if [ $? -eq 0 ]; then
   if test -f "$prog.dll"; then
    if ! test -f "$prog.runtime.config"; then
        echo "{
  \"runtimeOptions\": {
    \"framework\": {
      \"name\": \"Microsoft.NETCore.App\",
      \"version\": \"$fwkver\"
    }
  }
}"  > "$prog.runtimeconfig.json"
    fi
  fi
fi
echo /tmp/$prog.rsp: 
cat /tmp/$prog.rsp
rm /tmp/$prog.rsp

请参考以下链接:https://github.com/dotnet/roslyn/issues/12393#issuecomment-744142906 - Kinglionsz
请参考以下链接:https://github.com/dotnet/sdk/issues/8742#issuecomment-735047107。 - Kinglionsz
SDK位置可能不同。考虑使用以下命令:dotnethome=`dotnet --list-sdks | awk 'match($0,/\/.*\/[^ ]*/){sub("/sdk]","");print substr($0,RSTART,RLENGTH)}'` - Yılmaz Durmaz
并检查 $prog.runtime.config,然后写入 $prog.runtimeconfig.json :) 顺便说一下,我喜欢你的帖子和另一个运行结果的帖子。我正在尝试将它们合并到单个脚本中(目前不是单个脚本),但无法确定是否能完成它(不是专家)。您可能会比我更快地完成。干杯 ;) - Yılmaz Durmaz

0
简而言之,如果没有预定义的项目,它是不支持的。
但是@Andrew的评论表明,如果您准备好在命令行选项中列出每个单独的依赖项,包括隐式系统依赖项,那么仍然是可能的。
error CS0518: Predefined type 'System.Object' is not defined or imported #12393

目前,我们没有计划使使用csc.exe以这种方式变得容易。指导意见是暂时使用dotnet CLI工具。即使在此处进行了某些修改,也将由框架提供统一和/或简化的参考程序集来为编译器提供支持。编译器的类型或程序集解析永远不会比现在更复杂(按设计)。

另请参阅已关闭的Make it possible to invoke the compiler directly #7689

编译时间慢了20倍肯定令人失望。但嘿,这是他们的选择;) 谢谢你的答案。 - Andrew Savinykh
@AndrewSavinykh,我不得不使用一个仅适用于Windows的Roslyn编译器,它被捆绑在MSBuild中,以编译没有预配置项目的单个源文件:https://dev59.com/cnRB5IYBdhLWcg3wr44U#47624680 - Vadzim
1
顺便说一下,对于我的目的,RunCsc.sh 运行得相当不错。请参见此处此处 - Andrew Savinykh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接