Dart CLI 도구 패키징·배포하기

Dart CLI 도구 패키징·배포하기

Dart CLI 도구는 Pub 패키지 관리자를 통해 배포되는 독립 실행형(standalone) 명령줄 애플리케이션이에요. 나만의 CLI 도구를 만들면 개발자 유틸리티나 빌드 스크립트, 또는 기능이 가득한 데스크톱 콘솔 애플리케이션을 더 넓은 Dart 생태계와 공유할 수 있어요.

출처: Packaging and distributing Dart CLI tools

본문

진입점(entrypoint) 작성하기

사용자가 dart install을 통해 도구를 설치하면, Pub는 패키지의 pubspec.yaml 파일에 정의된 진입점을 찾아요.

먼저 패키지의 bin/ 디렉터리에 Dart 스크립트를 만드세요. 예를 들어 bin/my_tool.dart를 만들게요.

void main(List<String> arguments) {
  print('Hello from my tool!');
}

그런 다음 pubspec.yamlexecutables 섹션에서 이 스크립트를 명령어 이름에 연결해요.

name: my_package
version: 1.0.0
# ... other metadata

executables:
  my_tool: my_tool

여기서 키(my_tool)는 Pub가 사용자의 PATH에 넣어 주는 실행 명령어 이름이고, 값(my_tool)은 bin/my_tool.dart 스크립트에 대응돼요.

dart build cli로 컴파일하기

Dart 3.9부터는 dart build cli 명령어로 CLI 애플리케이션을 명시적으로 컴파일할 수 있어요.

내부적으로 dart install은 자동으로 dart build cli를 호출해서 도구를 위한 AOT 컴파일된 독립 실행형 네이티브 실행 파일을 만들어요.

AOT(Ahead-of-Time) 컴파일은 Dart VM을 부팅하거나 코드를 동적으로 JIT 컴파일하지 않아도 도구가 즉시 시작되게 해 줘요. 그래서 사용자 입장에서 실행 시간이 훨씬 빠르고 일관적이게 돼요.

Dart SDK 접근과 하위 프로세스 실행

dart format, dart test, 코드 생성기 같은 다른 개발자 명령어를 호출하는 도구를 만들 때는, JIT 스냅숏과 AOT 바이너리 사이의 실행 환경 차이를 염두에 두어야 해요.

AOT에서의 Platform.resolvedExecutable 동작

레거시 dart pub global activate(Dart JIT VM 안에서 실행됨)에서는 Platform.resolvedExecutable<dart-sdk>/bin/dart 바이너리를 직접 가리켰어요. 많은 레거시 패키지가 이 경로를 기준으로 SDK 라이브러리를 찾았어요.

// ❌ AOT 컴파일('dart install')에서는 안전하지 않음
final sdkDir = path.dirname(path.dirname(Platform.resolvedExecutable));

dart install(그리고 dart compile exe)에서는 Platform.resolvedExecutable이 컴파일된 애플리케이션 바이너리를 가리켜요(예: ~/.dart/install/app-bundles/<package>/.../bundle/bin/<executable>). Dart SDK 런타임을 가리키는 게 아니에요.

도구가 Platform.resolvedExecutable이나 Platform.executable에 의존한다면 다음과 같은 문제가 생겨요.

  • SDK 조회 실패: 도구의 애플리케이션 번들에는 SDK 컴파일러 산출물이 들어 있지 않아요.
  • 하위 프로세스가 무한히 실행: 두 속성 중 하나를 호출하면 dart 실행 파일 대신 도구 바이너리가 다시 실행돼요.

권장 해결책: package:cli_util

JIT와 AOT 컴파일 양쪽에서 호스트 Dart SDK를 안전하게 찾고 dart 바이너리를 찾으려면 package:cli_util(버전 ^0.6.0 이상)을 사용하세요.

dependencies:
  cli_util: ^0.6.0

package:cli_util은 SDK를 찾기 위해 여러 대체 위치(활성 런타임 경로, DART_SDK 환경 변수, Flutter 번들 SDK를 포함한 시스템 PATH, FLUTTER_ROOT)를 검사해요.

import 'dart:io';
import 'package:cli_util/cli_util.dart';

void runTool() {
// 1. Dart SDK 루트 디렉터리 경로 가져오기 (없으면 null)
final String? sdk = sdkPath;
if (sdk != null) {
  print('Found Dart SDK at: $sdk');
}

// 2. 하위 프로세스 실행용 호스트 `dart` 실행 파일 경로 가져오기
final String? dart = dartExecutable;

if (dart != null) {
// 안전하게 자식 Dart 프로세스 실행
Process.runSync(dart, ['format', '.']);
}
}

빌드 훅 추가하기

CLI 도구가 네이티브 C/C++ 라이브러리에 의존하거나 특정 데이터 애셋을 번들로 묶어야 한다면, 코드 애셋과 빌드 훅을 활용할 수 있어요.

hook/build.dart 스크립트를 작성하면, 사용자가 패키지를 설치할 때 Dart SDK가 네이티브 의존성을 컴파일하거나 다운로드하도록 지시할 수 있어요. dart install은 이러한 훅을 완전히 지원해서, 컴파일된 결과물(동적 라이브러리와 애셋)이 애플리케이션 번들 안에서 AOT 컴파일된 실행 파일 옆에 배치되도록 해요.

배포하기

패키지를 pub.dev에 게시(또는 Git 저장소에 푸시)하면, 사용자는 dart install 명령어로 전역에 설치할 수 있어요.

$ dart install my_package

이 명령어는 의존성을 해석하고, 빌드 훅을 실행하고, 실행 파일을 AOT 컴파일한 다음, 결과 바이너리를 $DART_DATA_HOME/install/bin 디렉터리에 넣어요.

플랫폼에 따라 기본 위치는 다음과 같아요.

플랫폼 기본 경로
macOS $HOME/Library/Application Support/Dart/install/bin
Linux $HOME/.local/share/dart/install/bin (또는 $XDG_DATA_HOME/dart/install/bin)
Windows %LOCALAPPDATA%\Dart\install\bin

pub global 사용자 옮기기

기존 Dart CLI 패키지를 관리하고 있다면, 문서(예: README.md)를 업데이트해서 레거시 dart pub global activate 명령어 대신 dart install을 권장하세요. dart install은 호스트 Dart SDK 버전과 분리된 더 빠른 독립 실행형 애플리케이션을 만들어요.

더 알아보기