Tuesday, March 12, 2019

Flutter와 Dart SDK: 버전 불일치의 원인과 올바른 업데이트 방법

Flutter는 구글이 개발한 오픈소스 UI 소프트웨어 개발 키트로, 하나의 코드베이스를 사용하여 모바일, 웹, 데스크톱 애플리케이션을 만들 수 있는 강력한 프레임워크입니다. Flutter의 핵심에는 효율적이고 현대적인 프로그래밍 언어인 Dart가 자리 잡고 있습니다. Flutter 개발 여정을 시작하거나, 기존 프로젝트를 유지보수할 때 개발자들은 필연적으로 자신의 개발 환경이 최신 상태인지, 모든 도구가 올바르게 설정되었는지 확인하는 과정을 거칩니다. 이때 가장 먼저 사용하는 명령어가 바로 flutter doctor입니다. 이 명령어는 Flutter 개발에 필요한 모든 요소들이 정상적으로 설치되고 구성되었는지 진단해주는 아주 유용한 도구입니다.

하지만 많은 개발자들이 이 과정에서 혼란스러운 상황을 마주하곤 합니다. 바로 Flutter가 사용하고 있다고 표시하는 Dart의 버전 문제입니다. 분명 최신 버전의 Flutter를 설치했고, Dart의 새로운 기능들을 사용하기 위해 버전을 확인했는데, flutter doctor가 보여주는 Dart 버전이 예상보다 낮게 나오는 경우가 빈번하기 때문입니다. 예를 들어 Dart 공식 홈페이지에서는 버전 3.3이 최신이라고 발표했는데, 내 터미널에서는 여전히 3.1.5와 같은 이전 버전이 표시되는 식입니다. 이런 상황에 부딪힌 개발자는 자연스럽게 '내 Dart SDK가 오래된 것인가?', '수동으로 업데이트해야 하나?' 와 같은 고민에 빠지게 됩니다. 이 글에서는 이러한 Flutter와 Dart 버전 불일치 문제가 왜 발생하는지 그 근본적인 원인을 파헤치고, 개발자들이 혼란을 겪지 않고 올바른 방법으로 Dart 버전을 관리할 수 있는 명확한 전략을 제시하고자 합니다.

혼란의 시작: `flutter doctor`가 보여주는 '낯선' Dart 버전

이 문제를 겪는 개발자의 시나리오를 구체적으로 따라가 보겠습니다. 당신은 Flutter의 새로운 기능과 성능 개선을 활용하기 위해 최신 버전의 Flutter SDK를 다운로드하여 설치했습니다. 설치 후, 개발 환경 설정을 마무리하기 위해 터미널을 열고 다음 명령어를 실행합니다.


flutter doctor

결과는 대부분의 항목에서 녹색 체크 표시와 함께 'No issues found!'라는 만족스러운 메시지를 보여줄 것입니다. 그런데 여기서 Dart 버전을 유심히 살펴보게 됩니다.


[✓] Flutter (Channel stable, 3.16.0, on macOS 14.1.1 23B81 darwin-arm64, locale ko-KR)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.84.2)
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

위 예시에서는 Flutter 버전만 표시되지만, flutter --version 명령어를 사용하면 Dart 버전도 함께 확인할 수 있습니다.


$ flutter --version
Flutter 3.16.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision db7116b461 (6 weeks ago) • 2023-11-17 15:37:52 -0800
Engine • revision 8569b7194f
Tools • Dart 3.2.0 • DevTools 2.28.2

여기서는 Dart 3.2.0 버전이 함께 표시됩니다. 하지만 만약 Dart 공식 블로그에서 Dart 3.3의 새로운 기능(예: 확장 타입)에 대한 발표를 보고 Flutter를 업데이트했는데도, 여전히 Dart 3.2.x 버전이 표시된다면 어떻게 될까요? 개발자는 당연히 무언가 잘못되었다고 생각하게 됩니다. 'Flutter는 최신 버전으로 업데이트되었는데, 왜 Dart는 그대로지?' 라는 의문이 생기는 것입니다.

혹은 시스템에 별도로 Dart SDK를 설치한 경우 더 큰 혼란이 발생할 수 있습니다. 예를 들어, Homebrew와 같은 패키지 매니저를 사용하여 최신 Dart SDK(가령, 3.3.0)를 설치했다고 가정해 봅시다. 터미널에서 dart --version을 실행하면 다음과 같이 나옵니다.


$ dart --version
Dart SDK version: 3.3.0 (stable) (Wed Feb 14 13:30:00 2024 +0000) on "macos_arm64"

하지만 flutter doctorflutter --version은 여전히 Flutter에 묶인 이전 버전(예: 3.2.0)을 가리킵니다. 시스템 전역에는 최신 버전의 Dart가 설치되어 있는데 왜 Flutter는 이를 사용하지 않는 것일까요? 이러한 불일치는 개발자에게 좌절감을 안겨주고, 문제 해결을 위해 불필요한 시간을 낭비하게 만드는 주된 원인이 됩니다.

왜 이런 현상이 발생할까? Flutter와 Dart의 특별한 관계

이러한 버전 불일치 현상은 버그나 설정 오류가 아닙니다. 이는 Flutter 프레임워크가 설계된 방식과 Dart SDK를 관리하는 전략에 깊숙이 뿌리내린, 의도된 결과입니다. 이 '특별한 관계'를 이해하면 모든 혼란이 명쾌하게 해결됩니다.

Flutter에 내장된 Dart SDK (Bundled Dart SDK)

가장 핵심적인 사실은 **Flutter는 시스템에 설치된 Dart SDK를 사용하지 않는다**는 것입니다. 대신, Flutter SDK는 자체적으로 완벽하게 호환되고 테스트된 특정 버전의 Dart SDK를 **내장**하고 있습니다. Flutter를 설치하면, 설치된 폴더 내부에 이 내장 Dart SDK가 함께 포함되어 있습니다.

이 내장 Dart SDK의 위치는 보통 다음 경로에서 찾을 수 있습니다.


[FLUTTER_SDK_PATH]/bin/cache/dart-sdk/

[FLUTTER_SDK_PATH]는 당신의 컴퓨터에 Flutter SDK가 설치된 경로입니다. 이 디렉토리 안을 살펴보면, 우리가 일반적으로 알고 있는 Dart SDK의 bin, lib, include 등의 폴더 구조를 그대로 발견할 수 있습니다. Flutter의 flutter, dart, pub 등의 명령어들은 시스템의 PATH 환경 변수에 등록된 Dart를 찾는 것이 아니라, 바로 이 내장된 경로의 Dart 실행 파일을 직접 호출하여 사용합니다.

터미널에서 which (macOS/Linux) 또는 where (Windows) 명령어를 사용해 보면 이 사실을 명확히 확인할 수 있습니다.


# 시스템 전역에 설치된 dart의 위치 (Homebrew로 설치한 경우)
$ which dart
/opt/homebrew/bin/dart

# Flutter가 사용하는 dart의 위치
$ which flutter dart
/Users/myuser/development/flutter/bin/dart

보는 바와 같이, flutter 명령어와 함께 사용하는 dart 명령어는 Flutter SDK 내부의 경로를 가리킵니다. 이것이 바로 당신이 시스템의 Dart를 아무리 최신 버전으로 업데이트해도 Flutter 프로젝트에는 아무런 영향을 주지 않는 이유입니다. Flutter는 자신만의 독립적이고 고립된 Dart 환경 위에서 동작하도록 설계되었습니다.

안정성을 위한 선택: 버전 피닝(Version Pinning)

그렇다면 Flutter는 왜 이렇게 번거로운 방식을 택했을까요? 그 이유는 단 하나, **'안정성'** 때문입니다.

Flutter 프레임워크는 수많은 요소들이 유기적으로 결합된 복잡한 시스템입니다. 프레임워크 자체(UI 위젯, 애니메이션 등), 저수준의 렌더링을 담당하는 그래픽 엔진(Skia), 그리고 이 모든 것을 구동하는 Dart 언어와 VM(Virtual Machine)이 긴밀하게 상호작용합니다. 만약 이 중 하나라도 호환되지 않는 버전으로 임의 변경된다면, 전체 시스템이 불안정해지거나 예측 불가능한 오류를 일으킬 수 있습니다.

예를 들어, Dart 언어의 특정 내부 API가 변경되었는데 Flutter 프레임워크는 아직 그 변경사항에 대응하지 못한 상태라고 가정해 봅시다. 만약 개발자가 임의로 최신 Dart SDK를 사용하도록 시스템을 변경한다면, 앱이 컴파일되지 않거나, 실행 중에 치명적인 오류와 함께 멈춰버리는 끔찍한 상황이 발생할 수 있습니다.

이러한 문제를 원천적으로 차단하기 위해 Flutter 팀은 **'버전 피닝(Version Pinning)'** 전략을 사용합니다. 특정 Flutter 버전(예: 3.16.0)을 릴리스할 때, 해당 버전과 가장 완벽하게 호환되고 모든 테스트를 통과한 특정 Dart SDK 버전(예: 3.2.0)을 '고정(pinning)'하여 함께 묶어서 배포하는 것입니다. 이렇게 함으로써 전 세계 수백만 명의 Flutter 개발자들은 모두 동일하고 예측 가능한 개발 환경에서 작업할 수 있게 되며, "제 컴퓨터에서는 됐는데, 다른 팀원 컴퓨터에서는 안 돼요"와 같은 전형적인 '환경 문제'를 최소화할 수 있습니다. 이는 대규모 프로젝트와 협업 환경에서 절대적으로 중요한 요소입니다.

버전 번호의 이면: 보이는 것과 실제 기능의 차이

그렇다면, 왜 최신 Flutter를 설치했음에도 불구하고 Dart 버전 번호가 기대보다 낮게 표시되는 걸까요? 여기에는 대규모 소프트웨어 프로젝트의 릴리스 관리와 관련된 미묘한 점이 숨어있습니다.

때로는 Flutter 팀이 차기 Dart 메이저/마이너 버전(예: 3.3.0)에 포함될 예정인 특정 기능이나 중요한 버그 수정사항을 현재 안정화된 Dart 버전(예: 3.2.x)에 **'백포팅(backporting)'**하여 먼저 적용할 수 있습니다. 즉, Dart SDK의 공식 버전 번호는 3.2.x로 표시되더라도, 실제로는 3.3.0의 일부 기능이나 패치가 이미 포함되어 있을 수 있다는 의미입니다.

이러한 결정은 안정성을 최우선으로 고려하기 때문에 내려집니다. 완전히 새로운 Dart 버전으로 넘어가기 전에, Flutter와의 호환성에 영향을 미치지 않는 선에서 중요한 개선 사항들을 선별적으로 가져와 현재 안정 채널의 사용자들에게 먼저 제공하는 것입니다. 이는 과거 Dart 2.1 버전이 사실상 2.2 버전의 기능을 포함했던 사례에서도 찾아볼 수 있는, Flutter 팀의 유서 깊은 관행입니다.

따라서 개발자는 flutter doctor가 보여주는 Dart 버전 '숫자'에만 집착할 필요가 없습니다. 그보다는 내가 사용 중인 Flutter 버전에 어떤 기능들이 포함되어 있는지를 공식 릴리스 노트나 블로그를 통해 확인하는 것이 더 중요합니다. 버전 번호는 단지 식별자일 뿐, 그 안에 담긴 실제 기능과 안정성은 Flutter 팀에 의해 철저히 관리되고 보장됩니다.

가장 흔한 실수: Dart를 직접 업데이트하려는 시도

Flutter와 Dart의 이러한 특별한 관계를 이해하지 못하면, 개발자들은 필연적으로 잘못된 해결책을 시도하게 됩니다. 바로 Flutter가 사용하는 Dart를 직접, 수동으로 업데이트하려는 시도입니다. 이는 효과가 없을 뿐만 아니라, 개발 환경을 더욱 복잡하게 만들 수 있습니다.

개발자들이 흔히 시도하는 잘못된 방법들은 다음과 같습니다.

  • Homebrew, Chocolatey, Scoop 등 패키지 매니저 사용: brew upgrade dartchoco upgrade dart-sdk 같은 명령어로 시스템 전역의 Dart를 업데이트합니다. 앞서 설명했듯이, 이는 Flutter가 참조하는 내장 Dart SDK가 아니므로 아무런 효과가 없습니다.
  • PATH 환경 변수 조작: 시스템의 PATH 환경 변수를 수정하여 새로 설치한 Dart SDK의 경로를 Flutter SDK 경로보다 앞에 두려고 시도합니다. 이 또한 대부분의 경우 효과가 없습니다. flutter 스크립트는 절대 경로를 사용하거나 내부 로직을 통해 자신의 내장 Dart를 찾아내기 때문에, 시스템 PATH의 우선순위를 무시합니다.
  • Flutter의 Dart SDK를 직접 교체: 가장 위험한 방법으로, [FLUTTER_SDK_PATH]/bin/cache/dart-sdk/ 폴더의 내용을 다운로드한 최신 Dart SDK로 덮어쓰려는 시도입니다. 이는 거의 100% 확률로 Flutter tool 자체의 오작동, 컴파일 실패, 런타임 에러 등 심각한 문제를 야기합니다. 각 파일의 미세한 차이가 Flutter 엔진과의 호환성을 깨뜨릴 수 있기 때문입니다.

이러한 시도들은 모두 Flutter의 핵심 설계 철학에 반하는 행동입니다. Flutter의 안정성은 '버전 피닝'이라는 약속 위에 세워져 있으며, 이 약속을 임의로 깨뜨리려는 시도는 결국 더 큰 문제로 이어질 뿐입니다.

올바른 접근법: Flutter를 통해 Dart를 관리하는 방법

그렇다면 정답은 무엇일까요? 해답은 놀랍도록 간단하며, Flutter가 제공하는 공식 워크플로우 안에 이미 완벽하게 준비되어 있습니다. Flutter 생태계 안에서 Dart를 다루는 올바른 방법은 다음과 같습니다.

정답은 `flutter upgrade`

**Flutter 개발 환경에서 Dart SDK를 업데이트하는 유일하고 올바른 방법은 Flutter 자체를 업그레이드하는 것입니다.**

터미널에서 다음 명령어를 실행하는 것만으로 모든 것이 해결됩니다.


flutter upgrade

이 명령어는 단순히 Flutter 프레임워크의 코드만 업데이트하는 것이 아닙니다. flutter upgrade가 수행하는 작업은 다음과 같습니다.

  1. 현재 사용 중인 채널(stable, beta 등)의 최신 Flutter SDK 버전을 원격 저장소에서 확인합니다.
  2. 최신 버전의 Flutter SDK를 다운로드하여 기존 버전을 교체합니다.
  3. 바로 이 과정에서, **새로운 Flutter 버전에 고정(pinning)된 새로운 버전의 Dart SDK를 함께 다운로드하여** bin/cache/dart-sdk 폴더에 설치합니다.
  4. Flutter 툴 자체를 새로운 Dart SDK로 다시 컴파일하는 등의 후속 작업을 자동으로 처리합니다.

결과적으로, flutter upgrade 한 번으로 Flutter와 Dart의 완벽한 조합을, Flutter 팀이 의도하고 완벽하게 테스트한 바로 그 조합을 얻게 되는 것입니다. 개발자는 아무것도 신경 쓸 필요 없이, 가장 안정적이고 최적화된 최신 개발 환경을 손쉽게 구성할 수 있습니다.

현재 버전 확인하기: `flutter --version`

내 개발 환경의 Flutter와 Dart 버전에 대한 가장 정확한 정보(source of truth)는 flutter doctor나 시스템의 dart --version이 아닌, 바로 flutter --version 명령어입니다. 이 명령어는 Flutter가 현재 '실제로' 사용하고 있는 도구들의 버전을 명확하게 보여줍니다.


$ flutter --version
Flutter 3.19.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision bae5e49429 (3 days ago) • 2024-02-13 17:46:18 -0800
Engine • revision 048d44c2c5
Tools • Dart 3.3.0 • DevTools 2.31.1

위 출력은 Flutter 3.19.0 버전이 Dart 3.3.0 버전을 사용하고 있음을 명확히 알려줍니다. 새로운 Dart 기능을 사용하고 싶다면, 먼저 flutter upgrade를 실행한 후 이 명령어로 Dart 버전이 원하는 버전으로 올라갔는지 확인하는 것이 올바른 순서입니다.

프로젝트의 언어 버전 명시: `pubspec.yaml`의 역할

여기서 한 가지 더 이해해야 할 중요한 개념은 프로젝트의 pubspec.yaml 파일에 있는 SDK 제약 조건입니다.


# pubspec.yaml

name: my_awesome_app
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.3.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  # ... other dependencies

environment.sdk 필드는 이 프로젝트가 **요구하는 Dart 언어의 최소/최대 버전**을 명시합니다. 이는 "이 프로젝트를 컴파일하려면 최소한 Dart 3.3.0 버전의 '언어 기능'을 지원하는 Dart SDK가 필요하다"는 의미입니다. 이는 앞서 설명한 Flutter에 내장된 '툴체인 버전'과는 미묘하게 다른 개념입니다.

  • 툴체인 버전 (Toolchain Version): flutter --version으로 확인하는, 실제로 설치된 Dart SDK의 버전입니다. 컴파일러, VM, 포매터 등을 포함합니다.
  • 언어 버전 (Language Version): pubspec.yaml에 명시하는, 프로젝트 코드에서 사용할 수 있는 Dart 언어의 문법, 기능 집합입니다.

예를 들어, Dart 3.3에 새로 도입된 '확장 타입(Extension Types)' 기능을 코드에 사용하고 싶다면, pubspec.yaml의 SDK 제약 조건을 '>=3.3.0 <4.0.0'으로 설정해야 합니다. 만약 이 제약 조건이 '>=3.2.0 <4.0.0'으로 되어 있다면, 실제 설치된 Dart SDK가 3.3.0이더라도 IDE나 컴파일러는 해당 기능을 사용할 수 없도록 제한합니다. 이는 하위 호환성을 보장하고 프로젝트가 특정 언어 기능 세트에 의존하고 있음을 명확히 하기 위한 중요한 장치입니다.

따라서, 새로운 Dart 언어 기능을 사용하는 워크플로우는 다음과 같이 정리할 수 있습니다. 1. flutter upgrade를 실행하여 최신 Flutter와 그에 맞는 Dart SDK를 설치한다. 2. flutter --version으로 설치된 Dart 툴체인 버전을 확인한다. 3. pubspec.yamlenvironment.sdk 버전을, 사용하려는 기능이 포함된 버전으로 상향 조정한다. 4. flutter pub get을 실행하여 변경사항을 프로젝트에 적용한다.

실제 시나리오로 이해하기: Dart 3의 새로운 기능 도입하기

지금까지의 개념들을 하나의 구체적인 시나리오에 적용하여 전체 과정을 완벽하게 이해해 보겠습니다.

**목표:** Dart 3.0에서 도입된 '패턴(Patterns)'과 '레코드(Records)' 기능을 사용하여 코드를 리팩토링하고 싶다.

  1. 현재 상태 확인:
    먼저 현재 개발 환경을 점검합니다. flutter --version을 실행하니 다음과 같이 나옵니다.
    
        $ flutter --version
        Flutter 3.10.0 • ...
        Tools • Dart 3.0.0 • ...
        
    현재 프로젝트의 pubspec.yaml 파일은 다음과 같습니다.
    
        environment:
          sdk: '>=2.19.0 <3.0.0'
        
    이 상태에서 패턴이나 레코드 문법을 사용하면 IDE에서 즉시 오류를 표시할 것입니다. SDK 제약 조건이 Dart 3.0 미만이기 때문입니다.
  2. SDK 제약 조건 업데이트:
    pubspec.yaml 파일을 열어 Dart 3.0의 기능을 사용하겠다고 명시합니다.
    
        environment:
          sdk: '>=3.0.0 <4.0.0'
        
    파일을 저장하고 flutter pub get을 실행합니다. 이제 프로젝트는 Dart 3.0+ 언어 기능을 사용할 준비가 되었습니다. 현재 설치된 Dart SDK가 3.0.0이므로 이 작업은 성공적으로 완료됩니다.
  3. 최신 Dart 기능(예: Dart 3.3)을 사용하고 싶을 때:
    시간이 흘러, 이제 Dart 3.3의 '확장 타입'을 사용하고 싶어졌습니다. 현재 Dart SDK는 3.0.0이므로 이 기능을 지원하지 않습니다.
    1. 잘못된 시도: brew upgrade dart를 실행. 시스템 Dart만 3.3.0으로 업데이트되고, Flutter의 Dart는 3.0.0에 머물러 있습니다. 아무런 소용이 없습니다.
    2. 올바른 조치: 터미널에서 flutter upgrade를 실행합니다.
  4. 업그레이드 및 확인:
    flutter upgrade가 완료된 후, 다시 버전을 확인합니다.
    
        $ flutter --version
        Flutter 3.19.0 • ...
        Tools • Dart 3.3.0 • ...
        
    이제 Flutter는 Dart 3.3.0 툴체인을 사용하고 있습니다. 마지막으로, pubspec.yaml을 다시 확인하거나 필요시 수정합니다.
    
        environment:
          sdk: '>=3.3.0 <4.0.0'
        
    이제 당신의 프로젝트는 Dart 3.3의 최신 기능인 '확장 타입'을 아무런 문제 없이 사용할 수 있는 완벽한 상태가 되었습니다.

이 시나리오는 Flutter/Dart 버전 관리의 모든 핵심 요소를 보여줍니다. 중요한 것은 항상 Flutter 프레임워크를 중심으로 생각하고, flutter upgrade라는 공식적인 통로를 통해 생태계 전체를 일관성 있게 업데이트하는 것입니다.

결론: Flutter 생태계를 신뢰하고 올바른 워크플로우를 따르자

flutter doctor가 보여주는 Dart 버전과 내가 기대하는 버전 사이의 불일치는 처음 겪는 개발자에게 상당한 혼란과 시간 낭비를 유발할 수 있습니다. 하지만 그 이면에는 Flutter 생태계 전체의 안정성과 예측 가능성을 지키기 위한 깊은 고민과 의도적인 설계가 깔려 있습니다.

이번 글을 통해 우리는 다음의 핵심 사실들을 명확히 이해했습니다.

  • Flutter는 독립적인 Dart SDK를 내장하고 있습니다. 시스템에 설치된 Dart는 Flutter 개발에 직접적인 영향을 주지 않습니다.
  • 안정성을 위한 '버전 피닝'은 핵심 철학입니다. Flutter 팀은 특정 Flutter 버전에 가장 완벽하게 호환되는 Dart 버전을 고정하여 함께 배포합니다.
  • 절대로 Dart를 수동으로 업데이트하려 시도하지 마세요. 이는 Flutter의 안정성을 해치는 지름길이며, 효과도 없습니다.
  • flutter upgrade가 유일한 정답입니다. 이 명령어 하나로 Flutter와 Dart의 가장 이상적인 조합을 손쉽게 얻을 수 있습니다.
  • flutter --version을 신뢰하세요. 내 환경의 실제 툴체인 버전을 확인하는 가장 정확한 방법입니다.
  • pubspec.yaml의 SDK 제약 조건으로 언어 버전을 관리하세요. 이를 통해 프로젝트가 필요로 하는 언어 기능을 명시적으로 제어할 수 있습니다.

결론적으로, Flutter 개발자는 Dart 버전 문제로 더 이상 고민할 필요가 없습니다. Flutter 팀이 제공하는 강력하고 안정적인 생태계를 신뢰하고, flutter upgrade라는 잘 닦인 길을 따라가기만 하면 됩니다. 보이는 버전 번호에 얽매이기보다는, 내가 사용하는 Flutter 버전에서 어떤 기능들을 사용할 수 있는지 공식 문서를 통해 확인하고, 이를 적극적으로 활용하여 더 나은 애플리케이션을 만드는 데 집중하는 것이 훨씬 더 생산적인 접근 방식일 것입니다. 이제 버전 불일치의 미스터리는 풀렸습니다. 올바른 지식으로 무장하고 즐거운 Flutter 개발 여정을 계속해 나가시길 바랍니다.


0 개의 댓글:

Post a Comment