bits-installer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|