Bazel과의 일상적인 상호작용은 주로 build
, test
, run
라는 세 가지 명령어를 통해 이루어집니다. 하지만 이러한 기능이 제한적이라고 느껴질 때가 있습니다. 패키지를 저장소에 푸시하거나, 최종 사용자를 위한 문서를 게시하거나, Kubernetes로 애플리케이션을 배포하려고 할 수 있습니다. 하지만 Bazel에는 publish
또는 deploy
명령어가 없습니다. 이러한 작업은 어디에 적합할까요?
bazel run 명령어
Bazel은 격리, 재현성, 증분성에 중점을 두므로 build
및 test
명령어는 위 작업에 도움이 되지 않습니다. 이러한 작업은 네트워크 액세스가 제한된 샌드박스에서 실행할 수 있으며 bazel build
마다 재실행되지 않을 수도 있습니다.
대신, 부작용을 갖으려는 작업을 위한 bazel run
를 사용합니다. Bazel 사용자는 실행 파일을 만드는 규칙에 익숙하며 규칙 작성자는 일반적인 패턴 집합을 따라 이를 '맞춤 동사'로 확장할 수 있습니다.
실제 환경: rules_k8s
예를 들어 Bazel의 Kubernetes 규칙인 rules_k8s
를 생각해 보세요. 다음과 같은 타겟이 있다고 가정해 보겠습니다.
# BUILD file in //application/k8s
k8s_object(
name = "staging",
kind = "deployment",
cluster = "testing",
template = "deployment.yaml",
)
k8s_object
규칙은 bazel build
이 staging
대상에서 사용될 때 표준 Kubernetes YAML 파일을 빌드합니다. 그러나 추가 타겟은 k8s_object
매크로에 의해 staging.apply
및 :staging.delete
와 같은 이름으로 생성됩니다. 이러한 스크립트는 이러한 작업을 실행하도록 빌드되며 bazel run
staging.apply
로 실행하면 자체 bazel k8s-apply
또는 bazel
k8s-delete
명령어처럼 작동합니다.
다른 예: ts_api_guardian_test
이 패턴은 Angular 프로젝트에서도 확인할 수 있습니다. ts_api_guardian_test
매크로는 두 개의 타겟을 생성합니다. 첫 번째는 생성된 일부 출력을 '골드' 파일(예상 출력이 포함된 파일)과 비교하는 표준 nodejs_test
타겟입니다. 이는 일반 bazel
test
호출로 빌드하고 실행할 수 있습니다. angular-cli
에서는 bazel test //etc/api:angular_devkit_core_api
를 사용하여 이러한 타겟 하나를 실행할 수 있습니다.
시간이 지남에 따라 타당한 이유로 이 골든 파일을 업데이트해야 할 수도 있습니다.
이를 수동으로 업데이트하는 것은 지루하고 오류가 발생하기 쉬우므로 이 매크로는 골든 파일과 비교하지 않고 골든 파일을 업데이트하는 nodejs_binary
타겟도 제공합니다. 실질적으로 동일한 테스트 스크립트를 호출 방식에 따라 '확인' 또는 '수락' 모드에서 실행하도록 작성할 수 있습니다. 이는 이미 배운 것과 동일한 패턴을 따릅니다. 네이티브 bazel test-accept
명령어는 없지만 bazel run //etc/api:angular_devkit_core_api.accept
를 사용하여 동일한 효과를 얻을 수 있습니다.
이 패턴은 매우 강력할 수 있으며, 인식하는 방법을 배우면 매우 흔하게 나타납니다.
자체 규칙 조정
매크로가 이 패턴의 핵심입니다. 매크로는 규칙과 같이 사용되지만 여러 타겟을 만들 수 있습니다. 일반적으로 기본 빌드 작업을 실행하는 지정된 이름의 타겟을 만듭니다. 즉, 일반 바이너리, Docker 이미지 또는 소스 코드의 보관 파일을 빌드할 수 있습니다. 이 패턴에서는 기본 타겟의 출력을 기반으로 부작용을 수행하는 스크립트를 생성하기 위해 추가 타겟을 생성합니다(예: 결과 바이너리 게시 또는 예상 테스트 출력 업데이트).
이를 설명하기 위해 Sphinx로 웹사이트를 생성하는 가상의 규칙을 매크로로 래핑하여 사용자가 준비되었을 때 게시할 수 있는 추가 타겟을 만듭니다. Sphinx로 웹사이트를 생성하기 위한 다음과 같은 기존 규칙을 살펴보세요.
_sphinx_site = rule(
implementation = _sphinx_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)
다음으로, 실행 시 생성된 페이지를 게시하는 스크립트를 빌드하는 다음과 같은 규칙을 살펴보겠습니다.
_sphinx_publisher = rule(
implementation = _publish_impl,
attrs = {
"site": attr.label(),
"_publisher": attr.label(
default = "//internal/sphinx:publisher",
executable = True,
),
},
executable = True,
)
마지막으로 다음 매크로를 정의하여 위의 두 규칙에 대한 타겟을 만듭니다.
def sphinx_site(name, srcs = [], **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML.
_sphinx_site(name = name, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
BUILD
파일에서 기본 타겟만 만드는 것처럼 매크로를 사용합니다.
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
이 예에서는 매크로가 표준 단일 Bazel 규칙인 것처럼 'docs' 대상이 생성됩니다. 빌드되면 규칙은 몇 가지 구성을 생성하고 Sphinx를 실행하여 수동 검사가 가능한 HTML 사이트를 생성합니다. 하지만 사이트 게시용 스크립트를 빌드하는 추가 'docs.publish' 타겟도 생성됩니다. 기본 타겟의 출력을 확인한 후 가상의 bazel publish
명령어와 마찬가지로 bazel run :docs.publish
를 사용하여 공개적으로 사용할 수 있도록 게시할 수 있습니다.
_sphinx_publisher
규칙의 구현은 바로 알 수 없습니다. 이러한 작업은 종종 런처 셸 스크립트를 작성합니다.
이 방법에서는 일반적으로 ctx.actions.expand_template
를 사용하여 매우 간단한 셸 스크립트를 작성합니다. 이 경우 기본 타겟의 출력 경로가 있는 게시자 바이너리를 호출합니다. 이렇게 하면 게시자
구현을 일반 상태로 유지할 수 있고 _sphinx_site
규칙은 HTML만 생성할 수 있으며
이 작은 스크립트만 있으면 둘을 결합할 수 있습니다.
rules_k8s
에서 .apply
가 하는 일은 실제로 다음과 같습니다. expand_template
는 apply.sh.tpl
를 기반으로 매우 간단한 Bash 스크립트를 작성하여 기본 타겟의 출력으로 kubectl
를 실행합니다. 그러면 이 스크립트를 bazel run :staging.apply
로 빌드하고 실행할 수 있어 k8s_object
대상에 k8s-apply
명령어를 효과적으로 제공할 수 있습니다.