Dart 2.6 발표: dart2native로 Dart를 자립형 네이티브 실행 파일로 컴파일하기

Dart 2.6 발표: dart2native로 Dart를 자립형 네이티브 실행 파일로 컴파일하기

Dart는 이미 모바일 기기와 웹을 위한 프로덕션 최적화 코드를 만드는 다양한 컴파일러 세트를 제공하고 있어요. 이 유연한 컴파일러 덕분에 우리 프레임워크 파트너들이 다양한 폼팩터를 대상으로 삼을 수 있죠. Android와 iOS의 Flutter 앱, 웹·데스크톱의 Flutter 앱, 웹의 AngularDart 앱, 임베디드 기기의 Google Assistant까지요.

오늘 우리는 기존 컴파일러 세트의 확장인 dart2native를 발표해요. 이 컴파일러는 Dart 프로그램을 AOT(사전 컴파일, ahead-of-time)로 컴파일된 머신 코드를 담은 자립형(self-contained) 실행 파일로 컴파일할 수 있게 해줘요. dart2native를 쓰면 macOS, Windows, Linux에서 명령줄용 도구를 Dart로 만들 수 있어요. 이 기능의 발표 사진도 이 기능 자체로 구현했답니다 :-)

출처: Announcing Dart 2.6 with dart2native: Compile Dart to self-contained native executables

본문

Dart Native와 dart2native 컴파일러

Dart는 몇 년 전부터 네이티브 머신 코드로의 AOT 컴파일을 지원해 왔고, 그래서 Dart Native는 상당히 성숙한 기술이에요. 다만 이전에는 이 능력을 Flutter를 통한 iOS와 Android 모바일 기기에서만 노출했어요.

dart2native로 우리는 네이티브 컴파일 지원을 macOS, Windows, Linux를 실행하는 전통적인 데스크톱 운영체제까지 확장해요. dart2native로 만든 실행 파일은 자립형이라 Dart SDK가 설치되지 않은 머신에서도 실행될 수 있어요. 그리고 Dart의 AOT 컴파일러로 컴파일되기 때문에 실행 파일은 몇 밀리초 만에 실행을 시작해요. 다른 Dart 컴파일러·런타임과 마찬가지로, 네이티브 코드로 컴파일할 때도 동일한 풍부하고 일관된 핵심 라이브러리를 Dart에서 쓸 수 있어요.

데스크톱 운영체제용 AOT 컴파일을 요청하는 고객이 정말 많았어요. 우리 이슈 트래커에서 여섯 번째로 높은 순위의 이슈였죠. 그래서 이 기능을 제공할 수 있게 돼서 정말 기쁘게 생각해요.

이전에 dart2aot를 썼다면, 2.6부터는 dart2native를 쓰면 돼요. dart2native는 dart2aot 기능의 상위 집합(superset)을 제공해요.

dart2native로 명령줄 앱 만들기

dart2native 컴파일러는 명령줄용 Dart 기반 앱을 만들고 배포하기에 아주 좋은 선택이에요. 이런 앱은 보통 dart:io(기본 I/O), package:http(네트워킹), package:args(인자 파싱) 같은 라이브러리를 사용해요. "hello, world" 앱을 실행 파일로 컴파일하는 기본 과정을 살펴볼게요.

소스 코드 hello.dart:

main() {
  print('Hello Dart developers');
}

hello.darthello 실행 파일로 컴파일해요.

$ dart2native src/hello.dart -o hello
Generated: /Users/mit/hello

hello를 실행 시간을 재면서 실행해 볼게요.

$ time ./hello
Hello Dart developers

real 0m0.049s
user 0m0.018s
sys 0m0.020s

명령이 시작해서 stdout으로 출력하고 종료하기까지 합쳐서 단 49밀리초밖에 걸리지 않는 걸 볼 수 있어요!

이미 몇몇 Dart 개발자가 명령줄 도구에 dart2native를 실험해 보고 있어요.

  • 인기 CSS 확장 도구인 SASS 팀의 Natalie는, 자신들의 Dart 기반 SASS 구현을 dart2native로 컴파일하도록 전환한 뒤 C++ 기반 구현인 LibSass와 성능이 경쟁력 있게 됐다고 보고했어요.
  • Dart DevRel 팀의 Filip은 자기 linkchecker 도구를 dart2native로 다시 컴파일했고, 작은 사이트를 확인할 때 27배 빨라졌다고 해요.

dart:ffi로 C 코드와 상호 운용하기

네이티브 앱은 종종 주변 운영체제의 네이티브 기능에 접근해야 해요. 이런 시스템 API는 보통 네이티브 C 기반 라이브러리로 노출되어 있고, Dart는 Dart 2.5에서 프리뷰로 출시한 C 인터럽 메커니즘인 dart:ffi를 통해 이런 라이브러리와 상호 운용하는 것을 지원해요. dart2native 컴파일러는 dart:ffi와 호환되므로, 이것을 사용하는 네이티브 Dart 앱을 만들고 컴파일할 수 있어요.

팀원 한 명이 최근 dart:ffi로 콘솔 앱 개발용 dart_console 라이브러리를 만들었어요. 창 크기 얻기, 커서 위치 읽기·설정, 색상 관리, 키와 컨트롤 시퀀스 읽기 같은 기능이 있죠. dart:ffi를 사용할 수 있다는 건 콘솔 앱을 위한 아주 강력한 언어라는 뜻이에요.

kilo: 500줄이 채 안 되는 Dart 코드로 만든 7MB 코드 에디터

Dart 핵심 라이브러리, dart:ffi, dart_console 라이브러리를 사용하면 꽤 흥미로운 콘솔 앱을 만들 수 있어요. dart_console 패키지에는 kilo의 완전한 데모가 들어 있는데, kilo는 약 500줄의 Dart 코드로 작성된 콘솔 텍스트 에디터예요. kilo라는 이름은 그 기원인 kilo.c에서 나왔어요. kilo.c는 약 1000줄의 C 구현이었죠.

dart2native 컴파일러로 이걸 쉽게 패키징할 수 있고, 결과적으로 7MB 자립형 코드 에디터가 나와요. 아래는 에디터를 컴파일하고, 컴파일된 에디터로 자기 자신의 소스 코드를 편집해서 버그를 고치는 데모예요.

dart2native로 서비스 만들기

dart2native 컴파일러의 또 다른 잠재적 용도는 작은 서비스예요 — 예를 들어 Flutter로 작성된 프론트엔드 앱을 지원하는 백엔드 같은 거죠. 최근 몇 년간 떠오르는 트렌드는 서버리스 컴퓨팅(serverless computing)에서 실행되는 서비스예요. 이는 완전히 관리되고 자동으로 확장되는 서비스로, 0으로 확장(실행 안 함)까지 포함해서 확장과 축소가 자동으로 이뤄져요. 실제 실행 중일 때만 과금되기 때문에 비용을 크게 낮출 잠재력이 있죠. Google Cloud는 Cloud Run으로 서버리스 컴퓨팅을 제공해요.

서버리스 백엔드에서는 서비스가 빠르게 시작하는 것이 중요해요. 전통적으로 Dart 기반 서비스는 JIT(just-in-time) 컴파일러로 실행됐지만, JIT 기반 실행은 코드가 컴파일되고 워밍업되어야 시작할 수 있기 때문에 시작 지연(latency)이 높아요. 서비스 코드를 AOT로 네이티브 코드에 컴파일하면 이 지연을 피하고 바로 실행을 시작할 수 있어요. 게다가 네이티브 코드로는 디스크 공간이 작고 자립형인 Dart 서비스를 만들어, Dart 서비스가 실행되는 컨테이너 크기를 크게 줄일 수 있어요. Dart 개발자 Paul Mundt가 최근 dart2native 컴파일러 사용 경험을 문서화했는데, Docker 이미지 크기를 JIT 컴파일 코드의 220MB에서 네이티브 코드의 20MB로 91%나 줄일 수 있었대요! 서버사이드 앱과 패키지에 대한 자세한 내용은 우리 문서를 참고하세요.

사용 가능 여부

dart2native 컴파일러는 Dart SDK 버전 2.6부터 사용할 수 있고, 오늘부터 dart.dev/get-dart에서 내려받을 수 있어요. SDK를 설치하면 bin/ 디렉토리와 PATH 안에서 새 컴파일러를 볼 수 있어요. Dart.dev에 더 많은 문서가 있어요.

Flutter를 통해 Dart SDK를 받는 경우, 현재 Flutter 빌드는 dart2native 지원이 불완전하다는 점을 참고하세요. dart.dev/get-dart에서 Dart 2.6 SDK를 설치하길 권장해요.

알려진 제한 사항

dart2native 컴파일러의 초기 버전에는 몇 가지 알려진 제한 사항이 있어요. 아래에 나열할게요. 어떤 이슈가 중요한지 GitHub 이슈 트래커의 이슈에 "thumbs up"을 달아 알려줄 수 있어요.

  • 크로스 컴파일 미지원 (이슈 28617): dart2native 컴파일러는 실행 중인 운영체제용 머신 코드만 만들 수 있어요. 그래서 세 운영체제 모두용 실행 파일을 만들려면 macOS, Windows, Linux에서 각각 컴파일러를 세 번 실행해야 해요. 그렇게 하는 한 가지 방법은 세 운영체제를 모두 지원하는 CI(지속적 통합) 제공자를 사용하는 거예요.
  • 서명 미지원 (이슈 39106): 생성된 실행 파일은 codesign, signtool 같은 표준 서명 도구와 호환되지 않는 형식을 사용해요.
  • dart:mirrorsdart:developer 미지원 (Dart 핵심 라이브러리 참고).

Dart 2.6의 기타 변경 사항

Dart SDK 버전 2.6에는 또 몇 가지 다른 변경 사항이 있어요.

우리는 Dart 2.5에서 C 인터럽 메커니즘인 dart:ffi의 프리뷰를 출시했어요. Dart 2.6에는 새 버전의 dart:ffi가 들어 있어요. 이 새 버전에는 API를 더 쓰기 쉽게 하고, 더 많은 타입 안전성을 제공하며, 메모리에 편리하게 접근할 수 있도록 하는 여러 breaking API 변경이 있어요. 자세한 내용은 Dart 2.6 체인지로그를 참고하세요. 이러한 변경과 함께 dart:ffi는 beta로 승격하고, 앞으로 API 변경이 훨씬 드물어지고 전반적으로 안정성이 높아질 거라고 기대해요. 계속해서 이슈 트래커로 피드백을 주세요.

Dart 2.6에는 흥미로운 새 언어 기능인 확장 메서드(extension methods)의 프리뷰도 포함되어 있어요. 이 기능을 완성하기 위해 아직 약간의 다듬기와 도구 작업이 남아 있지만, 다음 Dart SDK 버전에서 공식적으로 출시하길 기대해요. 그때 확장 메서드에 대해 훨씬 더 많은 이야기를 나누게 될 거예요. 지금은 이 기능 뒤의 설계 고려 사항을 읽어볼 수 있어요.

다음 단계

Dart 2.6 SDK를 내려받고(dart.dev/get-dart), dart2native로 멋진 걸 만들고, 그다음 우리에게 알려 주세요. 세부 사항을 공유하고 싶다면 이 글 맨 아래에 답글을 남겨 주세요. 여러분이 무엇을 만들지 기대돼요!

더 알아보기