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

Is it possible to compile a single C# code file with the .NET Core Roslyn compiler?

在旧的 .NET 中,我们曾经能够 运行 csc 编译器来编译单个 .cs 文件或多个文件。

对于 .NET Core,我们 dotnet build 坚持要有一个合适的项目文件。是否有独立的命令行编译器允许在没有项目的情况下编译源代码文件(并在同一命令行上列出引用的依赖项)?

在 Linux,当我安装旧的 csc 和新的 .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 为 7 秒,而旧 csc 为同一文件 Program.cs.

为数百毫秒

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

编译器可以直接调用

$ /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

总之,没有预定义项目是不支持的

但是@Andrew 的评论表明,如果您准备好在命令行选项中列出每个依赖项(包括隐式系统依赖项),它仍然是可能的。

来自error CS0518: Predefined type 'System.Object' is not defined or imported #12393

At the moment, we have no plan to make it easy to use csc.exe in this manner. The guidance is to use the dotnet CLI tooling for the time being. Even if some modification where to be made here, it would be on the framework to provide unified and/or simplified reference assemblies for the compiler. The compiler will never have more complicated type or assembly resolution than it does now (by design).

又见关闭Make it possible to invoke the compiler directly #7689.

是的,可以在 .NET Core 中使用 csc 或 vbc 编译器编译单个文件。

要直接调用 Roslyn 编译器,必须使用命令行驱动程序 csc.{exe|dll},因为 Roslyn 与旧的 csc.exe 不同不隐式引用 mscorlib.dll 有必要传递对所需依赖项的引用,即 System.RuntimeSystem.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 存储库中添加了一篇描述 Advanced scenario - Build and run application code with csc/vbc and 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

接受的答案是指使用 System.Private.CoreLib.dll,这是一个 运行 时间汇编,不推荐使用。来自 C# compiler developer's comments:

Attempting to use runtime assemblies as compile references is not supported and frequently breaks do to the structure of the runtime assemblies

相反,应该使用参考程序集。在 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 运行time 版本。我们将通过为控制台 (NETCoreApp) 应用程序创建通用 运行timeconfig 和随附的别名 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!
This is the scripts:



#!/bin/bash

#dotnethome=`dirname "[=10=]"`
dotnethome=`dirname \`which dotnet\``
sdkver=$(dotnet --version)
fwkver=$(dotnet --list-runtimes | grep Microsoft.NETCore.App | awk '{printf("%s", )}')
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=
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