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