Class: Rubycc::Link::LibraryResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/link/library_resolver.rb

Overview

Turns the command line's library requests (-l names and -L search directories) into the two concrete input sets the final link consumes: the shared objects whose exports satisfy imports (SharedLinker's needed:) and the relocatable inputs pulled in for their definitions (SharedLinker's inputs, where archives are taken lazily). It is the piece that stands between a driver's flags and the linker core: nothing here reads a compiler flag string — the caller has already split -lz into the name "z" and -L/opt/lib into a directory — so this class only does the resolution the ELF/GNU conventions prescribe.

Search follows the classic rule: each -L directory in command-line order, then the platform's default library directories (only those that exist), and within a single directory an unversioned lib<name>.so, a versioned lib<name>.so.<version>, and then lib<name>.a are preferred in that order. The first directory holding either form settles the request — the .so/.a preference does not reach across directories, so an earlier directory's .a wins over a later directory's .so, matching the traditional linker. A :filename request (-l:libfoo.so.1) looks that exact name up instead of composing lib…so/lib…a.

A resolved file is dispatched by what it actually is, read from its leading bytes: an ELF shared object (ET_DYN) joins needed, an ELF relocatable or an ar archive joins inputs, and anything else is treated as a GNU-ld text linker script (glibc ships libc.so as GROUP ( libc.so.6 … )) whose GROUP/INPUT file lists are expanded and each named file resolved in turn.

The transitive closure of shared dependencies is deliberately not followed: a .so that itself needs other .sos records those as its own DT_NEEDED and the runtime loader pulls them at load time; a static link only needs the directly requested libraries. Deduplication is by real path so the same library requested twice (or reached once directly and once through a script) contributes a single entry, keeping the output deterministic (N4).

Defined Under Namespace

Classes: Resolution

Constant Summary collapse

ELFMAG =

ELF and ar magics, and the e_type value that marks a shared object; a resolved file matching neither magic is taken to be a text linker script.

"\x7FELF".b
AR_MAGIC =
"!<arch>\n".b
ET_DYN =
3
TARGET_SYSTEM_DIRS =

The target's default library directories, consulted after every -L directory. Debian's multiarch directory is target-specific; using the x86_64 spelling unconditionally made a native aarch64 build unable to resolve even libc's ordinary -lm/-lpthread dependencies.

{
  "x86_64" => [
    "/usr/lib/x86_64-linux-gnu",
    "/usr/lib",
    "/lib/x86_64-linux-gnu",
    "/lib",
    "/usr/local/lib"
  ].freeze,
  "aarch64" => [
    "/usr/lib/aarch64-linux-gnu",
    "/usr/lib",
    "/lib/aarch64-linux-gnu",
    "/lib",
    "/usr/aarch64-linux-gnu/lib",
    "/usr/local/lib"
  ].freeze
}.freeze
MUSL_LIBC_PROVIDED_LIBRARIES =

musl folds the libraries that glibc normally exposes as separate DSOs into libc. Runtime images therefore have no libm.so, libpthread.so, or libdl.so (and often no unversioned libc.so either), even though extconf-generated link flags still request them. Only these known libc-provided names are eligible for the fallback; an absent arbitrary -lfoo must remain an error rather than silently depending on libc.

%w[c m pthread rt dl resolv util crypt nsl].freeze
MUSL_LIBC_FILE =

Alpine/musl runtime names. The loader is also the libc implementation, so it is a valid last-resort target when a libc.musl-* compatibility symlink is not present in a stripped runtime.

/\Alibc\.musl(?:-[^.]*)?\.so\.\d[\w.-]*\z/
MUSL_LOADER_FILE =
/\Ald-musl-[^.]+\.so\.\d[\w.-]*\z/
VERSIONED_SHARED_SUFFIX =
/\A\d[\w.-]*\z/
DEFAULT_SYSTEM_DIRS =

Kept as a public host-layout constant for callers and tests that need to inspect the default search path without selecting a cross target.

default_system_dirs.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(search_dirs: [], target: nil) ⇒ LibraryResolver

Returns a new instance of LibraryResolver.



141
142
143
144
145
# File 'lib/rubycc/link/library_resolver.rb', line 141

def initialize(search_dirs: [], target: nil)
  @dirs = (search_dirs + self.class.default_system_dirs(target: target)).select do |d|
    File.directory?(d)
  end
end

Instance Attribute Details

#dirsObject (readonly)

The ordered search path actually consulted (existing -L dirs followed by the existing default directories); exposed so a driver can report it.



149
150
151
# File 'lib/rubycc/link/library_resolver.rb', line 149

def dirs
  @dirs
end

Class Method Details

.default_system_dirs(target: nil) ⇒ Object

Return the conventional system library directories for target. A target triple is accepted because the driver normalizes one before passing it here. Unknown targets fall back to the host layout so that the resolver remains useful for diagnostics instead of inventing a multiarch path for an unsupported architecture.



90
91
92
93
94
95
96
97
# File 'lib/rubycc/link/library_resolver.rb', line 90

def default_system_dirs(target: nil)
  arch = normalize_target(target || RbConfig::CONFIG["host_cpu"])
  TARGET_SYSTEM_DIRS.fetch(arch) do
    TARGET_SYSTEM_DIRS.fetch(normalize_target(RbConfig::CONFIG["host_cpu"])) do
      TARGET_SYSTEM_DIRS.fetch("x86_64")
    end
  end
end

High-level convenience for a driver: resolves libraries and links them together with the object inputs into a shared object. The resolved archives follow the objects so the lazy pull-in sees the objects' undefined symbols first; the resolved shared objects become the dependencies imports bind against.



120
121
122
123
# File 'lib/rubycc/link/library_resolver.rb', line 120

def link(inputs, libraries: [], search_dirs: [], soname: nil, target: nil)
  r = resolve(libraries, search_dirs: search_dirs, target: target)
  SharedLinker.link(inputs + r.inputs, needed: r.needed, soname: soname)
end

Convenience: link and write the shared object to path.



126
127
128
129
# File 'lib/rubycc/link/library_resolver.rb', line 126

def link_to(inputs, path, libraries: [], search_dirs: [], soname: nil, target: nil)
  File.binwrite(path, link(inputs, libraries: libraries, search_dirs: search_dirs,
                           soname: soname, target: target))
end

.normalize_target(spec) ⇒ Object



99
100
101
102
103
104
105
106
# File 'lib/rubycc/link/library_resolver.rb', line 99

def normalize_target(spec)
  cpu = spec.to_s.split("-", 2).first.to_s
  case cpu
  when "amd64", "x64" then "x86_64"
  when "arm64"        then "aarch64"
  else cpu
  end
end

.resolve(libraries, search_dirs: [], target: nil) ⇒ Object

Resolves libraries (an ordered array of -l argument strings, each the text after -l, e.g. "z" or ":libfoo.so.1") against search_dirs (the -L directories, in order) and returns a Resolution.



111
112
113
# File 'lib/rubycc/link/library_resolver.rb', line 111

def resolve(libraries, search_dirs: [], target: nil)
  new(search_dirs: search_dirs, target: target).resolve(libraries)
end

Instance Method Details

#resolve(libraries) ⇒ Object



151
152
153
154
155
156
157
# File 'lib/rubycc/link/library_resolver.rb', line 151

def resolve(libraries)
  @needed = []
  @inputs = []
  @seen = Set.new
  libraries.each { |spec| resolve_spec(spec) }
  Resolution.new(needed: @needed, inputs: @inputs)
end