class MakeMakefile::Depend

Generates dependency rules for Ruby and C extension sources.

An extension project can update the autogenerated section of its depend file while running extconf.rb:

ruby extconf.rb --update-depend

The depend file must contain these markers:

# AUTOGENERATED DEPENDENCIES START
# AUTOGENERATED DEPENDENCIES END

MakeMakefile::Depend scans each source and the headers it includes without invoking a compiler. When updating an extension project, files outside the project tree, such as system and installed Ruby headers, are omitted. Use --depend-root=DIR when the project root cannot be detected from a .git directory.

Attributes

Source tree root used to resolve dependency paths.

Public Class Methods

Finds the nearest ancestor of path containing .git.

Returns path itself when no repository root can be found.

# File lib/mkmf/depend.rb, line 272
def find_root(path)
  path = File.expand_path(path)
  root = path
  loop do
    return path if File.exist?(File.join(path, ".git"))
    parent = File.dirname(path)
    break if parent == path
    path = parent
  end
  root
end

Runs the command-line interface with argv.

Returns whether the requested update or check succeeded.

# File lib/mkmf/depend.rb, line 259
def main(argv = ARGV, err: $stderr)
  options, inputs = parse_options(argv)
rescue OptionParser::ParseError => error
  err.puts "#{File.basename($0)}: #{error.message}"
  err.puts "Try '#{File.basename($0)} --help' for more information."
  false
else
  execute(inputs, **options)
end

Creates a dependency updater for root.

# File lib/mkmf/depend.rb, line 360
def initialize(root: DEFAULT_ROOT)
  @root = File.expand_path(root)
  @make_variables = {}
  @dependency_declarations = {}
  @dependency_targets = {}
  @dependency_contents = {}
  @source_cache = {}
  @stat_cache = {}
end

Public Instance Methods

Processes dependency inputs according to the selected update mode.

make_variables supplies values for Make variables in declarations. thread_model is a shortcut for its THREAD_MODEL entry.

Returns false only when check mode finds an outdated dependency file.

# File lib/mkmf/depend.rb, line 1069
def run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout,
        output: nil, make_variables: {}, nmake: false, sources: false,
        scope: nil, thread_model: nil, verbose: false)
  @source_cache.clear
  @stat_cache.clear
  case mode
  when :output
    raise ArgumentError, "output directory is missing" unless output
  when :stdout, :inplace, :check
    raise ArgumentError, "output requires output mode" if output
  else
    raise ArgumentError, "unknown update mode: #{mode.inspect}"
  end
  unless thread_model.nil?
    if thread_model.empty?
      raise ArgumentError, "thread model must not be empty"
    end
    make_variables = make_variables.merge("THREAD_MODEL" => thread_model)
  end
  select_make_variables(make_variables)
  if scope
    inputs = dependency_files(scope).map {|file| File.join(@root, file)}
  end
  output = File.expand_path(output) if output
  cache_signature = dependency_cache_signature(
    nmake: nmake, sources: sources,
  )
  changed = false
  inputs.each do |input|
    if input.end_with?(".c", ".y")
      out.puts makedepend(relative_source(input))
    else
      deps = dependency_file_content(input) || File.read(input)
      dependency_declarations(input, content: deps)
      dependency_targets(input, deps)
      match = MARK_SECTION.match(deps)
      next unless match
      raise "missing #{MARK_END} in #{input}" unless match.begin(1)

      destination = File.join(output, relative_source(input)) if output
      if destination && dependency_output_fresh?(destination, cache_signature)
        next
      end

      current_rules = match[0]
      accessed = Set.new
      expected = update_deps(
        current_rules, input, group: !nmake, verbose: verbose,
        accessed: accessed,
      )
      expected = minimize_deps(expected, input) if sources
      updated = match.pre_match + expected + match.post_match
      if mode == :output
        updated = normalize_dependency_rules(updated) unless nmake
        replace_file(destination, updated)
        accessed.add(File.expand_path(input))
        accessed.add(File.join(@root, "depend"))
        accessed.add(File.expand_path(__FILE__))
        accessed.add(File.join(@root, "tool/mkdepend.rb"))
        write_dependency_cache(destination, cache_signature, accessed)
      elsif same_dependency_rules?(current_rules, expected)
        next
      elsif mode == :inplace
        replace_file(input, updated)
        changed = true
      elsif mode == :check
        report_outdated_dependencies(input, current_rules, expected, err: err)
        changed = true
      else
        out.puts updated
        changed = true
      end
    end
  end
  if mode == :check && changed
    options = " --sources" if sources
    err.puts "\nupdate with:"
    err.puts "  ruby tool/mkdepend.rb --scope=all#{options} --inplace"
  end
  mode != :check || !changed
end

Updates the marked dependency section in extension file input.

source_map maps object targets to source paths. make_variables supplies values of Make variables used by manual dependency rules. Returns true when the file changed and false when its dependencies were already current.

# File lib/mkmf/depend.rb, line 934
def update_extension(input, source_map, make_variables: {}, nmake: false,
                     verbose: false)
  select_make_variables(make_variables)
  input = File.expand_path(input)
  deps = File.read(input) if File.file?(input)
  match = MARK_SECTION.match(deps) if deps
  unless match
    raise "#{input}: no autogenerated dependency section"
  end
  raise "missing #{MARK_END} in #{input}" unless match.begin(1)

  dependency_declarations(input, content: deps)
  dependency_targets(input, deps)
  generated = []
  source_map.each do |target, source|
    source = relative_source(source)
    warn "dependencies for #{source}:" if verbose
    makedepend(
      source, generated, target: target, input: input, project: true
    )
  end
  expected = compact_dependencies(generated.join, group: !nmake)
  updated = match.pre_match + expected + match.post_match
  return false if same_dependency_rules?(match[0], expected)

  replace_file(input, updated)
  true
end