Class: MakeMakefile::Depend
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| Inherits: | Object |
| Defined in: | lib/mkmf/depend.rb |
Overview
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
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.
Constant Summary
-
DEFAULT_ROOT =
private
Internal use only
# File 'lib/mkmf/depend.rb', line 64File.("../..", __dir__)
-
DEPENDENCY_CACHE_VERSION =
private
Internal use only
# File 'lib/mkmf/depend.rb', line 9761 -
MARK_END =
private
Internal use only
# File 'lib/mkmf/depend.rb', line 814"# AUTOGENERATED DEPENDENCIES END" -
MARK_SECTION =
private
Internal use only
# File 'lib/mkmf/depend.rb', line 815/^#{Regexp.escape(MARK_START)}[^\S\n]*(?:\z|\n\K(?m:.*?)(?=(^#{Regexp.escape(MARK_END)}(?:\n|\z))|\z))/ -
MARK_START =
private
Internal use only
# File 'lib/mkmf/depend.rb', line 813"# AUTOGENERATED DEPENDENCIES START"
Class Method Summary
-
.find_root(path)
Finds the nearest ancestor of
pathcontaining.git. -
.main(argv = ARGV, err: $stderr)
Runs the command-line interface with
argv. -
.new(root: DEFAULT_ROOT) ⇒ Depend
constructor
Creates a dependency updater for #root.
-
.execute(inputs, root: DEFAULT_ROOT, **options)
private
Creates an updater and runs it with the parsed command-line options.
-
.parse_options(argv)
private
Parses command-line arguments into runner options and input paths.
Instance Attribute Summary
-
#root
readonly
Source tree root used to resolve dependency paths.
Instance Method Summary
-
#run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout, output: nil, make_variables: {}, nmake: false, sources: false, scope: nil, thread_model: nil, verbose: false)
Processes dependency
inputsaccording to the selected updatemode. -
#update_extension(input, source_map, make_variables: {}, nmake: false, verbose: false)
Updates the marked dependency section in extension file
input. -
#compact_dependencies(rules, group: true)
private
Sorts and deduplicates
rules, optionally grouping targets by dependency. -
#declaration(map, name, input = nil)
private
Finds the declaration for
name, accounting for an input-relative name. -
#dependency_cache_signature(nmake:, sources:)
private
Returns the cache signature for options affecting generated dependencies.
-
#dependency_declarations(input = nil, source: nil, content: nil)
private
Returns merged global and file-local declarations for an input or source.
-
#dependency_file_content(path, content = nil)
private
Reads and caches a dependency file, or caches supplied
content. -
#dependency_files(scope = :all)
private
Returns marked dependency files selected by
scope. -
#dependency_input(source)
private
Returns the dependency file that governs
source. -
#dependency_output_fresh?(destination, signature) ⇒ Boolean
private
Returns whether
destinationand all recorded scan inputs are unchanged. -
#dependency_scanner(src, declarations, input)
private
Builds a
Scannerconfigured by the declarations governingsrc. -
#dependency_source?(source, input = nil) ⇒ Boolean
private
Returns whether
sourceis declared as generated or otherwise resolvable. -
#dependency_source_map(rules, input)
private
Returns an object-target to source-path mapping extracted from
rules. -
#dependency_source_rules(rules, input)
private
Extracts the best source rule for each object target in
rules. -
#dependency_sources(rules, input)
private
Returns the source paths identified by
rules. -
#dependency_stat(path)
private
Returns cached stat information, or
falsewhenpathdoes not exist. -
#dependency_target(name, input)
private
Finds the declared target matching
nameininput. -
#dependency_targets(input, content = nil)
private
Returns generated targets declared outside the marked section of
input. -
#dependency_vpath(input, source)
private
Returns the VPATH prefix used for dependencies of
source. -
#depends(files, vpath, source: nil, input: nil, declarations: nil, project: false)
private
Converts scanned
filesinto sorted Make dependencies forsource. -
#expand_dependency_variables(dependencies)
private
Expands known Make variables that stand for dependency lists.
-
#expand_make_variables(value, defaults: {})
private
Expands known Make variables embedded in
value. -
#expand_scan_declaration(name, source)
private
Expands matching wildcards in a
scandeclaration. -
#extension_dependency(file, source_dir)
private
Converts an extension dependency to the Make variable path it requires.
-
#makedepend(src, out = [], target: nil, input: nil, project: false, accessed: nil)
private
Appends Make dependency rules for
srctooutand returnsout. -
#minimize_deps(rules, input)
private
Reduces
rulesto source mappings needed to regenerate dependencies. -
#normalize_dependency_rules(rules)
private
Removes VPATH markers that are unnecessary in build-directory output.
-
#object_name(src)
private
Returns the Make object name for source path
src. -
#parse_dependency_declarations(path, content = nil)
private
Parses
mkdependdeclarations frompathor the suppliedcontent. -
#project_dependency(file, source_dir)
private
Converts a standalone project's file to a dependency relative to its source.
-
#relative_dependency(path)
private
Makes
pathrelative to #root without consulting the current directory. -
#relative_source(path)
private
Makes
pathrelative to #root when it refers to a source-tree file. -
#replace_file(path, content)
private
Atomically replaces
pathwithcontent, preserving its file mode. -
#report_outdated_dependencies(input, current, expected, err: $stderr)
private
Reports how dependency rules in
inputdiffer fromexpected. -
#resolve_dependency_source(src, input)
private
Resolves
srcto an existing, missing-library, or declared source path. -
#same_dependency_rules?(left, right) ⇒ Boolean
private
Returns whether two rule sets differ only by removable VPATH markers.
-
#select_make_variables(make_variables)
private
Selects known Make variable values used during dependency generation.
-
#update_deps(rules, input, group: true, verbose: false, accessed: nil)
private
Regenerates dependency
rulesfor the sources identified ininput. -
#write_dependency_cache(destination, signature, paths)
private
Records scan inputs used to produce
destination.
Constructor Details
.new(root: DEFAULT_ROOT) ⇒ Depend
Creates a dependency updater for #root.
# File 'lib/mkmf/depend.rb', line 360
def initialize(root: DEFAULT_ROOT) @root = File.(root) @make_variables = {} @dependency_declarations = {} @dependency_targets = {} @dependency_contents = {} @source_cache = {} @stat_cache = {} end
Class Method Details
.execute(inputs, root: DEFAULT_ROOT, **options) (private)
Creates an updater and runs it with the parsed command-line options.
# File 'lib/mkmf/depend.rb', line 354
def execute(inputs, root: DEFAULT_ROOT, **) new(root: root).run(inputs, **) end
.find_root(path)
Finds the nearest ancestor of path containing .git.
Returns path itself when no repository root can be found.
.main(argv = ARGV, err: $stderr)
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) , inputs = (argv) rescue OptionParser::ParseError => error err.puts "#{File.basename($0)}: #{error.}" err.puts "Try '#{File.basename($0)} --help' for more information." false else execute(inputs, **) end
.parse_options(argv) (private)
Parses command-line arguments into runner options and input paths.
# File 'lib/mkmf/depend.rb', line 287
def (argv) = {} make_variables = {} output = nil select_mode = proc do |mode| [:mode] = mode end parser = OptionParser.new do |opts| opts. = "Usage: #{File.basename($0)} [options] [VAR=value ...] [files]" opts.separator "" opts.separator "Input selection:" opts.on("--root=DIR", "source tree root") {|value| [:root] = value} opts.on( "--thread-model=MODEL", String, "thread model (same as THREAD_MODEL=MODEL)", ) do |value| make_variables["THREAD_MODEL"] = value end opts.on( "--scope=SCOPE", [:all, :core, :extensions], "dependency files to process (all, core, extensions)", ) do |value| [:scope] = value end opts.separator "" opts.separator "Update mode (last one wins; default: stdout):" opts.on("--output=DIR", "write dependencies under DIR") do |value| select_mode[:output] output = value end opts.on("--inplace", "update dependency files in place") do select_mode[:inplace] end opts.on("--check", "check dependency files without updating") do select_mode[:check] end opts.separator "" opts.separator "Output options:" opts.on("--nmake", "emit NMake dependency rules") do [:nmake] = true end opts.on("--sources", "emit source mappings") do [:sources] = true end opts.on("-v", "--verbose", "show scanned sources") do [:verbose] = true end end inputs = [] rest = parser.order(argv) do |arg| if /\A([A-Za-z_]\w*)=(.*)\z/m =~ arg make_variables[$1] = $2 else inputs << arg end end inputs.concat(rest) [:make_variables] = make_variables unless make_variables.empty? [:output] = output if [:mode] == :output [, inputs] end
Instance Attribute Details
#root (readonly)
Source tree root used to resolve dependency paths.
# File 'lib/mkmf/depend.rb', line 253
attr_reader :root
Instance Method Details
#compact_dependencies(rules, group: true) (private)
Sorts and deduplicates rules, optionally grouping targets by dependency.
# File 'lib/mkmf/depend.rb', line 778
def compact_dependencies(rules, group: true) return rules.lines.uniq.sort.join unless group grouped = Hash.new {|hash, dependency| hash[dependency] = []} lines = [] rules.each_line do |line| unless /\A(\S):\s(\S+)\s*\z/ =~ line lines << line next end target, dependency = $1, $2 if dependency.end_with?(".c", ".y") lines << "#{target}: #{dependency}\n" else grouped[dependency] << target end end grouped.each do |dependency, targets| targets.uniq.sort.each_slice(8) do |slice| lines << "#{slice.join(' ')}: #{dependency}\n" end end lines.uniq.sort.join end
#declaration(map, name, input = nil) (private)
Finds the declaration for name, accounting for an input-relative name.
# File 'lib/mkmf/depend.rb', line 533
def declaration(map, name, input = nil) candidates = [name] if input dir = File.dirname(relative_source(input)) candidates << name.delete_prefix(dir + "/") unless dir == "." end key = candidates.uniq.find {|candidate| map.key?(candidate)} [key, map[key]] if key end
#dependency_cache_signature(nmake:, sources:) (private)
Returns the cache signature for options affecting generated dependencies.
# File 'lib/mkmf/depend.rb', line 980
def dependency_cache_signature(nmake:, sources:) [DEPENDENCY_CACHE_VERSION, nmake, sources, @make_variables.sort].inspect end
#dependency_declarations(input = nil, source: nil, content: nil) (private)
Returns merged global and file-local declarations for an input or source.
# File 'lib/mkmf/depend.rb', line 493
def dependency_declarations(input = nil, source: nil, content: nil) input ||= dependency_input(source) input = relative_source(input || "depend") @dependency_declarations[input] ||= begin if input == "depend" path = File.join(@root, input) parse_dependency_declarations(path, dependency_file_content(path, content)) else global = dependency_declarations(File.join(@root, "depend")) path = File.absolute_path?(input) ? input : File.join(@root, input) local = parse_dependency_declarations( path, dependency_file_content(path, content), ) Declarations.new( global.scan.merge(local.scan), global.dependencies.merge(local.dependencies), global.defines.merge(local.defines), global.undefines.merge(local.undefines), ) end end end
#dependency_file_content(path, content = nil) (private)
Reads and caches a dependency file, or caches supplied content.
# File 'lib/mkmf/depend.rb', line 481
def dependency_file_content(path, content = nil) path = File.(path, @root) if content @dependency_contents[path] = content elsif @dependency_contents.key?(path) @dependency_contents[path] elsif File.file?(path) @dependency_contents[path] = File.read(path) end end
#dependency_files(scope = :all) (private)
Returns marked dependency files selected by scope.
# File 'lib/mkmf/depend.rb', line 821
def dependency_files(scope = :all) Dir.chdir(@root) do files = case scope when :all %w[depend enc/depend].concat(Dir.glob("ext/**/depend")) when :core %w[depend enc/depend] when :extensions Dir.glob("ext/**/depend") else raise ArgumentError, "unknown dependency scope: #{scope}" end files.select do |file| next false unless File.file?(file) content = File.read(file) next false unless content.include?(MARK_START) dependency_file_content(file, content) true end end end
#dependency_input(source) (private)
Returns the dependency file that governs source.
# File 'lib/mkmf/depend.rb', line 517
def dependency_input(source) return "depend" unless source source = relative_source(source) if source.start_with?("ext/") dir = File.dirname(source) until dir == "." || dir == "ext" return File.join(dir, "depend") if File.file?(File.join(@root, dir, "depend")) dir = File.dirname(dir) end elsif source.start_with?("enc/") return "enc/depend" end "depend" end
#dependency_output_fresh?(destination, signature) ⇒ Boolean (private)
Returns whether destination and all recorded scan inputs are unchanged.
# File 'lib/mkmf/depend.rb', line 995
def dependency_output_fresh?(destination, signature) cache_path = destination + ".mkdepend" return false unless (output_stat = dependency_stat(destination)) return false unless File.file?(cache_path) lines = File.readlines(cache_path, chomp: true) header = lines.shift&.split("\t", 5) return false unless header == [ "mkdepend", signature, output_stat.size.to_s, output_stat.mtime.to_i.to_s, output_stat.mtime.nsec.to_s, ] lines.all? do |line| state, path = line.split("\t", 2) next false unless path stat = dependency_stat(path) state == "+" ? stat && stat.mtime <= output_stat.mtime : !stat end rescue Errno::ENOENT false end
#dependency_scanner(src, declarations, input) (private)
Builds a Depend::Scanner configured by the declarations governing src.
# File 'lib/mkmf/depend.rb', line 717
def dependency_scanner(src, declarations, input) macros = {} macros["RUBY_EXTCONF_H"] = "extconf.h" if src.start_with?("ext/") defined = declaration(declarations.defines, src, input)&.last || [] undefined = %w[__cplusplus] if undefined_macros = declaration(declarations.undefines, src, input) undefined.concat(undefined_macros[1]) end Scanner.new( root: @root, include_dirs: [ @root, File.join(@root, "include"), File.join(@root, "prism"), File.dirname(src), ], macros: macros, targets: dependency_targets(input), aliases: declarations.scan, dependencies: declarations.dependencies, defined: defined, undefined: undefined, cache: @source_cache, ) end
#dependency_source?(source, input = nil) ⇒ Boolean (private)
Returns whether source is declared as generated or otherwise resolvable.
# File 'lib/mkmf/depend.rb', line 709
def dependency_source?(source, input = nil) declarations = dependency_declarations(input, source: source) declaration(declarations.scan, source, input) || declaration(declarations.dependencies, source, input) || dependency_target(source, input || dependency_input(source)) end
#dependency_source_map(rules, input) (private)
Returns an object-target to source-path mapping extracted from rules.
# File 'lib/mkmf/depend.rb', line 865
def dependency_source_map(rules, input) dependency_source_rules(rules, input).each_with_object({}) do |(target, data), result| result[target] = data[1] end end
#dependency_source_rules(rules, input) (private)
Extracts the best source rule for each object target in rules.
# File 'lib/mkmf/depend.rb', line 844
def dependency_source_rules(rules, input) input = relative_source(input) srcs = {} rules.each_line do |line| next unless %r[\A([-.\w/])\.(?:\$\(OBJEXT\)|o):\s(.+?)\s*\z] =~ line target, dependency = $1, $2 next unless src = dependency[%r{[-.\w/]+\.[cy]\z}] src = src.delete_prefix("/") source_name = File.basename(src, ".*") target_name = File.basename(target) next unless target_name == source_name || target_name.end_with?("-#{source_name}") local = input.start_with?("ext/") && !dependency.include?("$(top_srcdir)") src = File.join(File.dirname(input), src) if local && !src.start_with?("ext/") rank = src.end_with?(".y") ? 2 : local ? 1 : 0 current = srcs[target] srcs[target] = [rank, src, line] if !current || rank >= current.first end srcs.sort.to_h end
#dependency_sources(rules, input) (private)
Returns the source paths identified by rules.
# File 'lib/mkmf/depend.rb', line 872
def dependency_sources(rules, input) dependency_source_map(rules, input).values end
#dependency_stat(path) (private)
Returns cached stat information, or false when path does not exist.
# File 'lib/mkmf/depend.rb', line 985
def dependency_stat(path) path = File.(path) return @stat_cache[path] if @stat_cache.key?(path) @stat_cache[path] = File.stat(path) rescue Errno::ENOENT, Errno::ENOTDIR @stat_cache[path] = false end
#dependency_target(name, input) (private)
Finds the declared target matching name in input.
# File 'lib/mkmf/depend.rb', line 564
def dependency_target(name, input) input = relative_source(input) targets = dependency_targets(input) candidates = [name] dir = File.dirname(input) candidates << name.delete_prefix(dir + "/") unless dir == "." candidates.uniq.find {|candidate| targets.include?(candidate)} end
#dependency_targets(input, content = nil) (private)
Returns generated targets declared outside the marked section of input.
# File 'lib/mkmf/depend.rb', line 544
def dependency_targets(input, content = nil) input = relative_source(input) @dependency_targets[input] ||= begin targets = [] path = File.absolute_path?(input) ? input : File.join(@root, input) content = dependency_file_content(path, content) if content content.each_line do |line| if line.start_with?(MARK_START)..line.start_with?(MARK_END) next elsif target = line[/\A[^\s#][^:=]*?(?=\s*:(?!=))/] targets.concat(target.split) end end end targets.uniq end end
#dependency_vpath(input, source) (private)
Returns the VPATH prefix used for dependencies of source.
# File 'lib/mkmf/depend.rb', line 702
def dependency_vpath(input, source) return if source.start_with?("enc/") && (!input || relative_source(input) == "enc/depend") "{$(VPATH)}" end
#depends(files, vpath, source: nil, input: nil, declarations: nil, project: false) (private)
Converts scanned files into sorted Make dependencies for source.
# File 'lib/mkmf/depend.rb', line 638
def depends(files, vpath, source: nil, input: nil, declarations: nil, project: false) declaration_input = input || dependency_input(source) declarations ||= dependency_declarations(declaration_input, source: source) extension_dir = File.dirname(source) if source&.start_with?("ext/") project_dir = File.dirname(source) if project = lambda do |file, | if !.include?(file) && (dependencies = declaration(declarations.dependencies, file, declaration_input)) dependencies[1].flat_map {|dep| .call(dep, | [file])} else file end end files = files.flat_map {|file| .call(file, [])} files.each_with_object([]) do |file, deps| file = relative_dependency(file) dep = if file.start_with?('$(', '{$(') file elsif target = dependency_target(file, declaration_input) target elsif project_dir project_dependency(file, project_dir) elsif extension_dir extension_dependency(file, extension_dir) else case file when %r[\Aenc/unicode/[\d.]+/] "$(UNICODE_HDR_DIR)/#$'" when 'encindex.h', 'transcode_data.h', /\Areg(?!ex\b)\w+\.h\z/ "#{vpath || '$(top_srcdir)/'}#{file}" when /\.e?rb\z/ when %r[\Atool/] "$(top_srcdir)/#{file}" when %r[\Ainclude/\Kruby(?:/ruby|/version)?\.h\z] "$(hdrdir)/#$&" when %r[\A(?:internal|prism)\/] "$(top_srcdir)/#{file}" when %r[\Accan/] "$(CCAN_DIR)/#$'" when %r[\Aenc/] "#{vpath}#{file}" when %r[\Ainclude/ruby/], %r[\Amissing/] "#{vpath}#$'" else "#{vpath}#{file}" end end deps << dep if dep end.uniq.sort end
#expand_dependency_variables(dependencies) (private)
Expands known Make variables that stand for dependency lists.
# File 'lib/mkmf/depend.rb', line 402
def (dependencies) dependencies.flat_map do |dependency| if /\A\$\((\w+)\)\z/ =~ dependency && (values = @make_variables[$1]) values.flat_map(&:split) else (dependency) end end end
#expand_make_variables(value, defaults: {}) (private)
Expands known Make variables embedded in value.
# File 'lib/mkmf/depend.rb', line 384
def (value, defaults: {}) value.gsub(/\$\((\w+)\)/) do |variable| name = $1 values = if @make_variables.key?(name) @make_variables[name] elsif defaults.key?(name) Array(defaults[name]) else next variable end unless values.one? raise ArgumentError, "#{name} is not a scalar Make variable" end values.first.to_s end end
#expand_scan_declaration(name, source) (private)
Expands matching wildcards in a scan declaration.
Returns a generated-name to source-path mapping.
# File 'lib/mkmf/depend.rb', line 416
def (name, source) source_parts = source.split(/(\*\*|\*)/) captures = source_parts.count {|part| part == "*" || part == "**"} return unless captures == name.scan(/\*\*|\*/).size return {name => source} if captures.zero? pattern = source_parts.map do |part| case part when "**" then "(.*)" when "*" then "([^/]*)" else Regexp.escape(part) end end.join pattern = /\A#{pattern}\z/ Dir.glob(File.join(@root, source)).sort.each_with_object({}) do |path, map| path = relative_source(path) match = pattern.match(path) or next values = match.captures generated = name.gsub(/\*\*|\*/) {values.shift} map[generated] = path end end
#extension_dependency(file, source_dir) (private)
Converts an extension dependency to the Make variable path it requires.
# File 'lib/mkmf/depend.rb', line 599
def extension_dependency(file, source_dir) case file when %r[\A(?:#{Regexp.escape(source_dir)}/)?extconf\.h\z] "$(RUBY_EXTCONF_H)" when 'config.h' "$(arch_hdrdir)/ruby/config.h" when %r[\Ainclude/] "$(hdrdir)/#$'" when 'probes.h' else if file.start_with?(prefix = source_dir + "/") file.delete_prefix(prefix) elsif file.start_with?(prefix = File.dirname(source_dir) + "/") "$(srcdir)/../#{file.delete_prefix(prefix)}" elsif !file.include?("/") && File.file?(File.join(@root, source_dir, file)) file elsif /\.e?rb\z/.match?(file) nil else "$(top_srcdir)/#{file}" end end end
#makedepend(src, out = [], target: nil, input: nil, project: false, accessed: nil) (private)
Appends Make dependency rules for src to out and returns out.
# File 'lib/mkmf/depend.rb', line 744
def makedepend(src, out = [], target: nil, input: nil, project: false, accessed: nil) src = relative_dependency(src) declaration_input = input || dependency_input(src) declarations = dependency_declarations(declaration_input, source: src) vpath = dependency_vpath(input, src) scanner = dependency_scanner(src, declarations, declaration_input) scan_source = if mapping = declaration(declarations.scan, src, declaration_input) mapping[1] else src end files = scanner.scan(scan_source) accessed&.merge(scanner.accessed) if dependencies = declaration(declarations.dependencies, src, declaration_input) files.concat(dependencies[1]) end files << src if scan_source != src files << src.sub(/\.y\z/, ".c") if src.end_with?(".y") obj = if target src.start_with?("ext/") ? "#{target}.o" : "#{target}.$(OBJEXT)" else object_name(src) end depends(files, vpath, source: src, input: input, declarations: declarations, project: project).each do |dep| out << "#{obj}: #{dep}\n" end out end
#minimize_deps(rules, input) (private)
Reduces rules to source mappings needed to regenerate dependencies.
# File 'lib/mkmf/depend.rb', line 888
def minimize_deps(rules, input) sources = dependency_source_rules(rules, input) targets = rules.scan(%r[^([-\.\w/]+)\.(?:\$\(OBJEXT\)|o):]).flatten.uniq unless (missing = targets - sources.keys).empty? raise "no source files for #{missing.join(', ')} in #{input}" end sources.each_value {|_, src| resolve_dependency_source(src, input)} minimal = sources.values.map(&:last) if relative_source(input).start_with?("ext/") rules.each_line do |line| minimal << line if /\A.*:\s+\$\(srcdir\)\//.match?(line) end end minimal.uniq.sort.join end
#normalize_dependency_rules(rules) (private)
Removes VPATH markers that are unnecessary in build-directory output.
# File 'lib/mkmf/depend.rb', line 804
def normalize_dependency_rules(rules) rules.gsub(/\{(?:\.;)?\$\(VPATH\)\}/, '') end
#object_name(src) (private)
Returns the Make object name for source path src.
# File 'lib/mkmf/depend.rb', line 692
def object_name(src) stem = src.sub(/\.[cy]\z/, "") if src.start_with?("ext/") "#{File.basename(stem)}.o" else "#{stem.delete_prefix('missing/')}.$(OBJEXT)" end end
#parse_dependency_declarations(path, content = nil) (private)
Parses mkdepend declarations from path or the supplied content.
# File 'lib/mkmf/depend.rb', line 440
def parse_dependency_declarations(path, content = nil) declarations = Declarations.new({}, {}, {}, {}) content ||= File.read(path) if File.file?(path) return declarations unless content lineno = 0 content.each_line do |line| lineno += 1 case line[/\A#\s*mkdepend:\s*\K.*/] when nil next when /\Ascan\s(\S)\s*=>\s*(\S+)\s*\z/ scan = ($1, $2) unless scan && (!scan.empty? || !$2.include?("*")) raise "#{path}:#{lineno}: invalid mkdepend declaration: #{line.strip}" end declarations.scan.update(scan) when /\Adepends\s(\S)\s*=>\s*(.+?)\s*\z/ name, dependencies = $1, $2.split if declarations.dependencies.key?(name) raise "#{path}:#{lineno}: duplicate mkdepend declaration: #{name}" end declarations.dependencies[name] = dependencies when /\Adefine\s(\S)\s*=>\s*(.+?)\s*\z/ declarations.defines[$1] = $2.split when /\Aundef\s(\S)\s*=>\s*(.+?)\s*\z/ declarations.undefines[$1] = $2.split else raise "#{path}:#{lineno}: invalid mkdepend declaration: #{line.strip}" end end declarations.scan.transform_values! do |source| (source, defaults: {"THREAD_MODEL" => "pthread"}) end declarations.dependencies.transform_values! do |dependencies| (dependencies) end declarations end
#project_dependency(file, source_dir) (private)
Converts a standalone project's file to a dependency relative to its source.
# File 'lib/mkmf/depend.rb', line 624
def project_dependency(file, source_dir) source_dir = File.join(@root, source_dir) path = File.(file, @root) prefix = @root + File::SEPARATOR return unless path.start_with?(prefix) dependency = Pathname.new(path).relative_path_from( Pathname.new(source_dir) ).to_s.tr(File::SEPARATOR, "/") return dependency unless dependency.start_with?("../") "$(srcdir)/#{dependency}" end
#relative_dependency(path) (private)
Makes path relative to #root without consulting the current
directory. Unlike #relative_source, a relative name that does not
refer to a source-tree file is kept as-is: generated dependencies
such as builtin_binary.rbbin live in the build directory, and must
keep the name their Make rules use even when the tool runs in a
build directory nested inside the source tree.
# File 'lib/mkmf/depend.rb', line 587
def relative_dependency(path) = File.(path, @root) prefix = @root + File::SEPARATOR if .start_with?(prefix) && (File.absolute_path?(path) || File.exist?()) .delete_prefix(prefix) else path end end
#relative_source(path) (private)
Makes path relative to #root when it refers to a source-tree file.
# File 'lib/mkmf/depend.rb', line 574
def relative_source(path) = File.(path) = File.(path, @root) unless File.exist?() prefix = @root + File::SEPARATOR .start_with?(prefix) ? .delete_prefix(prefix) : path end
#replace_file(path, content) (private)
Atomically replaces path with content, preserving its file mode.
# File 'lib/mkmf/depend.rb', line 962
def replace_file(path, content) dir = File.dirname(path) FileUtils.mkdir_p(dir) mode = File.exist?(path) ? File.stat(path).mode & 0777 : 0644 Tempfile.create([File.basename(path), ".tmp"], dir) do |file| file.binmode file.write(content) file.flush file.chmod(mode) file.close File.rename(file.path, path) end end
#report_outdated_dependencies(input, current, expected, err: $stderr) (private)
Reports how dependency rules in input differ from expected.
# File 'lib/mkmf/depend.rb', line 1039
def report_outdated_dependencies(input, current, expected, err: $stderr) current_lines = current.lines expected_lines = expected.lines label = relative_source(input) err.puts "#{label}: dependencies are outdated" if current_lines.size <= 20 err.puts " current dependency rules:" current_lines.each {|line| err.puts " #{line.chomp}"} else current_word = current_lines.one? ? "rule" : "rules" expected_word = expected_lines.one? ? "source rule" : "source rules" err.puts " current section has #{current_lines.size} #{current_word}; " \ "expected #{expected_lines.size} #{expected_word}" end err.puts " expected source rules:" expected_lines.each {|line| err.puts " #{line.chomp}"} end
#resolve_dependency_source(src, input) (private)
Resolves src to an existing, missing-library, or declared source path.
# File 'lib/mkmf/depend.rb', line 877
def resolve_dependency_source(src, input) return src if File.file?(File.join(@root, src)) missing = "missing/#{src}" return missing if File.file?(File.join(@root, missing)) return src if dependency_source?(src, input) raise "source file not found: #{src} in #{input}" end
#run(inputs = ARGV, out: $stdout, err: $stderr, mode: :stdout, output: nil, make_variables: {}, nmake: false, sources: false, scope: nil, thread_model: nil, verbose: false)
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 1065
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.(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.(input)) accessed.add(File.join(@root, "depend")) accessed.add(File.(__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 = " --sources" if sources err.puts "\nupdate with:" err.puts " ruby tool/mkdepend.rb --scope=all#{} --inplace" end mode != :check || !changed end
#same_dependency_rules?(left, right) ⇒ Boolean (private)
Returns whether two rule sets differ only by removable VPATH markers.
# File 'lib/mkmf/depend.rb', line 809
def same_dependency_rules?(left, right) normalize_dependency_rules(left) == normalize_dependency_rules(right) end
#select_make_variables(make_variables) (private)
Selects known Make variable values used during dependency generation.
# File 'lib/mkmf/depend.rb', line 373
def select_make_variables(make_variables) make_variables = make_variables.to_h do |name, value| [name.to_s, Array(value).map(&:to_s)] end return if @make_variables == make_variables @make_variables = make_variables @dependency_declarations.clear end
#update_deps(rules, input, group: true, verbose: false, accessed: nil) (private)
Regenerates dependency rules for the sources identified in input.
# File 'lib/mkmf/depend.rb', line 905
def update_deps(rules, input, group: true, verbose: false, accessed: nil) sources = dependency_source_map(rules, input) targets = rules.scan(%r[^([-.\w/]+)\.(?:\$\(OBJEXT\)|o):]).flatten.uniq unless (missing = targets - sources.keys).empty? raise "no source files for #{missing.join(', ')} in #{input}" end generated = [] sources.each do |target, src| src = resolve_dependency_source(src, input) warn "dependencies for #{src}:" if verbose makedepend( src, generated, target: target, input: input, accessed: accessed, ) end compact_dependencies(generated.join, group: group) end
#update_extension(input, source_map, make_variables: {}, nmake: false, verbose: false)
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 930
def update_extension(input, source_map, make_variables: {}, nmake: false, verbose: false) select_make_variables(make_variables) input = File.(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
#write_dependency_cache(destination, signature, paths) (private)
Records scan inputs used to produce destination.
# File 'lib/mkmf/depend.rb', line 1019
def write_dependency_cache(destination, signature, paths) output_stat = File.stat(destination) lines = [ [ "mkdepend", signature, output_stat.size, output_stat.mtime.to_i, output_stat.mtime.nsec, ].join("\t") + "\n", ] paths.sort.each do |path| exists = if @source_cache.key?(path) @source_cache[path] else File.file?(path) end lines << "#{exists ? '+' : '-'}\t#{path}\n" end replace_file(destination + ".mkdepend", lines.join) end