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
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bits
|
2
|
+
# Used to provide a neat class based interface for reporting the last check
|
3
|
+
# error.
|
4
|
+
module ProviderReporting
|
5
|
+
module ClassMethods
|
6
|
+
def check_errors
|
7
|
+
@check_errors ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def last_check_error
|
11
|
+
check_errors[-1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_error(text)
|
15
|
+
check_errors << text
|
16
|
+
log.debug("Error on check: #{text}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(mod)
|
21
|
+
mod.extend ClassMethods
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'bits/logging'
|
2
|
+
require 'bits/bit_declaration'
|
3
|
+
require 'bits/bit'
|
4
|
+
require 'bits/package_proxy'
|
5
|
+
require 'bits/exceptions'
|
6
|
+
|
7
|
+
module Bits
|
8
|
+
class PPP
|
9
|
+
attr_accessor :bit, :provider, :package, :parameters, :path
|
10
|
+
|
11
|
+
def initialize(bit, provider, package, parameters, path)
|
12
|
+
@bit = bit
|
13
|
+
@provider = provider
|
14
|
+
@package = package
|
15
|
+
@parameters = parameters
|
16
|
+
@path = path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Repository
|
21
|
+
include Bits::Logging
|
22
|
+
|
23
|
+
attr_accessor :providers, :backend
|
24
|
+
|
25
|
+
def initialize(providers, backend)
|
26
|
+
@bitcache = {}
|
27
|
+
@providers = providers
|
28
|
+
@backend = backend
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_package(atom, criteria={})
|
32
|
+
ppps = []
|
33
|
+
|
34
|
+
iterate_packages(atom) do |bit, atom, provider, parameters, path|
|
35
|
+
begin
|
36
|
+
package = provider.query(atom)
|
37
|
+
rescue MissingPackage
|
38
|
+
log.warn "No such atom '#{atom}' for provider '#{provider.provider_id}'"
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
ppps << PPP.new(bit, provider, package, parameters, path)
|
43
|
+
end
|
44
|
+
|
45
|
+
if ppps.empty?
|
46
|
+
raise MissingProvidedPackage.new atom
|
47
|
+
end
|
48
|
+
|
49
|
+
return PackageProxy.new ppps, criteria
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def load_bit(atom)
|
55
|
+
log.debug "Loading bit: #{atom}"
|
56
|
+
return @bitcache[atom] unless @bitcache[atom].nil?
|
57
|
+
reader = backend.fetch(atom)
|
58
|
+
@bitcache[atom] = Bit.eval reader, atom
|
59
|
+
end
|
60
|
+
|
61
|
+
def iterate_packages(atom)
|
62
|
+
references = [[[], atom, nil]]
|
63
|
+
|
64
|
+
while not references.empty?
|
65
|
+
path, atom, filter = references.shift
|
66
|
+
|
67
|
+
if path.include? atom
|
68
|
+
raise "Circular reference: #{path.inspect.join ' -> '}"
|
69
|
+
end
|
70
|
+
|
71
|
+
current_bit = load_bit atom
|
72
|
+
|
73
|
+
path << current_bit.atom
|
74
|
+
|
75
|
+
# match all the providers that is specified by the bit and if
|
76
|
+
# provider_filter is defined, explicitly match that too.
|
77
|
+
matching = providers.select do |p|
|
78
|
+
current_bit.has_provider?(p.provider_id) and
|
79
|
+
(filter.nil? or filter == p.provider_id)
|
80
|
+
end
|
81
|
+
|
82
|
+
raise "No matching providers: #{current_bit}" if matching.empty?
|
83
|
+
|
84
|
+
# for each matching provider, find the data associated with this
|
85
|
+
# provider and bit.
|
86
|
+
matching.each do |provider|
|
87
|
+
provider_data = current_bit.get_provider_data provider.provider_id
|
88
|
+
|
89
|
+
if provider_data.kind_of? BitReference
|
90
|
+
references << [
|
91
|
+
Array.new(path),
|
92
|
+
provider_data.atom,
|
93
|
+
# only match for this specified provider.
|
94
|
+
provider.provider_id
|
95
|
+
]
|
96
|
+
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
if provider_data.kind_of? BitParameters
|
101
|
+
params = provider_data.parameters
|
102
|
+
|
103
|
+
atom = (params[:atom] || current_bit.atom)
|
104
|
+
|
105
|
+
params = {
|
106
|
+
:compiled => (params[:compiled] || false),
|
107
|
+
}
|
108
|
+
|
109
|
+
yield [current_bit, atom, provider, params, path]
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
raise "Unknown provider data '#{data}'"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/bits/spawn.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'bits/logging'
|
2
|
+
|
3
|
+
module Bits
|
4
|
+
ENOENT = 0x7f
|
5
|
+
PIPE = 0x01
|
6
|
+
NULL = 0x02
|
7
|
+
|
8
|
+
DEV_NULL = '/dev/null'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def handle_exit(pid, fd_cache, ignore_exitcode)
|
12
|
+
fd_cache.each do |key, fd|
|
13
|
+
fd.close
|
14
|
+
end
|
15
|
+
|
16
|
+
Process.waitpid pid
|
17
|
+
|
18
|
+
exitstatus = $?.exitstatus
|
19
|
+
|
20
|
+
if exitstatus == ENOENT then
|
21
|
+
raise Errno::ENOENT
|
22
|
+
end
|
23
|
+
|
24
|
+
if not ignore_exitcode and exitstatus != 0 then
|
25
|
+
raise "Bad exit status: #{exitstatus}"
|
26
|
+
end
|
27
|
+
|
28
|
+
exitstatus
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_file(type, global, fd_cache)
|
32
|
+
case type
|
33
|
+
when PIPE then IO.pipe
|
34
|
+
when NULL then (fd_cache[:null] ||= File.open(DEV_NULL, 'w'))
|
35
|
+
else type
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_child_file(type, global, fds)
|
40
|
+
case type
|
41
|
+
when PIPE then
|
42
|
+
fds[0].close
|
43
|
+
global.reopen fds[1]
|
44
|
+
else
|
45
|
+
global.reopen fds unless fds === global
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_parent_file(type, fds)
|
50
|
+
case type
|
51
|
+
when PIPE then
|
52
|
+
fds[1].close
|
53
|
+
fds[0]
|
54
|
+
else fds
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def spawn(args, params={})
|
59
|
+
fd_cache = {}
|
60
|
+
|
61
|
+
stdout = (params[:stdout] || $stdout)
|
62
|
+
stderr = (params[:stderr] || $stderr)
|
63
|
+
ignore_exitcode = params[:ignore_exitcode] || false
|
64
|
+
|
65
|
+
out = setup_file stdout, $stdout, fd_cache
|
66
|
+
err = setup_file stderr, $stderr, fd_cache
|
67
|
+
|
68
|
+
pid = fork do
|
69
|
+
handle_child_file stdout, $stdout, out
|
70
|
+
handle_child_file stderr, $stderr, err
|
71
|
+
|
72
|
+
begin
|
73
|
+
exec(*args)
|
74
|
+
rescue Errno::ENOENT
|
75
|
+
exit ENOENT
|
76
|
+
rescue
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
out = handle_parent_file stdout, out
|
82
|
+
err = handle_parent_file stderr, err
|
83
|
+
|
84
|
+
return handle_exit pid, fd_cache, ignore_exitcode unless block_given?
|
85
|
+
yield [out, err]
|
86
|
+
handle_exit pid, fd_cache, ignore_exitcode
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/bits/user.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bits
|
2
|
+
class User
|
3
|
+
def superuser?
|
4
|
+
raise "not implemented: superuser?"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class PosixUser < User
|
9
|
+
def superuser?
|
10
|
+
Process.uid == 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.setup_user
|
15
|
+
if RUBY_PLATFORM.include? 'darwin'
|
16
|
+
kind = PosixUser
|
17
|
+
elsif RUBY_PLATFORM.include? 'linux'
|
18
|
+
kind = PosixUser
|
19
|
+
elsif RUBY_PLATFORM.include? 'win32'
|
20
|
+
kind = Win32User
|
21
|
+
else
|
22
|
+
raise "Unsupported platform: #{RUBY_PLATFORM}"
|
23
|
+
end
|
24
|
+
|
25
|
+
kind.new
|
26
|
+
end
|
27
|
+
end
|
data/lib/bits/version.rb
ADDED
data/lib/bits.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'bits/backend/local'
|
4
|
+
require 'bits/backend/join'
|
5
|
+
require 'bits/package'
|
6
|
+
require 'bits/repository'
|
7
|
+
|
8
|
+
require 'bits/commands/install'
|
9
|
+
require 'bits/commands/remove'
|
10
|
+
require 'bits/commands/show'
|
11
|
+
require 'bits/commands/sync'
|
12
|
+
|
13
|
+
require 'bits/provider/python'
|
14
|
+
require 'bits/provider/apt'
|
15
|
+
require 'bits/provider/portage'
|
16
|
+
require 'bits/provider/homebrew'
|
17
|
+
|
18
|
+
require 'bits/external_interface'
|
19
|
+
require 'bits/user'
|
20
|
+
|
21
|
+
module Bits
|
22
|
+
class << self
|
23
|
+
def parse_options(args)
|
24
|
+
ns = {}
|
25
|
+
|
26
|
+
subcommands = Hash.new
|
27
|
+
available_providers = Array.new
|
28
|
+
unavailable_providers = Array.new
|
29
|
+
|
30
|
+
global = OptionParser.new do |global_opts|
|
31
|
+
global_opts.banner = "Usage: bits <command> [options]"
|
32
|
+
|
33
|
+
global_opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
34
|
+
ns[:verbose] = v
|
35
|
+
end
|
36
|
+
|
37
|
+
global_opts.on("-d", "--debug", "Enable debug logging") do |v|
|
38
|
+
@log.level = Log4r::DEBUG
|
39
|
+
end
|
40
|
+
|
41
|
+
global_opts.separator ""
|
42
|
+
global_opts.separator "Available commands:"
|
43
|
+
|
44
|
+
Bits.commands.values.sort_by{|c| c.command_id.to_s}.each do |klass|
|
45
|
+
global_opts.separator " #{klass.command_id}: #{klass.desc}"
|
46
|
+
subcommands[klass.command_id] = klass
|
47
|
+
end
|
48
|
+
|
49
|
+
Bits.providers.values.sort_by{|p| p.provider_id.to_s}.each do |provider_class|
|
50
|
+
if provider_class.check
|
51
|
+
available_providers << provider_class
|
52
|
+
else
|
53
|
+
unavailable_providers << provider_class
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
global_opts.separator "Providers:"
|
58
|
+
|
59
|
+
available_providers.each do |provider_class|
|
60
|
+
global_opts.separator " #{provider_class.provider_id}: #{provider_class.desc}"
|
61
|
+
end
|
62
|
+
|
63
|
+
global_opts.separator "Unavailable providers:"
|
64
|
+
unavailable_providers.each do |klass|
|
65
|
+
global_opts.separator " #{klass.provider_id}: #{klass.last_check_error}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
global.order!
|
70
|
+
command = ARGV.shift
|
71
|
+
|
72
|
+
if command.nil? then
|
73
|
+
$stderr.puts global.help
|
74
|
+
exit 0
|
75
|
+
end
|
76
|
+
|
77
|
+
command = command.to_sym
|
78
|
+
|
79
|
+
if subcommands[command].nil? then
|
80
|
+
$stderr.puts "No such command: #{command}"
|
81
|
+
$stderr.puts global.help
|
82
|
+
exit 0
|
83
|
+
end
|
84
|
+
|
85
|
+
command_klass = subcommands[command]
|
86
|
+
command = command_klass.new ns
|
87
|
+
|
88
|
+
command_parser = OptionParser.new do |opts|
|
89
|
+
command.setup opts
|
90
|
+
end
|
91
|
+
|
92
|
+
command_parser.order!
|
93
|
+
|
94
|
+
ns[:user] = setup_user
|
95
|
+
ns[:local_repository_dir] = setup_local_repository_dir ns
|
96
|
+
|
97
|
+
command = command_klass.new ns
|
98
|
+
providers = setup_providers available_providers, ns
|
99
|
+
backend = setup_backend ns
|
100
|
+
|
101
|
+
ns[:repository] = Bits::Repository.new(providers, backend)
|
102
|
+
|
103
|
+
return ARGV, command
|
104
|
+
end
|
105
|
+
|
106
|
+
# Setup the path to the local repository directory.
|
107
|
+
def setup_local_repository_dir(ns)
|
108
|
+
home = ENV['HOME']
|
109
|
+
raise "HOME environment variable not defined" if home.nil?
|
110
|
+
File.join home, '.bits'
|
111
|
+
end
|
112
|
+
|
113
|
+
def setup_logging
|
114
|
+
log = Log4r::Logger.new 'Bits'
|
115
|
+
log.outputters << Log4r::Outputter.stdout
|
116
|
+
log.level = Log4r::INFO
|
117
|
+
log
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_providers(available_providers, ns)
|
121
|
+
available_providers.collect do |provider_class|
|
122
|
+
provider_class.new ns
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def setup_backend(ns)
|
127
|
+
cwd_dir = File.join Dir.pwd, 'bits'
|
128
|
+
local_dir = ns[:local_repository_dir]
|
129
|
+
|
130
|
+
backends = Array.new
|
131
|
+
|
132
|
+
backends << LocalBackend.new(cwd_dir) if File.directory? cwd_dir
|
133
|
+
backends << LocalBackend.new(local_dir) if File.directory? local_dir
|
134
|
+
|
135
|
+
JoinBackend.new backends
|
136
|
+
end
|
137
|
+
|
138
|
+
def main(args)
|
139
|
+
@log = setup_logging
|
140
|
+
|
141
|
+
args, command = parse_options(args)
|
142
|
+
|
143
|
+
begin
|
144
|
+
command.entry args
|
145
|
+
ensure
|
146
|
+
Bits::ExternalInterface.close_interfaces
|
147
|
+
end
|
148
|
+
|
149
|
+
return 0
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
if __FILE__ == $0
|
155
|
+
exit Bits::main(ARGV)
|
156
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import json
|
5
|
+
|
6
|
+
try:
|
7
|
+
import pkg_resources
|
8
|
+
has_pkg_resources = True
|
9
|
+
except ImportError:
|
10
|
+
has_pkg_resources = False
|
11
|
+
|
12
|
+
try:
|
13
|
+
import portage
|
14
|
+
has_portage = True
|
15
|
+
except ImportError:
|
16
|
+
has_portage = False
|
17
|
+
|
18
|
+
|
19
|
+
class RequestError(Exception):
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class MissingRequestKey(Exception):
|
24
|
+
def __init__(self, key):
|
25
|
+
super(MissingRequestKey, self).__init__(
|
26
|
+
"Missing key '{0}' in request".format(key))
|
27
|
+
|
28
|
+
|
29
|
+
def ping_handler(args):
|
30
|
+
capabilities = list()
|
31
|
+
|
32
|
+
if has_portage:
|
33
|
+
capabilities.append("portage")
|
34
|
+
|
35
|
+
if has_pkg_resources:
|
36
|
+
capabilities.append("pkg_resources")
|
37
|
+
|
38
|
+
return {
|
39
|
+
"__type__": "pong",
|
40
|
+
"capabilities": capabilities
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
def find_project(package):
|
45
|
+
for p in pkg_resources.working_set:
|
46
|
+
if p.project_name == package:
|
47
|
+
return p
|
48
|
+
|
49
|
+
return None
|
50
|
+
|
51
|
+
|
52
|
+
def python_info_handler(request):
|
53
|
+
atom = request.get('atom')
|
54
|
+
|
55
|
+
if atom is None:
|
56
|
+
raise MissingRequestKey("atom")
|
57
|
+
|
58
|
+
project = find_project(atom)
|
59
|
+
|
60
|
+
if project is None:
|
61
|
+
return {
|
62
|
+
"__type__": "missing_atom",
|
63
|
+
"atom": atom,
|
64
|
+
}
|
65
|
+
|
66
|
+
return {
|
67
|
+
"__type__": "info",
|
68
|
+
"name": project.project_name,
|
69
|
+
"installed": project.version,
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
def portage_info_handler(request):
|
74
|
+
package = request['package']
|
75
|
+
|
76
|
+
if package is None:
|
77
|
+
raise MissingRequestKey("package")
|
78
|
+
|
79
|
+
vartree = portage.db[portage.root]['vartree']
|
80
|
+
porttree = portage.db[portage.root]['porttree']
|
81
|
+
|
82
|
+
name = None
|
83
|
+
installed = vartree.dep_bestmatch(package)
|
84
|
+
candidate = porttree.dep_bestmatch(package)
|
85
|
+
|
86
|
+
if not installed:
|
87
|
+
installed = None
|
88
|
+
else:
|
89
|
+
_, installed, rev = portage.pkgsplit(installed)
|
90
|
+
installed = "{0}-{1}".format(installed, rev)
|
91
|
+
|
92
|
+
if not candidate:
|
93
|
+
candidate = None
|
94
|
+
else:
|
95
|
+
name, candidate, rev = portage.pkgsplit(candidate)
|
96
|
+
candidate = "{0}-{1}".format(candidate, rev)
|
97
|
+
|
98
|
+
return {
|
99
|
+
"__type__": "info",
|
100
|
+
"name": name,
|
101
|
+
"installed": installed,
|
102
|
+
"candidate": candidate,
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
handlers = {
|
107
|
+
"ping": ping_handler,
|
108
|
+
"python_info": python_info_handler,
|
109
|
+
"portage_info": portage_info_handler,
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
def handle_request(line):
|
114
|
+
try:
|
115
|
+
request = json.loads(line)
|
116
|
+
except:
|
117
|
+
raise RequestError("Could not decode request, expected type: json")
|
118
|
+
|
119
|
+
request_type = request.get('__type__')
|
120
|
+
|
121
|
+
if request_type is None:
|
122
|
+
raise RequestError("Missing 'type' in request")
|
123
|
+
|
124
|
+
handler = handlers.get(request_type)
|
125
|
+
|
126
|
+
if handler is None:
|
127
|
+
raise RequestError("No handler for type '{0}'".format(request_type))
|
128
|
+
|
129
|
+
return handler(request)
|
130
|
+
|
131
|
+
|
132
|
+
def guarded_handle_request(line):
|
133
|
+
try:
|
134
|
+
return handle_request(line)
|
135
|
+
except RequestError, e:
|
136
|
+
return {"__type__": "error", "text": str(e)}
|
137
|
+
|
138
|
+
|
139
|
+
def main(args):
|
140
|
+
while True:
|
141
|
+
try:
|
142
|
+
line = sys.stdin.readline()
|
143
|
+
except KeyboardInterrupt:
|
144
|
+
break
|
145
|
+
|
146
|
+
if not line:
|
147
|
+
break
|
148
|
+
|
149
|
+
response = guarded_handle_request(line)
|
150
|
+
print >>sys.stdout, json.dumps(response)
|
151
|
+
sys.stdout.flush()
|
152
|
+
|
153
|
+
return 0
|
154
|
+
|
155
|
+
|
156
|
+
if __name__ == "__main__":
|
157
|
+
sys.exit(main(sys.argv[1:]))
|