pkg_noisrev 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/README.rdoc +102 -0
- data/Rakefile +58 -0
- data/bin/pkg_noisrev +70 -0
- data/doc/LICENSE +22 -0
- data/doc/NEWS.rdoc +5 -0
- data/doc/README.rdoc +102 -0
- data/doc/TODO +6 -0
- data/etc/pkg_noisrev.yaml +2 -0
- data/ext/extconf.rb +6 -0
- data/ext/rakefile.rb +24 -0
- data/ext/version.c +286 -0
- data/ext/version.h +13 -0
- data/lib/pkg_noisrev/fbsdpackage.rb +350 -0
- data/lib/pkg_noisrev/fbsdpackageversion.rb +18 -0
- data/lib/pkg_noisrev/meta.rb +9 -0
- data/lib/pkg_noisrev/threads.rb +99 -0
- data/lib/pkg_noisrev/trestle.rb +222 -0
- data/test/helper.rb +10 -0
- data/test/helper_trestle.rb +34 -0
- data/test/rake_git.rb +36 -0
- data/test/semis/package/invalid-1.0/+CONTENTS +0 -0
- data/test/semis/package/xmbdfed-4.7.1_2/+COMMENT +1 -0
- data/test/semis/package/xmbdfed-4.7.1_2/+CONTENTS +47 -0
- data/test/semis/package/zip-3.0/+COMMENT +1 -0
- data/test/semis/package/zip-3.0/+CONTENTS +35 -0
- data/test/semis/package/zip-3.0/+REQUIRED_BY +1 -0
- data/test/semis/ports/MOVED +23 -0
- data/test/semis/ports/archivers/zip/Makefile +35 -0
- data/test/semis/ports/x11-servers/xorg-server/Makefile +137 -0
- data/test/test_fbsdpackages.rb +70 -0
- data/test/test_fbsdports.rb +42 -0
- metadata +111 -0
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,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
|