pkg_noisrev 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/version.h ADDED
@@ -0,0 +1,13 @@
1
+ #ifndef D31D4EAD_4F6F_A672_4D5B_BD8D946207BC
2
+ #define D31D4EAD_4F6F_A672_4D5B_BD8D946207BC
3
+
4
+ #include <stdlib.h>
5
+ #include <limits.h>
6
+ #include <ctype.h>
7
+ #include <sys/cdefs.h>
8
+ #include <string.h>
9
+ #include <err.h>
10
+
11
+ int version_cmp(const char *pkg1, const char *pkg2);
12
+
13
+ #endif // D31D4EAD_4F6F_A672_4D5B_BD8D946207BC
@@ -0,0 +1,350 @@
1
+ require_relative 'threads'
2
+ require_relative 'fbsdpackageversion'
3
+
4
+ module Pkg_noisrev
5
+ class FbsdPackage
6
+ include Enumerable
7
+
8
+ # A placeholder for package.
9
+ class OnePackage
10
+ include Comparable
11
+
12
+ CATEGORY = ['Root (no dependencies, not depended on)',
13
+ 'Trunk (no dependencies, are depended on)',
14
+ 'Branch (have dependencies, are depended on)',
15
+ 'Leaf (have dependencies, not depended on)',
16
+ 'Unknown category']
17
+ attr_accessor :name, :ver, :origin, :ports_ver, :category
18
+
19
+ def initialize(name, ver, origin, ports_ver, category)
20
+ @name = name
21
+ @ver = ver
22
+ @origin = origin
23
+ @ports_ver = ports_ver
24
+ @category = category
25
+ end
26
+
27
+ # by name
28
+ def <=>(other)
29
+ @name <=> other.name
30
+ end
31
+ end
32
+
33
+ attr_reader :db_dir, :ports_dir
34
+
35
+ def initialize(db_dir, ports_dir)
36
+ @db_dir = db_dir
37
+ @ports_dir = ports_dir
38
+
39
+ @data = []
40
+ @data_massage = false
41
+
42
+ @queue = FbsdPackage.dir_collect(@db_dir)
43
+ end
44
+
45
+ def size
46
+ @data.size
47
+ end
48
+
49
+ def each(&block)
50
+ fail "call analyze method first" unless @data_massage
51
+ @data.each{ |i| block.call i }
52
+ end
53
+
54
+ # by size
55
+ def <=>(other)
56
+ fail "call analyze method first" unless @data_massage
57
+ @data.size <=> other.size
58
+ end
59
+
60
+ def analyze(log = nil)
61
+ pkg_total = @queue.size
62
+ $stdout.puts "#{pkg_total} total: left% processed/okays/failures | thread number: ok/failed\n"
63
+ $stdout.flush
64
+
65
+ thread_pool = []
66
+ 4.times {|i|
67
+ thread_pool[i] = MyThread.new(i, i) {
68
+ @queue.size.times {
69
+ item = @queue.pop(true) rescue break
70
+ r = FbsdPackage.parse_name item
71
+ origin = nil
72
+ category = nil
73
+ begin
74
+ origin, category = FbsdPackage.origin @db_dir, item
75
+ fail "cannot extract the origin for #{name}" unless origin
76
+
77
+ pver = FbsdPort.ver @ports_dir, origin
78
+ @data << OnePackage.new(r.first, r.last, origin, pver, category)
79
+ rescue
80
+ MyThread.current.stat.failed += 1
81
+ @data << OnePackage.new(r.first, r.last, origin, nil, category)
82
+ log.error "#{$!}" if log
83
+ else
84
+ MyThread.current.stat.ok += 1
85
+ end
86
+ }
87
+ }
88
+ }
89
+
90
+ stat = Spectator.new thread_pool, pkg_total, 1
91
+ stat.alarm # print the statistics every 1 second
92
+
93
+ thread_pool.each(&:join)
94
+ @data_massage = true
95
+ stat.alarm_finish
96
+ end
97
+
98
+ def self.parse_name(name)
99
+ t = name.split '-'
100
+ return [name, '0'] if t.size < 2
101
+ [t[0..-2].join('-'), t.last]
102
+ end
103
+
104
+ def self.dir_collect(d)
105
+ q = Queue.new
106
+ Dir.glob("#{d}/*").reject {|i| !File.directory?(i) }.map do |i|
107
+ q.push File.basename(i)
108
+ end
109
+ fail "no package records in #{d}" unless q.size > 0
110
+ q
111
+ end
112
+
113
+ # Return something like ['foo/bar', 3]
114
+ def self.origin(db_dir, name)
115
+ contents = File.read "#{db_dir}/#{name}/+CONTENTS"
116
+ db_required_by = "#{db_dir}/#{name}/+REQUIRED_BY"
117
+
118
+ category = nil
119
+ origin = nil
120
+ has_dep = contents.match(/^\s*@pkgdep /)
121
+
122
+ # Set a package category
123
+ #
124
+ # 0--Root (No dependencies, not depended on)
125
+ # 1--Trunk (No dependencies, are depended on)
126
+ # 2--Branch (Have dependencies, are depended on)
127
+ # 3--Leaf (Have dependencies, not depended on)
128
+ if File.size?(db_required_by)
129
+ category = 1
130
+ category = 2 if has_dep
131
+ else
132
+ category = 0
133
+ category = 3 if has_dep
134
+ end
135
+
136
+ origin = $1 if contents.match(/^\s*@comment\s+ORIGIN:(.+)$/)
137
+ [origin, category]
138
+ end
139
+
140
+ def print(mode, filter)
141
+ p = ->(item) {
142
+ cond = '='
143
+ if item.ports_ver
144
+ case FbsdPackageVersion.version_cmp(item.ver, item.ports_ver)
145
+ when -1
146
+ cond = '<'
147
+ when 1
148
+ cond = '>'
149
+ end
150
+ else
151
+ cond = '?'
152
+ end
153
+ puts "%21s %s %-21s %s" % [item.ver, cond, item.ports_ver, item.name]
154
+ }
155
+
156
+ packages = []
157
+ #
158
+ # filter packages
159
+ #
160
+ case filter
161
+ when ""
162
+ # all, no filter
163
+ packages = @data.sort
164
+ when 'outofsync'
165
+ packages = @data.reject {|i|
166
+ FbsdPackageVersion.version_cmp(i.ver,
167
+ (i.ports_ver ? i.ports_ver : "0")) == 0
168
+ }.sort
169
+ when 'missing'
170
+ packages = @data.reject {|i| i.ports_ver }.sort
171
+ else
172
+ fail "invalid filter: #{filter}"
173
+ end
174
+
175
+ #
176
+ # print packages
177
+ #
178
+ if mode == 'likeportmaster'
179
+ FbsdPackage.print_like_portmaster @ports_dir, packages
180
+ else
181
+ packages.each {|idx| p.call(idx) }
182
+ end
183
+ end
184
+
185
+ def self.print_like_portmaster(ports_dir, packages)
186
+ moved = FbsdPort.moved(ports_dir)
187
+
188
+ root = []
189
+ trunk = []
190
+ branch = []
191
+ leaf = []
192
+ godknowswhat = []
193
+ packages.sort.each {|i|
194
+ case i.category
195
+ when 0
196
+ root << i
197
+ when 1
198
+ trunk << i
199
+ when 2
200
+ branch << i
201
+ when 3
202
+ leaf << i
203
+ else
204
+ godknowswhat << i
205
+ end
206
+ }
207
+
208
+ outofsync = 0
209
+ p = ->(category, data) {
210
+ return if data.size == 0
211
+ puts "* #{OnePackage::CATEGORY[category]}, #{data.size}"
212
+ data.each {|i|
213
+ puts i.name + '-' + i.ver
214
+ if i.ports_ver
215
+ if FbsdPackageVersion.version_cmp(i.ver, i.ports_ver) != 0
216
+ puts " => Ports have another version: #{i.ports_ver}"
217
+ outofsync += 1
218
+ end
219
+ else
220
+ outofsync += 1
221
+ # check missing package in MOVED db
222
+ if m = moved[i.origin]
223
+ if m.movedto == ""
224
+ puts " => Deleted at #{m.date}: #{m.why}"
225
+ else
226
+ puts " => Moved at #{m.date} to #{m.movedto}: #{m.why}"
227
+ end
228
+ else
229
+ # this is your hand made package or your ports are old
230
+ puts " => Not found in ports"
231
+ end
232
+ end
233
+ }
234
+ puts ""
235
+ }
236
+
237
+ puts ""
238
+ p.call 0, root
239
+ p.call 1, trunk
240
+ p.call 2, branch
241
+ p.call 3, leaf
242
+ p.call 4, godknowswhat
243
+
244
+ puts "Total #{packages.size}, out of sync #{outofsync}."
245
+ end
246
+ end
247
+
248
+ class FbsdPort
249
+ # The last resort method of getting a version number: execute 'make'
250
+ # command.
251
+ #
252
+ # Return a string that represents a port version.
253
+ def self.ver_slow(ports_dir, origin)
254
+ # cannot use just a block for Dir.chdir due to a annoing warning
255
+ # under the another thread
256
+ dirsave = Dir.pwd
257
+
258
+ Dir.chdir ports_dir + '/' + origin
259
+ r = Trestle.cmd_run('make -V PKGVERSION')
260
+
261
+ Dir.chdir dirsave
262
+
263
+ fail "even executing make didn't help #{origin}" if r[0] != 0
264
+ r[2].strip
265
+ end
266
+
267
+ # Return a string that represents a port version.
268
+ #
269
+ # Tries to do that in 2 ways: (a) by primitive regexp parsing of
270
+ # Makefile and if that fails (b) by executing external 'make'
271
+ # command.
272
+ def self.ver(ports_dir, origin, rlevel = 0)
273
+ fail "recursion level for #{origin} is too high" if rlevel >= 3
274
+
275
+ makefile = ports_dir + '/' + origin + '/' + 'Makefile'
276
+ ver = {}
277
+
278
+ begin
279
+ File.open(makefile) {|f|
280
+ f.each {|line|
281
+ ['MASTERDIR', 'DISTVERSION', 'PORTVERSION', 'PORTREVISION', 'PORTEPOCH'].each {|idx|
282
+ ver[idx] = $1 if line.match(/^\s*#{idx}\s*[?:!]?=\s*(\S+)$/)
283
+ }
284
+ }
285
+ }
286
+ rescue
287
+ fail "(rlevel=#{rlevel}) #{$!}"
288
+ end
289
+
290
+ # Recursion! Some ports Makefiles don't contain version definitions
291
+ # but a link to a 'master' port.
292
+ if ver['MASTERDIR']
293
+ rlevel += 1
294
+ master_origin = ver['MASTERDIR'].sub(/\${.CURDIR}/, origin)
295
+ return ver(ports_dir, master_origin, rlevel)
296
+ end
297
+
298
+
299
+ # check if vars contain sane values
300
+ ok = true
301
+ ver.each {|k,v|
302
+ if v !~ /^[a-zA-Z0-9_,.-]+$/
303
+ ok = false
304
+ # puts makefile
305
+ break
306
+ end
307
+ }
308
+ ver['PORTVERSION'] = ver['DISTVERSION'] = nil if !ok
309
+
310
+ if ver['PORTVERSION'] || ver['DISTVERSION']
311
+ r = ver['PORTVERSION'] || ver['DISTVERSION']
312
+ r += "_#{ver['PORTREVISION']}" if ver['PORTREVISION']
313
+ r += ",#{ver['PORTEPOCH']}" if ver['PORTEPOCH']
314
+ return r
315
+ end
316
+
317
+ # try a dumb method
318
+ return FbsdPort.ver_slow(ports_dir, origin) rescue fail "(rlevel=#{rlevel}) #{$!}"
319
+
320
+ fail "(rlevel=#{rlevel}) cannot extract the version for #{makefile}"
321
+ end
322
+
323
+ # A placeholder for moved or removed single port.
324
+ #
325
+ # :movedto may be an empty string (indicates that the port was deleted)
326
+ Moved = Struct.new :movedto, :date, :why
327
+
328
+ # Parse /usr/ports/MOVED file into a hash.
329
+ # Return an empty hash on error.
330
+ def self.moved(ports_dir)
331
+ db_moved = ports_dir + '/MOVED'
332
+ r = {}
333
+ begin
334
+ File.open(db_moved) {|f|
335
+ f.each {|line|
336
+ next if line =~ /^\s*(#.*|\s*)$/ # comment or blank line
337
+ next if (entry = line.split '|').size != 4
338
+ r[entry.first] = Moved.new *entry[1..-1]
339
+ }
340
+ }
341
+ rescue
342
+ Trestle.warnx "parsing #{db_moved} failed: #{$!}"
343
+ return {}
344
+ end
345
+
346
+ r
347
+ end
348
+
349
+ end
350
+ end
@@ -0,0 +1,18 @@
1
+ require 'dl/import'
2
+
3
+ module Pkg_noisrev
4
+ module FbsdPackageVersion
5
+ extend DL::Importer
6
+
7
+ DLL_PATH = [File.dirname(__FILE__),
8
+ File.absolute_path("#{File.dirname(__FILE__)}/../../ext")]
9
+ DLL_NAME = 'version.so'
10
+
11
+ begin
12
+ dlload(DLL_PATH[0] + '/' + DLL_NAME)
13
+ rescue
14
+ dlload(DLL_PATH[1] + '/' + DLL_NAME)
15
+ end
16
+ extern 'int version_cmp(const char *, const char *)'
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Pkg_noisrev
2
+ module Meta
3
+ NAME = 'pkg_noisrev'
4
+ VERSION = '0.0.1'
5
+ AUTHOR = 'Alexander Gromnitsky'
6
+ EMAIL = 'alexander.gromnitsky@gmail.com'
7
+ HOMEPAGE = 'http://github.com/gromnitsky/' + NAME
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ module Pkg_noisrev
2
+
3
+ class Spectator
4
+ def initialize(thread_pool, total, sec, stream = $stdout)
5
+ @thread_pool = thread_pool
6
+ @total = total
7
+ @sec = sec
8
+
9
+ @stream = stream
10
+ @tty = stream.tty? ? true : false
11
+
12
+ @alarm = nil
13
+ end
14
+
15
+ def flush_line
16
+ @tty ? @stream.print("\r") : @stream.print("\n")
17
+ @stream.flush
18
+ end
19
+
20
+ def draw(thread_pool, total)
21
+ stat = ''
22
+ processed = 0
23
+ okays = 0
24
+ failures = 0
25
+
26
+ thread_pool.each {|i|
27
+ stat += " | %d: %d/%d" % [i.stat.nthread, i.stat.ok, i.stat.failed]
28
+ okays += i.stat.ok
29
+ failures += i.stat.failed
30
+ processed = okays + failures
31
+ }
32
+
33
+ # left%/processed/okays/failures
34
+ @stream.print '%d%% %d/%d/%d' % [((processed.to_f/total)*100).round(2), processed, okays, failures] + stat
35
+ flush_line
36
+ end
37
+
38
+ # Return a tread which will dump a statistics every @sec to a
39
+ # @stream about @thread_pool.
40
+ def alarm
41
+ @alarm = Thread.new(@thread_pool, @total, @sec) { |tp, total, s|
42
+ Thread.current.abort_on_exception = true
43
+ loop {
44
+ draw tp, total
45
+ break if alldone?
46
+ sleep s
47
+ }
48
+ }
49
+ end
50
+
51
+ def alarm_finish
52
+ if @alarm
53
+ @alarm.join
54
+ draw @thread_pool, @total
55
+ @stream.print "\n"
56
+ end
57
+ end
58
+
59
+ def done_right?
60
+ return false if (@thread_pool.inject(0) {|sum, i| sum+i.stat.ok }) == 0
61
+ true
62
+ end
63
+
64
+ def alldone?
65
+ # puts ""
66
+ # @thread_pool.each {|i| puts "#{i}=#{i.status}" }
67
+ @thread_pool.each {|i|
68
+ # thread status: false if terminated normally, nil if with exception
69
+ return false if (i.status != nil && i.status != false)
70
+ }
71
+ true
72
+ end
73
+ end
74
+
75
+ class MyThread < Thread
76
+ class Stat
77
+ attr_accessor :nthread, :ok, :failed
78
+
79
+ def initialize(n)
80
+ @nthread = n
81
+ @ok = 0
82
+ @failed = 0
83
+ end
84
+
85
+ def to_s
86
+ "%d (o/f): %d/%d" % [@nthread, @ok, @failed]
87
+ end
88
+ end
89
+
90
+ attr_accessor :stat
91
+
92
+ # n -- thread number
93
+ def initialize(*var, n)
94
+ @stat = Stat.new(n)
95
+ super(*var)
96
+ end
97
+ end
98
+
99
+ end