bits-installer 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/bits.rb +69 -58
- data/lib/bits/cache.rb +67 -0
- data/lib/bits/command.rb +18 -4
- data/lib/bits/commands/install.rb +10 -43
- data/lib/bits/commands/manifest.rb +71 -0
- data/lib/bits/commands/provider_query.rb +43 -0
- data/lib/bits/commands/provider_sync.rb +49 -0
- data/lib/bits/commands/show.rb +1 -1
- data/lib/bits/commands/sync.rb +10 -7
- data/lib/bits/exceptions.rb +21 -0
- data/lib/bits/execute_context.rb +1 -0
- data/lib/bits/external_interface.rb +14 -8
- data/lib/bits/installer_mixin.rb +96 -0
- data/lib/bits/package_proxy.rb +3 -2
- data/lib/bits/provider.rb +9 -1
- data/lib/bits/provider/apt.rb +10 -0
- data/lib/bits/provider/homebrew.rb +23 -22
- data/lib/bits/provider/portage.rb +2 -2
- data/lib/bits/provider/python.rb +89 -15
- data/lib/bits/provider/rubygems.rb +88 -0
- data/lib/bits/repository.rb +1 -1
- data/lib/bits/version.rb +1 -1
- data/lib/libexec/bits-python +10 -10
- data/lib/libexec/bits-ruby +198 -0
- metadata +9 -2
@@ -4,6 +4,7 @@ require 'bits/command_provider'
|
|
4
4
|
require 'bits/package'
|
5
5
|
require 'bits/logging'
|
6
6
|
require 'bits/spawn'
|
7
|
+
require 'bits/external_interface'
|
7
8
|
|
8
9
|
require 'json'
|
9
10
|
|
@@ -34,8 +35,7 @@ module Bits
|
|
34
35
|
true
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
-
super ns
|
38
|
+
def setup
|
39
39
|
@client = interfaces[:python]
|
40
40
|
end
|
41
41
|
|
data/lib/bits/provider/python.rb
CHANGED
@@ -5,6 +5,7 @@ require 'bits/spawn'
|
|
5
5
|
require 'bits/package'
|
6
6
|
require 'bits/logging'
|
7
7
|
require 'bits/exceptions'
|
8
|
+
require 'bits/cache'
|
8
9
|
|
9
10
|
require 'bits/external_interface'
|
10
11
|
|
@@ -18,6 +19,7 @@ module Bits
|
|
18
19
|
include Bits::CommandProvider
|
19
20
|
include Bits::ExternalInterface
|
20
21
|
include Bits::ProviderReporting
|
22
|
+
include Bits::Cache
|
21
23
|
|
22
24
|
PIP = 'pip'
|
23
25
|
INDEX = 'https://pypi.python.org/pypi'
|
@@ -37,34 +39,38 @@ module Bits
|
|
37
39
|
true
|
38
40
|
end
|
39
41
|
|
40
|
-
def
|
41
|
-
super ns
|
42
|
+
def setup
|
42
43
|
@client = XMLRPC::Client.new_from_uri(INDEX)
|
43
44
|
@client.http_header_extra = {'Content-Type' => 'text/xml'}
|
44
45
|
@python = self.class.interfaces[:python]
|
46
|
+
@cache = setup_cache ns[:bits_dir], provider_id
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
|
49
|
-
return nil if type == :missing_atom
|
50
|
-
response['installed']
|
51
|
-
end
|
49
|
+
def sync
|
50
|
+
t = Time.new
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
52
|
+
sync_file = File.join ns[:bits_dir], "#{provider_id}.sync"
|
53
|
+
last_sync = get_last_sync sync_file
|
54
|
+
|
55
|
+
if last_sync.nil?
|
56
|
+
content = read_full
|
57
|
+
else
|
58
|
+
content = read_partial last_sync
|
58
59
|
end
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
File.open(sync_file, 'w') do |f|
|
62
|
+
f.puts t.to_i.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
@cache.set content
|
66
|
+
@cache.save
|
62
67
|
end
|
63
68
|
|
64
69
|
def query(package_atom)
|
65
70
|
candidate = get_candidate_version package_atom
|
71
|
+
raise MissingPackage.new package_atom if candidate.nil?
|
66
72
|
current = get_installed_version package_atom
|
67
|
-
|
73
|
+
Bits::Package.new package_atom, current, candidate
|
68
74
|
end
|
69
75
|
|
70
76
|
def install(package)
|
@@ -82,5 +88,73 @@ module Bits
|
|
82
88
|
end
|
83
89
|
end
|
84
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def get_installed_version(package_atom)
|
95
|
+
type, response = @python.request :python_info, :atom => package_atom
|
96
|
+
return nil if type == :missing_atom
|
97
|
+
response['installed']
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_candidate_version(package_atom)
|
101
|
+
begin
|
102
|
+
result = @client.call(:package_releases, package_atom)
|
103
|
+
rescue SocketError
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
return nil if result.empty?
|
108
|
+
return result[0]
|
109
|
+
end
|
110
|
+
|
111
|
+
# get the number from the specified file when we where last synced to
|
112
|
+
# upstream repository.
|
113
|
+
def get_last_sync(sync_file)
|
114
|
+
if File.file? sync_file
|
115
|
+
File.new(sync_file).read.to_i
|
116
|
+
else
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_partial(last_sync)
|
122
|
+
result = @client.call :changelog, last_sync
|
123
|
+
|
124
|
+
releases = Hash.new
|
125
|
+
|
126
|
+
result.each do |name, version, timestamp, purpose|
|
127
|
+
next if purpose != 'new release'
|
128
|
+
|
129
|
+
releases[name] = {
|
130
|
+
:atom => name,
|
131
|
+
:candidate => version,
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
releases
|
136
|
+
end
|
137
|
+
|
138
|
+
# TODO: works really slowly right now, fix it
|
139
|
+
def read_full
|
140
|
+
remote_packages = @client.call :list_packages
|
141
|
+
|
142
|
+
puts remote_packages.size
|
143
|
+
|
144
|
+
packages = Hash.new
|
145
|
+
|
146
|
+
remote_packages.each do |name|
|
147
|
+
candidate = get_candidate_version name
|
148
|
+
|
149
|
+
next if candidate.nil?
|
150
|
+
|
151
|
+
packages[name] = {
|
152
|
+
:atom => name,
|
153
|
+
:candidate => candidate,
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
packages
|
158
|
+
end
|
85
159
|
end
|
86
160
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'bits/logging'
|
2
|
+
require 'bits/command_provider'
|
3
|
+
require 'bits/external_interface'
|
4
|
+
require 'bits/provider_reporting'
|
5
|
+
require 'bits/cache'
|
6
|
+
|
7
|
+
module Bits
|
8
|
+
define_provider :rubygems, \
|
9
|
+
:desc => "Provides interface for Rubygems" \
|
10
|
+
do
|
11
|
+
include Bits::Logging
|
12
|
+
include Bits::CommandProvider
|
13
|
+
include Bits::ExternalInterface
|
14
|
+
include Bits::ProviderReporting
|
15
|
+
include Bits::Cache
|
16
|
+
|
17
|
+
GEM = 'gem'
|
18
|
+
|
19
|
+
def self.check
|
20
|
+
unless self.setup_interface :ruby, :capabilities => [:rubygems]
|
21
|
+
check_error "Could not setup required interface"
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
|
25
|
+
log.debug "rubygems is available"
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup
|
30
|
+
@client = interfaces[:ruby]
|
31
|
+
@cache = setup_cache ns[:bits_dir], provider_id
|
32
|
+
end
|
33
|
+
|
34
|
+
def sync
|
35
|
+
type, response = @client.request :rubygems_candidates
|
36
|
+
|
37
|
+
unless type == :candidates
|
38
|
+
raise "Expected rubygems_candidate response but got: #{type}"
|
39
|
+
end
|
40
|
+
|
41
|
+
candidates = response['candidates']
|
42
|
+
|
43
|
+
log.info "Synced #{candidates.size} gems"
|
44
|
+
|
45
|
+
cache = candidates.inject({}){|h, i| h[i['atom']] = i; h}
|
46
|
+
|
47
|
+
@cache.set cache
|
48
|
+
@cache.save
|
49
|
+
end
|
50
|
+
|
51
|
+
def query(atom)
|
52
|
+
candidate = @cache[atom]
|
53
|
+
|
54
|
+
type, info = @client.request :rubygems_info, \
|
55
|
+
:package => atom, \
|
56
|
+
:remote => candidate.nil?
|
57
|
+
|
58
|
+
raise MissingPackage.new atom if type == :missing_package
|
59
|
+
raise "Expected info response but got: #{type}" unless type == :info
|
60
|
+
|
61
|
+
installed = info['installed']
|
62
|
+
|
63
|
+
candidate = if candidate.nil?
|
64
|
+
info['candidate']
|
65
|
+
else
|
66
|
+
candidate['version']
|
67
|
+
end
|
68
|
+
|
69
|
+
Bits::Package.new(atom, installed, candidate)
|
70
|
+
end
|
71
|
+
|
72
|
+
def install(package)
|
73
|
+
execute do
|
74
|
+
unless run [GEM, 'install', package.atom]
|
75
|
+
raise "Could not install package '#{package.atom}'"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove(package)
|
81
|
+
execute do
|
82
|
+
unless run [GEM, 'uninstall', package.atom]
|
83
|
+
raise "Could not remove package '#{package.atom}'"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/bits/repository.rb
CHANGED
data/lib/bits/version.rb
CHANGED
data/lib/libexec/bits-python
CHANGED
@@ -20,7 +20,7 @@ class RequestError(Exception):
|
|
20
20
|
pass
|
21
21
|
|
22
22
|
|
23
|
-
class MissingRequestKey(
|
23
|
+
class MissingRequestKey(RequestError):
|
24
24
|
def __init__(self, key):
|
25
25
|
super(MissingRequestKey, self).__init__(
|
26
26
|
"Missing key '{0}' in request".format(key))
|
@@ -137,18 +137,18 @@ def guarded_handle_request(line):
|
|
137
137
|
|
138
138
|
|
139
139
|
def main(args):
|
140
|
-
|
141
|
-
|
140
|
+
try:
|
141
|
+
while True:
|
142
142
|
line = sys.stdin.readline()
|
143
|
-
except KeyboardInterrupt:
|
144
|
-
break
|
145
143
|
|
146
|
-
|
147
|
-
|
144
|
+
if not line:
|
145
|
+
break
|
148
146
|
|
149
|
-
|
150
|
-
|
151
|
-
|
147
|
+
response = guarded_handle_request(line)
|
148
|
+
print >>sys.stdout, json.dumps(response)
|
149
|
+
sys.stdout.flush()
|
150
|
+
except KeyboardInterrupt:
|
151
|
+
pass
|
152
152
|
|
153
153
|
return 0
|
154
154
|
|
@@ -0,0 +1,198 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
HAS_RUBYGEMS = true
|
7
|
+
|
8
|
+
$load_path = Array.new $:
|
9
|
+
|
10
|
+
HAS_HOMEBREW = begin
|
11
|
+
$: << '/usr/local/Library/Homebrew'
|
12
|
+
|
13
|
+
require 'global'
|
14
|
+
require 'formula'
|
15
|
+
|
16
|
+
true
|
17
|
+
rescue LoadError
|
18
|
+
$:.replace $load_path
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
class RequestError < Exception; end
|
23
|
+
|
24
|
+
class MissingRequestKey < RequestError
|
25
|
+
def initialize(key)
|
26
|
+
super "Missing key '#{key}' in request"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ping_handler request
|
31
|
+
capabilities = []
|
32
|
+
|
33
|
+
capabilities << :homebrew if HAS_HOMEBREW
|
34
|
+
capabilities << :rubygems if HAS_RUBYGEMS
|
35
|
+
|
36
|
+
return {
|
37
|
+
:__type__ => "pong",
|
38
|
+
:capabilities => capabilities
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def rubygems_info_handler request
|
43
|
+
remote = request['remote']
|
44
|
+
package = request['package']
|
45
|
+
|
46
|
+
if package.nil?
|
47
|
+
raise MissingRequestKey.new 'package'
|
48
|
+
end
|
49
|
+
|
50
|
+
candidate_version = if remote
|
51
|
+
rubygems_candidate_version package
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
installed_version = rubygems_installed_version package
|
57
|
+
|
58
|
+
{
|
59
|
+
:__type__ => :info,
|
60
|
+
:installed => installed_version,
|
61
|
+
:candidate => candidate_version,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def rubygems_candidate_version package
|
66
|
+
dep = Gem::Dependency.new package
|
67
|
+
fetcher = Gem::SpecFetcher.fetcher
|
68
|
+
spec_tuples = fetcher.find_matching dep
|
69
|
+
|
70
|
+
if spec_tuples.empty?
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
spec, _ = spec_tuples[0]
|
75
|
+
_, version, _ = spec
|
76
|
+
|
77
|
+
version.version
|
78
|
+
end
|
79
|
+
|
80
|
+
def rubygems_installed_version package
|
81
|
+
result = Gem::Specification.find_all_by_name package
|
82
|
+
#result = Gem::Specification.source_index.find_name package
|
83
|
+
return nil if result.empty?
|
84
|
+
result.first.version.version
|
85
|
+
end
|
86
|
+
|
87
|
+
def iterate_candidates
|
88
|
+
fetcher = Gem::SpecFetcher.fetcher
|
89
|
+
|
90
|
+
fetcher.list.each do |source_uri, spec_tuples|
|
91
|
+
spec_tuples.each do |spec_tuple|
|
92
|
+
atom, version, _ = spec_tuple
|
93
|
+
yield atom, version.version
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def rubygems_candidates_handler request
|
99
|
+
candidates = Array.new
|
100
|
+
|
101
|
+
iterate_candidates do |atom, version|
|
102
|
+
candidates << {
|
103
|
+
:atom => atom,
|
104
|
+
:version => version,
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
{
|
109
|
+
:__type__ => :candidates,
|
110
|
+
:candidates => candidates,
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def homebrew_info_handler request
|
115
|
+
package = request['package']
|
116
|
+
|
117
|
+
if package.nil?
|
118
|
+
raise MissingRequestKey.new 'package'
|
119
|
+
end
|
120
|
+
|
121
|
+
installed, candidate = homebrew_get_version package
|
122
|
+
|
123
|
+
{
|
124
|
+
:__type__ => :info,
|
125
|
+
:installed => installed,
|
126
|
+
:candidate => candidate,
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def homebrew_get_version(package)
|
131
|
+
begin
|
132
|
+
f = Formula.factory(package)
|
133
|
+
rescue FormulaUnavailableError
|
134
|
+
return [nil, nil]
|
135
|
+
end
|
136
|
+
|
137
|
+
[f.installed_version, f.version]
|
138
|
+
end
|
139
|
+
|
140
|
+
HANDLERS = {
|
141
|
+
:ping => :ping_handler,
|
142
|
+
:homebrew_info => :homebrew_info_handler,
|
143
|
+
:rubygems_info => :rubygems_info_handler,
|
144
|
+
:rubygems_candidates => :rubygems_candidates_handler,
|
145
|
+
}
|
146
|
+
|
147
|
+
def handle_request(line)
|
148
|
+
begin
|
149
|
+
request = JSON.load line
|
150
|
+
rescue
|
151
|
+
raise RequestError.new "Could not decode request, expected type: json"
|
152
|
+
end
|
153
|
+
|
154
|
+
request_type = request['__type__']
|
155
|
+
|
156
|
+
if request_type.nil?
|
157
|
+
raise RequestError.new "Missing 'type' in request"
|
158
|
+
end
|
159
|
+
|
160
|
+
request_type = request_type.to_sym
|
161
|
+
|
162
|
+
handler_name = HANDLERS[request_type]
|
163
|
+
|
164
|
+
if handler_name.nil?
|
165
|
+
raise RequestError.new "No handler for type '#{request_type}'"
|
166
|
+
end
|
167
|
+
|
168
|
+
begin
|
169
|
+
handler = method(handler_name)
|
170
|
+
rescue NameError
|
171
|
+
raise RequestError.new "No method for handler '#{request_type}'"
|
172
|
+
end
|
173
|
+
|
174
|
+
handler.call request
|
175
|
+
end
|
176
|
+
|
177
|
+
def guarded_handle_request(line)
|
178
|
+
handle_request line
|
179
|
+
rescue RequestError => e
|
180
|
+
return {
|
181
|
+
:__type__ => "error",
|
182
|
+
:text => e.to_s,
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def main
|
187
|
+
$stdin.each do |line|
|
188
|
+
response = guarded_handle_request line
|
189
|
+
$stdout.puts JSON.dump response
|
190
|
+
$stdout.flush
|
191
|
+
end
|
192
|
+
|
193
|
+
return 0
|
194
|
+
rescue Interrupt
|
195
|
+
return 0
|
196
|
+
end
|
197
|
+
|
198
|
+
exit main
|