본문 바로가기

building system/bazel

nasm_library 구현해보기

반응형

개요

nasm.exe 기반으로 nasm_libray rule를 정의하는 작업은 매우 흥미롭다. 기본적으로 빌드 시스템이 외부 tool를 제어해 원하는 결과물은 만드는 tool chain 기술이라고 볼 수 있고, 단순한 nasm.exe 파일은 툴을 넘어서, 동시에 앞으로 만날 가장 단순한 형태의 컴파일러라고도 볼 수 있다.

풀어야 과제

풀어야 할 과제는 명확하다.
nasm_library가 작동하도록 nasm_library.bzl 파일을 생성해야 한다. nasm_librarysrc 항목은 filegroup 정보를 입력으로 사용할 수도 있지만, 일반 asm 파일을 받아낼 수 있어야 한다. 이렇게 만들어진 룰을 곧바로 cc_binarycc_library과 연계될 수 있어야 한다.

load("//project:nasm_library.bzl", "nasm_library")

filegroup(
    name = "asm_src",
    srcs = ["asm/hello.asm",],
)

filegroup(
    name = "main_src",
    srcs = ["main.cc"],
)

nasm_library(
    name = "asm_lib",
    srcs = [":asm_src", "asm/hello2.asm"],
    flags = ["-fwin64","-DWIN64","-D__x86_64__"],
    data = ["//assets:testdata"]
)

cc_binary(
    name = "main",
    srcs = [":main_src",":asm_lib"],
    deps = [],
    linkopts = ["/DEFAULTLIB:legacy_stdio_definitions.lib"],    
)

nasm_library.bzl 전체 구현 소스는 아래와 같다.

def _nasm_library_impl(ctx):
 tools_list = ctx.attr._exe.files.to_list()

 outputs = []
 for src in ctx.files.srcs:
  out = ctx.actions.declare_file(src.path[:len(src.path) - len(src.extension) - 1] + ".obj")
  outputs.append(out)  

  ctx.actions.run_shell(
        command =  "{} {} -o {} {}".format(
                    ctx.executable._exe.path,
                    " ".join(ctx.attr.flags),
                    out.path,
                    src.path
        ),
        arguments = [],
        inputs = [src],
        outputs = [out],
        tools = tools_list
  )   

 cc_info = ctx.attr._exe[CcInfo]

 runfiles = ctx.runfiles(files = [])
 for deps in (ctx.attr.data, ctx.attr.deps):
   for dep in deps:
     runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) 

 app =[]
 app.append(cc_info) 
 app.append(
   DefaultInfo(
    files = depset(outputs),
    runfiles = runfiles
   )
 )  

 return app


nasm_library = rule(
 implementation = _nasm_library_impl, 
 attrs = {
  "_exe": attr.label(
              default = Label("@nasm//:nasm"),
              executable = True,
              cfg = "target",
   ),   

  "srcs": attr.label_list(allow_files = [".asm"]),
  "deps": attr.label_list(allow_files = True),
  "data": attr.label_list(allow_files = True),
  "flags": attr.string_list(mandatory = False, allow_empty = True, default = [], doc = None),
 },
)

nasm.exe 파일을 먼저 컴파일하고 다음으로 컴파일된 nasm.exe 파일을 기반으로 asm 파일을 컴파일해야 한다.
어떻게 nasm.exe을 먼저 컴파일되도록 할 수 있을까?

ctx.actions.run_shelltools 항목에 추가에 ctx.attr._exe.files.to_list()를 사용하면 된다.
ctx.attr._exe.files는 nasm 타겟의 빌드 결과 파일 목록이다. ctx.actions.run_shelltools 항목에 의존해서 수행할 action 위치를 결정한다. 결국 마지막 단계에서 .asm 파일에 대한 action이 등장해야 한다.

ctx.executable._exe.pathnasm.exe 실행 파일 경로다.

 tools_list = ctx.attr._exe.files.to_list()
...

  ctx.actions.run_shell(
        command =  "{} {} -o {} {}".format(
                    ctx.executable._exe.path,
                    " ".join(ctx.attr.flags),
                    out.path,
                    src.path
        ),
        arguments = [],
        inputs = [src],
        outputs = [out],
        tools = tools_list
  )    

아래 코딩은 파일 이름의 확장자를 ".obj"로 변경한 후 outputs 리스트에 선언된 파일을 추가한다. outputs는 최종적으로 DefaultInfofiles 항목으로 전달해 해당 rule에서 만든 결과 파일임을 알려주어야 한다. 프로그램 실행 환경에서 필요한 데이터를 dep[DefaultInfo].default_runfiles로부터 모아, DefaultInforunfiles 항목으로 전달해야 한다. 이렇게 모아진 파일 정보는 맵핑 파일을 정보를 구성하거나, 일부 옵션을 조절하면, 실행 환경에서 사용할 수 있는 assets를 만들어낼 수 있다.

def _nasm_library_impl(ctx):
 tools_list = ctx.attr._exe.files.to_list()

 outputs = []
 for src in ctx.files.srcs:
  out = ctx.actions.declare_file(src.path[:len(src.path) - len(src.extension) - 1] + ".obj")
  outputs.append(out)  

  ctx.actions.run_shell(
        command =  "{} {} -o {} {}".format(
                    ctx.executable._exe.path,
                    " ".join(ctx.attr.flags),
                    out.path,
                    src.path
        ),
        arguments = [],
        inputs = [src],
        outputs = [out],
        tools = tools_list
  )   

 cc_info = ctx.attr._exe[CcInfo]

 runfiles = ctx.runfiles(files = [])
 for deps in (ctx.attr.data, ctx.attr.deps):
   for dep in deps:
     runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) 

 app =[]
 app.append(cc_info) 
 app.append(
   DefaultInfo(
    files = depset(outputs),
    runfiles = runfiles
   )
 )  

 return app

다음으로 해야 할 중요한 내용은 cc_로 시작하는 룰과 연계작업을 가능하도록 해야 한다. _exe과 함께 전달되 CcInfo 정보를 추출해 반환값의 일부 정보로 해야 한다.

 cc_info = ctx.attr._exe[CcInfo]

 app =[]
 app.append(cc_info) 
 ... 
 return app

implicit dependency로 nasm 빌드 결과를 사용할 예정이다. attr 이름은 외부에서 접근할 수 없도록 _로 시작하고 default 항목으로 외부 의존성 이름을 설정한다. "_exe"executable = True임으로 ctx.executable._exe로 실행 File 객체를 얻어낼 수 있다.

nasm_library = rule(
 implementation = _nasm_library_impl, 
 attrs = {
  "_exe": attr.label(
              default = Label("@nasm//:nasm"),
              executable = True,
              cfg = "target",
   ),   

  "srcs": attr.label_list(allow_files = [".asm"]),
  "deps": attr.label_list(allow_files = True),
  "data": attr.label_list(allow_files = True),
  "flags": attr.string_list(mandatory = False, allow_empty = True, default = [], doc = None),
 },
)

전체 실행 결과 보기
정상적으로 작동하는지 여부는 마지막 단계에서 5초 타임아웃이 작동하는 것으로 확인할 수 있다.

D:\buildsystem\bazel\test>bazelisk clean --expunge
Starting local Bazel server and connecting to it...
INFO: Starting clean.

D:\buildsystem\bazel\test>bazelisk run //project:main --compilation_mode=opt
INFO: Analyzed target //project:main (42 packages loaded, 317 targets configured).
INFO: From Compiling output/codeview.c:
...: warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss
INFO: From Compiling output/outcoff.c:
...: warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss
INFO: From Compiling nasmlib/mmap.c:
...: warning C4005: 'HAVE_STRUCT__STATI64': macro redefinition
external/nasm~2.14.02\config/msvc.h(117): note: see previous definition of 'HAVE_STRUCT__STATI64'
INFO: From Compiling nasmlib/file.c:
...: warning C4005: 'HAVE_STRUCT__STATI64': macro redefinition
external/nasm~2.14.02\config/msvc.h(117): note: see previous definition of 'HAVE_STRUCT__STATI64'
INFO: Found 1 target...
Target //project:main up-to-date:
  bazel-bin/project/main.exe
INFO: Elapsed time: 12.092s, Critical Path: 6.31s
INFO: 96 processes: 10 internal, 86 local.
INFO: Build completed successfully, 96 total actions
INFO: Running command line: bazel-bin/project/main.exe
world c++
Hello NASM1
Hello NASM2

src.zip
3.7 kB

반응형

'building system > bazel' 카테고리의 다른 글

Depsets 이해하기  (0) 2024.02.27
DefaultInfo 프로바이더 이해하기  (0) 2024.02.23
msvc 환경에서 bazel /MD 모드로 컴파일하기  (1) 2024.02.01
generated file 활용하기  (0) 2024.01.30
platforms module 사용하기  (0) 2024.01.29