Blob Blame Raw
use strict;
use warnings;

# Split "A B >= 1" dependencies string into ("A", "B >= 1") list.
sub appendsymbols {
    my ($array, $line) = @_;
    my $qualified;
    my $dependency;
    for my $token (split(/\s/, $line)) {
        if ($token =~ /\A[<>]?=\z/) {
            $qualified = 1;
            $dependency .= ' ' . $token;
        if (!$qualified) {
            if (defined $dependency) {
                push @$array, $dependency;
            $dependency = $token;
        if ($qualified) {
            $qualified = 0;
            $dependency .= ' ' . $token;
            push @$array, $dependency;
            $dependency = undef;
    if (defined $dependency) {
        push @$array, $dependency;

# Return true if the argument is a Perl dependency. Otherwise return false.
sub is_perl_dependency {
    my $dependency = shift;
    return ($dependency =~ /\Aperl\(/);

my $file = shift @ARGV;
if (!defined $file) {
	die "Missing an argument with an RPM build log!\n"

# Parse build log
open(my $log, '<', $file) or die "Could not open `$file': $!\n";
my ($package, %packages);
while (!eof($log)) {
    defined($_ = <$log>) or die "Error while reading from `$file': $!\n";

	if (/\AProcessing files: ([\S]+)-[^-]+-[^-]+$/) {
		$package = $1;
        $packages{$package}{requires} = [];
        $packages{$package}{provides} = [];
	} elsif ($package && /\AProvides: (.*)\z/) {
		appendsymbols($packages{$package}{provides}, $1);
	} elsif ($package && /\ARequires: (.*)\z/) {
		appendsymbols($packages{$package}{requires}, $1);

# Save dependencies into file
my $filename = 'gendep.macros';
open (my $gendep, '>', $filename) or
    die "Could not open `$filename' for writing: $!\n";
for my $package (sort keys %packages) {
    # Macro name
    my $macro = 'gendep_' . $package;
    $macro =~ s/[+-]/_/g;
    $gendep->print("%global $macro \\\n");
    # Macro value
    for my $dependency (@{$packages{$package}{requires}}) {
        if (is_perl_dependency($dependency)) {
            $gendep->print("Requires: $dependency \\\n");
    for my $dependency (@{$packages{$package}{provides}}) {
        if (is_perl_dependency($dependency)) {
            $gendep->print("Provides: $dependency \\\n");
    # Macro trailer
close($gendep) or die "Could not close `$filename': $!\n";

=encoding utf8

=head1 NAME

generatedependencies - Distil generated Perl dependencies from a build log


B<generatedependencies> I<BUILD_LOG>


It opens specified RPM build log I<BUILD_LOG>. It locates a protocol about
autogenerated dependencies. It stores the reported dependencies into F<./gendep.macros> file.

The output file will define macros C<gendep_I<BINARY_PACKAGE_NAME>>. A macro
for each binary package. The macro name will use underscores instead of
hyphens or other SPEC language special characters.

It will ignore non-Perl dependencies (not C<perl(*)>) as they do not come from
Perl dependency generator.

=head1 EXIT CODE

Returns zero, if no error occurred. Otherwise non-zero code is returned.

=head1 EXAMPLE

The invocation is:

    $ generatedependencies .build-5.24.0-364.fc25.log

The output is:

    $ grep -A5 perl_Devel_Peek gendep.macros
    %global gendep_perl_Devel_Peek \
    Requires: perl(Exporter) \
    Requires: perl(XSLoader) \
    Provides: perl(Devel::Peek) = 1.23 \
    %global gendep_perl_Devel_SelfStubber \

The output can be used in a spec file like:

    Name: perl
    Source0: gendep.macros
    %include %{SOURCE0}
    %package Devel-Peek
    %package Devel-SelfStubber

=head1 COPYING

Copyright (C) 2016  Petr Písař <>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <>.