bits-installer 0.1.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/bin/bits +20 -0
- data/ext/apt/Cache.cpp +68 -0
- data/ext/apt/Cache.h +18 -0
- data/ext/apt/Package.cpp +63 -0
- data/ext/apt/Package.h +30 -0
- data/ext/apt/PackageVersion.cpp +49 -0
- data/ext/apt/PackageVersion.h +24 -0
- data/ext/apt/apt_ext.cpp +38 -0
- data/ext/apt/apt_ext.h +4 -0
- data/ext/apt/extconf.rb +12 -0
- data/lib/bits/backend/join.rb +26 -0
- data/lib/bits/backend/local.rb +24 -0
- data/lib/bits/backend.rb +9 -0
- data/lib/bits/bit.rb +33 -0
- data/lib/bits/bit_declaration.rb +143 -0
- data/lib/bits/bit_reader/local.rb +13 -0
- data/lib/bits/bit_reader.rb +7 -0
- data/lib/bits/command.rb +48 -0
- data/lib/bits/command_provider.rb +39 -0
- data/lib/bits/commands/install.rb +75 -0
- data/lib/bits/commands/remove.rb +35 -0
- data/lib/bits/commands/show.rb +49 -0
- data/lib/bits/commands/sync.rb +34 -0
- data/lib/bits/exceptions.rb +12 -0
- data/lib/bits/execute_context.rb +24 -0
- data/lib/bits/external_interface.rb +204 -0
- data/lib/bits/logging.rb +24 -0
- data/lib/bits/package.rb +27 -0
- data/lib/bits/package_proxy.rb +61 -0
- data/lib/bits/provider/apt.rb +75 -0
- data/lib/bits/provider/homebrew.rb +59 -0
- data/lib/bits/provider/portage.rb +69 -0
- data/lib/bits/provider/python.rb +86 -0
- data/lib/bits/provider.rb +77 -0
- data/lib/bits/provider_reporting.rb +24 -0
- data/lib/bits/repository.rb +118 -0
- data/lib/bits/spawn.rb +89 -0
- data/lib/bits/user.rb +27 -0
- data/lib/bits/version.rb +3 -0
- data/lib/bits.rb +156 -0
- data/lib/libexec/bits-python +157 -0
- metadata +153 -0
data/lib/bits/command.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Bits
|
4
|
+
# Baseclass for commands.
|
5
|
+
class Command
|
6
|
+
attr_reader :ns
|
7
|
+
|
8
|
+
def setup(opts)
|
9
|
+
raise "not implemented: setup"
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(ns)
|
13
|
+
@ns = ns
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def commands
|
19
|
+
@commands ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_command(command_id, params={}, &block)
|
23
|
+
if commands[command_id]
|
24
|
+
raise "Command already defined: #{command_id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc = params[:desc] || "(no description)"
|
28
|
+
|
29
|
+
klass = Class.new(Command) do
|
30
|
+
@command_id = command_id
|
31
|
+
@name = command_id.to_s.capitalize
|
32
|
+
@desc = desc
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_reader :command_id, :name, :desc
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"Bits::#{@name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
klass.class_eval(&block)
|
44
|
+
|
45
|
+
commands[command_id] = klass
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'bits/spawn'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Bits
|
5
|
+
# Helper functions for writing providers which mainly interacts with external
|
6
|
+
# commands.
|
7
|
+
module CommandProvider
|
8
|
+
module ClassMethods
|
9
|
+
def check_command(command, name)
|
10
|
+
begin
|
11
|
+
exit_code = Bits.spawn command, :stdout => NULL
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
log.debug "#{name} command not available"
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
if exit_code != 0 then
|
18
|
+
log.debug "#{name} command could not be invoked"
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
log.debug "#{name} command available"
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_command(*args)
|
28
|
+
self.class.check_command(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(*args)
|
32
|
+
self.class.run(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.included(base)
|
36
|
+
base.extend ClassMethods
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'bits/command'
|
2
|
+
require 'bits/logging'
|
3
|
+
|
4
|
+
require 'highline'
|
5
|
+
|
6
|
+
module Bits
|
7
|
+
define_command :install, :desc => 'Install a package' do
|
8
|
+
include Bits::Logging
|
9
|
+
|
10
|
+
def setup(opts)
|
11
|
+
ns[:force] = false
|
12
|
+
ns[:compiled] = nil
|
13
|
+
|
14
|
+
opts.banner = "Usage: bits install <bit>"
|
15
|
+
opts.on('--[no-]compiled', "Insist on installing an already compiled variant or not") do |v|
|
16
|
+
ns[:compiled] = v
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--force', "Insist on installing even if packages already installed") do |v|
|
20
|
+
ns[:force] = v
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def entry(args)
|
25
|
+
if args.empty? then
|
26
|
+
puts parser.help
|
27
|
+
return 1
|
28
|
+
end
|
29
|
+
|
30
|
+
atom = args[0]
|
31
|
+
|
32
|
+
repository = ns[:repository]
|
33
|
+
|
34
|
+
parameters = {}
|
35
|
+
parameters[:compiled] = ns[:compiled] if ns.has_key? :compiled
|
36
|
+
|
37
|
+
p = repository.find_package atom, parameters
|
38
|
+
|
39
|
+
if p.installed? and not ns[:force]
|
40
|
+
log.info "Already installed '#{atom}' using provider(s): #{p.providers_s}"
|
41
|
+
return 0
|
42
|
+
end
|
43
|
+
|
44
|
+
matching = p.matching_ppps
|
45
|
+
|
46
|
+
raise "No matching PPP could be found" if matching.empty?
|
47
|
+
|
48
|
+
ppp = pick_one atom, matching
|
49
|
+
|
50
|
+
install_ppp atom, ppp
|
51
|
+
return 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def pick_one(atom, matching)
|
55
|
+
return matching[0] if matching.size == 1
|
56
|
+
|
57
|
+
hl = HighLine.new $stdin
|
58
|
+
|
59
|
+
hl.choose do |menu|
|
60
|
+
menu.prompt = "Which provider would you like to install '#{atom}' with?"
|
61
|
+
matching.each do |match|
|
62
|
+
menu.choice(match.provider.provider_id) { match }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def install_ppp(atom, ppp)
|
68
|
+
provider = ppp.provider
|
69
|
+
package = ppp.package
|
70
|
+
|
71
|
+
log.info "Installing '#{atom}' using provider: #{provider.provider_id}"
|
72
|
+
provider.install package
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bits/command'
|
2
|
+
require 'bits/logging'
|
3
|
+
|
4
|
+
module Bits
|
5
|
+
define_command :remove, :desc => "Remove a package" do
|
6
|
+
include Bits::Logging
|
7
|
+
|
8
|
+
def setup(opts)
|
9
|
+
opts.banner = "Usage: bits remove <bit>"
|
10
|
+
end
|
11
|
+
|
12
|
+
def entry(args)
|
13
|
+
if args.empty? then
|
14
|
+
puts parser.help
|
15
|
+
return 1
|
16
|
+
end
|
17
|
+
|
18
|
+
atom = args[0]
|
19
|
+
|
20
|
+
repository = @ns[:repository]
|
21
|
+
|
22
|
+
p = repository.find_package atom
|
23
|
+
|
24
|
+
unless p.installed?
|
25
|
+
log.info "Package not installed '#{atom}'"
|
26
|
+
return 0
|
27
|
+
end
|
28
|
+
|
29
|
+
log.info "Removing '#{atom}' using provider(s): #{p.providers_s}"
|
30
|
+
|
31
|
+
p.remove
|
32
|
+
return 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'bits/command'
|
2
|
+
require 'bits/logging'
|
3
|
+
require 'bits/exceptions'
|
4
|
+
|
5
|
+
module Bits
|
6
|
+
define_command :show, :desc => "Show a package" do
|
7
|
+
include Bits::Logging
|
8
|
+
|
9
|
+
def setup(opts)
|
10
|
+
opts.banner = "Usage: bits show <bit>"
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Show information about the specified bit."
|
13
|
+
end
|
14
|
+
|
15
|
+
def entry(args)
|
16
|
+
if args.empty? then
|
17
|
+
puts parser.help
|
18
|
+
return 1
|
19
|
+
end
|
20
|
+
|
21
|
+
atom = args[0]
|
22
|
+
|
23
|
+
repository = ns[:repository]
|
24
|
+
|
25
|
+
criteria = {}
|
26
|
+
criteria[:compiled] = ns[:compiled] if ns.has_key? :compiled
|
27
|
+
|
28
|
+
begin
|
29
|
+
p = repository.find_package atom, criteria
|
30
|
+
rescue MissingBit
|
31
|
+
puts "No such atom '#{atom}'"
|
32
|
+
return 1
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "PPPs:"
|
36
|
+
|
37
|
+
p.ppps.each do |ppp|
|
38
|
+
puts " #{ppp.provider.provider_id}:"
|
39
|
+
puts " Bit: #{ppp.bit.atom}"
|
40
|
+
puts " Package Atom: #{ppp.package.atom}"
|
41
|
+
puts " Installed: #{ppp.package.installed_s}"
|
42
|
+
puts " Candidate: #{ppp.package.candidate_s}"
|
43
|
+
puts " Parameters: #{ppp.parameters}"
|
44
|
+
end
|
45
|
+
|
46
|
+
return 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'bits/command'
|
2
|
+
require 'bits/logging'
|
3
|
+
require 'bits/exceptions'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Bits
|
8
|
+
define_command :sync, :desc => "sync local repository" do
|
9
|
+
include Bits::Logging
|
10
|
+
|
11
|
+
GIT = 'git'
|
12
|
+
CLONE_URL = 'https://github.com/udoprog/bits-repo'
|
13
|
+
|
14
|
+
def setup(opts)
|
15
|
+
opts.banner = "Usage: bits sync"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Sync local repository"
|
18
|
+
end
|
19
|
+
|
20
|
+
def entry(args)
|
21
|
+
dir = ns[:local_repository_dir]
|
22
|
+
|
23
|
+
setup_original dir unless File.directory? dir
|
24
|
+
|
25
|
+
Dir.chdir(dir) do
|
26
|
+
Bits.spawn [GIT, 'pull', 'origin', 'master']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup_original(dir)
|
31
|
+
Bits.spawn [GIT, 'clone', CLONE_URL, dir]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Bits
|
2
|
+
class ProviderException < Exception; end
|
3
|
+
|
4
|
+
# Is raised when a package being requested does not exist.
|
5
|
+
class MissingPackage < ProviderException; end
|
6
|
+
|
7
|
+
# Is raised when a bit does not exist.
|
8
|
+
class MissingBit < ProviderException; end
|
9
|
+
|
10
|
+
# Is raised when a package being requested does not exist.
|
11
|
+
class MissingProvidedPackage < ProviderException; end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bits
|
2
|
+
class ExecuteContext
|
3
|
+
SUDO = 'sudo'
|
4
|
+
|
5
|
+
def initialize(user)
|
6
|
+
@user = user
|
7
|
+
end
|
8
|
+
|
9
|
+
def spawn(args, params={})
|
10
|
+
superuser = params[:superuser] || false
|
11
|
+
|
12
|
+
if superuser and not @user.superuser?
|
13
|
+
Bits.spawn [SUDO] + args, params
|
14
|
+
else
|
15
|
+
Bits.spawn args, params
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(args, params={})
|
20
|
+
params[:ignore_exitcode] = true
|
21
|
+
spawn(args, params) == 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'bits/logging'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Bits
|
7
|
+
module ExternalInterface
|
8
|
+
class Interface
|
9
|
+
include Bits::Logging
|
10
|
+
|
11
|
+
attr_reader :capabilities
|
12
|
+
|
13
|
+
def initialize(id, args, stdin, stdout, pid)
|
14
|
+
@id = id
|
15
|
+
@args = args
|
16
|
+
@stdin = stdin
|
17
|
+
@stdout = stdout
|
18
|
+
@pid = pid
|
19
|
+
@capabilities = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# end the child process by closing stdin.
|
23
|
+
def end
|
24
|
+
@stdin.close
|
25
|
+
Process.wait @pid
|
26
|
+
$?
|
27
|
+
end
|
28
|
+
|
29
|
+
def info(atom)
|
30
|
+
type, response = request :info, :atom => atom
|
31
|
+
end
|
32
|
+
|
33
|
+
def ping
|
34
|
+
begin
|
35
|
+
type, response = request :ping
|
36
|
+
rescue
|
37
|
+
log.debug "problem while pinging interface '#{@id}': #{$!}"
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
raise "Expected pong but got #{type}" unless type == :pong
|
42
|
+
@capabilities = (response['capabilities'] || []).map(&:to_sym)
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
|
46
|
+
def request(type, command={})
|
47
|
+
command[:__type__] = type
|
48
|
+
|
49
|
+
write JSON.dump(command)
|
50
|
+
data = read
|
51
|
+
|
52
|
+
raise "empty response" if data.nil?
|
53
|
+
|
54
|
+
response = JSON.load(data)
|
55
|
+
|
56
|
+
response_type = response['__type__'].to_sym
|
57
|
+
|
58
|
+
if response_type == :error
|
59
|
+
error_text = response['text']
|
60
|
+
raise "Error in interface: #{error_text}"
|
61
|
+
end
|
62
|
+
|
63
|
+
[response_type, response]
|
64
|
+
end
|
65
|
+
|
66
|
+
def close
|
67
|
+
return if @stdin.nil? and @stdout.nil?
|
68
|
+
reap_child
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def reap_child
|
74
|
+
@stdin.close unless @stdin.nil?
|
75
|
+
@stdout.close unless @stdout.nil?
|
76
|
+
|
77
|
+
@stdin = nil
|
78
|
+
@stdout = nil
|
79
|
+
|
80
|
+
# don't hang since write might have reaped it already.
|
81
|
+
Process.wait @pid, Process::WNOHANG
|
82
|
+
end
|
83
|
+
|
84
|
+
def write(data)
|
85
|
+
return if @stdin.nil?
|
86
|
+
@stdin.puts data
|
87
|
+
@stdin.flush
|
88
|
+
rescue
|
89
|
+
reap_child
|
90
|
+
raise
|
91
|
+
end
|
92
|
+
|
93
|
+
# read or no-op if it has been closed.
|
94
|
+
def read
|
95
|
+
return if @stdout.nil?
|
96
|
+
@stdout.gets
|
97
|
+
rescue
|
98
|
+
reap_child
|
99
|
+
raise
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module ClassMethods
|
104
|
+
# access global interface cache for class methods.
|
105
|
+
def interfaces
|
106
|
+
ExternalInterface.interfaces
|
107
|
+
end
|
108
|
+
|
109
|
+
# Spawn an interface that is shared between all users.
|
110
|
+
def setup_interface(id, params = {})
|
111
|
+
capabilities = params[:capabilities] || []
|
112
|
+
|
113
|
+
interface = spawn_interface id
|
114
|
+
|
115
|
+
if interface.nil?
|
116
|
+
log.debug "Interface '#{id}' not available"
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
|
120
|
+
missing_capabilities = capabilities - interface.capabilities
|
121
|
+
|
122
|
+
unless missing_capabilities.empty?
|
123
|
+
missing_s = missing_capabilities.join ', '
|
124
|
+
log.debug "Interface '#{id}' is available, but is missing capabilities: #{missing_s}"
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
|
128
|
+
has_s = capabilities.join ', '
|
129
|
+
log.debug "Interface '#{id}' is available with capabilities: #{has_s}"
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def spawn_interface(id)
|
136
|
+
unless interfaces[id].nil?
|
137
|
+
return interfaces[id]
|
138
|
+
end
|
139
|
+
|
140
|
+
libexec_path = path_to_libexec "bits-#{id}"
|
141
|
+
|
142
|
+
command = [libexec_path]
|
143
|
+
|
144
|
+
stdout_r, stdout_w = IO.pipe
|
145
|
+
stdin_r, stdin_w = IO.pipe
|
146
|
+
|
147
|
+
pid = fork do
|
148
|
+
stdin_w.close
|
149
|
+
stdout_r.close
|
150
|
+
|
151
|
+
$stdin.reopen stdin_r
|
152
|
+
$stdout.reopen stdout_w
|
153
|
+
|
154
|
+
begin
|
155
|
+
exec(*command)
|
156
|
+
rescue Errno::ENOENT
|
157
|
+
$stdout.close
|
158
|
+
$stdin.close
|
159
|
+
exit ENOENT
|
160
|
+
end
|
161
|
+
|
162
|
+
exit 1
|
163
|
+
end
|
164
|
+
|
165
|
+
stdin_r.close
|
166
|
+
stdout_w.close
|
167
|
+
|
168
|
+
interface = Interface.new(id, command, stdin_w, stdout_r, pid)
|
169
|
+
|
170
|
+
if not interface.ping
|
171
|
+
interface.close
|
172
|
+
return nil
|
173
|
+
end
|
174
|
+
|
175
|
+
interfaces[id] = interface
|
176
|
+
end
|
177
|
+
|
178
|
+
def path_to_libexec(name)
|
179
|
+
File.join File.dirname(File.expand_path(__FILE__)), File.join('..', 'libexec', name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# global cache for interfaces.
|
184
|
+
def self.interfaces
|
185
|
+
@interfaces ||= {}
|
186
|
+
end
|
187
|
+
|
188
|
+
# access global interface cache for instances.
|
189
|
+
def interfaces
|
190
|
+
self.class.interfaces
|
191
|
+
end
|
192
|
+
|
193
|
+
# close all global interfaces
|
194
|
+
def self.close_interfaces
|
195
|
+
interfaces.each do |id, interface|
|
196
|
+
interface.close
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.included(base)
|
201
|
+
base.extend ClassMethods
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|