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
@@ -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:]))
|