MakeMakefile
MakeMakefile
MakeMakefile(관례적으로 mkmf)는 Ruby C 확장(extension)이 C 확장을 Ruby와 서드파티 라이브러리에 올바르게 컴파일·링크할 Makefile을 생성하도록 돕는 모듈이에요. extconf.rb에서 require 'mkmf'로 불러와 쓰죠.
출처: Ruby 4.0 API
본문
C 확장을 만들려면 Ruby 헤더 경로, 컴파일러 옵션, 링크할 라이브러리 등이 시스템마다 달라져요. mkmf는 그런 환경 차이를 조사해서(have_* 계열), 조사 결과를 헤더와 Makefile로 뽑아내는(create_header, create_makefile) 흐름으로 동작해요.
가장 흔한 사용 패턴을 먼저 볼게요.
# extconf.rb
require 'mkmf'
have_func('realpath')
have_header('sys/utime.h')
create_header
create_makefile('foo')
have_func, have_header로 시스템에 뭐가 있는지 확인하고, create_header로 그 결과를 extconf.h에 담고, create_makefile로 최종 Makefile을 만들어요.
상수 (Constants)
ASSEMBLE_C— C 파일을 어셈블러 소스로 변환하는 명령.ASSEMBLE_CXX— C++ 파일을 어셈블러 소스로 변환하는 명령.CLEANINGS— 확장 빌드 디렉터리를 정리하는 Makefile 규칙.COMMON_HEADERS— Ruby C 확장용 공통 헤더.COMMON_LIBS— Ruby C 확장용 공통 라이브러리.COMPILE_C— C 파일을 컴파일하는 명령.COMPILE_CXX— C++ 파일을 컴파일하는 명령.COMPILE_RULES— make 컴파일 규칙.CONFIG— Ruby가 빌드될 때의 기본값으로 만든 makefile 설정.CXX_EXT— C++ 컴파일러로 컴파일하는 파일의 확장자.C_EXT— C 컴파일러로 컴파일하는 파일의 확장자.EXPORT_PREFIX— 심볼에 자동으로 붙는 프리픽스.HDR_EXT— 헤더 파일의 확장자.LANGUAGE— 이 모듈이 대상으로 하는 언어.LIBARG— 링커에 라이브러리를 추가하는 인자.LIBPATHFLAG— 링커에 라이브러리 경로를 추가하는 인자.LINK_SO— 공유 라이브러리를 링크하는 명령.MAIN_DOES_NOTHING— 아무 일도 하지 않는 Cmain함수.ORIG_LIBPATH—LIB환경 변수의 원래 값.RPATHFLAG— 링커에 런타임 라이브러리 경로를 추가하는 인자.RULE_SUBST— NMake용 규칙 치환.RbConfig— Ruby 인터프리터의 빌드 설정을 저장하는 모듈.mkconfig.rb가 ruby를 빌드할 때 만들고,mkmf가 호환되는 네이티브 확장을 빌드하기 위해 써요. 이 파일을 수정해도 다음에 ruby를 빌드하면 사라져요.SRC_EXT— 소스 파일의 확장자.TRY_LINK— 라이브러리 링크를 테스트하기 위해 프로그램을 컴파일하는 명령.UNIVERSAL_INTS—convertible_int용 타입 이름들.
클래스 메서드
[](name)
name 언어의 모듈을 가져와요.
[]=(name, mod)
name 언어의 모듈을 정의해요. (클래스 메서드가 아니고 모듈 자체에 대한 접근자 형태로, MakeMakefile[name] = mod처럼 쓰는 방식이에요.)
rm_f(*files)
files를 삭제해요. FileUtils.rm_f와 같아요.
rm_rf(*files)
files를 재귀적으로 삭제해요. FileUtils.rm_rf와 같아요.
인스턴스 메서드
append_cflags(flags, **opts)
주어진 각 C 컴파일러 플래그가 받아들여지는지 확인하고, 받아들여지면 $CFLAGS에 덧붙여요.
append_ldflags(flags, **opts)
주어진 각 링커 플래그가 받아들여지는지 확인하고, 받아들여지면 $LDFLAGS에 덧붙여요.
check_signedness(type, headers = nil, opts = nil, &b)
주어진 type의 부호성(signed/unsigned)을 돌려줘요. 추가로 찾아볼 headers를 지정할 수 있어요. type이 숫자 타입이면, 타입 이름을 대문자로 바꿔 앞에 SIGNEDNESS_OF_를 붙인 매크로를 컴파일러에 전달해요(unsigned면 =X가 양의 정수, signed면 음의 정수). 예를 들어 size_t가 unsigned면 check_signedness('size_t')는 +1을 돌려주고 SIGNEDNESS_OF_SIZE_T=+1 매크로가 전달돼요.
check_sizeof(type, headers = nil, opts = "", &b)
주어진 type의 크기를 돌려줘요. 찾으면 타입 이름을 대문자로 바꿔 앞에 SIZEOF_를 붙인 SIZEOF_xxx=크기 매크로가 컴파일러에 전달돼요. 예를 들어 check_sizeof('mystruct')가 12를 돌려주면 SIZEOF_MYSTRUCT=12 매크로가 전달돼요.
conftest_source()
설정 검사용 임시 소스 파일 이름을 돌려줘요.
convertible_int(type, headers = nil, opts = nil, &b)
주어진 type의 변환 가능한 정수 타입을 돌려줘요. convertible이란 실제로 같은 타입이거나 같은 타입에서 typedef된 타입을 뜻해요. 예를 들어 foobar_t가 unsigned long으로 정의돼 있으면 convertible_int("foobar_t")는 "unsigned long"을 돌려주고 다음 매크로들을 정의해요.
#define TYPEOF_FOOBAR_T unsigned long
#define FOOBART2NUM ULONG2NUM
#define NUM2FOOBART NUM2ULONG
create_header(header = "extconf.h")
have_func, have_header 등 다른 메서드가 만든 매크로 정의들을 모아 헤더 파일로 생성해요. 각 정의는 header 파일 이름(기본값 "extconf.h") 기반의 커스텀 ifndef로 감싸져요.
# extconf.rb
require 'mkmf'
have_func('realpath')
have_header('sys/utime.h')
create_header
create_makefile('foo')
그러면 다음과 같은 extconf.h가 만들어져요.
#ifndef EXTCONF_H
#define EXTCONF_H
#define HAVE_REALPATH 1
#define HAVE_SYS_UTIME_H 1
#endif
이 메서드는 extconf.rb 앞부분에서 설정한 정의들을 파일로 뽑아내는 것이니, 보통 스크립트에서 마지막 무렵에 호출하는 걸 권장해요.
create_makefile(target, srcprefix = nil) { |conf| ... }
지금까지 만든 옵션과 전처리기 상수를 담아 확장용 Makefile을 생성해요.
target 이름은 C 확장에 정의된 전역 함수 이름에서 Init_을 뺀 것과 일치해야 해요. 예를 들어 C 확장이 Init_foo로 정의돼 있다면 target은 "foo"예요.
target 이름에 /가 있으면 마지막 이름만 target 이름으로 해석하고, 나머지는 최상위 디렉터리 이름으로 봐요. 곧 생성된 Makefile이 그 디렉터리 구조를 따르도록 조정돼요. "test/foo"를 target으로 넘기면 확장이 test 디렉터리에 설치되고, 나중에 require 'test/foo'로 불러와야 해요.
srcprefix는 소스 파일이 빌드 스크립트와 다른 디렉터리에 있을 때 써요. 소스 파일을 직접 복사할 필요가 없게 하면서, 생성된 Makefile의 target_prefix도 제대로 설정돼요. target_prefix를 설정하면 make install 시 생성된 바이너리가 RbConfig::CONFIG['sitearchdir'] 아래에서 로컬 파일시스템 구조를 흉내 낸 디렉터리에 설치돼요.
ext/
extconf.rb
test/
foo.c
create_makefile('test/foo', 'test')
이러면 make install할 때 다음과 같은 트리가 생성돼요.
/path/to/ruby/sitearchdir/test/foo.so
srcprefix 인자는 기본 소스 디렉터리(현재 디렉터리)를 덮어쓰는 데도 쓸 수 있어요. VPATH의 일부로 포함되고 INCFLAGS 목록에 추가돼요.
블록을 주면 생성될 Makefile의 설정 부분을 문자열 배열로 yield하고, 그 돌려준 값이 새 설정 부분으로 쓰여요.
create_makefile('foo') {|conf|
[
*conf,
"MACRO_YOU_NEED = something",
]
}
소스 디렉터리에 depend 파일이 있으면 그 내용이 depend_rules 메서드로 포맷되어 생성된 Makefile에 포함돼요.
create_tmpsrc(src) { |src| ... }
COMMON_HEADERS와 src로 임시 소스 파일을 만들어요. 블록을 주면 만들어진 소스 문자열을 yield하고, 돌려준 문자열을 소스 코드로 써요.
depend_rules(depend)
depend 파일의 데이터 내용을 처리해요. 각 줄이 파일 이름일 거라 기대해요. 결과를 Makefile 포맷으로 돌려줘요.
dir_config(target)
dir_config(target, prefix)
dir_config(target, idefault, ldefault)
사용자가 명령줄에서 다양한 "with" 옵션으로 설정할 수 있게 target 이름을 지정해요. 예를 들어 target이 "foo"면 사용자가 --with-foo-dir=prefix, --with-foo-include=dir, --with-foo-lib=dir 옵션으로 헤더/라이브러리 파일을 찾을 위치를 알려줄 수 있어요.
추가 파라미터로 기본값을 줄 수 있어요. 하나면 기본 prefix로, 두 개면 순서대로 "include"와 "lib" 기본값으로 해석돼요. 반환값은 결정된 "include"·"lib" 디렉터리 배열이고, 대응하는 옵션이 없고 기본값도 없으면 어느 쪽이든 nil일 수 있어요.
dir_config는 라이브러리와 include 파일을 찾을 위치 목록에 추가만 해요. 라이브러리를 애플리케이션에 링크하지는 않아요.
dummy_makefile(srcdir)
아무 일도 안 하는 스텁(stub) Makefile을 만들어요.
egrep_cpp(pat, src, opt = "", &b)
src가 C 전처리기로 전처리될 수 있고 패턴 pat과 일치하는지 여부를 돌려줘요. 블록을 주면 컴파일 전에 소스를 인자로 호출해서 수정할 수 있어요. pat이 Regexp면 프로세스 내에서, 아니면 egrep(1) 명령으로 검사해요.
enable_config(config, default=nil) { |config, default| ... }
--enable-config 또는 --disable-config 옵션의 존재를 검사해요. enable 옵션이 있으면 true, disable 옵션이 있으면 false, 둘 다 없으면 기본값을 돌려줘요. 디버그 정보 같은 커스텀 정의를 추가할 때 유용해요.
if enable_config("debug")
$defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG"
end
find_header(header, *paths)
주어진 header를 제공된 paths 중 어디선가 찾는지 알려줘요. 찾으면 발견된 경로가 컴파일러에 보내는 include 디렉터리 목록(-I 스위치)에 추가돼요.
find_library(lib, func, *paths, &b)
func 진입점이 지정된 paths 중 하나에서 라이브러리 lib 안에 있는지 돌려줘요. func가 nil이면 main() 함수를 진입점으로 써요. lib이 발견되면 그 경로가 검색·링크되는 라이브러리 경로 목록에 추가돼요.
have_framework(fw, &b)
주어진 framework가 시스템에 있는지 돌려줘요. 있으면 프레임워크 이름을 대문자로 바꿔 앞에 HAVE_FRAMEWORK_를 붙인 매크로가 컴파일러에 전달돼요. 예를 들어 have_framework('Ruby')가 true면 HAVE_FRAMEWORK_RUBY 매크로가 전달돼요. fw가 프레임워크 이름과 헤더 파일 이름의 쌍이면, 보통 프레임워크 이름과 같은 헤더 대신 그 헤더를 검사해요.
have_func(func, headers = nil, opt = "", &b)
함수 func가 공통 헤더나 준 headers 중 하나에 있는지 돌려줘요. 있으면 함수 이름을 대문자로 바꿔 앞에 HAVE_를 붙인 매크로가 전달돼요. 추가 라이브러리 안의 함수를 검사하려면 먼저 have_library()로 그 라이브러리를 확인해야 해요. 예를 들어 have_func('foo')가 true면 HAVE_FOO 매크로가 전달돼요.
have_header(header, preheaders = nil, opt = "", &b)
주어진 header 파일이 시스템에 있는지 돌려줘요. 있으면 파일 이름을 대문자로 바꿔 앞에 HAVE_를 붙인 매크로가 전달돼요. have_header('foo.h')가 true면 HAVE_FOO_H 매크로가 전달돼요.
have_library(lib, func = nil, headers = nil, opt = "", &b)
진입점 func가 lib 안에 있는지 돌려줘요. func가 nil이면 기본으로 main() 진입점을 써요. 찾으면 그 라이브러리를 확장 링크 시 사용할 라이브러리 목록에 추가해요. headers를 주면 func를 찾을 때 그 헤더 파일들도 포함해요. 링크할 라이브러리의 실제 이름은 --with-FOOlib 설정 옵션으로 바꿀 수 있어요.
have_macro(macro, headers = nil, opt = "", &b)
macro가 공통 헤더나 준 headers 중 하나에 정의돼 있는지 돌려줘요. opt로 넘긴 옵션은 컴파일러로 전달돼요.
have_struct_member(type, member, headers = nil, opt = "", &b)
타입 type의 구조체에 member가 있는지 돌려줘요. 없거나 구조체 타입을 찾지 못하면 false를 돌려줘요. 찾으면 타입·멤버 이름을 대문자로 바꿔 앞에 HAVE_를 붙인 매크로가 전달돼요. have_struct_member('struct foo', 'bar')가 true면 HAVE_STRUCT_FOO_BAR 매크로가 전달돼요. 하위 호환을 위해 HAVE_ST_BAR도 정의돼요.
have_var(var, headers = nil, opt = "", &b)
변수 var가 공통 헤더나 준 headers 중 하나에 있는지 돌려줘요. 있으면 변수 이름을 대문자로 바꿔 앞에 HAVE_를 붙인 매크로가 전달돼요. 추가 라이브러리 안의 변수를 검사하려면 먼저 have_library()로 확인해야 해요.
log_src(src, heading="checked program was")
src를 로그에 남겨요.
modified?(target, times)
target 파일이 존재하고 times 전부보다 같거나 새면 그 타임스탬프를 돌려줘요.
pkg_config(pkg, *options)
설치된 라이브러리에 대한 컴파일/링크 정보를 [cflags, ldflags, libs] 튜플로 돌려줘요. 다음 명령 중 먼저 찾아지는 것을 사용해요.
- 명령줄에
--with-{pkg}-config={command}가 주어지면{command} {options} {pkg}-config {options}pkg-config {options} {pkg}
얻은 값들은 $INCFLAGS, $CFLAGS, $LDFLAGS, $libs에 덧붙여져요. options 인자를 하나 이상 주면 그 옵션들로 config 명령을 호출하고, 위의 전역 값들을 건드리지 않은 채 정리된 출력 문자열만 돌려줘요.
what_type?(type, member = nil, headers = nil, &b)
type의 타입을 나타내는 문자열을 돌려줘요. member가 nil이 아니면 type의 member 타입을 돌려줘요.
with_config(config, default=nil) { |config, default| ... }
--with-config 또는 --without-config 옵션의 존재를 검사해요. with 옵션이 있으면 true, without 옵션이 있으면 false, 둘 다 없으면 기본값을 돌려줘요. 커스텀 정의를 추가할 때 유용해요.
if with_config("debug")
$defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG"
end
with_cppflags(flags) { || ... }
$CPPFLAGS를 flags로 설정하고 블록을 실행해요. 블록이 falsy 값을 돌려주면 $CPPFLAGS를 이전 값으로 되돌리고, 아니면 flags로 유지돼요.
xpopen(command, *mode, &block)
xsystem과 비슷하게 command를 실행하되, 열린 파이프를 yield해요.
xsystem(command, werror: false) → true or false
변수를 확장해 command를 실행하고, Kernel#system처럼 종료 상태를 돌려줘요. werror가 true이고 에러 출력이 비어있지 않으면 false를 돌려줘요. 출력은 로그로 남겨져요.