Doom Emacs에 CSharp 개발 환경 구축하기

4 minute read

doom emacs로 설정하는 방법을 설명한다. doom emacs 설정 프레임워크를 사용하지 않는다면 doom emacs 모듈에서 사용하는 패키지 정보를 참고해서 구축하면 된다.

(doom!
 :tools
 lsp                   ; M-x vscode
 :lang
 (csharp +dotnet +lsp) ; unity, .NET, and mono shenanigans
 )

lsp modulecsharp module을 추가한다.

.NET SDK 설치

.NET 다운로드 페이지에서 원하는 버전의 SDK를 다운로드 받아서 설치한다.

$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.400
 Commit:    7771abd614

런타임 환경:
 OS Name:     Mac OS X
 OS Version:  12.5
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.400/

global.json file:
  Not found

Host:
  Version:      6.0.8
  Architecture: arm64
  Commit:       55fb7ef977

.NET SDKs installed:
  6.0.400 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Download .NET:
  https://aka.ms/dotnet-download

Learn about .NET Runtimes and SDKs:
  https://aka.ms/dotnet/runtimes-sdk-info

omnisharp-roslyn 설치

visual studio가 부럽지 않을 .NET 개발 플랫폼 백엔드를 설치해야 한다. 부럽지 않다는 건 사실 거짓말. C#은 visual studio가 최고다. emacs 사랑으로 커버할 수 있을 정도로 C# 언어 서비스를 제공할 수 있는 백엔드가 필요하다.

csharp-ls, omnisharp-roslyn 이렇게 두 가지 선택지가 있다. 대세로 보이는 omnisharp-roslyn 을 설치한다.

M-x lsp-install-server

자동 설치를 지원한다. 위 명령을 입력하고 omnisharp 를 선택하면 omnisharp-roslyn 을 설치한다.

lsp-mode 패키지 업그레이드

M-x lsp-install-server

자동 설치를 하면 omnisharp-osx.zip 파일을 설치한다. 게다가 macOS에서는 mono를 추가로 설치해야 한다. releases 페이지를 보면 .NET 6 지원에 M1 맥도 지원하는 것처럼 보이는 omnisharp-osx-arm64-net6.0.zip 파일을 설치하려면 어떻게 해야 할까?

(setq lsp-csharp-omnisharp-roslyn-download-url
      "https://github.com/OmniSharp/omnisharp-roslyn/releases/download/v1.39.1/omnisharp-osx-arm64-net6.0.zip")

이런 식으로 변수를 세팅하면 될 것 같은데, 우선 최신 소스를 살펴보기로 했다. 얼~ 바뀌었다. M1이면 omnisharp-osx-arm64-net6.0.zip 파일을 다운로드 받는다. 866e9a3ccb1b 커밋에서 변경됐다.

doom emacs의 lsp module에서는 더 예전 버전을 사용하고 있기에 직접 버전을 올려줘야 한다.

(package! lsp-mode :pin "a0e1210f626cb7b5db16a9454d3bf61322d299df")

이렇게 doom emacs 설정을 추가하고

$ doom sync -u

sync 명령을 실행해서 패키지 업데이트를 하게 했다. -u 옵션을 붙여줘야지 내가 업데이트한 lsp-mode 패키지의 pin 커밋 아이디를 체크아웃한다.

/etc/dotnet/install_location 수정 - macOS

이러고 잘 되면 좋으련만 omnisharp-roslyn 실행에 문제가 생겼다.

A fatal error occurred. The required library libhostfxr.dylib could not be found.
If this is a self-contained application, that library should exist in [/Users/ohyecloudy/.emacs.d/.local/etc/lsp/omnisharp-roslyn/latest/omnisharp-roslyn/].
If this is a framework-dependent application, install the runtime in the global location [/usr/local/share/dotnet/x64] or use the DOTNET_ROOT environment variable to specify the runtime location or register the runtime location in [/etc/dotnet/install_location].

The .NET runtime can be found at:
  - https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=arm64&rid=osx.11.1-arm64&apphost_version=6.0.0-preview.7.21317.1

libhostfxr.dylib 파일을 못 찾아서 에러가 난다. dotnet root를 제대로 찾지 못하는 것 같다.

$ export COREHOST_TRACE=1

디버깅 정보를 출력하게 환경 변수를 세팅하고

$ ./OmniSharp
Tracing enabled @ Fri Sep  2 14:04:26 2022 UTC
--- Invoked apphost [version: 6.0.0-preview.7.21317.1, commit hash: 96a4671bc52e70024da409f5f48b0abaa30cb901] main = {
./OmniSharp
}
The managed DLL bound to this executable is: 'OmniSharp.dll'
Using environment variable DOTNET_ROOT=[/usr/local/share/dotnet/dotnet] as runtime location.
Looking for install_location file in '/etc/dotnet/install_location'.
Using install location '/usr/local/share/dotnet/x64'.
A fatal error occurred. The required library libhostfxr.dylib could not be found.
If this is a self-contained application, that library should exist in [/Users/ohyecloudy/.emacs.d/.local/etc/lsp/omnisharp-roslyn/latest/omnisharp-roslyn/].
If this is a framework-dependent application, install the runtime in the global location [/usr/local/share/dotnet/x64] or use the DOTNET_ROOT environment variable to specify the runtime location or register the runtime location in [/etc/dotnet/install_location].

COREHOST_TRACE 환경 변수 도움이 된다. /etc/dotnet/install_location 파일 내용이 문제였다.

$ cat /etc/dotnet/install_location
/usr/local/share/dotnet/x64

x64 플랫폼으로 설치한 적이 있는데, 그때 남은 찌그래기인가? 아니다 새로 싹 지우고 arm64 플랫폼으로 설치해도 파일 내용이 이렇다.

$ emacs /etc/dotnet/install_location
$ cat /etc/dotnet/install_location
/usr/local/share/dotnet

/usr/local/share/dotnet 디렉터리로 파일 내용을 수정했다.

$ ./OmniSharp
{"Event":"log","Body":{"LogLevel":"INFORMATION","Name":"OmniSharp.Stdio.Host","Message":"Starting OmniSharp on Unknown 0.0 (Unknown)"},"Seq":1,"Type":"event"}
...

잘 된다.

dotnet 6.0으로 컴파일한 omnisharp 바이너리 사용 - windows

(when IS-WINDOWS
  (after! lsp-mode
    (setq lsp-csharp-omnisharp-roslyn-download-url
          (concat "https://github.com/omnisharp/omnisharp-roslyn/releases/latest/download/"
                  "omnisharp-win-x64-net6.0.zip"
                  )
          )
    )
  )

windows에서는 omnisharp-win-x64.zip 파일을 다운로드 받고 있어서 직접 지정해서 받게 했다.

잘 되는지 기본 테스트

준비가 됐으면 뭐다? hello world 한 번 찍어봐야 한다. .NET Tutorial - Hello World in 5 minutes를 따라해봤다.

$ dotnet new console -o MyApp -f net6.0

애플리케이션을 만든다.

M-x sharper-main-transient

명령을 실행 후 메뉴에서 Run application 을 실행한다.

Determining projects to restore...
All projects are up-to-date for restore.
MyApp -> /Users/ohyecloudy/src/MyApp/bin/Debug/net6.0/MyApp.dll
Hello, World!

잘 된다. 끝~

org babel 지원

#+begin_src csharp
  System.Console.WriteLine("Hello World!");
#+end_src

실행해보니 에러가 난다

/usr/bin/bash: line 1: mono: command not found
/usr/bin/bash: line 1: mcs: command not found

컴파일엔 mcs 를 사용하고 mono 런타임을 사용하기 때문이다. dotnet 6.0을 쓰려고 mono 설치를 안 했는데, 이것 때문에 mono를 쓰긴 싫다.

ob-csharp 코드를 찾아보다 dotnet-script를 사용하는 PR(pull request)를 발견했다. 이걸 써보기로 결정.

dotnet tool install -g dotnet-script

dotnet-script는 쉽게 설치했다.

(require 'ob-cs)

(org-babel-do-load-languages 'org-babel-load-languages
                             (append org-babel-load-languages
                                     '((cs . t))))

(after! org
  (add-to-list 'org-src-lang-modes '("cs" . "csharp")))

doom emacs는 org-contrib 패키지를 사용한다. 여기에 ob-csharp 패키지가 포함되어 있는데, dotnet-script를 사용하는 버전으로 덮어쓰기가 안 된다. 그래서 파일을 복사해 수정해서 직접 로드하게 고쳤다. commit 579d112af. 충돌을 피하려고 csharp 이 아닌 cs 로 정의했다.

#+begin_src cs
  var version = Environment.Version;
  Console.WriteLine(version.ToString());
#+end_src

#+RESULTS:
: 6.0.8

잘 된다.

링크

C-x C-s C-x C-c