콘텐츠로 이동

경로 지정

같은 설정 파일이라도 환경에 따라 놓여야 하는 위치가 다른 경우는 흔합니다. 예를 들어 PostgreSQL의 클라이언트인 psql의 설정 파일은 문서에 따르면 환경에 따라 다음과 같이 경로가 달라집니다.

  • POSIX: PSQLRC 환경 변수가 있다면 해당 값을 경로로 사용하되, 없다면 ~/.psqlrc 사용.
  • Windows: PSQLRC 환경 변수가 있다면 해당 값을 경로로 사용하되, 없다면 %APPDATA%\postgresql\psqlrc.conf 사용.

Dojang은 이러한 복잡한 경우에 대응하기 위해 경로 지정(routing)이라는 개념을 이용합니다.

디렉터리 경로 지정

많은 프로그램이 설정 파일을 Linux에서는 $XDG_CONFIG_HOME에, macOS에서는 ~/Library/Application Support에, Windows에서는 %AppData% 또는 %LocalAppData%에 저장합니다.1 따라서 여러 환경에서 같은 설정 파일을 쓰려면 해당 경로들을 하나로 묶어서 경로를 지정하는 게 편합니다. 아래는 선언은 그 예를 보여줍니다.

[dirs.app_config]
linux = "$XDG_CONFIG_HOME"
mac = "$HOME/Library/Application Support"
win = "$AppData"

[monikers.linux]
os = "linux"

[monikers.mac]
os = "macos"

[monikers.win]
os = "windows"

여기서 경로 규칙 이름인 app_config는 저장소 내의 디렉터리 이름이기도 합니다. 경로 규칙 이름은 저장소 안의 상대 경로여야 합니다. 절대 경로나 .. 구성 요소를 포함한 이름은 저장소 밖의 원본을 가리킬 수 있으므로 거부합니다. C:config처럼 드라이브가 지정된 이름과 \config 같은 Windows 루트 상대 이름도 모든 플랫폼에서 거부합니다. 따라서 공유 선언 파일을 Windows에서 체크아웃해도 저장소 밖의 원본을 선택하지 않습니다. 경로를 정규화한 뒤에도 이름이 서로 달라야 하므로 한 선언 파일에 config./config를 함께 정의할 수 없습니다. 저장소 안에 파일들이 아래와 같이 있다고 할 때 …

  • dojang.toml
  • app_config/
    • alacritty/
      • alacritty.toml
    • ghc/
      • ghci.conf
    • nvim/ init.vim

Linux에서는 아래 경로에 파일들이 생길 것입니다.

  • /home/$USER/.config/alacritty/
  • /home/$USER/.config/alacritty/alacritty.toml
  • /home/$USER/.config/ghc/
  • /home/$USER/.config/ghc/ghci.conf
  • /home/$USER/.config/nvim/
  • /home/$USER/.config/nvim/init.vim

하지만 만약 XDG_CONFIG_HOME 환경 변수가 정의되어 있지 않다면 어떻게 될까요? Linux에서의 app_config 경로 규칙은 빈 경로, 즉 Dojang을 실행시키는 시점의 현재 작업 디렉터리(CWD, current working directory)가 되어버릴 것입니다. 이를 대비하고 싶다면 아래와 같이 경로 규칙을 살짝 고쳐볼 수 있습니다.

linux = "${XDG_CONFIG_HOME:-$HOME/.config}"

위 문법은 XDG_CONFIG_HOME 환경 변수가 있다면 그 값을 취하되, 그렇지 않다면 ~/.config 경로를 따르라는 뜻입니다. 이와 같은 고급 기능에 대해서는 파일 경로 표현식 문서에 나와 있습니다.

무관한 파일 무시

홈 디렉터리처럼 설정 파일 이외에도 다양한 자료가 있을 수 있는 경우, 설정 파일과 무관한 파일들이 너무 많이 보여 dojang status 결과를 맨눈으로 보기 어렵습니다. 게다가 파일이 너무 많을 경우 느려지기도 합니다.

이를 피하려면 ignores 구획에 무시할 파일들을 지정해 주면 됩니다. 예를 들어, 아래와 같이 home 경로 규칙을 추가했다고 합시다.

[dirs.home]
linux = "$HOME"
mac = "$HOME"
win = "$UserProfile"

이제 home 경로 규칙이 지정하는 디렉터리 안에 있는 파일들은 모두 dojang status 결과에 나타납니다. 하지만 이 중에서도 Documents 디렉터리는 무시하고 싶을 수 있습니다. 이럴 때는 아래와 같이 ignores 구획에 무시할 파일들을 지정해 주면 됩니다.

[ignores]
home = [
  "Documents",
]

실제로는 홈 디렉터리에는 무시할 파일이 그렇지 않은 파일보다 훨씬 많은 탓에, 그냥 모든 파일을 무시하는 게 대체로 더 편합니다.

[ignores]
home = ["*"]

이렇게 모든 파일을 무시해도 dojang reflect -f 명령으로 파일을 하나씩 지정하여 반영할 수 있습니다. 한 번 저장소에 추가된 파일은 무시 목록에 있어도 관리됩니다.

경로 규칙이 겹치는 경우

위 예시처럼 app_config 경로 규칙을 Linux에서 XDG_CONFIG_HOME, 즉 ~/.config로 지정해 둔 상태에서, 홈 디렉터리에 대한 경로 규칙을 추가한다고 가정합시다.

[dirs.home]
linux = "$HOME"
mac = "$HOME"
win = "$UserProfile"

대부분의 경우, home이 지정한 디렉터리 안에 app_config가 지정한 디렉터리도 포함하게 됩니다. 이렇게 두 개 이상의 경로 규칙에서 겹치는 디렉터리가 있을 경우 관리할 때 실수하기 쉽기 때문에, 다음과 같이 한쪽에서 무시하게 해야 합니다.

[ignores]
home = [
  ".config",
  "AppData/Roaming",
  "Library/Application Support"
]

단일 파일 경로 지정

어떤 설정 파일의 경우 환경에 따라 디렉터리 뿐만 아니라 파일 이름 자체가 달라지기도 하는데, 그럴 때는 차라리 파일 단위로 경로를 지정하는 게 깔끔할 수 있습니다. 예를 들어, 본 문서의 맨 처음에 예시로 들었던 psql 설정 파일은 아래처럼 파일 단위로 경로를 지정할 수 있습니다.

[files.".psqlrc"]
posix = "${PSQLRC:-$HOME/.psqlrc}"
win = "${PSQLRC:-${AppData:-$UserProfile/AppData/Roaming}/postgresql/psqlrc.conf}"

[monikers.posix]
os = ["linux", "macos"]

[monikers.win]
os = "windows"

인라인 경로 규칙 조건

위의 간결 문법은 이름 붙은 모니커만으로 경로 규칙을 표현할 수 있을 때 유용합니다. 상세 경로 규칙에서는 환경 서술어를 분기의 when 필드에 직접 쓸 수 있습니다.

[[files.".psqlrc"]]
moniker = "posix"
path = "${PSQLRC:-$HOME/.psqlrc}"

[[files.".psqlrc"]]
when = "os = windows && arch = aarch64"
path = "${PSQLRC:-$UserProfile/AppData/Roaming/postgresql/psqlrc.conf}"

[[files.".psqlrc"]]
when = "os = android"

상세 경로 규칙은 순서가 있는 배열입니다. 각 분기에는 monikerwhen 중 정확히 하나만 있어야 하며, path를 둘 수 있습니다. moniker 분기는 같은 선언 파일에 정의된 모니커를 참조합니다. when 분기에서는 환경 서술어의 모든 문법을 쓸 수 있습니다. path를 생략하면 해당 분기는 공 경로 규칙이 됩니다. 이와 달리 path = ""는 빈 경로 표현식입니다.

분기는 나열된 순서대로 검사하며 처음 일치한 분기가 대상 경로를 결정합니다. 이 순서는 명시적이므로, 뒤에 있는 분기의 서술어가 더 구체적이더라도 앞으로 이동하지 않습니다. 간결 경로 규칙에는 아래에서 설명하는 구체성 규칙을 적용합니다.

인라인 서술어가 더 명확할 때, 여러 조건이 같은 모니커에 대응될 때, 또는 반복된 분기를 서로 구별해 유지해야 할 때 상세 형식을 쓰세요. 기존 간결 형식도 계속 유효하며 단순한 모니커 전용 경로 규칙에 적합합니다.

경로 규칙 우선순위

간결 경로 규칙에서 현재 환경과 일치하는 항목이 여러 개일 경우, Dojang은 구체성(specificity)을 기준으로 어떤 항목을 사용할지 결정합니다. 서술어가 구체적일수록 우선순위가 높습니다. 이를 통해 특수한 설정이 일반적인 설정보다 먼저 적용됩니다. 상세 경로 규칙에는 위에서 설명한 명시적 분기 순서를 적용합니다.

구체성 계산 방법

구체성은 서술어가 참이 되기 위해 충족해야 하는 조건의 수로 측정됩니다. 핵심 규칙은 다음과 같습니다.

  • 단일 조건(예: os = linux, arch = "x86_64")의 구체성은 1입니다.

  • 논리곱(&&) 연산자는 각 부분의 구체성을 합산합니다. 예를 들어, os = linux && arch = "x86_64"의 구체성은 2입니다. 두 조건 모두 충족되어야 하기 때문입니다.

  • 논리합(||) 연산자는 각 부분 중 최댓값을 취합니다. 예를 들어, os = linux || os = macos의 구체성은 1입니다. 하나의 조건만 충족되면 되기 때문입니다.

  • 모니커 참조는 해석되는 서술어의 구체성을 물려받습니다. os = linux && arch = "x86_64"로 정의된 모니커는 해당 표현식을 직접 쓴 것과 동일한 구체성을 갖습니다.

  • **always**의 구체성은 0으로, 모든 환경과 일치하지만 가장 낮은 우선순위를 갖습니다.

예시

다음과 같이 설정 파일에 대한 경로 규칙이 있는 선언을 생각해 봅시다.

[files.".bashrc"]
apple-silicon = "~/Library/bashrc"
posix = "$HOME/.bashrc"
linux = "$HOME/.bashrc.linux"

[monikers.apple-silicon]
when = "os = macos && arch = aarch64"

[monikers.posix]
when = "os in (linux, macos)"

[monikers.linux]
when = "os = linux"

이 설정에서:

  • apple-silicon의 구체성은 2입니다 (&&로 두 조건을 결합)
  • posix의 구체성은 1입니다 (단일 in 검사)
  • linux의 구체성은 1입니다 (단일 = 검사)

Apple Silicon Mac(aarch64 아키텍처의 macOS)에서는 apple-siliconposix 모두 일치합니다. 하지만 apple-silicon이 더 높은 구체성(2 > 1)을 가지므로 선택됩니다.

Intel Mac(x86_64 아키텍처의 macOS)에서는 posix만 일치합니다.

Linux에서는 posixlinux 모두 동일한 구체성으로 일치합니다. 이 경우 선언 파일에서의 순서가 동점 해결 기준이 됩니다. 서술어 비교에서 먼저 나타나는 쪽이 우선합니다.

실용적인 팁

  • 일반적인 것부터 시작해 구체적인 것을 추가: 먼저 넓은 범위의 경로 규칙 (예: Unix 계열 시스템을 위한 posix)을 정의한 다음, 특정 환경을 위한 더 구체적인 경로 규칙(예: linux, apple-silicon)을 추가하여 재정의합니다.

  • 특수화에는 && 사용: 환경별 동작이 필요할 때는 &&로 조건들을 결합하여 우선순위가 높은 더 구체적인 서술어를 만드세요.

  • 겹치는 || 경로 규칙 피하기: ||를 사용하는 경로 규칙은 구체성이 크게 증가하지 않습니다. 모호한 일치가 발생한다면, && 조건으로 구조를 재구성하는 것을 고려해 보세요.

배포 링크

경로 규칙은 보통 원본을 대상 경로로 복사하고, 대상 경로에서 한 변경은 저장소로 반영할 수 있습니다. kind = "symlink"를 선언한 분기는 그 대신 배포 링크를 배치합니다. 배포 링크는 저장소 체크아웃 안의 원본의 절대 경로를 가리키는, 대상 경로의 심볼릭 링크입니다.

[[dirs.nvim]]
moniker = "posix"
path = "$XDG_CONFIG_HOME/nvim"
kind = "symlink"

배포 링크는 의도적으로 단방향입니다. 대상 경로를 통한 읽기와 쓰기가 곧 저장소 원본에 대한 것이므로, 대상 경로를 편집하는 일이 곧 원본을 편집하는 일이고 반영할 것이 없습니다. dojang reflect--force를 쓰더라도, 링크된 디렉터리 아래의 경로를 지정하더라도 배포 링크를 무조건 거부합니다. dojang apply는 없는 링크를 만들고, (예를 들어 저장소 체크아웃이 이동한 뒤) 링크 대상이 더 이상 원본과 일치하지 않는 링크를 고치며, 대상 경로에 이미 있는 일반 파일이나 디렉터리는 --force로 대체하지 않는 한 충돌로 취급합니다.

링크로 배포된 디렉터리는 순회 경계이기도 합니다. 다른 어떤 경로 규칙도 그 안으로 경로를 정할 수 없고, 더 넓은 경로 규칙이 그 안을 읽거나 쓰지도 않습니다.

경로 규칙 소유권

대상 경로의 모든 항목은 정확히 하나의 경로 규칙이 소유합니다. 두 경로 규칙의 대상 경로가 중첩되면 — 예를 들어 $XDG_CONFIG_HOME을 대상 경로로 하는 디렉터리 경로 규칙과 $XDG_CONFIG_HOME/nvim을 대상 경로로 하는 더 구체적인 경로 규칙 — 더 구체적인 대상 경로를 가진 경로 규칙이 그 하위 트리를 소유하며, 더 넓은 경로 규칙은 제거를 포함해 그 안의 항목을 읽거나 쓰지 않습니다.

모호하지 않은 소유권을 제공할 수 없는 경로 규칙 설정은 어떤 것도 변경하기 전에 종료 코드 38로 거부됩니다:

  • 두 활성 경로 규칙이 어휘적으로든 심볼릭 링크를 해석한 뒤로든 같은 대상 경로로 전개되는 경우;
  • 대상 경로가 다른 경로 규칙의 배포 링크 안에 있는 경우;
  • 대상 경로가 저장소 체크아웃 자체와 겹치거나, 심볼릭 링크를 통해 자신의 원본과 겹치는 경우.

공 경로 규칙

어떤 설정 파일들은 환경에 따라 존재하지 않거나 필요하지 않을 수도 있습니다. 이럴 때는 아예 해당 환경에서는 경로를 지정하지 않을 수 있는데, 이를 공 경로 규칙(null routing)이라 합니다. 예를 들어, Windows에서만 필요한 설정 파일이 있다면 아래와 같이 경로를 지정할 수 있습니다.

[files."Microsoft.WindowsTerminal.json"]
win = "$LocalAppData/Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json"

위 설정 파일은 Windows에서만 필요하므로, Linux나 macOS에서는 파일이 아예 생기지 않게 됩니다.


  1. 예를 들어, 크로스플랫폼 앱 프레임워크 Electron에서는 app.getPath("appData") API를 쓰면 환경에 따라 다른 경로를 반환합니다. 마찬가지로, GLib의 get_user_config_dir() API도 비슷하게 동작합니다.