패키지 의존성의 개념

Dart에서 패키지 의존성 관리는 프로젝트가 다른 라이브러리나 패키지에 의존하는 경우에 필요한 작업이다. Dart 프로젝트는 여러 개의 외부 패키지를 필요로 할 수 있으며, 이 패키지들은 프로젝트의 동작에 중요한 역할을 한다. Dart 생태계에서는 pubspec.yaml 파일을 통해 이러한 패키지들을 관리한다.

pubspec.yaml 파일은 프로젝트의 메타데이터와 의존성을 정의하는 핵심 파일로, 여기에는 다음과 같은 정보가 포함된다:

패키지 의존성 관리를 효율적으로 하기 위해서는 버전 관리를 잘 설정하고, 각 패키지가 서로 충돌하지 않도록 해야 한다.

버전 규칙과 호환성

Dart는 패키지 의존성 관리에서 세맨틱 버전 관리(Semantic Versioning, SemVer)를 따른다. SemVer은 패키지의 버전을 세 부분으로 나누어 관리한다:

\text{Version} = \text{Major}.\text{Minor}.\text{Patch}

Dart에서 버전 번호 앞에 다음과 같은 제약을 사용할 수 있다:

pubspec.yaml 파일 예시

다음은 pubspec.yaml 파일의 예시이다. 여기서 프로젝트가 특정 패키지들에 의존하고 있음을 확인할 수 있다:

name: my_project
description: A sample Dart project
version: 1.0.0
environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  http: ^0.13.0
  json_annotation: ^4.0.0

dev_dependencies:
  build_runner: ^2.0.0
  json_serializable: ^4.1.0

위 파일에서 dependencies 항목은 프로젝트에서 사용되는 패키지들의 목록을 나타내며, dev_dependencies는 개발 환경에서만 필요한 패키지를 정의한다.

이와 같은 구조는 패키지 간 의존성을 명확하게 정의하며, 프로젝트를 빌드하거나 배포할 때 일관성 있는 환경을 유지하는 데 도움을 준다. 특히, build_runner와 같은 패키지는 코드 생성이나 빌드 프로세스를 자동화하는 데 사용되며, 일반적으로 개발 단계에서만 필요하다.

의존성 충돌 해결

프로젝트가 여러 패키지를 의존하게 될 때, 동일한 패키지의 서로 다른 버전이 요구될 수 있다. 이를 의존성 충돌이라고 하며, Dart에서는 이러한 충돌을 해결하기 위해 pub resolve 메커니즘을 사용한다. pub get 명령을 실행하면 Dart는 pubspec.lock 파일을 생성하여 실제로 사용되는 패키지 버전들을 고정시킨다.

Dart에서 의존성 충돌을 해결하는 방법에는 두 가지가 있다:

  1. 버전 제약을 완화: 의존하는 패키지의 버전 범위를 넓게 설정하여 충돌을 피할 수 있다. 예를 들어, 특정 패키지의 버전을 ^1.0.0에서 ^1.0.0 <=1.2.3으로 확장할 수 있다.
  2. 패키지 업데이트: 호환되지 않는 버전의 패키지를 최신 버전으로 업데이트하여 충돌을 해결할 수 있다. 이를 통해 최신 API를 사용하면서 호환성을 유지할 수 있다.

pubspec.lock 파일

pubspec.lock 파일은 pubspec.yaml 파일의 의존성을 기반으로 Dart 패키지 관리자(pub)가 해결한 실제 패키지 버전을 기록하는 파일이다. 이 파일은 프로젝트의 의존성 트리를 고정된 상태로 유지하여, 동일한 패키지 버전을 사용하도록 보장한다. pubspec.lock 파일이 없다면, 다른 개발자가 동일한 코드를 실행할 때 패키지 버전이 달라질 수 있어, 예기치 않은 버그나 비호환 문제가 발생할 수 있다.

pubspec.lock 파일의 주요 목적은 다음과 같다:

pub getpub upgrade 명령어

Dart에서 패키지 의존성을 관리할 때 자주 사용하는 두 가지 명령어가 있다: pub getpub upgrade.

pub get

pub get 명령어는 pubspec.yaml 파일을 기반으로 필요한 패키지를 설치하고, 그 패키지들의 정확한 버전 정보를 pubspec.lock 파일에 기록한다. 이 명령어는 일반적으로 프로젝트를 처음 시작할 때 또는 새로운 패키지를 추가한 후 실행한다. pub get은 이미 pubspec.lock에 기록된 버전 정보를 우선적으로 사용하여, 의존성 버전이 변하지 않도록 보장한다.

$ dart pub get

pub upgrade

pub upgrade 명령어는 패키지들의 버전을 최신으로 업데이트한다. 만약 특정 패키지가 새로운 기능이나 버그 수정을 포함한 버전을 출시했다면, 이 명령어를 통해 의존성 목록을 최신 상태로 유지할 수 있다. pub upgrade는 기존의 pubspec.lock 파일을 무시하고, 가능한 최신 버전으로 패키지들을 다시 해결한다.

$ dart pub upgrade

버전 충돌 해결 시나리오

다음과 같은 예시를 통해 버전 충돌을 해결하는 과정을 살펴보자. 만약 두 개의 패키지가 서로 다른 버전의 http 패키지에 의존하고 있다면, Dart 패키지 관리자는 이를 해결하려고 시도할 것이다.

이러한 상황에서는, 두 패키지가 모두 만족할 수 있는 호환 가능한 버전을 찾는 것이 중요하다. Dart의 의존성 해결 알고리즘은 가능한 경우 두 버전을 모두 충족할 수 있는 최적의 버전을 찾아낸다. 만약 호환 가능한 버전이 존재하지 않으면, 에러가 발생하고 개발자는 의존성을 수정해야 한다.

dev_dependencies와 의존성 분리

프로젝트의 의존성은 크게 두 가지로 분류된다:

  1. dependencies: 애플리케이션 코드에서 실제로 사용되는 패키지들
  2. dev_dependencies: 테스트나 빌드 도구와 같은 개발 단계에서만 사용되는 패키지들

예를 들어, test 패키지와 같은 도구는 애플리케이션이 배포될 때는 필요하지 않지만, 개발 중에는 필수적이다. 이러한 개발 의존성은 dev_dependencies 섹션에 따로 정의할 수 있다. 이렇게 분리함으로써, 애플리케이션 배포 시 불필요한 패키지들을 포함하지 않도록 최적화할 수 있다.

pubspec.yaml의 의존성 제약 설정

pubspec.yaml 파일에서 의존성을 설정할 때, Dart에서는 다양한 방식으로 의존성을 제어할 수 있다. Dart 프로젝트는 외부 패키지뿐만 아니라 로컬 패키지나 Git에서 가져온 패키지, 그리고 패키지 아카이브에서 직접 설치한 패키지를 사용할 수 있다. 각 의존성은 아래와 같은 방식으로 정의된다:

패키지 의존성

가장 일반적인 방법은 pub.dev에서 제공하는 패키지를 dependencies에 추가하는 것이다. 패키지는 버전 제약과 함께 추가되며, pub가 이를 자동으로 다운로드하고 관리한다.

dependencies:
  http: ^0.13.0
  json_annotation: ^4.0.0

Git 의존성

특정 패키지를 Git 저장소에서 직접 가져오고 싶을 때는, Git URL을 사용하여 의존성을 추가할 수 있다. Git 의존성은 저장소 URL뿐만 아니라 특정 브랜치나 태그, 커밋 해시로도 지정할 수 있다.

dependencies:
  my_package:
    git:
      url: https://github.com/username/my_package.git
      ref: master

로컬 패키지 의존성

로컬 디렉토리에서 패키지를 의존성으로 추가할 수도 있다. 프로젝트 내부의 다른 모듈을 테스트하거나 공유할 때 사용된다.

dependencies:
  my_local_package:
    path: ../my_local_package

의존성 관리 자동화 도구

의존성 관리는 Dart 생태계에서 자동화된 도구로 더욱 효율적으로 처리할 수 있다. 이러한 도구는 패키지 관리뿐만 아니라 코드 빌드 및 테스트에도 도움이 된다.

$ dart run build_runner build

이러한 도구를 사용하면 수작업으로 패키지를 설치하거나 관리할 필요 없이, 프로젝트에서 사용하는 패키지들을 자동으로 관리할 수 있다. 이를 통해 개발자는 더 빠르고 일관된 개발 환경을 유지할 수 있다.

Pub 캐시 관리

Pub는 패키지를 설치할 때 캐시 디렉토리를 사용하여 동일한 패키지를 여러 번 다운로드하지 않도록 한다. 기본적으로 ~/.pub-cache 경로에 패키지가 저장되며, 이는 동일한 패키지를 사용하는 여러 프로젝트가 있을 때 다운로드 시간을 절약해 준다. 때로는 캐시된 패키지에 문제가 발생하거나, 버전 충돌이 있을 경우 캐시를 지워야 할 수도 있다.

캐시를 삭제하는 명령어는 다음과 같다:

$ dart pub cache clean

이 명령어는 모든 캐시된 패키지를 삭제하며, 이후 프로젝트에서 다시 필요한 패키지를 다운로드하게 된다.

의존성 감사와 보안

의존성 관리에서 중요한 부분 중 하나는 외부 패키지의 보안이다. Dart 생태계에서는 패키지의 안전성을 보장하기 위해 몇 가지 툴을 제공한다. 예를 들어, 패키지가 신뢰할 수 없는 소스에서 다운로드된 경우, 경고가 발생하며 개발자가 이를 확인할 수 있도록 한다.

또한 pub.dev에서는 각 패키지에 대해 Dart 패키지 스코어를 제공하여, 패키지의 품질과 유지 관리 상태를 점검할 수 있다. 이러한 점수는 패키지의 최신 버전 여부, 문서화 수준, 그리고 코드의 품질을 기반으로 평가된다.

보안적인 관점에서, 패키지를 사용할 때는 반드시 최신 버전으로 유지하고, 가능하면 의존성을 주기적으로 감사하여 문제를 예방하는 것이 중요하다.