pkg_noisrev 0.0.1

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/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