bits-installer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Bits
2
+ VERSION = '0.1.0'
3
+ end
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:]))