의존성 지옥(Dependency hell)은 다른 소프트웨어 패키지의 특정 버전에 의존성(dependencies)을 가지는 소프트웨어 패키지를 설치하려는 일부 소프트웨어 사용자의 좌절감을 나타내는 구어체 용어입니다.
의존성 문제는 여러 패키지가 같은 공유 패키지 또는 라이브러리에 대한 의존성을 가지지만, 그것들이 서로 다른 호환되지 않는 공유 패키지 버전에 종속될 때 발생합니다. 만약 공유 패키지 또는 라이브러리가 단일 버전에만 설치될 수 있으면, 사용자는 의존성 패키지의 최신 또는 이전 버전을 획득함으로써 문제를 해결해야 할 수 있습니다. 이것은, 차례로, 다른 의존성을 깨뜨릴 수 있고 문제를 또 다른 패키지의 집합으로 밀어 넣을 수 있습니다.
Problems
의존성 지옥은 여러 형태를 취합니다:
- Many dependencies : 응용 프로그램은 긴 다운로드, 많은 양의 디스크 공간을 요구하고, 이식성이 매우 높은 많은 라이브러리에 의존합니다(모든 라이브러리는 이미 이식되어 있어 응용 프로그램 자체를 쉽게 이식할 수 있습니다). 그것은 역시 모든 종속성을 찾는 것이 어려울 수 있으며, 이는 저장소를 가짐으로써 수정될 수 있습니다 (아래 참조). 이것은 부분적으로 불가피합니다; 주어진 컴퓨팅 플랫폼 (예를 들어 Java)에 구축된 응용 프로그램에는 해당 플랫폼을 설치되어어 함을 요구하지만, 추가 응용 프로그램은 그것을 요구하지 않습니다. 이것은 응용 프로그램이 큰 라이브러리의 작은 부분을 사용하거나 (이것은 코드 리팩토링으로 해결될 수 있음), 간단한 응용 프로그램이 많은 라이브러리에 의존하면 특히 문제입니다.
- Long chains of dependencies : 만약 app이 앱이 liba에 의존하면, 이것은libb에 의존하고, ..., 이것은 libz에 의존합니다. 만약 의존성이 수동으로 해결되어야 하면, 예를 들어, app을 설치하려고 시도할 때, 사용자에게 먼저 liba를 설치하라는 메시지를 표시하고 liba를 설치하려고 할 때 사용자에게 libb를 설치하라는 메시지가 표시되는, 등이면, 이것은 "많은 의존성"과 다릅니다. 때때로, 어쨌든, 이 긴 의존성의 체인 동안, 같은 패키지의 두 가지 다른 버전이 요구되면 충돌이 발생합니다 (아래 충돌하는 의존성(conflicting dependencies)을 참조하십시오). 이들 긴 종속성 체인은 모든 종속성을 자동으로 해결하는 패키지 관리자를 가짐으로써 해결될 수 있습니다. (모든 종속성을 수동으로 해결하기 위한) 번거로움 이외에, 수동 해결은 의존성 주기 또는 충돌을 감출 수 있습니다.
- Conflicting dependencies : 한 소프트웨어에 대한 의존성을 해결하는 것은 두더지 때리기와 유사한 방식으로 또 다른 소프트웨어의 호환성을 깨뜨릴 수 있습니다. 만약 app1이 libfoo 1.2에 의존하고, app2가 libfoo 1.3에 의존하고, 다른 버전의 libfoo가 동시에 설치될 수 없으면, app1과 app2는 동시에 사용될 수 없습니다 (또는 설치 프로그램이 의존성을 확인하면 설치할 수 없습니다). 가능할 때, 이것은 다른 의존성의 동시 설치를 허용함으로써 해결됩니다. 대안적으로, 그것에 종속된 모든 소프트웨어를 따른 기존 의존성은 새 의존성을 설치하기 위해 제거되어야 합니다. 다른 배포자로부터 패키지를 설치하는 리눅스 시스템의 문제 (이는 권장되지 않거나 작동하는 것으로 가정됨)는 결과적으로 긴 의존성 체인이 수천 개의 패키지가 의존하는 C 표준 라이브러리 (예를 들어 C standard library)의 충돌 버전으로 이어질 수 있다는 것입니다. 이것이 발생하면, 사용자는 모든 그들의 패키지를 제거하라는 메시지를 받을 것입니다.
- Circular dependencies : 만약 application A가 application B에 의존하고 특정 버전없이버전 없이 실행할 수 없지만, application B는, 차례로, application A에 의존하고 특정 버전 없이 실행할 수 없으면, 임의의 응용 프로그램을 업그레이드하는 것은 또 다른 응용 프로그램을 깨뜨릴 것입니다. 이 계획은 분기에서 더 깊을 수 있습니다. 그것이 핵심 시스템에 영향을 미치거나 소프트웨어 자체를 업데이트하면 그 영향은 상당히 클 수 있습니다: 특정 런-타임 라이브러리 (B)가 작동해야 하는 패키지 관리자 (A)는 이 라이브러리 (B)를 다음 버전으로 업그레이드합니다. 잘못된 라이브러리(B) 버전으로 인해, 패키지 관리자 (A)가 이제 손상됩니다 - 따라서 라이브러리 (B)의 롤백 또는 다운그레이드가 가능하지 않습니다. 보통의 해결책은 때때로 임시 환경 내에서 두 응용 프로그램을 모두 다운로드하고 배포하는 것입니다.
- Package manager dependencies : 패키지 관리자 (예를 들어, APT)를 통해 준비된 패키지를 설치하면 의존성 지옥이 발생할 수 있지만, 주요 패키지 관리자가 성숙하고 공식 저장소가 잘 유지 관리되기 때문에 그럴 가능성은 없습니다. 이것은 데비안과 우분투와 같은 주요 파생 패포판의 현재 출시의 경우입니다. 의존성 지옥은, 어쨌든, 패키지 설치 프로그램 (예를 들어, RPM 또는 dpkg)을 통해 직접 패키지를 설치하면 발생할 수 있습니다.
- Diamond dependency : 라이브러리 A가 라이브러리 B와 C에 종속할 때, B와 C 모두 라이브러리 D에 종속되지만, B는 버전 D.1이 요구하고 C는 버전 D.2를 요구합니다. 최종 실행 파일에는 D 버전이 하나만 존재할 수 있으므로 빌드가 실패합니다. Yum과 같은 패키지 관리자는 저장소 패키지 사이에 충돌이 일어나기 쉬우며, CentOS 및 Red Hat Enterprise Linux와 같은 리눅스 배포판에서 의존성 지옥을 초래합니다.
Solutions
- Version numbering : 이 문제에 대한 매우 공통적인 해결책은 표준화된 번호-매기기 시스템을 사용하는 것입니다. 여기서 소프트웨어는 각 버전 (일명 주요 버전)에 대한 특정 숫자와 각 개정판 (일명 보조 버전)에 대한 하위-숫자, 예를 들어: 10.1, 또는 5.7를 사용합니다. 주요 버전은 해당 버전을 사용했던 프로그램이 더 이상 호환되지 않을 때만 변경됩니다. 보조 버전은 다른 소프트웨어가 작동하는 것을 방해하지 않는 간단한 개정으로도 변경될 수 있습니다. 이와 같은 경우 소프트웨어 패키지는 특정 주요 버전과 보조 버전 (특정 보조 버전 이상)이 있는 구성 요소를 간단히 요청할 수 있습니다. 따라서 보조 버전이 변경되더라도 계속 작동하고 의존성이 성공적으로 해결됩니다. Semantic Versioning (일명 "SemVer")은 특정 형식의 숫자를 사용하여 소프트웨어 버전 관리 시스템을 만드는 기술 사양을 생성하려는 노력의 한 예입니다.
- Private per application versions : Windows 2000에 도입된 Windows File Protection이 응용 프로그램에게 시스템 DLL을 덮어쓰는 것으로부터 방지했습니다. 개발자는 대신 응용 프로그램 디렉토리에 있는 응용 프로그램당 라이브러리 복사본, "개별 DLL(Private DLLs)"을 사용하도록 권장되었습니다. 이것은 지역적 경로가 시스템 전체 라이브러리가 있는 시스템 디렉토리보다 항상 우선 순위가 높은 윈도우 검색 경로 특성을 사용합니다. 이것은 특정 응용 프로그램에 의한 라이브러리 버전의 쉽고 효과적인 섀도잉을 허용하고, 따라서 의존성 지옥을 방지합니다. PC-BSD, 버전 8.2를 포함하여 그 버전까지, TrueOS (FreeBSD 기반 운영 시스템)의 이전 버전은 패키지와 종속성을 /Programs에서 자체 포함된 디렉터리에 배치하여, 시스템 라이브러리가 업그레이드되거나 변경되면 손상을 방지합니다. 그것은 패키지 관리를 위해 자체 "PBI" (Push Button Installer)를 사용합니다.
- Side-by-side installation of multiple versions : 버전 번호 매기기 해결책은 버전 번호 매기기를 운영 시스템 지원 기능으로 승격하여 개선될 수 있습니다. 이를 통해 응용 프로그램은 고유한 이름과 버전 번호 제약으로 모듈/라이브러리를 요청할 수 있으므로 라이브러리/모듈 버전 중개 책임을 응용 프로그램에서 운영 시스템으로 효과적으로 이전할 수 있습니다. 공유 모듈은 그런-다음 이전 또는 이후 버전의 모듈에 종속된 응용 프로그램이 중단될 위험 없이 중앙 저장소에 배치될 수 있습니다. 각 버전은 같은 모듈의 다른 버전과 나란히 자체 엔트리를 얻습니다. 이 해결책은 Windows Vista 이후 Microsoft Windows 운영 시스템에서 사용됩니다. 여기서, 전역 어셈블리 캐시는 관련 서비스와 함께 중앙 저장소를 구현하고 설치 시스템/패키지 관리자와 통합됩니다. 젠투 리눅스는 여러 버전의 공유 라이브러리를 설치할 수 있도록 하는 슬로팅(slotting)이라는 개념으로 이 문제를 해결합니다.
- Smart package management : 일부 패키지 관리자는 상호-의존적인 소프트웨어 구성 요소가 동시에 업그레이드되어, 주요 번호 비호환성 문제도 해결하는 스마트 업그레이드를 수행할 수 있습니다. 현재 많은 리눅스 배포판은 의존성 문제를 해결하기 위해 저장소-기반 패키지 관리 시스템도 구현했습니다. 이들 시스템은 사전-정의된 소프트웨어 저장소에서 검색함으로써 의존성을 자동으로 해결하도록 설계된 RPM, dpkg, 또는 다른 패키징 시스템의 상위 계층입니다. 이들 시스템의 예로는 Apt, Yum, Urpmi, ZYpp, Portage, Pacman, 등을 포함합니다. 전형적으로, 소프트웨어 저장소는 FTP 사이트 또는 웹사이트, 지역 컴퓨터의 디렉토리 또는 네트워크를 통해 공유되는 디렉터리 또는 훨씬 덜 일반적으로 CD 또는 DVD와 같은 이동식 미디어의 디렉터리입니다. 이것은 전형적으로 리눅스 배포판 공급자에 의해 유지 관리되고 전 세계적으로 미러링되는 해당 저장소에 패키지된 소프트웨어에 대한 의존성 지옥을 제거합니다. 이들 저장소는 종종 거대하지만, 모든 소프트웨어를 포함할 수 없으므로 의존성 지옥이 계속 발생할 수 있습니다. 모든 경우에서, 의존성 지옥은 여전히 저장소 관리자에 의해 직면되고 있습니다.
- Installer options : 소프트웨어의 다른 부분은 서로 다른 의존성을 갖기 때문에 각각의 새 패키지가 몇 개 더 설치되어야 하므로, 의존성 요구 사항의 악순환 또는 계속 확장되는 요구 사항 트리에 빠질 수 있습니다. 데비안의 고급 패키징 도구(APT)와 같은 시스템은 사용자에게 다양한 해결책을 제공하고, 사용자에게 원하는 대로 해결책을 수락하거나 거부할 수 있도록 허용함으로써 이 문제를 해결할 수 있습니다.
- Easy adaptability in programming : 응용 프로그램 소프트웨어가 프로그래머가 OS, 창 관리자 또는 데스크탑 환경을 다루는 인터페이스 계층을 새로운 표준이나 변화하는 표준에 쉽게 적응시킬 수 있는 방식으로 설계되면, 프로그래머는 데스크탑 환경 작성자의 알림만 모니터링하면 되거나 구성 요소 라이브러리 설계자가 최소한의 노력과 시간과 비용이 많이 드는 재설계 없이 사용자를 위한 업데이트로 소프트웨어를 신속하게 조정할 수 있습니다. 이 방법을 사용하면 프로그래머가 관련된 사람에게 부담이 되지 않는 합리적인 알림 프로세스를 유지하도록 의존하는 사람들에게 압력을 가할 수 있습니다.
- Strict compatibility requirement in code development and maintenance : 응용 프로그램과 라이브러리가 하향 호환성을 염두에 두고 개발과 유지 관리된다면, 임의의 응용 프로그램 또는 라이브러리도 손상 없이 언제든지 새 버전으로 교체될 수 있습니다. 이것이 다수의 의존성을 완화하지는 않지만, 패키지 관리자 또는 설치 프로그램의 작업을 훨씬 쉽게 만듭니다.
- Software appliances : 의존성 문제를 피하는 또 다른 방법은 응용 프로그램을 소프트웨어 어플라이언스로 배포하는 것입니다. 소프트웨어 어플라이언스는 사용자가 더 이상 소프트웨어 의존성을 해결하는 것에 대해 걱정할 필요가 없도록 사전 통합된 자체 포함 장치에 의존성을 캡슐화합니다. 대신 소프트웨어 어플라이언스 개발자에게 부담이 전가됩니다.
- Portable applications : 완전히 독립적이고 이미 설치될 필요가 없는 응용 프로그램 (또는 존재하는 관례적인 응용 프로그램의 버전)입니다. 필요한 모든 구성 요소가 포함되도록 코딩되거나 필요한 모든 파일을 자체 디렉토리에 보관하도록 설계되었으며 종속성 문제를 일으키지 않습니다. 이것들은 종종 연결된 시스템과 독립적으로 실행될 수 있습니다. RISC OS와 리눅스에 대한 ROX Desktop에서 응용 프로그램은 거의 같은 방식으로 작동하는 응용 프로그램 디렉토리를 사용합니다. 프로그램과 해당 의존성은 자체 디렉토리 (폴더)에 자체적으로 포함됩니다. 이러한 배포 방법은 유닉스-계열 플랫폼용으로 설계된 응용 프로그램을 윈도우로 이식할 때도 유용한 것으로 입증되었으며, 가장 눈에 띄는 단점은 같은 공유 라이브러리를 여러 번 설치하는 것입니다. 예를 들어, gedit, GIMP, 및 HexChat에 대해 윈도우 설치 프로그램에는 모두 이러한 프로그램이 위젯을 렌더링하는 데 사용하는 같은 GTK 툴킷 복사본을 포함하고 있습니다. 반면에 각 응용 프로그램에 서로 다른 버전의 GTK가 요구되면, 이는 올바른 동작이고 성공적으로 의존성 지옥을 피할 수 있습니다.
Platform-specific
특정 컴퓨팅 플랫폼에서, "의존성 지옥"은 종종 지역 특정 이름, 일반적으로 구성 요소 이름으로 사용됩니다.
- DLL Hell – Microsoft Windows에서 발생하는 일종의 의존성 지옥입니다.
- Extension conflict – classic Mac OS에서 발생하는 일종의 의존성 지옥입니다.
- JAR hell – 2004년 Apache Maven과 같은 빌드 도구가 이 문제를 해결하기 전에 Java Runtime Environment에서 발생하는 일종의 종속성 지옥입니다.
- RPM hell – 리눅스의 Red Hat 배포판과 RPM을 패키지 관리자로 사용하는 기타 배포판에서 발생하는 일종의 종속성 지옥입니다.