模块 MakeMakefile

mkmf.rb 用于 Ruby C 扩展,用于生成 Makefile,该 Makefile 将正确编译和链接 C 扩展到 Ruby 和第三方库。

常量

ASSEMBLE_C

在生成的 Makefile 中将 C 文件转换为汇编程序源代码的命令

ASSEMBLE_CXX

在生成的 Makefile 中将 C++ 文件转换为汇编程序源代码的命令

CLEANINGS

将清理扩展构建目录的 Makefile 规则

COMMON_HEADERS

Ruby C 扩展的通用头文件

COMMON_LIBS

Ruby C 扩展的通用库

COMPILE_C

在生成的 Makefile 中编译 C 文件的命令

COMPILE_CXX

在生成的 Makefile 中编译 C++ 文件的命令

COMPILE_RULES

make 编译规则

CONFIG

使用 Ruby 构建时的默认值进行的 makefile 配置。

CONFTEST_CXX
CXX_EXT

使用 C++ 编译器编译的文件的扩展名

C_EXT

使用 C 编译器编译的文件的扩展名

EXPORT_PREFIX
HDR_EXT

头文件的扩展名

LIBARG

将库添加到链接器的参数

LIBPATHFLAG

将库路径添加到链接器的参数

链接共享库的命令

MAIN_DOES_NOTHING

不执行任何工作的 C 主函数

ORIG_LIBPATH
RPATHFLAG
RULE_SUBST
SRC_EXT

源文件的扩展名

用于编译程序以测试链接库的命令

UNIVERSAL_INTS

私有类方法

[](name) 点击切换源代码
# File lib/mkmf.rb, line 2844
def self.[](name)
  @lang.fetch(name)
end
[]=(name, mod) 点击切换源代码
# File lib/mkmf.rb, line 2848
def self.[]=(name, mod)
  @lang[name] = mod
end

公共实例方法

append_cflags(flags, *opts) 点击切换源代码

检查每个给定的 C 编译器标志是否可接受,如果可接受则将其追加到 $CFLAGS 中。

flags

一个 C 编译器标志,可以是 StringArray 类型

# File lib/mkmf.rb, line 1015
def append_cflags(flags, *opts)
  Array(flags).each do |flag|
    if checking_for("whether #{flag} is accepted as CFLAGS") {
         try_cflags(flag, *opts)
       }
      $CFLAGS << " " << flag
    end
  end
end
check_signedness(type, headers = nil, opts = nil, &b) 点击切换源代码

返回给定 type 的符号性。您可以选择指定额外的 headers 来搜索 type

如果找到 type 并且它是一个数值类型,则使用 type 名称(大写)、以 SIGNEDNESS_OF_ 开头、后跟 type 名称、最后是 =X(其中“X”是正整数表示 type 是无符号的,负整数表示 type 是有符号的)作为预处理器常量传递给编译器。

例如,如果 size_t 定义为无符号,则 check_signedness('size_t') 将返回 +1,并且 SIGNEDNESS_OF_SIZE_T=+1 预处理器宏将传递给编译器。对于 check_signedness('int'),将设置 SIGNEDNESS_OF_INT=-1 宏。

# File lib/mkmf.rb, line 1416
def check_signedness(type, headers = nil, opts = nil, &b)
  typedef, member, prelude = typedef_expr(type, headers)
  signed = nil
  checking_for("signedness of #{type}", STRING_OR_FAILED_FORMAT) do
    signed = try_signedness(typedef, member, [prelude], opts, &b) or next nil
    $defs.push("-DSIGNEDNESS_OF_%s=%+d" % [type.tr_cpp, signed])
    signed < 0 ? "signed" : "unsigned"
  end
  signed
end
check_sizeof(type, headers = nil, opts = "", &b) 点击切换源代码

返回给定 type 的大小。您可以选择指定额外的 headers 来搜索 type

如果找到,宏将作为预处理器常量传递给编译器,使用类型名称(大写),在前面加上SIZEOF_,后面加上类型名称,然后加上=X,其中“X”是实际大小。

例如,如果check_sizeof('mystruct')返回12,那么SIZEOF_MYSTRUCT=12预处理器宏将传递给编译器。

# File lib/mkmf.rb, line 1387
def check_sizeof(type, headers = nil, opts = "", &b)
  typedef, member, prelude = typedef_expr(type, headers)
  prelude << "#{typedef} *rbcv_ptr_;\n"
  prelude = [prelude]
  expr = "sizeof((*rbcv_ptr_)#{"." << member if member})"
  fmt = STRING_OR_FAILED_FORMAT
  checking_for checking_message("size of #{type}", headers), fmt do
    if size = try_constant(expr, prelude, opts, &b)
      $defs.push(format("-DSIZEOF_%s=%s", type.tr_cpp, size))
      size
    end
  end
end
convertible_int(type, headers = nil, opts = nil, &b) 点击切换源代码

返回给定type的可转换整数类型。您可以选择指定额外的headers来搜索type可转换实际上是指相同的类型,或者从相同的类型typedef而来。

如果type是整数类型,并且找到了可转换类型,则以下宏将作为预处理器常量传递给编译器,使用type名称(大写)。

  • TYPEOF_,后面加上type名称,然后加上=X,其中“X”是找到的可转换类型名称。

  • TYP2NUMNUM2TYP,其中TYPtype名称(大写),将_t后缀替换为“T”,后面加上=X,其中“X”是将type转换为Integer对象的宏名称,反之亦然。

例如,如果foobar_t定义为无符号长整型,那么convertible_int("foobar_t")将返回“无符号长整型”,并定义以下宏

#define TYPEOF_FOOBAR_T unsigned long
#define FOOBART2NUM ULONG2NUM
#define NUM2FOOBART NUM2ULONG
# File lib/mkmf.rb, line 1451
def convertible_int(type, headers = nil, opts = nil, &b)
  type, macname = *type
  checking_for("convertible type of #{type}", STRING_OR_FAILED_FORMAT) do
    if UNIVERSAL_INTS.include?(type)
      type
    else
      typedef, member, prelude = typedef_expr(type, headers, &b)
      if member
        prelude << "static rbcv_typedef_ rbcv_var;"
        compat = UNIVERSAL_INTS.find {|t|
          try_static_assert("sizeof(rbcv_var.#{member}) == sizeof(#{t})", [prelude], opts, &b)
        }
      else
        next unless signed = try_signedness(typedef, member, [prelude])
        u = "unsigned " if signed > 0
        prelude << "extern rbcv_typedef_ foo();"
        compat = UNIVERSAL_INTS.find {|t|
          try_compile([prelude, "extern #{u}#{t} foo();"].join("\n"), opts, :werror=>true, &b)
        }
      end
      if compat
        macname ||= type.sub(/_(?=t\z)/, '').tr_cpp
        conv = (compat == "long long" ? "LL" : compat.upcase)
        compat = "#{u}#{compat}"
        typename = type.tr_cpp
        $defs.push(format("-DSIZEOF_%s=SIZEOF_%s", typename, compat.tr_cpp))
        $defs.push(format("-DTYPEOF_%s=%s", typename, compat.quote))
        $defs.push(format("-DPRI_%s_PREFIX=PRI_%s_PREFIX", macname, conv))
        conv = (u ? "U" : "") + conv
        $defs.push(format("-D%s2NUM=%s2NUM", macname, conv))
        $defs.push(format("-DNUM2%s=NUM2%s", macname, conv))
        compat
      end
    end
  end
end
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

鉴于create_header方法根据您在 extconf.rb 文件中早些时候设置的定义生成文件,您可能希望将其作为脚本中调用的最后一个方法之一。

# File lib/mkmf.rb, line 1743
def create_header(header = "extconf.h")
  message "creating %s\n", header
  sym = header.tr_cpp
  hdr = ["#ifndef #{sym}\n#define #{sym}\n"]
  for line in $defs
    case line
    when /^-D([^=]+)(?:=(.*))?/
      hdr << "#define #$1 #{$2 ? Shellwords.shellwords($2)[0].gsub(/(?=\t+)/, "\\\n") : 1}\n"
    when /^-U(.*)/
      hdr << "#undef #$1\n"
    end
  end
  hdr << "#endif\n"
  hdr = hdr.join("")
  log_src(hdr, "#{header} is")
  unless (File.read(header) == hdr rescue false)
    File.open(header, "wb") do |hfile|
      hfile.write(hdr)
    end
  end
  $extconf_h = header
end
create_makefile(target, srcprefix = nil) { |conf| ... } 点击切换源代码

生成扩展的 Makefile,并传递您可能通过其他方法生成的任何选项和预处理器常量。

target 名称应与您在 C 扩展中定义的全局函数名称相对应,减去 Init_。例如,如果您的 C 扩展定义为 Init_foo,那么您的目标将只是“foo”。

如果目标名称中存在任何“/”字符,则只有最后一个名称被解释为目标名称,其余部分被视为顶级目录名称,生成的 Makefile 将相应地更改以遵循该目录结构。

例如,如果您传递“test/foo”作为目标名称,您的扩展将安装在“test”目录下。这意味着,为了稍后在 Ruby 程序中加载该文件,必须遵循该目录结构,例如 require 'test/foo'

当您的源文件不在与构建脚本相同的目录中时,应使用 srcprefix。这不仅可以消除您手动将源文件复制到与构建脚本相同的目录的需要,而且还可以在生成的 Makefile 中设置正确的 target_prefix

设置 target_prefix 将反过来在您的 RbConfig::CONFIG['sitearchdir'] 下的目录中安装生成的二进制文件,该目录在您运行 make install 时模拟您的本地文件系统。

例如,给定以下文件树

ext/
  extconf.rb
  test/
    foo.c

并给定以下代码

create_makefile('test/foo', 'test')

这将在生成的 Makefile 中将 target_prefix 设置为“test”。反过来,这将通过 make install 命令安装时创建以下文件树

/path/to/ruby/sitearchdir/test/foo.so

建议您使用这种方法来生成您的 makefile,而不是手动复制文件,因为某些第三方库可能依赖于正确设置的 target_prefix

srcprefix 参数可用于覆盖默认源目录,即当前目录。它包含在 VPATH 中,并添加到 INCFLAGS 列表中。

# File lib/mkmf.rb, line 2271
  def create_makefile(target, srcprefix = nil)
    $target = target
    libpath = $DEFLIBPATH|$LIBPATH
    message "creating Makefile\n"
    MakeMakefile.rm_f "#{CONFTEST}*"
    if CONFIG["DLEXT"] == $OBJEXT
      for lib in libs = $libs.split(' ')
        lib.sub!(/-l(.*)/, %%"lib\\1.#{$LIBEXT}"%)
      end
      $defs.push(format("-DEXTLIB='%s'", libs.join(",")))
    end

    if target.include?('/')
      target_prefix, target = File.split(target)
      target_prefix[0,0] = '/'
    else
      target_prefix = ""
    end

    srcprefix ||= "$(srcdir)/#{srcprefix}".chomp('/')
    RbConfig.expand(srcdir = srcprefix.dup)

    ext = ".#{$OBJEXT}"
    orig_srcs = Dir[File.join(srcdir, "*.{#{SRC_EXT.join(%q{,})}}")]
    if not $objs
      srcs = $srcs || orig_srcs
      $objs = []
      objs = srcs.inject(Hash.new {[]}) {|h, f|
        h.key?(o = File.basename(f, ".*") << ext) or $objs << o
        h[o] <<= f
        h
      }
      unless objs.delete_if {|b, f| f.size == 1}.empty?
        dups = objs.map {|b, f|
          "#{b[/.*\./]}{#{f.collect {|n| n[/([^.]+)\z/]}.join(',')}}"
        }
        abort "source files duplication - #{dups.join(", ")}"
      end
    else
      $objs.collect! {|o| File.basename(o, ".*") << ext} unless $OBJEXT == "o"
      srcs = $srcs || $objs.collect {|o| o.chomp(ext) << ".c"}
    end
    $srcs = srcs

    hdrs = Dir[File.join(srcdir, "*.{#{HDR_EXT.join(%q{,})}}")]

    target = nil if $objs.empty?

    if target and EXPORT_PREFIX
      if File.exist?(File.join(srcdir, target + '.def'))
        deffile = "$(srcdir)/$(TARGET).def"
        unless EXPORT_PREFIX.empty?
          makedef = %{$(RUBY) -pe "$$_.sub!(/^(?=\\w)/,'#{EXPORT_PREFIX}') unless 1../^EXPORTS$/i" #{deffile}}
        end
      else
        makedef = %{(echo EXPORTS && echo $(TARGET_ENTRY))}
      end
      if makedef
        $cleanfiles << '$(DEFFILE)'
        origdef = deffile
        deffile = "$(TARGET)-$(arch).def"
      end
    end
    origdef ||= ''

    if $extout and $INSTALLFILES
      $cleanfiles.concat($INSTALLFILES.collect {|files, dir|File.join(dir, files.delete_prefix('./'))})
      $distcleandirs.concat($INSTALLFILES.collect {|files, dir| dir})
    end

    if $extmk and $static
      $defs << "-DRUBY_EXPORT=1"
    end

    if $extmk and not $extconf_h
      create_header
    end

    libpath = libpathflag(libpath)

    dllib = target ? "$(TARGET).#{CONFIG['DLEXT']}" : ""
    staticlib = target ? "$(TARGET).#$LIBEXT" : ""
    conf = configuration(srcprefix)
    conf << "\
libpath = #{($DEFLIBPATH|$LIBPATH).join(" ")}
LIBPATH = #{libpath}
DEFFILE = #{deffile}

CLEANFILES = #{$cleanfiles.join(' ')}
DISTCLEANFILES = #{$distcleanfiles.join(' ')}
DISTCLEANDIRS = #{$distcleandirs.join(' ')}

extout = #{$extout && $extout.quote}
extout_prefix = #{$extout_prefix}
target_prefix = #{target_prefix}
LOCAL_LIBS = #{$LOCAL_LIBS}
LIBS = #{$LIBRUBYARG} #{$libs} #{$LIBS}
ORIG_SRCS = #{orig_srcs.collect(&File.method(:basename)).join(' ')}
SRCS = $(ORIG_SRCS) #{(srcs - orig_srcs).collect(&File.method(:basename)).join(' ')}
OBJS = #{$objs.join(" ")}
HDRS = #{hdrs.map{|h| '$(srcdir)/' + File.basename(h)}.join(' ')}
LOCAL_HDRS = #{$headers.join(' ')}
TARGET = #{target}
TARGET_NAME = #{target && target[/\A\w+/]}
TARGET_ENTRY = #{EXPORT_PREFIX || ''}Init_$(TARGET_NAME)
DLLIB = #{dllib}
EXTSTATIC = #{$static || ""}
STATIC_LIB = #{staticlib unless $static.nil?}
#{!$extout && defined?($installed_list) ? "INSTALLED_LIST = #{$installed_list}\n" : ""}
TIMESTAMP_DIR = #{$extout && $extmk ? '$(extout)/.timestamp' : '.'}
" #"
    # TODO: fixme
    install_dirs.each {|d| conf << ("%-14s= %s\n" % d) if /^[[:upper:]]/ =~ d[0]}
    sodir = $extout ? '$(TARGET_SO_DIR)' : '$(RUBYARCHDIR)'
    n = '$(TARGET_SO_DIR)$(TARGET)'
    cleanobjs = ["$(OBJS)"]
    if $extmk
      %w[bc i s].each {|ex| cleanobjs << "$(OBJS:.#{$OBJEXT}=.#{ex})"}
    end
    if target
      config_string('cleanobjs') {|t| cleanobjs << t.gsub(/\$\*/, "$(TARGET)#{deffile ? '-$(arch)': ''}")}
    end
    conf << "\
TARGET_SO_DIR =#{$extout ? " $(RUBYARCHDIR)/" : ''}
TARGET_SO     = $(TARGET_SO_DIR)$(DLLIB)
CLEANLIBS     = #{'$(TARGET_SO) ' if target}#{config_string('cleanlibs') {|t| t.gsub(/\$\*/) {n}}}
CLEANOBJS     = #{cleanobjs.join(' ')} *.bak
TARGET_SO_DIR_TIMESTAMP = #{timestamp_file(sodir, target_prefix)}
" #"

    conf = yield(conf) if block_given?
    mfile = File.open("Makefile", "wb")
    mfile.puts(conf)
    mfile.print "
all:    #{$extout ? "install" : target ? "$(DLLIB)" : "Makefile"}
static: #{$extmk && !$static ? "all" : "$(STATIC_LIB)#{$extout ? " install-rb" : ""}"}
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
" #"
    mfile.print CLEANINGS
    fsep = config_string('BUILD_FILE_SEPARATOR') {|s| s unless s == "/"}
    if fsep
      sep = ":/=#{fsep}"
      fseprepl = proc {|s|
        s = s.gsub("/", fsep)
        s = s.gsub(/(\$\(\w+)(\))/) {$1+sep+$2}
        s.gsub(/(\$\{\w+)(\})/) {$1+sep+$2}
      }
      rsep = ":#{fsep}=/"
    else
      fseprepl = proc {|s| s}
      sep = ""
      rsep = ""
    end
    dirs = []
    mfile.print "install: install-so install-rb\n\n"
    dir = sodir.dup
    mfile.print("install-so: ")
    if target
      f = "$(DLLIB)"
      dest = "$(TARGET_SO)"
      stamp = '$(TARGET_SO_DIR_TIMESTAMP)'
      if $extout
        mfile.puts dest
        mfile.print "clean-so::\n"
        mfile.print "\t-$(Q)$(RM) #{fseprepl[dest]} #{fseprepl[stamp]}\n"
        mfile.print "\t-$(Q)$(RMDIRS) #{fseprepl[dir]}#{$ignore_error}\n"
      else
        mfile.print "#{f} #{stamp}\n"
        mfile.print "\t$(INSTALL_PROG) #{fseprepl[f]} #{dir}\n"
        if defined?($installed_list)
          mfile.print "\t@echo #{dir}/#{File.basename(f)}>>$(INSTALLED_LIST)\n"
        end
      end
      mfile.print "clean-static::\n"
      mfile.print "\t-$(Q)$(RM) $(STATIC_LIB)\n"
    else
      mfile.puts "Makefile"
    end
    mfile.print("install-rb: pre-install-rb do-install-rb install-rb-default\n")
    mfile.print("install-rb-default: pre-install-rb-default do-install-rb-default\n")
    mfile.print("pre-install-rb: Makefile\n")
    mfile.print("pre-install-rb-default: Makefile\n")
    mfile.print("do-install-rb:\n")
    mfile.print("do-install-rb-default:\n")
    for sfx, i in [["-default", [["lib/**/*.rb", "$(RUBYLIBDIR)", "lib"]]], ["", $INSTALLFILES]]
      files = install_files(mfile, i, nil, srcprefix) or next
      for dir, *files in files
        unless dirs.include?(dir)
          dirs << dir
          mfile.print "pre-install-rb#{sfx}: #{timestamp_file(dir, target_prefix)}\n"
        end
        for f in files
          dest = "#{dir}/#{File.basename(f)}"
          mfile.print("do-install-rb#{sfx}: #{dest}\n")
          mfile.print("#{dest}: #{f} #{timestamp_file(dir, target_prefix)}\n")
          mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $(@D)\n")
          if defined?($installed_list) and !$extout
            mfile.print("\t@echo #{dest}>>$(INSTALLED_LIST)\n")
          end
          if $extout
            mfile.print("clean-rb#{sfx}::\n")
            mfile.print("\t-$(Q)$(RM) #{fseprepl[dest]}\n")
          end
        end
      end
      mfile.print "pre-install-rb#{sfx}:\n"
      if files.empty?
        mfile.print("\t@$(NULLCMD)\n")
      else
        q = "$(MAKE) -q do-install-rb#{sfx}"
        if $nmake
          mfile.print "!if \"$(Q)\" == \"@\"\n\t@#{q} || \\\n!endif\n\t"
        else
          mfile.print "\t$(Q1:0=@#{q} || )"
        end
        mfile.print "$(ECHO1:0=echo) installing#{sfx.sub(/^-/, " ")} #{target} libraries\n"
      end
      if $extout
        dirs.uniq!
        unless dirs.empty?
          mfile.print("clean-rb#{sfx}::\n")
          for dir in dirs.sort_by {|d| -d.count('/')}
            stamp = timestamp_file(dir, target_prefix)
            mfile.print("\t-$(Q)$(RM) #{fseprepl[stamp]}\n")
            mfile.print("\t-$(Q)$(RMDIRS) #{fseprepl[dir]}#{$ignore_error}\n")
          end
        end
      end
    end
    if target and !dirs.include?(sodir)
      mfile.print "$(TARGET_SO_DIR_TIMESTAMP):\n\t$(Q) $(MAKEDIRS) $(@D) #{sodir}\n\t$(Q) $(TOUCH) $@\n"
    end
    dirs.each do |d|
      t = timestamp_file(d, target_prefix)
      mfile.print "#{t}:\n\t$(Q) $(MAKEDIRS) $(@D) #{d}\n\t$(Q) $(TOUCH) $@\n"
    end

    mfile.print <<-SITEINSTALL

site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb

    SITEINSTALL

    return unless target

    mfile.print ".SUFFIXES: .#{(SRC_EXT + [$OBJEXT, $ASMEXT]).compact.join(' .')}\n"
    mfile.print "\n"

    compile_command = "\n\t$(ECHO) compiling $(<#{rsep})\n\t$(Q) %s\n\n"
    command = compile_command % COMPILE_CXX
    asm_command = compile_command.sub(/compiling/, 'translating') % ASSEMBLE_CXX
    CXX_EXT.each do |e|
      each_compile_rules do |rule|
        mfile.printf(rule, e, $OBJEXT)
        mfile.print(command)
        mfile.printf(rule, e, $ASMEXT)
        mfile.print(asm_command)
      end
    end
    command = compile_command % COMPILE_C
    asm_command = compile_command.sub(/compiling/, 'translating') % ASSEMBLE_C
    C_EXT.each do |e|
      each_compile_rules do |rule|
        mfile.printf(rule, e, $OBJEXT)
        mfile.print(command)
        mfile.printf(rule, e, $ASMEXT)
        mfile.print(asm_command)
      end
    end

    mfile.print "$(TARGET_SO): "
    mfile.print "$(DEFFILE) " if makedef
    mfile.print "$(OBJS) Makefile"
    mfile.print " $(TARGET_SO_DIR_TIMESTAMP)" if $extout
    mfile.print "\n"
    mfile.print "\t$(ECHO) linking shared-object #{target_prefix.sub(/\A\/(.*)/, '\1/')}$(DLLIB)\n"
    mfile.print "\t-$(Q)$(RM) $(@#{sep})\n"
    link_so = LINK_SO.gsub(/^/, "\t$(Q) ")
    if srcs.any?(&%r"\.(?:#{CXX_EXT.join('|')})\z".method(:===))
      link_so = link_so.sub(/\bLDSHARED\b/, '\&XX')
    end
    mfile.print link_so, "\n\n"
    unless $static.nil?
      mfile.print "$(STATIC_LIB): $(OBJS)\n\t-$(Q)$(RM) $(@#{sep})\n\t"
      mfile.print "$(ECHO) linking static-library $(@#{rsep})\n\t$(Q) "
      mfile.print "$(AR) #{config_string('ARFLAGS') || 'cru '}$@ $(OBJS)"
      config_string('RANLIB') do |ranlib|
        mfile.print "\n\t-$(Q)#{ranlib} $(@)#{$ignore_error}"
      end
    end
    mfile.print "\n\n"
    if makedef
      mfile.print "$(DEFFILE): #{origdef}\n"
      mfile.print "\t$(ECHO) generating $(@#{rsep})\n"
      mfile.print "\t$(Q) #{makedef} > $@\n\n"
    end

    depend = File.join(srcdir, "depend")
    if File.exist?(depend)
      mfile.print("###\n", *depend_rules(File.read(depend)))
    else
      mfile.print "$(OBJS): $(HDRS) $(ruby_headers)\n"
    end

    $makefile_created = true
  ensure
    mfile.close if mfile
  end
depend_rules(depend) 点击切换源代码

处理“depend”文件的数据内容。此文件的每一行都应为一个文件名。

以 Makefile 格式返回查找结果的输出。

# File lib/mkmf.rb, line 2156
def depend_rules(depend)
  suffixes = []
  depout = []
  cont = implicit = nil
  impconv = proc do
    each_compile_rules {|rule| depout << (rule % implicit[0]) << implicit[1]}
    implicit = nil
  end
  ruleconv = proc do |line|
    if implicit
      if /\A\t/ =~ line
        implicit[1] << line
        next
      else
        impconv[]
      end
    end
    if m = /\A\.(\w+)\.(\w+)(?:\s*:)/.match(line)
      suffixes << m[1] << m[2]
      implicit = [[m[1], m[2]], [m.post_match]]
      next
    elsif RULE_SUBST and /\A(?!\s*\w+\s*=)[$\w][^#]*:/ =~ line
      line.sub!(/\s*\#.*$/, '')
      comment = $&
      line.gsub!(%r"(\s)(?!\.)([^$(){}+=:\s\\,]+)(?=\s|\z)") {$1 + RULE_SUBST % $2}
      line = line.chomp + comment + "\n" if comment
    end
    depout << line
  end
  depend.each_line do |line|
    line.gsub!(/\.o\b/, ".#{$OBJEXT}")
    line.gsub!(/\{\$\(VPATH\)\}/, "") unless $nmake
    line.gsub!(/\$\((?:hdr|top)dir\)\/config.h/, $config_h)
    if $nmake && /\A\s*\$\(RM|COPY\)/ =~ line
      line.gsub!(%r"[-\w\./]{2,}"){$&.tr("/", "\\")}
      line.gsub!(/(\$\((?!RM|COPY)[^:)]+)(?=\))/, '\1:/=\\')
    end
    if /(?:^|[^\\])(?:\\\\)*\\$/ =~ line
      (cont ||= []) << line
      next
    elsif cont
      line = (cont << line).join
      cont = nil
    end
    ruleconv.call(line)
  end
  if cont
    ruleconv.call(cont.join)
  elsif implicit
    impconv.call
  end
  unless suffixes.empty?
    depout.unshift(".SUFFIXES: ." + suffixes.uniq.join(" .") + "\n\n")
  end
  if $extconf_h
    depout.unshift("$(OBJS): $(RUBY_EXTCONF_H)\n\n")
    depout.unshift("$(OBJS): $(hdrdir)/ruby/win32.h\n\n") if $mswin or $mingw
  end
  depout.flatten!
  depout
end
dir_config(target) 点击切换源代码
dir_config(target, prefix)
dir_config(target, idefault, ldefault)

设置一个 target 名称,用户可以使用该名称在命令行上配置各种“with”选项。例如,如果目标设置为“foo”,那么用户可以使用 --with-foo-dir=prefix--with-foo-include=dir--with-foo-lib=dir 命令行选项来告诉系统在哪里搜索头文件/库文件。

您可以传递额外的参数来指定默认值。如果给出一个参数,它将被视为默认的 prefix,如果给出两个参数,它们将被视为“include”和“lib”的默认值,顺序依次为“include”和“lib”。

在任何情况下,返回值都将是一个包含确定的“include”和“lib”目录的数组,如果在没有指定默认值的情况下没有给出相应的命令行选项,则这两个目录中的任何一个都可能为 nil。

请注意,dir_config 只会将搜索库和包含文件的目录添加到列表中。它不会将库链接到您的应用程序。

# File lib/mkmf.rb, line 1793
def dir_config(target, idefault=nil, ldefault=nil)
  key = [target, idefault, ldefault].compact.join("\0")
  if conf = $config_dirs[key]
    return conf
  end

  if dir = with_config(target + "-dir", (idefault unless ldefault))
    defaults = Array === dir ? dir : dir.split(File::PATH_SEPARATOR)
    idefault = ldefault = nil
  end

  idir = with_config(target + "-include", idefault)
  if conf = $arg_config.assoc("--with-#{target}-include")
    conf[1] ||= "${#{target}-dir}/include"
  end
  ldir = with_config(target + "-lib", ldefault)
  if conf = $arg_config.assoc("--with-#{target}-lib")
    conf[1] ||= "${#{target}-dir}/#{_libdir_basename}"
  end

  idirs = idir ? Array === idir ? idir.dup : idir.split(File::PATH_SEPARATOR) : []
  if defaults
    idirs.concat(defaults.collect {|d| d + "/include"})
    idir = ([idir] + idirs).compact.join(File::PATH_SEPARATOR)
  end
  unless idirs.empty?
    idirs.collect! {|d| "-I" + d}
    idirs -= Shellwords.shellwords($CPPFLAGS)
    unless idirs.empty?
      $CPPFLAGS = (idirs.quote << $CPPFLAGS).join(" ")
    end
  end

  ldirs = ldir ? Array === ldir ? ldir.dup : ldir.split(File::PATH_SEPARATOR) : []
  if defaults
    ldirs.concat(defaults.collect {|d| "#{d}/#{_libdir_basename}"})
    ldir = ([ldir] + ldirs).compact.join(File::PATH_SEPARATOR)
  end
  $LIBPATH = ldirs | $LIBPATH

  $config_dirs[key] = [idir, ldir]
end
dummy_makefile(srcdir) 点击切换源代码

创建一个简单的 Makefile。

# File lib/mkmf.rb, line 2125
  def dummy_makefile(srcdir)
    configuration(srcdir) << <<RULES << CLEANINGS
CLEANFILES = #{$cleanfiles.join(' ')}
DISTCLEANFILES = #{$distcleanfiles.join(' ')}

all install static install-so install-rb: Makefile
        @$(NULLCMD)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb

RULES
  end
enable_config(config, default=nil) { |config, default| ... } 点击切换源代码

测试是否存在 --enable-config--disable-config 选项。如果给出启用选项,则返回 true,如果给出禁用选项,则返回 false,否则返回默认值。

这对于添加自定义定义(例如调试信息)很有用。

示例

if enable_config("debug")
   $defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG"
end
# File lib/mkmf.rb, line 1705
def enable_config(config, default=nil)
  if arg_config("--enable-"+config)
    true
  elsif arg_config("--disable-"+config)
    false
  elsif block_given?
    yield(config, default)
  else
    return default
  end
end
find_executable(bin, path = nil) 点击切换源代码

path 上搜索可执行文件 bin。默认路径是您的 PATH 环境变量。如果未定义,它将尝试搜索 /usr/local/bin、/usr/ucb、/usr/bin 和 /bin。

如果找到,它将返回完整路径,包括可执行文件名,以及它被找到的位置。

请注意,此方法不会实际影响生成的 Makefile。

# File lib/mkmf.rb, line 1635
def find_executable(bin, path = nil)
  checking_for checking_message(bin, path) do
    find_executable0(bin, path)
  end
end
find_header(header, *paths) 点击切换源代码

指示 mkmf 在提供的任何 paths 中搜索给定的 header,并返回它是否在这些路径中找到。

如果找到头文件,则它被找到的路径将被添加到发送到编译器(通过 -I 开关)的包含目录列表中。

# File lib/mkmf.rb, line 1199
def find_header(header, *paths)
  message = checking_message(header, paths)
  header = cpp_include(header)
  checking_for message do
    if try_header(header)
      true
    else
      found = false
      paths.each do |dir|
        opt = "-I#{dir}".quote
        if try_header(header, opt)
          $INCFLAGS << " " << opt
          found = true
          break
        end
      end
      found
    end
  end
end
find_library(lib, func, *paths, &b) 点击切换源代码

返回在指定 paths 中的某个库 lib 中是否可以找到入口点 func,其中 paths 是一个字符串数组。如果 funcnil,则使用 main() 函数作为入口点。

如果找到 lib,则它被找到的路径将被添加到搜索和链接的库路径列表中。

# File lib/mkmf.rb, line 1073
def find_library(lib, func, *paths, &b)
  dir_config(lib)
  lib = with_config(lib+'lib', lib)
  paths = paths.flat_map {|path| path.split(File::PATH_SEPARATOR)}
  checking_for checking_message(func && func.funcall_style, LIBARG%lib) do
    libpath = $LIBPATH
    libs = append_library($libs, lib)
    begin
      until r = try_func(func, libs, &b) or paths.empty?
        $LIBPATH = libpath | [paths.shift]
      end
      if r
        $libs = libs
        libpath = nil
      end
    ensure
      $LIBPATH = libpath if libpath
    end
    r
  end
end
find_type(type, opt, *headers, &b) 点击切换源代码

返回静态类型 type 的定义位置。

您也可以传递额外的标志到 opt,这些标志将被传递给编译器。

另请参见 have_type

# File lib/mkmf.rb, line 1296
def find_type(type, opt, *headers, &b)
  opt ||= ""
  fmt = "not found"
  def fmt.%(x)
    x ? x.respond_to?(:join) ? x.join(",") : x : self
  end
  checking_for checking_message(type, nil, opt), fmt do
    headers.find do |h|
      try_type(type, h, opt, &b)
    end
  end
end
have_const(const, headers = nil, opt = "", &b) 点击切换源代码

返回常量 const 是否已定义。您可以选择将 consttype 作为 [const, type] 传递,例如

have_const(%w[PTHREAD_MUTEX_INITIALIZER pthread_mutex_t], "pthread.h")

您也可以传递额外的 headers 以检查,除了常见的头文件之外,还可以传递额外的标志到 opt,这些标志将被传递给编译器。

如果找到,一个宏将作为预处理器常量传递给编译器,使用类型名称(大写),在 HAVE_CONST_ 前面加上。

例如,如果 have_const('foo') 返回 true,那么 HAVE_CONST_FOO 预处理器宏将被传递给编译器。

# File lib/mkmf.rb, line 1344
def have_const(const, headers = nil, opt = "", &b)
  checking_for checking_message([*const].compact.join(' '), headers, opt) do
    try_const(const, headers, opt, &b)
  end
end
have_framework(fw, &b) 点击切换源代码

返回给定的 framework 是否可以在您的系统上找到。如果找到,一个宏将作为预处理器常量传递给编译器,使用框架名称(大写),并在前面加上 HAVE_FRAMEWORK_

例如,如果 have_framework('Ruby') 返回 true,那么 HAVE_FRAMEWORK_RUBY 预处理器宏将被传递给编译器。

如果 fw 是框架名称及其头文件名称的配对,则检查该头文件,而不是使用通常使用的与框架同名的头文件。

# File lib/mkmf.rb, line 1170
def have_framework(fw, &b)
  if Array === fw
    fw, header = *fw
  else
    header = "#{fw}.h"
  end
  checking_for fw do
    src = cpp_include("#{fw}/#{header}") << "\n" "int main(void){return 0;}"
    opt = " -framework #{fw}"
    if try_link(src, opt, &b) or (objc = try_link(src, "-ObjC#{opt}", &b))
      $defs.push(format("-DHAVE_FRAMEWORK_%s", fw.tr_cpp))
      # TODO: non-worse way than this hack, to get rid of separating
      # option and its argument.
      $LDFLAGS << " -ObjC" if objc and /(\A|\s)-ObjC(\s|\z)/ !~ $LDFLAGS
      $LIBS << opt
      true
    else
      false
    end
  end
end
have_func(func, headers = nil, opt = "", &b) 点击切换源代码

返回函数 func 是否可以在通用头文件中或您提供的任何 headers 中找到。如果找到,一个宏将作为预处理器常量传递给编译器,使用函数名称(大写),并在前面加上 HAVE_

要检查附加库中的函数,您需要先使用 have_library() 检查该库。func 应为函数名或带参数的函数名。

例如,如果 have_func('foo') 返回 true,那么 HAVE_FOO 预处理器宏将被传递给编译器。

# File lib/mkmf.rb, line 1107
def have_func(func, headers = nil, opt = "", &b)
  checking_for checking_message(func.funcall_style, headers, opt) do
    if try_func(func, $libs, headers, opt, &b)
      $defs << "-DHAVE_#{func.sans_arguments.tr_cpp}"
      true
    else
      false
    end
  end
end
have_header(header, preheaders = nil, opt = "", &b) 点击切换源代码

返回给定的 header 文件是否可以在您的系统上找到。如果找到,一个宏将作为预处理器常量传递给编译器,使用头文件名(大写),并在前面加上 HAVE_

例如,如果 have_header('foo.h') 返回 true,那么 HAVE_FOO_H 预处理器宏将被传递给编译器。

# File lib/mkmf.rb, line 1147
def have_header(header, preheaders = nil, opt = "", &b)
  dir_config(header[/.*?(?=\/)|.*?(?=\.)/])
  checking_for header do
    if try_header(cpp_include(preheaders)+cpp_include(header), opt, &b)
      $defs.push(format("-DHAVE_%s", header.tr_cpp))
      true
    else
      false
    end
  end
end
have_library(lib, func = nil, headers = nil, opt = "", &b) 点击切换源代码

返回给定的入口点 func 是否可以在 lib 中找到。如果 funcnil,则默认使用 main() 入口点。如果找到,它会将库添加到链接扩展时要使用的库列表中。

如果提供了 headers,它将包含这些头文件作为它在搜索 func 时查找的头文件。

要链接的库的真实名称可以通过--with-FOOlib配置选项更改。

# File lib/mkmf.rb, line 1047
def have_library(lib, func = nil, headers = nil, opt = "", &b)
  dir_config(lib)
  lib = with_config(lib+'lib', lib)
  checking_for checking_message(func && func.funcall_style, LIBARG%lib, opt) do
    if COMMON_LIBS.include?(lib)
      true
    else
      libs = append_library($libs, lib)
      if try_func(func, libs, headers, opt, &b)
        $libs = libs
        true
      else
        false
      end
    end
  end
end
have_macro(macro, headers = nil, opt = "", &b) 点击切换源代码

返回macro是否在公共头文件中或您提供的任何headers中定义。

您传递给opt的任何选项都将传递给编译器。

# File lib/mkmf.rb, line 1030
def have_macro(macro, headers = nil, opt = "", &b)
  checking_for checking_message(macro, headers, opt) do
    macro_defined?(macro, cpp_include(headers), opt, &b)
  end
end
have_struct_member(type, member, headers = nil, opt = "", &b) 点击切换源代码

返回类型为type的结构体是否包含member。如果不存在,或者找不到结构体类型,则返回false。您可以选择指定其他headers,以便在其中查找结构体(除了公共头文件)。

如果找到,则使用类型名称和成员名称(大写)作为预处理器常量传递给编译器,并在前面加上HAVE_

例如,如果have_struct_member('struct foo', 'bar')返回true,则HAVE_STRUCT_FOO_BAR预处理器宏将传递给编译器。

HAVE_ST_BAR也为向后兼容性而定义。

# File lib/mkmf.rb, line 1235
  def have_struct_member(type, member, headers = nil, opt = "", &b)
    checking_for checking_message("#{type}.#{member}", headers) do
      if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
int s = (char *)&((#{type}*)0)->#{member} - (char *)0;
#{MAIN_DOES_NOTHING}
SRC
        $defs.push(format("-DHAVE_%s_%s", type.tr_cpp, member.tr_cpp))
        $defs.push(format("-DHAVE_ST_%s", member.tr_cpp)) # backward compatibility
        true
      else
        false
      end
    end
  end
have_type(type, headers = nil, opt = "", &b) 点击切换源代码

返回静态类型type是否已定义。您可以选择传递其他headers以与公共头文件一起检查。

您也可以传递额外的标志到 opt,这些标志将被传递给编译器。

如果找到,则使用类型名称(大写)作为预处理器常量传递给编译器,并在前面加上HAVE_TYPE_

例如,如果have_type('foo')返回true,则HAVE_TYPE_FOO预处理器宏将传递给编译器。

# File lib/mkmf.rb, line 1283
def have_type(type, headers = nil, opt = "", &b)
  checking_for checking_message(type, headers, opt) do
    try_type(type, headers, opt, &b)
  end
end
have_var(var, headers = nil, opt = "", &b) 点击切换源代码

返回变量var是否可以在公共头文件中或您提供的任何headers中找到。如果找到,则使用变量名称(大写)作为预处理器常量传递给编译器,并在前面加上HAVE_

要检查其他库中的变量,您需要先使用have_library()检查该库。

例如,如果have_var('foo')返回true,则HAVE_FOO预处理器宏将传递给编译器。

# File lib/mkmf.rb, line 1129
def have_var(var, headers = nil, opt = "", &b)
  checking_for checking_message(var, headers, opt) do
    if try_var(var, headers, opt, &b)
      $defs.push(format("-DHAVE_%s", var.tr_cpp))
      true
    else
      false
    end
  end
end
pkg_config(pkg, *options) 点击切换源代码

通过以下命令中找到的第一个命令,返回已安装库的编译/链接信息,以 [cflags, ldflags, libs] 元组的形式。

  1. 如果通过命令行选项给出 --with-{pkg}-config={command}{command} {options}

  2. {pkg}-config {options}

  3. pkg-config {options} {pkg}

其中 options 是不带连字符的选项名称,例如 --cflags 标志的 "cflags"

获得的值将附加到 $INCFLAGS$CFLAGS$LDFLAGS$libs

如果给出了一个或多个 options 参数,则使用这些选项调用配置命令,并返回一个剥离的输出字符串,而不修改上面提到的任何全局值。

# File lib/mkmf.rb, line 1855
def pkg_config(pkg, *options)
  fmt = "not found"
  def fmt.%(x)
    x ? x.inspect : self
  end

  checking_for "pkg-config for #{pkg}", fmt do
    _, ldir = dir_config(pkg)
    if ldir
      pkg_config_path = "#{ldir}/pkgconfig"
      if File.directory?(pkg_config_path)
        Logging.message("PKG_CONFIG_PATH = %s\n", pkg_config_path)
        envs = ["PKG_CONFIG_PATH"=>[pkg_config_path, ENV["PKG_CONFIG_PATH"]].compact.join(File::PATH_SEPARATOR)]
      end
    end
    if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig)
    # if and only if package specific config command is given
    elsif ($PKGCONFIG ||=
           (pkgconfig = with_config("pkg-config") {config_string("PKG_CONFIG") || "pkg-config"}) &&
           find_executable0(pkgconfig) && pkgconfig) and
         xsystem([*envs, $PKGCONFIG, "--exists", pkg])
      # default to pkg-config command
      pkgconfig = $PKGCONFIG
      args = [pkg]
    elsif find_executable0(pkgconfig = "#{pkg}-config")
    # default to package specific config command, as a last resort.
    else
      pkgconfig = nil
    end
    if pkgconfig
      get = proc {|opts|
        opts = Array(opts).map { |o| "--#{o}" }
        opts = xpopen([*envs, pkgconfig, *opts, *args], err:[:child, :out], &:read)
        Logging.open {puts opts.each_line.map{|s|"=> #{s.inspect}"}}
        opts.strip if $?.success?
      }
    end
    orig_ldflags = $LDFLAGS
    if get and !options.empty?
      get[options]
    elsif get and try_ldflags(ldflags = get['libs'])
      if incflags = get['cflags-only-I']
        $INCFLAGS << " " << incflags
        cflags = get['cflags-only-other']
      else
        cflags = get['cflags']
      end
      libs = get['libs-only-l']
      if cflags
        $CFLAGS += " " << cflags
        $CXXFLAGS += " " << cflags
      end
      if libs
        ldflags = (Shellwords.shellwords(ldflags) - Shellwords.shellwords(libs)).quote.join(" ")
      else
        libs, ldflags = Shellwords.shellwords(ldflags).partition {|s| s =~ /-l([^ ]+)/ }.map {|l|l.quote.join(" ")}
      end
      $libs += " " << libs

      $LDFLAGS = [orig_ldflags, ldflags].join(' ')
      Logging::message "package configuration for %s\n", pkg
      Logging::message "incflags: %s\ncflags: %s\nldflags: %s\nlibs: %s\n\n",
                       incflags, cflags, ldflags, libs
      [[incflags, cflags].join(' '), ldflags, libs]
    else
      Logging::message "package configuration for %s is not found\n", pkg
      nil
    end
  end
end
try_const(const, headers = nil, opt = "", &b) 点击切换源代码

返回常量 const 是否已定义。

另请参见 have_const

# File lib/mkmf.rb, line 1313
  def try_const(const, headers = nil, opt = "", &b)
    const, type = *const
    if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
typedef #{type || 'int'} conftest_type;
conftest_type conftestval = #{type ? '' : '(int)'}#{const};
SRC
      $defs.push(format("-DHAVE_CONST_%s", const.tr_cpp))
      true
    else
      false
    end
  end
try_type(type, headers = nil, opt = "", &b) 点击切换源代码

返回静态类型 type 是否已定义。

另请参见 have_type

# File lib/mkmf.rb, line 1256
  def try_type(type, headers = nil, opt = "", &b)
    if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
typedef #{type} conftest_type;
int conftestval[sizeof(conftest_type)?1:-1];
SRC
      $defs.push(format("-DHAVE_TYPE_%s", type.tr_cpp))
      true
    else
      false
    end
  end
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
# File lib/mkmf.rb, line 1670
def with_config(config, default=nil)
  config = config.sub(/^--with[-_]/, '')
  val = arg_config("--with-"+config) do
    if arg_config("--without-"+config)
      false
    elsif block_given?
      yield(config, default)
    else
      break default
    end
  end
  case val
  when "yes"
    true
  when "no"
    false
  else
    val
  end
end

私有实例方法

cc_command(opt="") 点击切换源代码
# File lib/mkmf.rb, line 2875
def cc_command(opt="")
  conf = cc_config(opt)
  RbConfig::expand("$(CXX) #$INCFLAGS #$CPPFLAGS #$CXXFLAGS #$ARCH_FLAG #{opt} -c #{CONFTEST_CXX}",
                   conf)
end
conftest_source() 点击切换源代码
# File lib/mkmf.rb, line 2871
def conftest_source
  CONFTEST_CXX
end
have_devel?() 点击切换源代码
# File lib/mkmf.rb, line 2863
def have_devel?
  unless defined? @have_devel
    @have_devel = true
    @have_devel = try_link(MAIN_DOES_NOTHING)
  end
  @have_devel
end