librarianp 0.1.2
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 +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +255 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +235 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/librarian/action/base.rb +24 -0
- data/lib/librarian/action/clean.rb +44 -0
- data/lib/librarian/action/ensure.rb +24 -0
- data/lib/librarian/action/install.rb +95 -0
- data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
- data/lib/librarian/action/resolve.rb +46 -0
- data/lib/librarian/action/update.rb +44 -0
- data/lib/librarian/action.rb +5 -0
- data/lib/librarian/algorithms.rb +133 -0
- data/lib/librarian/cli/manifest_presenter.rb +89 -0
- data/lib/librarian/cli.rb +225 -0
- data/lib/librarian/config/database.rb +205 -0
- data/lib/librarian/config/file_source.rb +47 -0
- data/lib/librarian/config/hash_source.rb +33 -0
- data/lib/librarian/config/source.rb +149 -0
- data/lib/librarian/config.rb +7 -0
- data/lib/librarian/dependency.rb +153 -0
- data/lib/librarian/dsl/receiver.rb +42 -0
- data/lib/librarian/dsl/target.rb +171 -0
- data/lib/librarian/dsl.rb +102 -0
- data/lib/librarian/environment/runtime_cache.rb +101 -0
- data/lib/librarian/environment.rb +230 -0
- data/lib/librarian/error.rb +4 -0
- data/lib/librarian/helpers.rb +29 -0
- data/lib/librarian/linter/source_linter.rb +55 -0
- data/lib/librarian/lockfile/compiler.rb +66 -0
- data/lib/librarian/lockfile/parser.rb +123 -0
- data/lib/librarian/lockfile.rb +29 -0
- data/lib/librarian/logger.rb +46 -0
- data/lib/librarian/manifest.rb +146 -0
- data/lib/librarian/manifest_set.rb +150 -0
- data/lib/librarian/mock/cli.rb +19 -0
- data/lib/librarian/mock/dsl.rb +15 -0
- data/lib/librarian/mock/environment.rb +21 -0
- data/lib/librarian/mock/extension.rb +9 -0
- data/lib/librarian/mock/source/mock/registry.rb +83 -0
- data/lib/librarian/mock/source/mock.rb +80 -0
- data/lib/librarian/mock/source.rb +1 -0
- data/lib/librarian/mock/version.rb +5 -0
- data/lib/librarian/mock.rb +1 -0
- data/lib/librarian/posix.rb +129 -0
- data/lib/librarian/resolution.rb +46 -0
- data/lib/librarian/resolver/implementation.rb +238 -0
- data/lib/librarian/resolver.rb +94 -0
- data/lib/librarian/rspec/support/cli_macro.rb +120 -0
- data/lib/librarian/source/basic_api.rb +45 -0
- data/lib/librarian/source/git/repository.rb +193 -0
- data/lib/librarian/source/git.rb +172 -0
- data/lib/librarian/source/local.rb +54 -0
- data/lib/librarian/source/path.rb +56 -0
- data/lib/librarian/source.rb +2 -0
- data/lib/librarian/spec.rb +13 -0
- data/lib/librarian/spec_change_set.rb +173 -0
- data/lib/librarian/specfile.rb +19 -0
- data/lib/librarian/support/abstract_method.rb +21 -0
- data/lib/librarian/ui.rb +64 -0
- data/lib/librarian/version.rb +3 -0
- data/lib/librarian.rb +11 -0
- data/librarian.gemspec +47 -0
- data/spec/functional/cli_spec.rb +27 -0
- data/spec/functional/posix_spec.rb +32 -0
- data/spec/functional/source/git/repository_spec.rb +199 -0
- data/spec/functional/source/git_spec.rb +174 -0
- data/spec/support/fakefs.rb +37 -0
- data/spec/support/method_patch_macro.rb +30 -0
- data/spec/support/project_path_macro.rb +14 -0
- data/spec/support/with_env_macro.rb +22 -0
- data/spec/unit/action/base_spec.rb +18 -0
- data/spec/unit/action/clean_spec.rb +102 -0
- data/spec/unit/action/ensure_spec.rb +37 -0
- data/spec/unit/action/install_spec.rb +111 -0
- data/spec/unit/algorithms_spec.rb +131 -0
- data/spec/unit/config/database_spec.rb +320 -0
- data/spec/unit/dependency/requirement_spec.rb +12 -0
- data/spec/unit/dependency_spec.rb +212 -0
- data/spec/unit/dsl_spec.rb +173 -0
- data/spec/unit/environment/runtime_cache_spec.rb +73 -0
- data/spec/unit/environment_spec.rb +209 -0
- data/spec/unit/lockfile/parser_spec.rb +162 -0
- data/spec/unit/lockfile_spec.rb +65 -0
- data/spec/unit/manifest/version_spec.rb +11 -0
- data/spec/unit/manifest_set_spec.rb +202 -0
- data/spec/unit/manifest_spec.rb +36 -0
- data/spec/unit/mock/environment_spec.rb +25 -0
- data/spec/unit/mock/source/mock_spec.rb +22 -0
- data/spec/unit/resolver_spec.rb +299 -0
- data/spec/unit/source/git_spec.rb +29 -0
- data/spec/unit/spec_change_set_spec.rb +169 -0
- metadata +257 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
class Manifest
|
5
|
+
|
6
|
+
class Version
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
args = initialize_normalize_args(args)
|
11
|
+
|
12
|
+
self.backing = Gem::Version.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_gem_version
|
16
|
+
backing
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(other)
|
20
|
+
to_gem_version <=> other.to_gem_version
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
to_gem_version.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class} #{to_s}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize_normalize_args(args)
|
34
|
+
args.map do |arg|
|
35
|
+
arg = [arg] if self.class === arg
|
36
|
+
arg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :backing
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :source, :name, :extra
|
44
|
+
private :source=, :name=, :extra=
|
45
|
+
|
46
|
+
def initialize(source, name, extra = nil)
|
47
|
+
assert_name_valid! name
|
48
|
+
|
49
|
+
self.source = source
|
50
|
+
self.name = name
|
51
|
+
self.extra = extra
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{name}/#{version} <#{source}>"
|
56
|
+
end
|
57
|
+
|
58
|
+
def version
|
59
|
+
defined_version || fetched_version
|
60
|
+
end
|
61
|
+
|
62
|
+
def version=(version)
|
63
|
+
self.defined_version = _normalize_version(version)
|
64
|
+
end
|
65
|
+
|
66
|
+
def version?
|
67
|
+
return unless defined_version
|
68
|
+
|
69
|
+
defined_version == fetched_version
|
70
|
+
end
|
71
|
+
|
72
|
+
def latest
|
73
|
+
@latest ||= source.manifests(name).first
|
74
|
+
end
|
75
|
+
|
76
|
+
def outdated?
|
77
|
+
latest.version > version
|
78
|
+
end
|
79
|
+
|
80
|
+
def dependencies
|
81
|
+
defined_dependencies || fetched_dependencies
|
82
|
+
end
|
83
|
+
|
84
|
+
def dependencies=(dependencies)
|
85
|
+
self.defined_dependencies = _normalize_dependencies(dependencies)
|
86
|
+
end
|
87
|
+
|
88
|
+
def dependencies?
|
89
|
+
return unless defined_dependencies
|
90
|
+
|
91
|
+
defined_dependencies.zip(fetched_dependencies).all? do |(a, b)|
|
92
|
+
a.name == b.name && a.requirement == b.requirement
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def satisfies?(dependency)
|
97
|
+
dependency.requirement.satisfied_by?(version)
|
98
|
+
end
|
99
|
+
|
100
|
+
def install!
|
101
|
+
source.install!(self)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
attr_accessor :defined_version, :defined_dependencies
|
107
|
+
|
108
|
+
def environment
|
109
|
+
source.environment
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetched_version
|
113
|
+
@fetched_version ||= _normalize_version(fetch_version!)
|
114
|
+
end
|
115
|
+
|
116
|
+
def fetched_dependencies
|
117
|
+
@fetched_dependencies ||= _normalize_dependencies(fetch_dependencies!)
|
118
|
+
end
|
119
|
+
|
120
|
+
def fetch_version!
|
121
|
+
source.fetch_version(name, extra)
|
122
|
+
end
|
123
|
+
|
124
|
+
def fetch_dependencies!
|
125
|
+
source.fetch_dependencies(name, version, extra)
|
126
|
+
end
|
127
|
+
|
128
|
+
def _normalize_version(version)
|
129
|
+
Version.new(version)
|
130
|
+
end
|
131
|
+
|
132
|
+
def _normalize_dependencies(dependencies)
|
133
|
+
if Hash === dependencies
|
134
|
+
dependencies = dependencies.map{|k, v| Dependency.new(k, v, nil)}
|
135
|
+
end
|
136
|
+
dependencies.sort_by(&:name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def assert_name_valid!(name)
|
140
|
+
name =~ /\A\S(?:.*\S)?\z/ and return
|
141
|
+
|
142
|
+
raise ArgumentError, "name (#{name.inspect}) must be sensible"
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "librarian/algorithms"
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
class ManifestSet
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def shallow_strip(manifests, names)
|
8
|
+
new(manifests).shallow_strip!(names).send(method_for(manifests))
|
9
|
+
end
|
10
|
+
def deep_strip(manifests, names)
|
11
|
+
new(manifests).deep_strip!(names).send(method_for(manifests))
|
12
|
+
end
|
13
|
+
def shallow_keep(manifests, names)
|
14
|
+
new(manifests).shallow_keep!(names).send(method_for(manifests))
|
15
|
+
end
|
16
|
+
def deep_keep(manifests, names)
|
17
|
+
new(manifests).deep_keep!(names).send(method_for(manifests))
|
18
|
+
end
|
19
|
+
def cyclic?(manifests)
|
20
|
+
manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
|
21
|
+
manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
|
22
|
+
adj_algs.cyclic?(manifest_pairs)
|
23
|
+
end
|
24
|
+
def sort(manifests)
|
25
|
+
manifests = Hash[manifests.map{|m| [m.name, m]}] if Array === manifests
|
26
|
+
manifest_pairs = Hash[manifests.map{|k, m| [k, m.dependencies.map{|d| d.name}]}]
|
27
|
+
manifest_names = adj_algs.tsort_cyclic(manifest_pairs)
|
28
|
+
manifest_names.map{|n| manifests[n]}
|
29
|
+
end
|
30
|
+
private
|
31
|
+
def method_for(manifests)
|
32
|
+
case manifests
|
33
|
+
when Hash
|
34
|
+
:to_hash
|
35
|
+
when Array
|
36
|
+
:to_a
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def adj_algs
|
40
|
+
Algorithms::AdjacencyListDirectedGraph
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(manifests)
|
45
|
+
self.index = Hash === manifests ? manifests.dup : index_by(manifests, &:name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_a
|
49
|
+
index.values
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_hash
|
53
|
+
index.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
def dup
|
57
|
+
self.class.new(index)
|
58
|
+
end
|
59
|
+
|
60
|
+
def shallow_strip(names)
|
61
|
+
dup.shallow_strip!(names)
|
62
|
+
end
|
63
|
+
|
64
|
+
def shallow_strip!(names)
|
65
|
+
assert_strings!(names)
|
66
|
+
|
67
|
+
names.each do |name|
|
68
|
+
index.delete(name)
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def deep_strip(names)
|
74
|
+
dup.deep_strip!(names)
|
75
|
+
end
|
76
|
+
|
77
|
+
def deep_strip!(names)
|
78
|
+
strippables = dependencies_of(names)
|
79
|
+
shallow_strip!(strippables)
|
80
|
+
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def shallow_keep(names)
|
85
|
+
dup.shallow_keep!(names)
|
86
|
+
end
|
87
|
+
|
88
|
+
def shallow_keep!(names)
|
89
|
+
assert_strings!(names)
|
90
|
+
|
91
|
+
names = Set.new(names) unless Set === names
|
92
|
+
index.reject! { |k, v| !names.include?(k) }
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def deep_keep(names)
|
97
|
+
dup.conservative_strip!(names)
|
98
|
+
end
|
99
|
+
|
100
|
+
def deep_keep!(names)
|
101
|
+
keepables = dependencies_of(names)
|
102
|
+
shallow_keep!(keepables)
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def consistent?
|
108
|
+
index.values.all? do |manifest|
|
109
|
+
in_compliance_with?(manifest.dependencies)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def in_compliance_with?(dependencies)
|
114
|
+
dependencies.all? do |dependency|
|
115
|
+
manifest = index[dependency.name]
|
116
|
+
manifest && manifest.satisfies?(dependency)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
attr_accessor :index
|
123
|
+
|
124
|
+
def assert_strings!(names)
|
125
|
+
non_strings = names.reject{|name| String === name}
|
126
|
+
non_strings.empty? or raise TypeError, "names must all be strings"
|
127
|
+
end
|
128
|
+
|
129
|
+
# Straightforward breadth-first graph traversal algorithm.
|
130
|
+
def dependencies_of(names)
|
131
|
+
names = Array === names ? names.dup : names.to_a
|
132
|
+
assert_strings!(names)
|
133
|
+
|
134
|
+
deps = Set.new
|
135
|
+
until names.empty?
|
136
|
+
name = names.shift
|
137
|
+
next if deps.include?(name)
|
138
|
+
|
139
|
+
deps << name
|
140
|
+
names.concat index[name].dependencies.map(&:name)
|
141
|
+
end
|
142
|
+
deps.to_a
|
143
|
+
end
|
144
|
+
|
145
|
+
def index_by(enum)
|
146
|
+
Hash[enum.map{|obj| [yield(obj), obj]}]
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'librarian/cli'
|
2
|
+
require 'librarian/mock'
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Mock
|
6
|
+
class Cli < Librarian::Cli
|
7
|
+
|
8
|
+
module Particularity
|
9
|
+
def root_module
|
10
|
+
Mock
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include Particularity
|
15
|
+
extend Particularity
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "librarian/environment"
|
2
|
+
require "librarian/mock/dsl"
|
3
|
+
require "librarian/mock/version"
|
4
|
+
|
5
|
+
module Librarian
|
6
|
+
module Mock
|
7
|
+
class Environment < Environment
|
8
|
+
|
9
|
+
def install_path
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def registry(options = nil, &block)
|
14
|
+
@registry ||= Source::Mock::Registry.new
|
15
|
+
@registry.merge!(options, &block)
|
16
|
+
@registry
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Librarian
|
2
|
+
module Mock
|
3
|
+
module Source
|
4
|
+
class Mock
|
5
|
+
class Registry
|
6
|
+
|
7
|
+
module Dsl
|
8
|
+
|
9
|
+
class Top
|
10
|
+
def initialize(sources)
|
11
|
+
@sources = sources
|
12
|
+
end
|
13
|
+
def source(name, &block)
|
14
|
+
@sources[name] ||= {}
|
15
|
+
Source.new(@sources[name]).instance_eval(&block) if block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Source
|
20
|
+
def initialize(source)
|
21
|
+
@source = source
|
22
|
+
end
|
23
|
+
def spec(name, version = nil, &block)
|
24
|
+
@source[name] ||= []
|
25
|
+
unless version
|
26
|
+
Spec.new(@source[name]).instance_eval(&block) if block
|
27
|
+
else
|
28
|
+
Spec.new(@source[name]).version(version, &block)
|
29
|
+
end
|
30
|
+
@source[name] = @source[name].sort_by{|a| Manifest::Version.new(a[:version])}.reverse
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Spec
|
35
|
+
def initialize(spec)
|
36
|
+
@spec = spec
|
37
|
+
end
|
38
|
+
def version(name, &block)
|
39
|
+
@spec << { :version => name, :dependencies => {} }
|
40
|
+
Version.new(@spec.last[:dependencies]).instance_eval(&block) if block
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Version
|
45
|
+
def initialize(version)
|
46
|
+
@version = version
|
47
|
+
end
|
48
|
+
def dependency(name, *requirement)
|
49
|
+
@version[name] = requirement
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def run!(sources, &block)
|
55
|
+
Top.new(sources).instance_eval(&block) if block
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
clear!
|
63
|
+
end
|
64
|
+
def clear!
|
65
|
+
self.sources = { }
|
66
|
+
end
|
67
|
+
def merge!(options = nil, &block)
|
68
|
+
clear! if options && options[:clear]
|
69
|
+
Dsl.run!(sources, &block) if block
|
70
|
+
end
|
71
|
+
def [](name)
|
72
|
+
sources[name] ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
attr_accessor :sources
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'librarian/manifest'
|
2
|
+
require 'librarian/source/basic_api'
|
3
|
+
require 'librarian/mock/source/mock/registry'
|
4
|
+
|
5
|
+
module Librarian
|
6
|
+
module Mock
|
7
|
+
module Source
|
8
|
+
class Mock
|
9
|
+
include Librarian::Source::BasicApi
|
10
|
+
|
11
|
+
lock_name 'MOCK'
|
12
|
+
spec_options []
|
13
|
+
|
14
|
+
attr_accessor :environment
|
15
|
+
private :environment=
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
def initialize(environment, name, options)
|
19
|
+
self.environment = environment
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
name
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
other &&
|
29
|
+
self.class == other.class &&
|
30
|
+
self.name == other.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_spec_args
|
34
|
+
[name, {}]
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_lock_options
|
38
|
+
{:remote => name}
|
39
|
+
end
|
40
|
+
|
41
|
+
def registry
|
42
|
+
environment.registry[name]
|
43
|
+
end
|
44
|
+
|
45
|
+
def manifest(name, version, dependencies)
|
46
|
+
manifest = Manifest.new(self, name)
|
47
|
+
manifest.version = version
|
48
|
+
manifest.dependencies = dependencies
|
49
|
+
manifest
|
50
|
+
end
|
51
|
+
|
52
|
+
def manifests(name)
|
53
|
+
if d = registry[name]
|
54
|
+
d.map{|v| manifest(name, v[:version], v[:dependencies])}
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def install!(manifest)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
name
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_version(name, extra)
|
68
|
+
extra
|
69
|
+
end
|
70
|
+
|
71
|
+
def fetch_dependencies(name, version, extra)
|
72
|
+
d = registry[name]
|
73
|
+
m = d.find{|v| v[:version] == version.to_s}
|
74
|
+
m[:dependencies]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'librarian/mock/source/mock'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'librarian/mock/extension'
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
require "librarian/error"
|
4
|
+
|
5
|
+
module Librarian
|
6
|
+
module Posix
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Cross-platform way of finding an executable in the $PATH.
|
11
|
+
#
|
12
|
+
# which('ruby') #=> /usr/bin/ruby
|
13
|
+
#
|
14
|
+
# From:
|
15
|
+
# https://github.com/defunkt/hub/commit/353031307e704d860826fc756ff0070be5e1b430#L2R173
|
16
|
+
def which(cmd)
|
17
|
+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(';') : ['']
|
18
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
19
|
+
path = File.expand_path(path)
|
20
|
+
exts.each do |ext|
|
21
|
+
exe = File.join(path, cmd + ext)
|
22
|
+
return exe if File.file?(exe) && File.executable?(exe)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def which!(cmd)
|
29
|
+
which(cmd) or raise Error, "cannot find #{cmd}"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class CommandFailure < Error
|
35
|
+
class << self
|
36
|
+
def raise!(command, status, message)
|
37
|
+
ex = new(message)
|
38
|
+
ex.command = command
|
39
|
+
ex.status = status
|
40
|
+
ex.set_backtrace caller
|
41
|
+
raise ex
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :command, :status
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
|
50
|
+
if defined?(JRuby) # built with jruby-1.7.9 in mind
|
51
|
+
|
52
|
+
def rescuing(*klasses)
|
53
|
+
begin
|
54
|
+
yield
|
55
|
+
rescue *klasses
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def run!(command, options = { })
|
60
|
+
out, err = nil, nil
|
61
|
+
chdir = options[:chdir].to_s if options[:chdir]
|
62
|
+
env = options[:env] || { }
|
63
|
+
old_env = Hash[env.keys.map{|k| [k, ENV[k]]}]
|
64
|
+
out, err, wait = nil, nil, nil
|
65
|
+
begin
|
66
|
+
ENV.update env
|
67
|
+
Dir.chdir(chdir || Dir.pwd) do
|
68
|
+
IO.popen3(*command) do |i, o, e, w|
|
69
|
+
rescuing(Errno::EBADF){ i.close } # jruby/1.9 can raise EBADF
|
70
|
+
out, err, wait = o.read, e.read, w
|
71
|
+
end
|
72
|
+
end
|
73
|
+
ensure
|
74
|
+
ENV.update old_env
|
75
|
+
end
|
76
|
+
s = wait ? wait.value : $? # wait is 1.9+-only
|
77
|
+
s.success? or CommandFailure.raise! command, s, err
|
78
|
+
out
|
79
|
+
end
|
80
|
+
|
81
|
+
else
|
82
|
+
|
83
|
+
if RUBY_VERSION < "1.9"
|
84
|
+
|
85
|
+
def run!(command, options = { })
|
86
|
+
i, o, e = IO.pipe, IO.pipe, IO.pipe
|
87
|
+
pid = fork do
|
88
|
+
$stdin.reopen i[0]
|
89
|
+
$stdout.reopen o[1]
|
90
|
+
$stderr.reopen e[1]
|
91
|
+
[i[1], i[0], o[0], e[0]].each &:close
|
92
|
+
ENV.update options[:env] || { }
|
93
|
+
Dir.chdir options[:chdir].to_s if options[:chdir]
|
94
|
+
exec *command
|
95
|
+
end
|
96
|
+
[i[0], i[1], o[1], e[1]].each &:close
|
97
|
+
Process.waitpid pid
|
98
|
+
$?.success? or CommandFailure.raise! command, $?, e[0].read
|
99
|
+
o[0].read
|
100
|
+
ensure
|
101
|
+
[i, o, e].flatten(1).each{|io| io.close unless io.closed?}
|
102
|
+
end
|
103
|
+
|
104
|
+
else
|
105
|
+
|
106
|
+
def run!(command, options = { })
|
107
|
+
i, o, e = IO.pipe, IO.pipe, IO.pipe
|
108
|
+
opts = {:in => i[0], :out => o[1], :err => e[1]}
|
109
|
+
opts[:chdir] = options[:chdir].to_s if options[:chdir]
|
110
|
+
command = command.dup
|
111
|
+
command.unshift options[:env] || { }
|
112
|
+
command.push opts
|
113
|
+
pid = Process.spawn(*command)
|
114
|
+
[i[0], i[1], o[1], e[1]].each &:close
|
115
|
+
Process.waitpid pid
|
116
|
+
$?.success? or CommandFailure.raise! command, $?, e[0].read
|
117
|
+
o[0].read
|
118
|
+
ensure
|
119
|
+
[i, o, e].flatten(1).each{|io| io.close unless io.closed?}
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|