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_local
=> 'is_local', # if module is available locally
22 _blocking
=> '_blocking',
23 _missing
=> '_missing',
25 _pkgname
=> '_pkgname',
26 _prereqs
=> '_prereqs',
31 use File
::Basename
qw{ basename
};
32 use List
::MoreUtils
qw{ firstidx
};
34 use POE
::Filter
::Line
;
37 my $rpm_locked = ''; # only one rpm transaction at a time
41 # $ apt-file find Audio/MPD.pm
42 # libaudio-mpd-perl: /usr/share/perl5/Audio/MPD.pm
44 # - find dist hosting module
45 # - computing dependencies
46 # - installing dependencies
47 # - check cooker availability
50 # - check local availability
53 # - wait for kenobi build
58 my ($pkg, %params) = @_;
64 my $class = ref($pkg) || $pkg;
76 my ($k, $self) = @_[KERNEL
, OBJECT
];
77 $k->post('app', 'available_on_bs', $self);
81 my ($k, $self) = @_[KERNEL
, OBJECT
];
84 my $name = $self->name;
85 my $pkgname = $self->_pkgname;
86 my $cmd = "mdvsys submit $pkgname";
87 $self->_log_new_step('Submitting package upstream', "Running command: $cmd" );
92 my $wheel = POE
::Wheel
::Run
->new(
94 StdoutEvent
=> '_stdout',
95 StderrEvent
=> '_stderr',
96 StdoutFilter
=> POE
::Filter
::Line
->new,
97 StderrFilter
=> POE
::Filter
::Line
->new,
99 $k->sig( CHLD
=> '_build_upstream' );
101 # need to store the wheel, otherwise the process goes woo!
102 $self->_wheel($wheel);
106 my ($k, $self) = @_[KERNEL
, OBJECT
];
108 # we don't want to re-build the prereqs, even if we're not at their
109 # most recent version. and cpanplus --nobuildprereqs does not work
110 # as one thinks (it's "don't rebuild prereqs if we're at latest version,
111 # but rebuild anyway if we're not at latest version").
112 # and somehow, the ignore list with regex /(?<!$name)$/ does not work.
113 # so we're stuck with ignore modules one by one - sigh.
115 $ignore .= "--ignore '^$_\$' " foreach @
{ $self->_prereqs };
117 # preparing command. note that we do want --force, to be able to extract
118 # the rpm and srpm pathes from the output.
119 my $name = $self->name;
120 my $cmd = "cpan2dist $ignore --force --format=CPANPLUS::Dist::Mdv $name";
121 $self->_log_new_step('Building package', "Running command: $cmd" );
126 my $wheel = POE
::Wheel
::Run
->new(
128 CloseEvent
=> '_cpan2dist',
129 StdoutEvent
=> '_stdout',
130 StderrEvent
=> '_stderr',
131 StdoutFilter
=> POE
::Filter
::Line
->new,
132 StderrFilter
=> POE
::Filter
::Line
->new,
135 # need to store the wheel, otherwise the process goes woo!
136 $self->_wheel($wheel);
140 sub import_upstream
{
142 my ($k, $self) = @_[KERNEL
, OBJECT
];
145 my $name = $self->name;
146 my $srpm = $self->_srpm;
147 my $cmd = "mdvsys import $srpm";
148 $self->_log_new_step('Importing package upstream', "Running command: $cmd" );
153 my $wheel = POE
::Wheel
::Run
->new(
155 StdoutEvent
=> '_stdout',
156 StderrEvent
=> '_stderr',
157 StdoutFilter
=> POE
::Filter
::Line
->new,
158 StderrFilter
=> POE
::Filter
::Line
->new,
160 $k->sig( CHLD
=> '_import_upstream' );
162 # need to store the wheel, otherwise the process goes woo!
163 $self->_wheel($wheel);
167 my ($k, $self) = @_[KERNEL
, OBJECT
];
170 my $name = $self->name;
171 my $cmd = "cpanp /prereqs show $name";
172 $self->_log_new_step('Finding module prereqs', "Running command: $cmd" );
177 my $wheel = POE
::Wheel
::Run
->new(
179 CloseEvent
=> '_find_prereqs',
180 StdoutEvent
=> '_stdout',
181 StderrEvent
=> '_stderr',
182 StdoutFilter
=> POE
::Filter
::Line
->new,
183 StderrFilter
=> POE
::Filter
::Line
->new,
186 # need to store the wheel, otherwise the process goes woo!
187 $self->_wheel($wheel);
190 sub install_from_dist
{
191 my ($k, $self) = @_[KERNEL
, OBJECT
];
192 my $name = $self->name;
194 # check whether there's another rpm transaction
196 $self->_log_prefixed_lines("waiting for rpm lock... (owned by $rpm_locked)");
197 $k->delay( install_from_dist
=> 1 );
203 my $cmd = "sudo urpmi --auto 'perl($name)'";
204 $self->_log_new_step('Installing from upstream', "Running command: $cmd" );
209 my $wheel = POE
::Wheel
::Run
->new(
211 StdoutEvent
=> '_stdout',
212 StderrEvent
=> '_stderr',
213 Conduit
=> 'pty-pipe', # urpmi wants a pty
214 StdoutFilter
=> POE
::Filter
::Line
->new,
215 StderrFilter
=> POE
::Filter
::Line
->new,
217 $k->sig( CHLD
=> '_install_from_dist' );
219 # need to store the wheel, otherwise the process goes woo!
220 $self->_wheel($wheel);
223 sub install_from_local
{
224 my ($k, $self) = @_[KERNEL
, OBJECT
];
225 my $name = $self->name;
227 # check whether there's another rpm transaction
229 $self->_log_prefixed_lines("waiting for rpm lock... (owned by $rpm_locked)");
230 $k->delay( install_from_local
=> 1 );
236 my $rpm = $self->_rpm;
237 my $cmd = "sudo rpm -Uv $rpm";
238 $self->_log_new_step('Installing from local', "Running command: $cmd" );
243 my $wheel = POE
::Wheel
::Run
->new(
245 StdoutEvent
=> '_stdout',
246 StderrEvent
=> '_stderr',
247 StdoutFilter
=> POE
::Filter
::Line
->new,
248 StderrFilter
=> POE
::Filter
::Line
->new,
250 $k->sig( CHLD
=> '_install_from_local' );
252 # need to store the wheel, otherwise the process goes woo!
253 $self->_wheel($wheel);
257 my ($k, $self) = @_[KERNEL
, OBJECT
];
260 my $name = $self->name;
261 my $cmd = "urpmq --whatprovides 'perl($name)'";
262 $self->_log_new_step('Checking if packaged upstream', "Running command: $cmd" );
267 my $wheel = POE
::Wheel
::Run
->new(
269 #CloseEvent => '_is_in_dist', # FIXME: cf rt#42757
270 StdoutEvent
=> '_stdout',
271 StderrEvent
=> '_stderr',
272 Conduit
=> 'pty-pipe', # urpmq wants a pty
273 StdoutFilter
=> POE
::Filter
::Line
->new,
274 StderrFilter
=> POE
::Filter
::Line
->new,
276 $k->sig( CHLD
=> '_is_in_dist' );
278 # need to store the wheel, otherwise the process goes woo!
279 $self->_wheel($wheel);
284 my ($k, $self) = @_[KERNEL
, OBJECT
];
286 my $name = $self->name;
287 my $cmd = qq{ require $name };
288 $self->_log_new_step(
289 'Checking if module is installed',
290 "Evaluating command: $cmd"
294 my $what = $@
|| "$name loaded successfully\n";
295 $k->post('ui', 'append', $self, $what);
297 my $is_installed = $@
eq '';
298 my $status = $is_installed ?
'installed' : 'not installed';
299 $self->_log_result("$name is $status locally.");
300 $k->post('app', 'local_status', $self, $is_installed);
305 sub _build_upstream
{
306 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
308 # since it's a sigchld handler, it also gets called for other
309 # spawned processes. therefore, screen out processes that are
310 # not related to this object.
311 return unless defined $self->_wheel;
312 return unless $self->_wheel->PID == $pid;
315 $self->_wheel(undef);
317 # we don't have a real way to know when the build is finished,
318 # and when the package is available upstream. therefore, we're going
319 # to ask the user to signal when it's available...
320 my $name = $self->name;
321 $self->_log_result( "$name has been submitted upstream." );
322 my $question = "type 'enter' when package is available on build system upstream";
323 $k->post('ui', 'ask_user', $self, $question, 'available_on_bs');
328 my ($k, $self, $id) = @_[KERNEL
, OBJECT
, ARG0
];
329 my $name = $self->name;
332 my $wheel = $self->_wheel;
333 $self->_wheel(undef);
335 # check whether the package has been built correctly.
336 my $output = $self->_output;
338 $rpm = $1 if $output =~ /rpm created successfully: (.*\.rpm)/;
339 $srpm = $1 if $output =~ /srpm available: (.*\.src.rpm)/;
341 my ($status, @result);
342 if ( $rpm && $srpm ) {
345 "$name has been successfully built",
346 "srpm created: $srpm",
350 # storing path to interesting files
354 # storing package name
355 my $pkgname = basename
$srpm;
356 $pkgname =~ s/-\d.*$//;
357 $self->_pkgname( $pkgname );
361 @result = ( "error while building $name" );
364 # update main application
365 $self->_log_result(@result);
366 $k->post('app', 'cpan2dist_status', $self, $status);
369 sub _import_upstream
{
370 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
372 # since it's a sigchld handler, it also gets called for other
373 # spawned processes. therefore, screen out processes that are
374 # not related to this object.
375 return unless defined $self->_wheel;
376 return unless $self->_wheel->PID == $pid;
379 $self->_wheel(undef);
382 my $name = $self->name;
383 my $exval = $rv >> 8;
384 my $status = $exval ?
'not been' : 'been';
385 $self->_log_result( "$name has $status imported upstream." );
386 $k->post('app', 'upstream_import', $self, !$exval);
391 my ($k, $self, $id) = @_[KERNEL
, OBJECT
, ARG0
];
394 my $wheel = $self->_wheel;
395 $self->_wheel(undef);
398 my @lines = split /\n/, $self->_output;
399 my @tabbed = grep { s/^\s+// } @lines;
400 my $idx = firstidx
{ /^Module\s+Req Ver.*Satisfied/ } @tabbed;
401 my @wanted = @tabbed[ $idx+1 .. $#tabbed ];
402 my @prereqs = map { (split /\s+/, $_)[0] } @wanted;
405 $self->_prereqs( \
@prereqs );
407 ?
map { "prereq found: $_" } @prereqs
408 : 'No prereqs found.';
409 $self->_log_result(@logs);
410 $k->post('app', 'prereqs', $self, @prereqs);
413 sub _install_from_dist
{
414 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
416 # since it's a sigchld handler, it also gets called for other
417 # spawned processes. therefore, screen out processes that are
418 # not related to this object.
419 return unless defined $self->_wheel;
420 return unless $self->_wheel->PID == $pid;
423 $self->_wheel(undef);
429 my $name = $self->name;
430 my $exval = $rv >> 8;
431 my $status = $exval ?
'not been' : 'been';
432 $self->_log_result( "$name has $status installed from upstream." );
433 $k->post('app', 'upstream_install', $self, !$exval);
437 sub _install_from_local
{
438 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
440 # since it's a sigchld handler, it also gets called for other
441 # spawned processes. therefore, screen out processes that are
442 # not related to this object.
443 return unless defined $self->_wheel;
444 return unless $self->_wheel->PID == $pid;
447 $self->_wheel(undef);
453 my $name = $self->name;
454 my $rpm = $self->_rpm;
455 my $exval = $rv >> 8;
456 my $status = $exval ?
'not been' : 'been';
457 $self->_log_result( "$name has $status installed from $rpm." );
458 $k->post('app', 'local_install', $self, !$exval);
463 my($k, $self, $pid, $rv) = @_[KERNEL
, OBJECT
, ARG1
, ARG2
];
465 # since it's a sigchld handler, it also gets called for other
466 # spawned processes. therefore, screen out processes that are
467 # not related to this object.
468 return unless defined $self->_wheel;
469 return unless $self->_wheel->PID == $pid;
472 # FIXME: should be done in CloseEvent
473 $self->_wheel(undef);
475 # check if we got a hit
476 # urpmq returns 0 if found, 1 otherwise.
477 my $name = $self->name;
478 my $exval = $rv >> 8;
480 my $status = $exval ?
'not' : 'already';
481 $self->_log_result( "$name is $status packaged upstream." );
482 $k->post('app', 'upstream_status', $self, !$exval);
486 my ($k, $self, $line) = @_[KERNEL
, OBJECT
, ARG0
];
487 $k->post('ui', 'append', $self, "stderr: $line\n");
491 my ($k, $self, $line) = @_[KERNEL
, OBJECT
, ARG0
];
493 $self->_output( $self->_output . $line );
494 $k->post('ui', 'append', $self, "stdout: $line");
498 # -- poe inline states
501 my ($k, $self) = @_[KERNEL
, OBJECT
];
503 $k->alias_set($self);
504 $k->alias_set($self->name);
505 $k->post('ui', 'module_spawned', $self);
506 $k->post('app', 'module_spawned', $self);
516 my ($self, $name) = @_;
517 $self->_blocking->{$name} = 1;
521 my ($self, $name) = @_;
522 $self->_blocking({});
527 my $blocking = $self->_blocking;
528 return sort keys %$blocking;
534 my ($self, $name) = @_;
535 warn "$self / $name";
536 $self->_missing->{$name} = 1;
540 my ($self, $name) = @_;
541 delete $self->_missing->{$name};
546 my $missing = $self->_missing;
547 return sort keys %$missing;
552 sub _log_empty_line
{
553 my ($self, $nb) = @_;
554 $nb //= 1; #/ FIXME padre syntaxic color glitch
555 POE
::Kernel
->post('ui', 'append', $self, "\n" x
$nb);
558 sub _log_prefixed_lines
{
559 my ($self, @lines) = @_;
562 POE
::Kernel
->post('ui', 'append', $self, $_)
563 for map { "$prefix $_\n" } @lines;
567 my ($self, $step, $comment) = @_;
569 $self->_log_prefixed_lines('-' x
10, $step, '', $comment, '');
570 $self->_log_empty_line;
574 my ($self, @lines) = @_;
576 $self->_log_empty_line;
577 $self->_log_prefixed_lines( '', @lines, '', '' );
587 App::CPAN2Pkg::Module - poe session to drive a module packaging
593 C<App::CPAN2Pkg::Module> implements a POE session driving the whole
594 packaging process of a given module.
596 It is spawned by C<App::CPAN2Pkg> and implements the logic related to
597 the module availability in the distribution.
603 This package is also a class, used B<internally> to store private data
604 needed for the packaging stuff.
612 =item my $module = App::CPAN2Pkg::Module->new(name=>$name)
620 The following accessors are available:
624 =item is_local() - whether the module is installed locally
626 =item name() - the module name
632 =head2 Public methods
636 =item blocking_add( $module )
638 Add C<$module> to the list of modules that current object is blocking
639 from locally before trying to build
643 =item blocking_clear( $module )
645 Remove C<$module> from the list of modules missing locally. This means that
646 module has been built and installed by cpan2pkg.
649 =item blocking_list( )
651 Get the list of modules missing before trying to build the object.
654 =item missing_add( $module )
656 Add C<$module> to the list of modules missing locally before trying to build
660 =item missing_del( $module )
662 Remove C<$module> from the list of modules missing locally. This means that
663 module has been built and installed by cpan2pkg.
666 =item missing_list( )
668 Get the list of modules missing before trying to build the object.
676 =head2 Public events accepted
681 =item available_on_bs()
683 Sent when module is available on upstream build system.
686 =item build_upstream()
688 Submit package to be build on upstream build system.
693 Build a native package for this module, using C<cpan2dist> with the C<--force> flag.
698 Start looking for any other module needed by current module.
701 =item import_upstream()
703 Try to import module into upstream distribution.
706 =item install_from_dist()
708 Try to install module from upstream distribution.
711 =item install_from_local()
713 Try to install module from package freshly build.
718 Check whether the package is provided by an existing upstream package.
723 Check whether the package is installed locally.
732 For all related information (bug reporting, source code repository,
733 etc.), refer to C<App::CPAN2Pkg>'s pod, section C<SEE ALSO>.
739 Jerome Quelin, C<< <jquelin@cpan.org> >>
743 =head1 COPYRIGHT & LICENSE
745 Copyright (c) 2009 Jerome Quelin, all rights reserved.
747 This program is free software; you can redistribute it and/or modify
748 it under the same terms as Perl itself.