bits-installer 0.1.1 → 0.3.0
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.
- 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
|