Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

권장 워크플로

전체 부트스트래핑 과정은 시간이 꽤 오래 걸립니다. 작업을 더 편하게 해 줄 몇 가지 방법을 소개합니다.

pre-push 훅 설치하기

코드 품질을 보장하기 위한 내부 도구인 tidy를 통과하지 못하면 CI가 자동으로 빌드를 실패 처리합니다. 원하는 경우 푸시할 때마다 ./x test tidy를 자동으로 실행하는 Git 훅을 설치하여 코드가 기준을 충족하는지 확인할 수 있습니다. 훅이 실패하면 ./x test tidy --bless를 실행한 뒤 변경 사항을 커밋하십시오. 나중에 pre-push 동작이 바람직하지 않다고 판단되면 .git/hooks에 있는 pre-push 파일을 삭제하면 됩니다.

미리 만들어 둔 git 훅이 src/etc/pre-push.sh에 있습니다. 이 파일을 .git/hooks 폴더에 pre-push라는 이름으로 복사하면 됩니다(.sh 확장자는 빼야 합니다!).

./x setup을 실행하는 과정에서 훅을 설치할 수도 있습니다!

설정 확장

여러 작업을 하다 보면 서로 다른 부트스트랩 설정 사이를 전환해야 할 때가 있습니다. 이전 설정을 나중에 다시 쓰려고 보관해 두고 싶을 때도 있습니다. 하지만 원본 설정 값을 아무 파일에나 저장해 두고 직접 복사해 붙여 넣다 보면 금세 지저분해질 수 있으며, 여러 설정을 오랫동안 사용해 왔다면 더욱 그렇습니다.

여러 설정을 더 쉽게 관리하려면 설정 확장을 만들 수 있습니다.

예를 들어 cross.toml이라는 간단한 설정 파일을 만들 수 있습니다:

[build]
build = "x86_64-unknown-linux-gnu"
host = ["i686-unknown-linux-gnu"]
target = ["i686-unknown-linux-gnu"]


[llvm]
download-ci-llvm = false

[target.x86_64-unknown-linux-gnu]
llvm-config = "/path/to/llvm-19/bin/llvm-config"

그런 다음 bootstrap.toml에 이 파일을 포함하십시오:

include = ["cross.toml"]

확장 안에 다른 확장을 재귀적으로 포함할 수도 있습니다.

참고: include 필드에서 재정의는 오른쪽에서 왼쪽 순서로 적용됩니다. 예를 들어 include = ["a.toml", "b.toml"]에서는 b.toml 확장이 a.toml을 재정의합니다. 또한 상위 확장은 항상 내부 확장을 재정의합니다.

rustc를 위한 rust-analyzer 설정하기

“library”(라이브러리) 트리 검사하기

“library”(라이브러리) 트리를 검사하려면 stage1 컴파일러가 필요하며, 이는 일부 컴퓨터에서는 무거운 작업이 될 수 있습니다. 이러한 이유로 bootstrap에는 rust.download-rustc를 사용할 수 없는 경우 “library”(라이브러리) 트리 검사를 건너뛰는 --skip-std-check-if-no-download-rustc라는 플래그가 있습니다. rust-analyzer 때문에 컴퓨터에 큰 부하가 걸리는 것을 피하고 싶다면 rust-analyzer 설정의 ./x check 명령에 --skip-std-check-if-no-download-rustc 플래그를 추가하면 됩니다.

프로젝트 로컬 rust-analyzer 설정

rust-analyzer는 파일을 저장할 때마다 코드를 검사하고 포맷하도록 도와줍니다. 기본적으로 rust-analyzer는 cargo check와 rustfmt 명령을 실행하지만, rustc를 작업할 때는 이 명령들을 재정의하여 이러한 도구를 작업에 더 알맞게 조정한 버전을 사용하게 할 수 있습니다. 사용자 정의 설정을 사용하면 rust-analyzer가 소스를 검사할 때는 ./x check를, 포맷할 때는 stage 0 rustfmt를 사용하게 할 수 있습니다.

기본 rust-analyzer.check.overrideCommand 명령줄은 저장소에 있는 모든 크레이트와 도구를 검사합니다. 특정 부분을 작업하고 있다면 작업 중인 부분만 검사하도록 명령을 재정의하여 검사 시간을 절약할 수 있습니다. 예를 들어 컴파일러를 작업하고 있다면 명령을 x check compiler --json-output으로 재정의하여 컴파일러 부분만 검사할 수 있습니다. x check --help --verbose를 실행하면 사용할 수 있는 부분을 확인할 수 있습니다.

./x setup editor를 실행하면 지원되는 편집기 중 하나에 대한 프로젝트 로컬 LSP 설정 파일을 만들라는 안내가 표시됩니다. ./x setup을 실행하는 과정의 한 단계로 설정 파일을 만들 수도 있습니다.

rust-analyzer에 공유 빌드 디렉터리 사용하기

기본적으로 rust-analyzer가 bootstrap 명령을 실행할 때는 명령줄에서 수동으로 실행하는 빌드와 별개의 빌드 디렉터리를 사용합니다. 디스크 공간을 절약하려면 생성된 LSP 설정 파일에서 이 설정을 재정의할 수 있습니다.

그러나 다음과 같은 이유로 권장하지 않습니다.

  • 각 빌드가 빌드 디렉터리를 잠가 다른 빌드를 기다리게 하므로, rust-analyzer가 백그라운드에서 명령을 실행하는 동안에는 명령줄 빌드를 실행할 수 없게 됩니다.
  • 컴파일러 플래그나 기타 설정이 서로 충돌하여 한 빌드가 이전에 빌드된 산출물을 삭제할 위험이 커지며, 경우에 따라 추가로 다시 빌드해야 합니다.

Visual Studio Code

./x setup editor에서 vscode를 선택하면 Visual Studio Code를 구성하는 .vscode/settings.json 파일을 만들라는 안내가 표시됩니다. 권장 rust-analyzer 설정은 src/etc/rust_analyzer_settings.json에 있습니다.

저장할 때마다 ./x check를 실행하는 것이 불편하다면 VS Code에서는 대신 빌드 작업을 사용할 수 있습니다.

// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "./x check",
            "command": "./x check",
            "type": "shell",
            "problemMatcher": "$rustc",
            "presentation": { "clear": true },
            "group": { "kind": "build", "isDefault": true }
        }
    ]
}

Neovim

Neovim 사용자에게는 몇 가지 선택지가 있습니다.

  1. 가장 쉬운 방법은 neoconf.nvim을 사용하는 것이지만, 이 플러그인은 더 이상 사용되지 않는 require('lspconfig') API를 사용하므로 neovim 0.11 이상에서 경고가 표시됩니다.
  2. coc.nvim을 사용하는 방법도 있지만, 이 경우 node.js가 설치되어 있어야 합니다.
  3. 사용자 정의 스크립트로 rust-analyzer 설정을 불러오는 방법도 있습니다.

neoconf.nvim

neoconf.nvim을 사용하면 네이티브 LSP에서 프로젝트 로컬 설정 파일을 사용할 수 있습니다. 사용 방법은 아래와 같습니다. 이 단계를 진행하려면 rust-analyzer가 이미 Neovim에 구성되어 있어야 합니다. 구성 방법은 여기에서 확인할 수 있습니다.

  1. 먼저 플러그인을 설치하십시오. README에 안내된 단계를 따르면 설치할 수 있습니다.
  2. ./x setup editor를 실행하고 vscode를 선택하여 .vscode/settings.json 파일을 만드십시오. 이 파일이 감지되면 프로젝트를 열 때 neoconf가 rust-analyzer 설정을 자동으로 읽고 갱신할 수 있습니다. Neovim은 VS Code의 ${workspaceFolder} 변수를 확장하지 않으므로, 생성된 파일에 있는 이 변수를 모두 rust 저장소의 절대 경로로 바꾸십시오.

coc.nvim

coc.nvim을 사용하는 경우 ./x setup editor를 실행하고 vim을 선택하면 .vim/coc-settings.json을 생성할 수 있습니다. 이 설정은 :CocLocalConfig로 편집할 수 있습니다. 권장 설정은 src/etc/rust_analyzer_settings.json에 있습니다.

사용자 지정 LSP 설정

neovim 0.11 이상을 사용하는 경우 nvim-lspconfig와 사용자 지정 스크립트만으로 rust-analyzer를 구성할 수 있습니다.

  1. rust-analyzer LSP가 설정되어 있는지 확인하십시오
  2. 다음 내용으로 $HOME/.config/nvim/after/plugged/rust_analyzer.lua를 생성하십시오:
-- Capture the default functions from nvim-lspconfig/lsp/rust_analyzer.lua before overriding it.
-- This file is in after/plugin to guarantee nvim-lspconfig has been initialised already.
local default_root_dir = vim.lsp.config["rust_analyzer"].root_dir
local default_before_init = vim.lsp.config["rust_analyzer"].before_init

vim.lsp.config("rust_analyzer", {
    cmd = { "rust-analyzer" },
    filetypes = { "rust" },
    -- To support rust-lang/rust, we need to detect when we're in the rust repo and use the git root
    -- instead of cargo project root.
    root_dir = function(bufnr, on_dir)
        local git_root = vim.fs.root(bufnr, { ".git" })
        if git_root then
            if vim.uv.fs_stat(vim.fs.joinpath(git_root, "src/etc/rust_analyzer_zed.json")) then
                on_dir(git_root)
                return
            end
        end
        -- For anything that doesn't match rust-lang/rust, fallback to default root_dir
        default_root_dir(bufnr, on_dir)
    end,
    before_init = function(init_params, config)
        -- When inside rust-lang/rust, we need to use the special rust-analyzer settings.
        local settings = vim.fs.joinpath(config.root_dir, "src/etc/rust_analyzer_zed.json")
        if vim.uv.fs_stat(settings) then
            local file = io.open(settings)
            -- nvim 0.12+ supports comments otherwise you'll need content:gsub("//[^\n]*", "").
            local json = vim.json.decode(file:read("*a"), { skip_comments = true })
            file:close()
            config.settings["rust-analyzer"] = vim.tbl_deep_extend(
                "force", -- Overwrite with the special settings when there is a conflict.
                config.settings["rust-analyzer"] or {},
                json.lsp["rust-analyzer"].initialization_options
            )
        end
        default_before_init(init_params, config)
    end,
})

vim.lsp.enable("rust_analyzer")

위에서 설명한 빌드 작업을 사용하려면 설정에서 직접 명령을 만들거나, VSCode의 task.json 파일을 읽을 수 있는 overseer.nvim 같은 플러그인을 설치한 뒤 위와 동일한 안내를 따르면 됩니다.

Emacs

Emacs는 Eglot을 통해 프로젝트 로컬 설정과 함께 rust-analyzer를 지원합니다. rust-analyzer와 함께 Eglot을 설정하는 방법은 여기에서 확인할 수 있습니다. Rust 개발을 위해 Emacs와 Eglot을 전반적으로 설정했다면 ./x setup editor를 실행하고 emacs를 선택할 수 있으며, 그러면 Eglot용 권장 설정이 담긴 .dir-locals.el을 생성하라는 안내가 표시됩니다. 권장 설정은 src/etc/rust_analyzer_eglot.el에 있습니다. 프로젝트별 Eglot 설정에 관한 자세한 내용은 매뉴얼을 참고하십시오.

Helix

Helix는 LSP와 rust-analyzer 지원을 기본으로 제공합니다. 여기에 설명된 대로 languages.toml을 통해 구성할 수 있습니다. ./x setup editor를 실행하고 helix를 선택할 수 있으며, 그러면 Helix용 권장 설정이 담긴 languages.toml을 생성하라는 안내가 표시됩니다. 권장 설정은 src/etc/rust_analyzer_helix.toml에 있습니다.

Zed

Zed는 LSP와 rust-analyzer 지원을 기본으로 제공합니다. 여기에 설명된 대로 .zed/settings.json을 통해 구성할 수 있습니다. ./x setup editor에서 zed를 선택하면 Zed를 권장 설정으로 구성하는 .zed/settings.json 파일을 생성하라는 안내가 표시됩니다. 권장 rust-analyzer 설정은 src/etc/rust_analyzer_zed.json에 있습니다.

확인하고, 확인하고, 또 확인하십시오

간단한 리팩터링을 할 때는 ./x check를 계속 실행하는 것이 유용할 수 있습니다. 위에서 설명한 대로 rust-analyzer를 설정했다면 파일을 저장할 때마다 이 작업이 자동으로 수행됩니다. 여기서는 컴파일러가 빌드될 수 있는지만 확인하지만, 대개는 이것으로 충분합니다(예: 메서드 이름을 바꿀 때). 그런 다음 실제로 테스트를 실행해야 할 때 ./x build를 실행하면 됩니다.

사실, 코드가 동작할지 100% 확신하지 못할 때에도 테스트를 미루는 것이 유용한 경우가 있습니다. 그러면 리팩터링 커밋을 계속 쌓아 나가다가 나중에 한꺼번에 테스트를 실행할 수 있습니다. 그런 다음 git bisect를 사용하여 어느 커밋이 문제를 일으켰는지 정확하게 찾아낼 수 있습니다. 이 방식의 좋은 부수 효과는 마지막에 꽤 잘게 나뉜 커밋들이 남고, 그 커밋들이 모두 빌드되며 테스트를 통과한다는 점입니다. 이는 리뷰에 도움이 되는 경우가 많습니다.

nightly를 사용하도록 rustup 구성하기

부트스트랩 과정의 일부는 rustfmt 같은 도구의 고정된 nightly 버전을 사용합니다. 저장소에서 cargo fmt 같은 명령이 올바르게 동작하게 하려면, rustup으로 nightly 툴체인을 설치한 다음 이 명령을 실행하십시오:

cd <path to rustc repo>
rustup override set nightly

워크트리를 설정한 모든 디렉터리에 대해서도 이 작업을 잊지 마십시오. src/stage0에 지정된 고정 nightly 버전을 사용해야 할 수도 있지만, 대개는 일반 nightly 채널로도 동작합니다.

참고 x가 사용하는 이 실제 rustfmt로 vscode를 구성하는 방법은 vscode 관련 섹션을, 부트스트랩한 컴파일러용 rustup 툴체인을 설정하는 방법은 rustup 관련 섹션을 참고하십시오

참고 이렇게 해도 cargo로 rustc를 직접 빌드할 수 있게 되는 것은 아닙니다. 컴파일러나 표준 라이브러리 작업에는 여전히 x를 사용해야 하며, 이 설정은 단지 cargo fmt를 사용할 수 있게 해 줄 뿐입니다.

CI-rustc로 더 빠르게 빌드하기

컴파일러를 작업하는 것이 아니라면, 컴파일러 트리를 빌드할 필요가 없는 경우가 많습니다. 예를 들어, 컴파일러 빌드를 건너뛰고 library 트리나 src/tools 아래의 도구만 빌드할 수 있습니다. 이를 위해서는 구성에서 download-rustc 옵션을 설정하여 이 기능을 활성화해야 합니다. 이 옵션은 부트스트랩이 stage > 0 단계에서 최신 nightly 컴파일러를 사용하도록 지시합니다. 즉, stage0 컴파일러와 stage > 0 단계용 download-rustc 컴파일러라는 두 개의 미리 컴파일된 컴파일러를 갖게 됩니다. 이렇게 하면 트리 내 컴파일러를 빌드할 필요가 전혀 없습니다. 그 결과, 트리 내 컴파일러를 빌드하지 않으므로 빌드 시간이 크게 줄어듭니다.

--keep-stage-std로 더 빠르게 재빌드하기

때로는 컴파일러가 빌드되는지 확인하는 것만으로는 충분하지 않습니다. 흔한 예로, 어떤 상태의 값을 살펴보거나 문제를 더 잘 이해하기 위해 debug! 문을 추가해야 하는 경우가 있습니다. 그런 경우에는 전체 빌드가 굳이 필요하지 않습니다. 부트스트랩의 캐시 무효화를 우회하면 이러한 빌드를 더 빨리 끝낼 수 있는 경우가 많습니다. 유일한 단점은 약간의 편법이 필요하고 동작하지 않는 컴파일러가 만들어질 수 있다는 것입니다(다만 이는 쉽게 발견하고 고칠 수 있습니다).

필요한 명령의 순서는 다음과 같습니다:

  • 최초 빌드: ./x build library
  • 이후 빌드: ./x build library --keep-stage-std=1

앞서 언급했듯이, --keep-stage-std=1의 효과는 기존 표준 라이브러리를 재사용할 수 있다고 그냥 가정하는 것입니다. 컴파일러를 수정하고 있다면 이 가정은 대개 맞습니다. 어쨌든 표준 라이브러리는 변경하지 않았기 때문입니다. 하지만 때로는 그렇지 않습니다. 예를 들어, 컴파일러가 타입과 기타 상태를 rlib 파일에 인코딩하는 방식을 제어하는 컴파일러의 “메타데이터” 부분을 수정하고 있거나, 메타데이터에 포함되는 것(MIR의 정의 등)을 수정하고 있는 경우입니다.

즉, --keep-stage-std=1을 사용하면 컴파일 과정에서 이상한 동작이 나타날 수 있습니다. 예를 들어 이상한 ICE나 기타 패닉이 발생할 수 있습니다. 그런 경우에는 명령에서 --keep-stage-std=1을 제거하고 다시 빌드하기만 하면 됩니다.

테스트를 실행할 때도 --keep-stage-std=1을 사용할 수 있습니다. 예를 들면 다음과 같습니다:

  • 첫 테스트 실행: ./x test tests/ui
  • 이후 테스트 실행: ./x test tests/ui --keep-stage-std=1

증분 컴파일 사용하기

또한 --incremental 플래그를 활성화하면 이후 재빌드에서 시간을 더 절약할 수 있습니다:

./x test tests/ui --incremental --test-args issue-1234

매 명령마다 이 플래그를 붙이고 싶지 않다면 bootstrap.toml에서 활성화할 수 있습니다:

[rust]
incremental = true

증분 컴파일은 평소보다 디스크 공간을 더 많이 사용한다는 점에 유의하십시오. 디스크 공간이 걱정된다면 build 디렉터리의 크기를 가끔 확인하는 것이 좋습니다.

최적화 세부 조정하기

optimize = false로 설정하면 컴파일러가 테스트에 쓰기에는 너무 느려집니다. 하지만 테스트 주기를 개선하기 위해, 다시 빌드해야 하는 크레이트에 대해서만 선택적으로 최적화를 비활성화할 수 있습니다(출처). 예를 들어 rustc_mir_build를 작업할 때는 rustc_mir_build와 rustc_driver 크레이트의 증분 재빌드에 가장 많은 시간이 걸립니다. 따라서 루트 Cargo.toml에 다음과 같이 설정할 수 있습니다:

[profile.release.package.rustc_mir_build]
opt-level = 0
[profile.release.package.rustc_driver]
opt-level = 0

여러 브랜치에서 동시에 작업하기

여러 브랜치에서 병렬로 작업하는 것은 다소 번거로울 수 있습니다. 한 브랜치에서 컴파일러를 빌드하면 이전 빌드와 증분 컴파일 캐시를 덮어쓰기 때문입니다. 한 가지 해결책은 저장소를 여러 개 클론해 두는 것이지만, 그러면 Git 메타데이터를 여러 번 저장해야 하고 각 클론을 개별적으로 업데이트해야 합니다.

다행히 Git에는 워크트리라는 더 나은 해결책이 있습니다. 워크트리를 사용하면 모두 같은 Git 데이터베이스를 공유하는 여러 개의 “작업 트리“를 만들 수 있습니다. 게다가 모든 워크트리가 같은 객체 데이터베이스를 공유하므로, 어느 워크트리에서든 브랜치(예: main)를 업데이트하면 모든 워크트리에서 새 커밋을 사용할 수 있습니다. 다만 한 가지 주의할 점은 서브모듈은 공유되지 않는다는 것입니다. 서브모듈은 여전히 여러 번 클론됩니다.

Rust 저장소의 루트 디렉터리에 있다면, 다음 명령을 실행하여 새 “rust2” 디렉터리에 “연결된 작업 트리“를 만들 수 있습니다:

git worktree add ../rust2

main을 기반으로 한 새 브랜치용 워크트리를 새로 만드는 방법은 다음과 같습니다:

git worktree add -b my-feature ../rust2 main

이제 그 rust2 폴더를 rustc를 수정하고 빌드하기 위한 별도의 작업 공간으로 사용할 수 있습니다!

nix로 작업하기

src/tools/nix-dev-shell에 여러 nix 구성이 정의되어 있습니다.

direnv를 사용한다면 src/tools/nix-dev-shell/envrc-flake 또는 src/tools/nix-dev-shell/envrc-shell에 대한 심볼릭 링크를 만들 수 있습니다

ln -s ./src/tools/nix-dev-shell/envrc-flake ./.envrc # Use flake

또는

ln -s ./src/tools/nix-dev-shell/envrc-shell ./.envrc # Use nix-shell

flake를 사용한다면 다음 명령으로 flake도 반드시 업데이트하십시오:

nix flake update --flake ./src/tools/nix-dev-shell

이 셸은 모든 의존성이 올바르게 설정된 상태로 ./x.py 스크립트를 실행하는 x라는 명령을 생성합니다.

참고

NixOS가 아닌 배포판에서 nix를 사용할 때는 bootstrap.toml에서 build.patch-binaries-for-nix = true를 설정해야 할 수도 있다는 점에 유의하십시오. 부트스트랩은 nix 안에서 실행 중인지 감지하여 패치를 자동으로 활성화하려고 시도하지만, 이 감지에서 거짓 음성(false negative)이 발생할 수 있습니다.

nix 셸을 사용해 bootstrap.toml을 관리할 수도 있습니다:

let
  config = pkgs.writeText "rustc-config" ''
    # Your bootstrap.toml content goes here
  ''
pkgs.mkShell {
  /* ... */
  # This environment variable tells bootstrap where our bootstrap.toml is.
  RUST_BOOTSTRAP_CONFIG = config;
}

셸 자동 완성

Bash, Zsh, Fish 또는 PowerShell을 사용한다면 src/etc/completions에서 ./x용으로 자동 생성된 셸 자동 완성 스크립트를 찾을 수 있습니다.

원하는 셸의 자동 완성을 불러오려면 source ./src/etc/completions/x.<extension>을 사용하고, PowerShell에서는 & .\src\etc\completions\x.ps1을 사용하면 됩니다. 이 명령을 셸의 시작 스크립트(예: .bashrc)에 추가하면 이 자동 완성이 자동으로 로드됩니다.