2 # This file is part of App::CPAN2Pkg.
3 # Copyright (c) 2009 Jerome Quelin, all rights reserved.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the same terms as Perl itself.
10 package App
::CPAN2Pkg
::Module
;
19 is_avail_on_bs
=> 'is_avail_on_bs',
20 is_local
=> 'is_local', # if module is available locally
23 _blocking
=> '_blocking',
24 _missing
=> '_missing',
26 _pkgname
=> '_pkgname',
27 _prereqs
=> '_prereqs',
32 use File
::Basename
qw{ basename
};
33 use List
::MoreUtils
qw{ firstidx
};
35 use POE
::Filter
::Line
;
38 my $rpm_locked = ''; # only one rpm transaction at a time
42 # $ apt-file find Audio/MPD.pm
43 # libaudio-mpd-perl: /usr/share/perl5/Audio/MPD.pm
45 # - find dist hosting module
46 # - computing dependencies
47 # - installing dependencies
48 # - check cooker availability
51 # - check local availability
54 # - wait for kenobi build
59 my ($pkg, %params) = @_;
65 my $class = ref($pkg) || $pkg;
77 my ($k, $self) = @_[KERNEL
, OBJECT
];
78 $self->is_avail_on_bs(1);
79 $k->post('app', 'available_on_bs', $self);
83 my ($k, $self) = @_[KERNEL
, OBJECT
];
86 my $name = $self->name;
87 my $pkgname = $self->_pkgname;
88 my $cmd = "mdvsys submit $pkgname";
89 $self->_log_new_step('Submitting package upstream', "Running command: $cmd" );
94 my $wheel = POE
::Wheel
::Run
->new(
96 StdoutEvent
=> '_stdout',
97 StderrEvent
=> '_stderr',
98 StdoutFilter
=> POE
::Filter
::Line
->new,
99 StderrFilter
=> POE
::Filter
::Line
->new,
101 $k->sig( CHLD
=> '_build_upstream' );
103 # need to store the wheel, otherwise the process goes woo!
104 $self->_wheel($wheel);
108 my ($k, $self) = @_[KERNEL
, OBJECT
];
110 # we don't want to re-build the prereqs, even if we're not at their
111 # most recent version. and cpanplus --nobuildprereqs does not work
112 # as one thinks (it's "don't rebuild prereqs if we're at latest version,
113 # but rebuild anyway if we're not at latest version").
114 # and somehow, the ignore list with regex /(?<!$name)$/ does not work.
115 # so we're stuck with ignore modules one by one - sigh.
117 $ignore .= "--ignore '^$_\$' " foreach @
{ $self->_prereqs };
119 # preparing command. note that we do want --force, to be able to extract
120 # the rpm and srpm pathes from the output.
121 my $name = $self->name;
122 my $cmd = "cpan2dist $ignore --force --format=CPANPLUS::Dist::Mdv $name";
123 $self->_log_new_step('Building package', "Running command: $cmd" );
128 my $wheel = POE
::Wheel
::Run
->new(
130 CloseEvent
=> '_cpan2dist',
131 StdoutEvent
=> '_stdout',
132 StderrEvent
=> '_stderr',
133 StdoutFilter
=> POE
::Filter
::Line
->new,
134 StderrFilter
=> POE
::Filter
::Line
->new,
137 # need to store the wheel, otherwise the process goes woo!
138 $self->_wheel($wheel);
142 sub import_upstream
{
144 my ($k, $self) = @_[KERNEL
, OBJECT
];
147 my $name = $self->name;
148 my $srpm = $self->_srpm;
149 my $cmd = "mdvsys import $srpm";
150 $self->_log_new_step('Importing package upstream', "Running command: $cmd" );
155 my $wheel = POE
::Wheel
::Run
->new(
157 StdoutEvent
=> '_stdout',
158 StderrEvent
=> '_stderr',
159 StdoutFilter
=> POE
::Filter
::Line
->new,
160 StderrFilter
=> POE
::Filter
::Line
->new,
162 $k->sig( CHLD
=> '_import_upstream' );
164 # need to store the wheel, otherwise the process goes woo!
165 $self->_wheel($wheel);
169 my ($k, $self) = @_[KERNEL
, OBJECT
];
172 my $name = $self->name;
173 my $cmd = "cpanp /prereqs show $name";
174 $self->_log_new_step('Finding module prereqs', "Running command: $cmd" );
179 my $wheel = POE
::Wheel
::Run
->new(
181 CloseEvent
=> '_find_prereqs',
182 StdoutEvent
=> '_stdout',
183 StderrEvent
=> '_stderr',
184 StdoutFilter
=> POE
::Filter
::Line
->new,
185 StderrFilter
=> POE
::Filter
::Line
->new,
188 # need to store the wheel, otherwise the process goes woo!
189 $self->_wheel($wheel);
192 sub install_from_dist
{
193 my ($k, $self) = @_[KERNEL
, OBJECT
];
194 my $name = $self->name;
196 # check whether there's another rpm transaction
198 $self->_log_prefixed_lines("waiting for rpm lock... (owned by $rpm_locked)");
199 $k->delay( install_from_dist
=> 1 );
205 my $cmd = "sudo urpmi --auto 'perl($name)'";
206 $self->_log_new_step('Installing from upstream', "Running command: $cmd" );
211 my $wheel = POE
::Wheel
::Run
->new(
213 StdoutEvent
=> '_stdout',
214 StderrEvent
=> '_stderr',
215 Conduit
=> 'pty-pipe', # urpmi wants a pty
216 StdoutFilter
=> POE
::Filter
::Line
->new,
217 StderrFilter
=> POE
::Filter
::Line
->new,
219 $k->sig( CHLD
=> '_install_from_dist' );
221 # need to store the wheel, otherwise the process goes woo!
222 $self->_wheel($wheel);
225 sub install_from_local
{
226 my ($k, $self) = @_[KERNEL
, OBJECT
];
227 my $name = $self->name;
229 # check whether there's another rpm transaction
231 $self->_log_prefixed_lines("waiting for rpm lock... (owned by $rpm_locked)");
232 $k->delay( install_from_local
=> 1 );
238 my $rpm = $self->_rpm;
239 my $cmd = "sudo rpm -Uv $rpm";
240 $self->_log_new_step('Installing from local', "Running command: $cmd" );
245 my $wheel = POE
::Wheel
::Run
->new(
247 StdoutEvent
=> '_stdout',
248 StderrEvent
=> '_stderr',
249 StdoutFilter
=> POE
::Filter
::Line
->new,
250 StderrFilter
=> POE
::Filter
::Line
->new,
252 $k->sig( CHLD
=> '_install_from_local' );
254 # need to store the wheel, otherwise the process goes woo!
255 $self->_wheel($wheel);
259 my ($k, $self) = @_[KERNEL
, OBJECT
];
262 my $name = $self->name;
263 my $cmd = "urpmq --whatprovides 'perl($name)'";
264 $self->_log_new_step('Checking if packaged upstream', "Running command: $cmd" );
269 my $wheel = POE
::Wheel
::Run
->new(
271 #CloseEvent => '_is_in_dist', # FIXME: cf rt#42757
272 StdoutEvent
=> '_stdout',
273 StderrEvent
=> '_stderr',
274 Conduit
=> 'pty-pipe', # urpmq wants a pty
275 StdoutFilter
=> POE
::Filter
::Line
->new,
276 StderrFilter
=> POE
::Filter
::Line
->new,
278 $k->sig( CHLD
=> '_is_in_dist' );
280 # need to store the wheel, otherwise the process goes woo!
281 $self->_wheel($wheel);
286 my ($k, $self) = @_[KERNEL
, OBJECT
];
288 my $name = $self->name;
289 my $cmd = qq{ require $name };
290 $self->_log_new_step(
291 'Checking if module is installed',
292 "Evaluating command: $cmd"
296 my $what = $@
|| "$name loaded successfully\n";
297 $k->post('ui', 'append', $self, $what);
299 my $is_installed = $@
eq '';
300 my $status = $is_installed ?
'installed' : 'not installed';
301 $self->_log_result("$name is $status locally.");
302 $k->post('app', 'local_status', $self, $is_installed);
307 sub _build_upstream
{
308 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
310 # since it's a sigchld handler, it also gets called for other
311 # spawned processes. therefore, screen out processes that are
312 # not related to this object.
313 return unless defined $self->_wheel;
314 return unless $self->_wheel->PID == $pid;
317 $self->_wheel(undef);
319 # we don't have a real way to know when the build is finished,
320 # and when the package is available upstream. therefore, we're going
321 # to ask the user to signal when it's available...
322 my $name = $self->name;
323 $self->_log_result( "$name has been submitted upstream." );
324 my $question = "type 'enter' when package is available on build system upstream";
325 $k->post('ui', 'ask_user', $self, $question, 'available_on_bs');
330 my ($k, $self, $id) = @_[KERNEL
, OBJECT
, ARG0
];
331 my $name = $self->name;
334 my $wheel = $self->_wheel;
335 $self->_wheel(undef);
337 # check whether the package has been built correctly.
338 my $output = $self->_output;
340 $rpm = $1 if $output =~ /rpm created successfully: (.*\.rpm)/;
341 $srpm = $1 if $output =~ /srpm available: (.*\.src.rpm)/;
343 my ($status, @result);
344 if ( $rpm && $srpm ) {
347 "$name has been successfully built",
348 "srpm created: $srpm",
352 # storing path to interesting files
356 # storing package name
357 my $pkgname = basename
$srpm;
358 $pkgname =~ s/-\d.*$//;
359 $self->_pkgname( $pkgname );
363 @result = ( "error while building $name" );
366 # update main application
367 $self->_log_result(@result);
368 $k->post('app', 'cpan2dist_status', $self, $status);
371 sub _import_upstream
{
372 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
374 # since it's a sigchld handler, it also gets called for other
375 # spawned processes. therefore, screen out processes that are
376 # not related to this object.
377 return unless defined $self->_wheel;
378 return unless $self->_wheel->PID == $pid;
381 $self->_wheel(undef);
384 my $name = $self->name;
385 my $exval = $rv >> 8;
386 my $status = $exval ?
'not been' : 'been';
387 $self->_log_result( "$name has $status imported upstream." );
388 $k->post('app', 'upstream_import', $self, !$exval);
393 my ($k, $self, $id) = @_[KERNEL
, OBJECT
, ARG0
];
396 my $wheel = $self->_wheel;
397 $self->_wheel(undef);
400 my @lines = split /\n/, $self->_output;
401 my @tabbed = grep { s/^\s+// } @lines;
402 my $idx = firstidx
{ /^Module\s+Req Ver.*Satisfied/ } @tabbed;
403 my @wanted = @tabbed[ $idx+1 .. $#tabbed ];
404 my @prereqs = map { (split /\s+/, $_)[0] } @wanted;
407 $self->_prereqs( \
@prereqs );
409 ?
map { "prereq found: $_" } @prereqs
410 : 'No prereqs found.';
411 $self->_log_result(@logs);
412 $k->post('app', 'prereqs', $self, @prereqs);
415 sub _install_from_dist
{
416 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
418 # since it's a sigchld handler, it also gets called for other
419 # spawned processes. therefore, screen out processes that are
420 # not related to this object.
421 return unless defined $self->_wheel;
422 return unless $self->_wheel->PID == $pid;
425 $self->_wheel(undef);
431 my $name = $self->name;
432 my $exval = $rv >> 8;
433 my $status = $exval ?
'not been' : 'been';
434 $self->_log_result( "$name has $status installed from upstream." );
435 $k->post('app', 'upstream_install', $self, !$exval);
439 sub _install_from_local
{
440 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
442 # since it's a sigchld handler, it also gets called for other
443 # spawned processes. therefore, screen out processes that are
444 # not related to this object.
445 return unless defined $self->_wheel;
446 return unless $self->_wheel->PID == $pid;
449 $self->_wheel(undef);
455 my $name = $self->name;
456 my $rpm = $self->_rpm;
457 my $exval = $rv >> 8;
458 my $status = $exval ?
'not been' : 'been';
459 $self->_log_result( "$name has $status installed from $rpm." );
460 $k->post('app', 'local_install', $self, !$exval);
465 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
467 # since it's a sigchld handler, it also gets called for other
468 # spawned processes. therefore, screen out processes that are
469 # not related to this object.
470 return unless defined $self->_wheel;
471 return unless $self->_wheel->PID == $pid;
474 # FIXME: should be done in CloseEvent
475 $self->_wheel(undef);
477 # check if we got a hit
478 # urpmq returns 0 if found, 1 otherwise.
479 my $name = $self->name;
480 my $exval = $rv >> 8;
482 $self->is_avail_on_bs( !$exval );
483 my $status = $exval ?
'not' : 'already';
484 $self->_log_result( "$name is $status packaged upstream." );
485 $k->post('app', 'upstream_status', $self, !$exval);
489 my ($k, $self, $line) = @_[KERNEL
, OBJECT
, ARG0
];
490 $k->post('ui', 'append', $self, "stderr: $line\n");
494 my ($k, $self, $line) = @_[KERNEL
, OBJECT
, ARG0
];
496 $self->_output( $self->_output . $line );
497 $k->post('ui', 'append', $self, "stdout: $line");
501 # -- poe inline states
504 my ($k, $self) = @_[KERNEL
, OBJECT
];
506 $k->alias_set($self);
507 $k->alias_set($self->name);
508 $k->post('ui', 'module_spawned', $self);
509 $k->post('app', 'module_spawned', $self);
519 my ($self, $name) = @_;
520 $self->_blocking->{$name} = 1;
524 my ($self, $name) = @_;
525 $self->_blocking({});
530 my $blocking = $self->_blocking;
531 return sort keys %$blocking;
537 my ($self, $name) = @_;
538 warn "$self / $name";
539 $self->_missing->{$name} = 1;
543 my ($self, $name) = @_;
544 delete $self->_missing->{$name};
549 my $missing = $self->_missing;
550 return sort keys %$missing;
555 sub _log_empty_line
{
556 my ($self, $nb) = @_;
557 $nb //= 1; #/ FIXME padre syntaxic color glitch
558 POE
::Kernel
->post('ui', 'append', $self, "\n" x
$nb);
561 sub _log_prefixed_lines
{
562 my ($self, @lines) = @_;
565 POE
::Kernel
->post('ui', 'append', $self, $_)
566 for map { "$prefix $_\n" } @lines;
570 my ($self, $step, $comment) = @_;
572 $self->_log_prefixed_lines('-' x
10, $step, '', $comment, '');
573 $self->_log_empty_line;
577 my ($self, @lines) = @_;
579 $self->_log_empty_line;
580 $self->_log_prefixed_lines( '', @lines, '', '' );
590 App::CPAN2Pkg::Module - poe session to drive a module packaging
596 C<App::CPAN2Pkg::Module> implements a POE session driving the whole
597 packaging process of a given module.
599 It is spawned by C<App::CPAN2Pkg> and implements the logic related to
600 the module availability in the distribution.
606 This package is also a class, used B<internally> to store private data
607 needed for the packaging stuff.
615 =item my $module = App::CPAN2Pkg::Module->new(name=>$name)
623 The following accessors are available:
627 =item is_avail_on_bs() - whether the module is available on build system
629 =item is_local() - whether the module is installed locally
631 =item name() - the module name
637 =head2 Public methods
641 =item blocking_add( $module )
643 Add C<$module> to the list of modules that current object is blocking
644 from locally before trying to build
648 =item blocking_clear( $module )
650 Remove C<$module> from the list of modules missing locally. This means that
651 module has been built and installed by cpan2pkg.
654 =item blocking_list( )
656 Get the list of modules missing before trying to build the object.
659 =item missing_add( $module )
661 Add C<$module> to the list of modules missing locally before trying to build
665 =item missing_del( $module )
667 Remove C<$module> from the list of modules missing locally. This means that
668 module has been built and installed by cpan2pkg.
671 =item missing_list( )
673 Get the list of modules missing before trying to build the object.
681 =head2 Public events accepted
686 =item available_on_bs()
688 Sent when module is available on upstream build system.
691 =item build_upstream()
693 Submit package to be build on upstream build system.
698 Build a native package for this module, using C<cpan2dist> with the C<--force> flag.
703 Start looking for any other module needed by current module.
706 =item import_upstream()
708 Try to import module into upstream distribution.
711 =item install_from_dist()
713 Try to install module from upstream distribution.
716 =item install_from_local()
718 Try to install module from package freshly build.
723 Check whether the package is provided by an existing upstream package.
728 Check whether the package is installed locally.
737 For all related information (bug reporting, source code repository,
738 etc.), refer to C<App::CPAN2Pkg>'s pod, section C<SEE ALSO>.
744 Jerome Quelin, C<< <jquelin@cpan.org> >>
748 =head1 COPYRIGHT & LICENSE
750 Copyright (c) 2009 Jerome Quelin, all rights reserved.
752 This program is free software; you can redistribute it and/or modify
753 it under the same terms as Perl itself.