debendencies 1.0.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cfb4b703eb13f5256ed10c8608f31ff2fb3458301dde2d0e599f63994cb8cf78
4
+ data.tar.gz: da9f79cad3cf0dd3b62dcf63a6bcdb141da6155d6e618a4768a9a2b4c55f0828
5
+ SHA512:
6
+ metadata.gz: 8310ab69c81538f55178835ed89b479c8d3c46fc3e073537fd57007b7752055e343b77b811ad465c9365ec9c876da4620088762ab233674e461df260b101315b
7
+ data.tar.gz: acd7ed848689e4ced849bc5ae4227a6324b197268d64c35bd5d031e9f378725856742a3086e5206c1f8ac40485ac522a3fd4011f22e066be79f270acbf4bec94
data/bin/debendencies ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/debendencies/cli"
3
+ Debendencies::CLI.new.run
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ require "optparse"
3
+ require_relative "../debendencies"
4
+ require_relative "version"
5
+
6
+ class Debendencies
7
+ class CLI
8
+ def initialize
9
+ @options = {
10
+ format: "oneline",
11
+ }
12
+ end
13
+
14
+ def run
15
+ option_parser.parse!
16
+ require "json" if @options[:format] == "json"
17
+
18
+ paths = ARGV
19
+ if paths.empty?
20
+ puts option_parser
21
+ exit 1
22
+ end
23
+
24
+ debendencies = Debendencies.new(logger: get_logger)
25
+ begin
26
+ debendencies.scan(*paths)
27
+ dependencies = debendencies.resolve
28
+ rescue Error => e
29
+ abort(e.message)
30
+ end
31
+
32
+ case @options[:format]
33
+ when "oneline"
34
+ puts dependencies.map { |d| d.to_s }.join(", ")
35
+ when "multiline"
36
+ dependencies.each { |d| puts d.to_s }
37
+ when "json"
38
+ puts JSON.generate(dependencies.map { |d| d.as_json })
39
+ else
40
+ puts "Invalid format: #{@options[:format]}"
41
+ exit 1
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def option_parser
48
+ @option_parser ||= OptionParser.new do |opts|
49
+ opts.banner = "Usage: debendencies <PATHS...>"
50
+
51
+ opts.on("-f", "--format FORMAT", "Output format (oneline|multiline|json). Default: oneline") do |format|
52
+ if !["oneline", "multiline", "json"].include?(format)
53
+ abort "Invalid format: #{format.inspect}"
54
+ end
55
+ @options[:format] = format
56
+ end
57
+
58
+ opts.on("--verbose", "Show verbose output") do
59
+ @options[:verbose] = true
60
+ end
61
+
62
+ opts.on("-h", "--help", "Show this help message") do
63
+ puts opts
64
+ exit
65
+ end
66
+
67
+ opts.on("--version", "Show version") do
68
+ puts VERSION_STRING
69
+ exit
70
+ end
71
+ end
72
+ end
73
+
74
+ def get_logger
75
+ if @options[:verbose]
76
+ require "logger"
77
+ Logger.new(STDERR)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ require "set"
3
+ require_relative "errors"
4
+ require_relative "utils"
5
+
6
+ class Debendencies
7
+ module Private
8
+ class << self
9
+ # Extracts from an ELF file using `objdump`:
10
+ #
11
+ # - The ELF file's own soname, if possible. Can be nil.
12
+ # - The list of shared library dependencies (sonames).
13
+ #
14
+ # @param path [String] Path to the ELF file to analyze.
15
+ # @return [String, Array<Array<String>>]
16
+ # @raise [Error] If `objdump` fails.
17
+ def extract_soname_and_dependency_libs(path)
18
+ popen(["objdump", "-p", path],
19
+ spawn_error_message: "Error scanning ELF file dependencies: cannot spawn 'objdump'",
20
+ fail_error_message: "Error scanning ELF file dependencies: 'objdump' failed") do |io|
21
+ soname = nil
22
+ dependent_libs = []
23
+
24
+ io.each_line do |line|
25
+ case line
26
+ when /^\s*SONAME\s+(.+)$/
27
+ soname = $1.strip
28
+ when /^\s*NEEDED\s+(.+)$/
29
+ dependent_libs << $1.strip
30
+ end
31
+ end
32
+
33
+ [soname, dependent_libs]
34
+ end
35
+ end
36
+
37
+ # Extracts dynamic symbols from ELF files using `nm`.
38
+ #
39
+ # @param paths [Array<String>] Paths to the ELF files to analyze.
40
+ # @param cache [Hash<String, Set<String>>]
41
+ # @return [Set<String>] Set of dynamic symbols.
42
+ # @raise [Error] If `nm` fails.
43
+ def extract_dynamic_symbols(paths, cache = {})
44
+ result = Set.new
45
+
46
+ paths.each do |path|
47
+ subresult = cache[path] ||=
48
+ popen(["nm", "-D", path],
49
+ spawn_error_message: "Error extracting dynamic symbols from #{path}: cannot spawn 'nm'",
50
+ fail_error_message: "Error extracting dynamic symbols from #{path}: 'nm' failed") do |io|
51
+ io.each_line.lazy.map do |line|
52
+ # Line is in the following format:
53
+ #
54
+ # U waitpid
55
+ # 0000000000126190 B want_pending_command
56
+ # ^^^^^^^^^^^^^^^^^^^^
57
+ # we want to extract this
58
+ $1 if line =~ /^\S*\s+[A-Za-z]\s+(.+)/
59
+ end.compact.to_set
60
+ end
61
+ result.merge(subresult)
62
+ end
63
+
64
+ result
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Debendencies
4
+ class Error < StandardError; end
5
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Debendencies
4
+ # Represents a single Debian package dependency, e.g., `libc`.
5
+ # Could potentially have version constraints, e.g., `libc (>= 2.28, <= 2.30)`.
6
+ #
7
+ # `version_constraints` is either nil or non-empty.
8
+ class PackageDependency
9
+ attr_reader :name, :version_constraints
10
+
11
+ def initialize(name, version_constraints = nil)
12
+ @name = name
13
+ @version_constraints = version_constraints
14
+ end
15
+
16
+ def eql?(other)
17
+ @name == other.name && @version_constraints == other.version_constraints
18
+ end
19
+
20
+ alias_method :==, :eql?
21
+
22
+ def hash
23
+ @name.hash ^ @version_constraints.hash
24
+ end
25
+
26
+ def as_json
27
+ result = { name: name }
28
+ result[:version_constraints] = version_constraints.map { |vc| vc.as_json } if version_constraints
29
+ result
30
+ end
31
+
32
+ def to_s
33
+ if version_constraints.nil?
34
+ name
35
+ else
36
+ "#{name} (#{version_constraints.map { |vc| vc.to_s }.join(", ")})"
37
+ end
38
+ end
39
+ end
40
+
41
+ # Represents a version constraint, e.g., `>= 2.28-1`.
42
+ class VersionConstraint
43
+ # A comparison operator, e.g., `>=`.
44
+ #
45
+ # @return [String]
46
+ attr_reader :operator
47
+
48
+ # A Debian package version, e.g., `2.28-1`.
49
+ # @return [String]
50
+ attr_reader :version
51
+
52
+ def initialize(operator, version)
53
+ @operator = operator
54
+ @version = version
55
+ end
56
+
57
+ def eql?(other)
58
+ @operator == other.operator && @version == other.version
59
+ end
60
+
61
+ alias_method :==, :eql?
62
+
63
+ def hash
64
+ @operator.hash ^ @version.hash
65
+ end
66
+
67
+ def as_json
68
+ { operator: operator, version: version }
69
+ end
70
+
71
+ def to_s
72
+ "#{operator} #{version}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require "open3"
3
+ require_relative "errors"
4
+ require_relative "elf_analysis"
5
+ require_relative "symbols_file_parsing"
6
+ require_relative "utils"
7
+
8
+ class Debendencies
9
+ module Private
10
+ class << self
11
+ # Finds the package providing a specific library soname. This is done using `dpkg-query -S`.
12
+ #
13
+ # @return [String] The package name (like "libc6"), or nil if no package provides the library.
14
+ def find_package_providing_lib(soname)
15
+ output, error_output, status = Open3.capture3("dpkg-query", "-S", "*/#{soname}")
16
+ if !status.success?
17
+ if !status.signaled? && error_output.include?("no path found matching pattern")
18
+ return nil
19
+ else
20
+ raise Error, "Error finding packages that provide #{soname}: 'dpkg-query' failed: #{status}: #{error_output.chomp}"
21
+ end
22
+ end
23
+
24
+ # Output is in the following format:
25
+ # libfoo1:amd64: /usr/lib/x86_64-linux-gnu/libfoo.so.1
26
+ #
27
+ # The architecture could be omitted, like so:
28
+ # libfoo1: /usr/lib/x86_64-linux-gnu/libfoo1.so.1
29
+ #
30
+ # In theory, the output could contain multiple results, indicating alternatives.
31
+ # We don't support alternatives, so we just return the first result.
32
+ # See rationale in HOW-IT-WORKS.md.
33
+
34
+ return nil if output.empty?
35
+ line = output.split("\n").first
36
+ line.split(":", 2).first
37
+ end
38
+
39
+ # Finds the minimum version of the package that provides the necessary library symbols
40
+ # used by the given ELF files.
41
+ def find_min_package_version(soname, symbols_file_path, dependent_elf_file_paths, symbol_extraction_cache = {}, logger = nil)
42
+ dependent_symbols = extract_dynamic_symbols(dependent_elf_file_paths, symbol_extraction_cache)
43
+ return nil if dependent_symbols.empty?
44
+
45
+ max_used_package_version = nil
46
+
47
+ list_symbols(symbols_file_path, soname) do |dependency_symbol, package_version|
48
+ if dependent_symbols.include?(dependency_symbol)
49
+ logger&.info("Found in-use dependency symbol: #{dependency_symbol} (version: #{package_version})")
50
+ if max_used_package_version.nil? || package_version > max_used_package_version
51
+ max_used_package_version = package_version
52
+ end
53
+ end
54
+ end
55
+
56
+ max_used_package_version
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Debendencies
4
+ module Private
5
+ # Represents a package version in the format used by Debian packages.
6
+ # This class is only used internally to compare package versions.
7
+ # It's not exposed through the public API.
8
+ #
9
+ # Version number formats and comparison rules are defined in the Debian Policy Manual:
10
+ # https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
11
+ # https://pmhahn.github.io/dpkg-compare-versions/
12
+ class PackageVersion
13
+ include Comparable
14
+
15
+ attr_reader :epoch, :upstream_version, :debian_revision
16
+
17
+ def initialize(version_string)
18
+ @version_string = version_string
19
+ # Parse version into epoch, upstream_version, and debian_revision
20
+ @epoch, version = parse_epoch(version_string)
21
+ @upstream_version, @debian_revision = parse_upstream_and_revision(version)
22
+ end
23
+
24
+ def <=>(other)
25
+ # Compare epoch
26
+ result = @epoch <=> other.epoch
27
+ return result unless result == 0
28
+
29
+ # Compare upstream version
30
+ result = compare_upstream_version(@upstream_version, other.upstream_version)
31
+ return result unless result == 0
32
+
33
+ # Compare debian revision
34
+ compare_debian_revision(@debian_revision, other.debian_revision)
35
+ end
36
+
37
+ def to_s
38
+ @version_string
39
+ end
40
+
41
+ private
42
+
43
+ def parse_epoch(version)
44
+ if version.include?(":")
45
+ epoch, rest = version.split(":", 2)
46
+ [epoch.to_i, rest]
47
+ else
48
+ [0, version]
49
+ end
50
+ end
51
+
52
+ def parse_upstream_and_revision(version)
53
+ if version.include?("-")
54
+ upstream, debian_revision = version.rpartition("-").values_at(0, 2)
55
+ else
56
+ upstream = version
57
+ debian_revision = ""
58
+ end
59
+ [upstream, debian_revision]
60
+ end
61
+
62
+ def compare_upstream_version(ver1, ver2)
63
+ compare_version_parts(split_version(ver1), split_version(ver2))
64
+ end
65
+
66
+ def compare_debian_revision(ver1, ver2)
67
+ # Empty string counts as 0 in Debian revision comparison
68
+ ver1 = "0" if ver1.empty?
69
+ ver2 = "0" if ver2.empty?
70
+ compare_version_parts(split_version(ver1), split_version(ver2))
71
+ end
72
+
73
+ def split_version(version)
74
+ version.scan(/\d+|[a-zA-Z]+|~|[^\da-zA-Z~]+/)
75
+ end
76
+
77
+ def compare_version_parts(parts1, parts2)
78
+ parts1.zip(parts2).each do |part1, part2|
79
+ # Handle nil cases
80
+ part1 ||= ""
81
+ part2 ||= ""
82
+
83
+ if part1 =~ /^\d+$/ && part2 =~ /^\d+$/
84
+ result = part1.to_i <=> part2.to_i
85
+ else
86
+ result = compare_lexically(part1, part2)
87
+ end
88
+ return result unless result == 0
89
+ end
90
+
91
+ parts1.size <=> parts2.size
92
+ end
93
+
94
+ def compare_lexically(part1, part2)
95
+ # Special handling for '~' which sorts before everything else
96
+ return -1 if part1 == "~" && part2 != "~"
97
+ return 1 if part1 != "~" && part2 == "~"
98
+
99
+ part1 <=> part2
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require_relative "package_version"
3
+
4
+ class Debendencies
5
+ module Private
6
+ class << self
7
+ # Parses a symbols file. Yields:
8
+ #
9
+ # - All symbols for the specified library soname.
10
+ # - The package version that provides that symbol.
11
+ #
12
+ # For example, it yields `["fopen@GLIBC_1.0", "5"]`.
13
+ #
14
+ # @param path [String] Path to the symbols file.
15
+ # @param soname [String] Soname of the library to yield symbols for.
16
+ # @yield [String, PackageVersion]
17
+ def list_symbols(path, soname)
18
+ File.open(path, "r:utf-8") do |f|
19
+ # Skips lines in the symbols file until we encounter the start of the section for the given library
20
+ f.each_line do |line|
21
+ break if line.start_with?("#{soname} ")
22
+ end
23
+
24
+ f.each_line do |line|
25
+ # Ignore alternative package specifiers and metadata fields like these:
26
+ #
27
+ # | libtinfo6 #MINVER#, libtinfo6 (<< 6.2~)
28
+ # * Build-Depends-Package: libncurses-dev
29
+ next if line =~ /^\s*[\|\*]/
30
+
31
+ # We look for a line like this:
32
+ #
33
+ # NCURSES6_TIC_5.0.19991023@NCURSES6_TIC_5.0.19991023 6.1
34
+ #
35
+ # Stop when we reach the section for next library
36
+ break if line !~ /^\s+(\S+)\s+(\S+)/
37
+
38
+ raw_symbol = $1
39
+ package_version_string = $2
40
+ yield [raw_symbol.sub(/@Base$/, ""), PackageVersion.new(package_version_string)]
41
+ end
42
+ end
43
+ end
44
+
45
+ def find_symbols_file(package_name, architecture)
46
+ path = File.join(symbols_dir, "#{package_name}:#{architecture}.symbols")
47
+ path if File.exist?(path)
48
+ end
49
+
50
+ private
51
+
52
+ # Mocked during tests.
53
+ def symbols_dir
54
+ "/var/lib/dpkg/info"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require_relative "errors"
3
+
4
+ class Debendencies
5
+ module Private
6
+ ELF_MAGIC = String.new("\x7FELF").force_encoding("binary").freeze
7
+
8
+ class << self
9
+ def elf_file?(path)
10
+ File.open(path, "rb") do |f|
11
+ f.read(4) == ELF_MAGIC
12
+ end
13
+ end
14
+
15
+ def path_resembles_library?(path)
16
+ !!(path =~ /\.so($|\.\d+)/)
17
+ end
18
+
19
+ def dpkg_architecture
20
+ read_string_envvar("DEB_HOST_ARCH") ||
21
+ read_string_envvar("DEB_BUILD_ARCH") ||
22
+ @dpkg_architecture ||= begin
23
+ popen(["dpkg", "--print-architecture"],
24
+ spawn_error_message: "Error getting dpkg architecture: cannot spawn 'dpkg'",
25
+ fail_error_message: "Error getting dpkg architecture: 'dpkg --print-architecture' failed") do |io|
26
+ io.read.chomp
27
+ end
28
+ end
29
+ end
30
+
31
+ # Runs a command and yields its standard output as an IO object.
32
+ # Like IO.popen but with better error handling.
33
+ # On success, returns the result of the block, otherwise raises an Error.
34
+ def popen(command_args, spawn_error_message:, fail_error_message:)
35
+ begin
36
+ begin
37
+ io = IO.popen(command_args)
38
+ rescue SystemCallError => e
39
+ raise Error, "#{spawn_error_message}: #{e}"
40
+ end
41
+
42
+ result = yield io
43
+ ensure
44
+ io.close if io
45
+ end
46
+
47
+ if $?.success?
48
+ result
49
+ else
50
+ raise Error, "#{fail_error_message}: #{$?}"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def read_string_envvar(name)
57
+ value = ENV[name]
58
+ value if value && !value.empty?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Debendencies
4
+ VERSION_STRING = "1.0.0.pre1"
5
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+ require "set"
3
+ require_relative "debendencies/elf_analysis"
4
+ require_relative "debendencies/package_finding"
5
+ require_relative "debendencies/package_dependency"
6
+ require_relative "debendencies/errors"
7
+ require_relative "debendencies/utils"
8
+
9
+ class Debendencies
10
+ def initialize(logger: nil)
11
+ @logger = logger
12
+
13
+ # Shared libraries (sonames) that have been scanned.
14
+ @scanned_libs = Set.new
15
+
16
+ # Shared libraries (sonames) that the scanned ELF files depend on.
17
+ # Maps each soname to an array of ELF file path that depend on it (the dependents).
18
+ @dependency_libs = {}
19
+
20
+ @symbol_extraction_cache = {}
21
+ end
22
+
23
+ def scan(*paths)
24
+ paths.each do |path|
25
+ if File.directory?(path)
26
+ scan_directory(path)
27
+ else
28
+ scan_file(path)
29
+ end
30
+ end
31
+ end
32
+
33
+ # Resolves the Debian package dependencies of all scanned ELF files.
34
+ # Returns an array of PackageDependency objects:
35
+ #
36
+ # [
37
+ # PackageDependency.new('libc6', [VersionConstraint.new('>=', '2.28')]),
38
+ # PackageDependency.new('libfoo1'),
39
+ # ]
40
+ #
41
+ # @return [Array<PackageDependency>]
42
+ def resolve
43
+ result = []
44
+
45
+ @dependency_libs.each_pair do |dependency_soname, dependent_elf_file_paths|
46
+ # ELF files in a package could depend on libraries included in the same package,
47
+ # so omit resolving scanned libraries.
48
+ if @scanned_libs.include?(dependency_soname)
49
+ @logger&.info("Skipping dependency resolution for scanned library: #{dependency_soname}")
50
+ next
51
+ end
52
+
53
+ package_name = Private.find_package_providing_lib(dependency_soname)
54
+ raise Error, "Error resolving package dependencies: no package provides #{dependency_soname}" if package_name.nil?
55
+ @logger&.info("Resolved package providing #{dependency_soname}: #{package_name}")
56
+ version_constraints = maybe_create_version_constraints(package_name, dependency_soname, dependent_elf_file_paths)
57
+ @logger&.info("Resolved version constraints: #{version_constraints&.map { |vc| vc.as_json }.inspect}")
58
+
59
+ result << PackageDependency.new(package_name, version_constraints)
60
+ end
61
+
62
+ result.uniq!
63
+ result
64
+ end
65
+
66
+ private
67
+
68
+ def scan_directory(dir)
69
+ Dir.glob("**/*", base: dir) do |entry|
70
+ path = File.join(dir, entry)
71
+
72
+ if File.symlink?(path)
73
+ # Libraries tend to have multiple symlinks (e.g. libfoo.so -> libfoo.so.1 -> libfoo.so.1.2.3)
74
+ # and we only want to process libraries once, so ignore symlinks.
75
+ @logger&.warn("Skipping symlink: #{path}")
76
+ next
77
+ end
78
+
79
+ scan_file(path) if File.file?(path) && File.executable?(path)
80
+ end
81
+ end
82
+
83
+ def scan_file(path)
84
+ @logger&.info("Scanning ELF file: #{path}")
85
+ return @logger&.warn("Skipping non-ELF file: #{path}") if !Private.elf_file?(path)
86
+
87
+ soname, dependency_libs = Private.extract_soname_and_dependency_libs(path)
88
+ @logger&.info("Detected soname: #{soname || "(none)"}")
89
+ @logger&.info("Detected dependencies: #{dependency_libs.inspect}")
90
+ if Private.path_resembles_library?(path) && soname.nil?
91
+ raise Error, "Error scanning ELF file: cannot determine shared library name (soname) for #{path}"
92
+ end
93
+
94
+ @scanned_libs << soname if soname
95
+ dependency_libs.each do |dependency_soname|
96
+ dependents = (@dependency_libs[dependency_soname] ||= [])
97
+ dependents << path
98
+ end
99
+ end
100
+
101
+ def maybe_create_version_constraints(package_name, soname, dependent_elf_files)
102
+ symbols_file_path = Private.find_symbols_file(package_name, Private.dpkg_architecture)
103
+ if symbols_file_path
104
+ @logger&.info("Found symbols file for #{package_name}: #{symbols_file_path}")
105
+ min_version = Private.find_min_package_version(soname,
106
+ symbols_file_path,
107
+ dependent_elf_files,
108
+ @symbol_extraction_cache,
109
+ @logger)
110
+ [VersionConstraint.new(">=", min_version.to_s)] if min_version
111
+ else
112
+ @logger&.warn("No symbols file found for #{package_name}")
113
+ nil
114
+ end
115
+ end
116
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: debendencies
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre1
5
+ platform: ruby
6
+ authors:
7
+ - Hongli Lai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-08-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: set
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: stringio
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: optparse
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0.4'
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '2'
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0.4'
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '2'
75
+ - !ruby/object:Gem::Dependency
76
+ name: open3
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0.1'
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '2'
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0.1'
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '2'
95
+ - !ruby/object:Gem::Dependency
96
+ name: tmpdir
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0.1'
102
+ - - "<"
103
+ - !ruby/object:Gem::Version
104
+ version: '2'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0.1'
112
+ - - "<"
113
+ - !ruby/object:Gem::Version
114
+ version: '2'
115
+ - !ruby/object:Gem::Dependency
116
+ name: fileutils
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '1'
122
+ type: :runtime
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '1'
129
+ - !ruby/object:Gem::Dependency
130
+ name: tempfile
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0.1'
136
+ - - "<"
137
+ - !ruby/object:Gem::Version
138
+ version: '2'
139
+ type: :runtime
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0.1'
146
+ - - "<"
147
+ - !ruby/object:Gem::Version
148
+ version: '2'
149
+ description: Scans executables and shared libraries for their shared library dependencies,
150
+ and outputs a list of Debian package names that provide those libraries.
151
+ email:
152
+ - hongli@hongli.nl
153
+ executables:
154
+ - debendencies
155
+ extensions: []
156
+ extra_rdoc_files: []
157
+ files:
158
+ - bin/debendencies
159
+ - lib/debendencies.rb
160
+ - lib/debendencies/cli.rb
161
+ - lib/debendencies/elf_analysis.rb
162
+ - lib/debendencies/errors.rb
163
+ - lib/debendencies/package_dependency.rb
164
+ - lib/debendencies/package_finding.rb
165
+ - lib/debendencies/package_version.rb
166
+ - lib/debendencies/symbols_file_parsing.rb
167
+ - lib/debendencies/utils.rb
168
+ - lib/debendencies/version.rb
169
+ homepage: https://github.com/FooBarWidget/debendencies
170
+ licenses:
171
+ - MIT
172
+ metadata: {}
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 3.5.3
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Debian package shared library dependencies inferer
192
+ test_files: []