Class: Bundler::Spinel::Vendorer
- Inherits:
-
Object
- Object
- Bundler::Spinel::Vendorer
- Defined in:
- lib/bundler/spinel/vendorer.rb
Overview
“Make it work” — the plugin’s primary job. Spinel has no load path (plain ‘require “x”` resolves only against <spinel>/lib) and inlines `require_relative`. So to actually use a resolved dependency in a Spinel build, its source has to be placed somewhere Spinel will follow, with the require wiring generated. This is the reusable form of what projects do by hand today (e.g. Toy’s build_tep_app.sh concatenation, Roundhouse vendoring part of Tep).
Given a Gemfile.lock, vendor each gem’s ‘lib/` into `<into>/<name>/` and emit `<into>/deps.rb` — a manifest of `require_relative`s in lock order. A Spinel program then just does `require_relative “vendor/spinel/deps”`.
Gating is layered on but advisory here: placement and compatibility are different jobs. ‘vendor` warns on non-compatible gems (so the experience is nicer) but still places them; `check` is the hard gate.
Instance Method Summary collapse
-
#initialize(engine: Engine.new, ledger: Ledger.new) ⇒ Vendorer
constructor
A new instance of Vendorer.
-
#resolve_source(spec, lock_dir) ⇒ Object
path:/git: lockfile sources (toy ↔ tep is the headline case) point at a local tree; we don’t go through ‘gem fetch`.
-
#topo_sort(specs) ⇒ Object
Order specs so every gem’s runtime dependencies come before it — a DFS post-order over ‘spec.dependencies`, with an alphabetical tiebreak for determinism and a visiting-set guard so a dependency cycle degrades to some stable order instead of looping.
- #vendor(lockfile = "Gemfile.lock", into: "vendor/spinel", warn_incompatible: true, ext_overrides: {}, ext_disable: []) ⇒ Object
Constructor Details
Instance Method Details
#resolve_source(spec, lock_dir) ⇒ Object
path:/git: lockfile sources (toy ↔ tep is the headline case) point at a local tree; we don’t go through ‘gem fetch`. For GEM sources we fall back to the cache-backed RubyGems fetcher. Issue: OriPekelman/spinelgems#3.
92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/bundler/spinel/vendorer.rb', line 92 def resolve_source(spec, lock_dir) src = spec.source if src.respond_to?(:path) && src.path path = src.path.to_s # Bundler stores PATH as relative-to-lockfile; resolve to abs. path = File.(path, lock_dir) unless File.absolute_path?(path) unless File.directory?(path) raise Error, "path: source for #{spec.name} not found: #{path}" end return path end @fetcher.fetch(spec.name, spec.version.to_s) end |
#topo_sort(specs) ⇒ Object
Order specs so every gem’s runtime dependencies come before it — a DFS post-order over ‘spec.dependencies`, with an alphabetical tiebreak for determinism and a visiting-set guard so a dependency cycle degrades to some stable order instead of looping. Deps not present in this lockset (stdlib/default gems) are skipped. (spinelgems#19)
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/bundler/spinel/vendorer.rb', line 69 def topo_sort(specs) by_name = specs.each_with_object({}) { |s, h| h[s.name] = s } ordered = [] state = {} # name => :visiting | :done visit = lambda do |spec| st = state[spec.name] return if st == :done || st == :visiting state[spec.name] = :visiting spec.dependencies.sort_by(&:name).each do |dep| dn = dep.respond_to?(:name) ? dep.name : dep.to_s visit.call(by_name[dn]) if by_name[dn] end state[spec.name] = :done ordered << spec end specs.sort_by(&:name).each { |s| visit.call(s) } ordered end |
#vendor(lockfile = "Gemfile.lock", into: "vendor/spinel", warn_incompatible: true, ext_overrides: {}, ext_disable: []) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/bundler/spinel/vendorer.rb', line 31 def vendor(lockfile = "Gemfile.lock", into: "vendor/spinel", warn_incompatible: true, ext_overrides: {}, ext_disable: []) parsed = Bundler::LockfileParser.new(File.read(lockfile)) lock_dir = File.dirname(File.(lockfile)) into = File.(into) FileUtils.mkdir_p(into) disable = (ext_disable + ENV["SPINEL_EXT_DISABLE"].to_s.split(",")).map(&:strip).reject(&:empty?) manifest = [] exts = 0 # Topological order (dependencies before dependents), not the lockfile's # alphabetical `specs` (spinelgems#19): Spinel has no load path, so # deps.rb is a *flattened single load* — each gem's entrypoint # require_relative'd once, in order. A dependent loaded before its # dependency would reference not-yet-defined constants. tep→spinel_kit # is the first real case (it sorted right only by alphabetical luck). topo_sort(parsed.specs).each do |spec| name = spec.name version = spec.version.to_s src = resolve_source(spec, lock_dir) dest = File.join(into, name) place(src, dest) exts += wire_extensions(src, dest, ext_overrides, disable) if (target = require_target(name, dest)) manifest << { require: target, libdir: "#{File.basename(dest)}/lib" } end note_compat(name, version) if warn_incompatible end write_manifest(into, manifest) { into: into, count: manifest.size, extensions: exts } end |