#!/usr/bin/perl -w

use strict;
use warnings;

use Carp;
use RPM::Header;
use Getopt::Long;
use Pod::Usage;
use File::Basename;
use File::Temp qw/tempfile tempdir/;


my ($help);
my %relations_to_drop;
my $format='name';
my $cycleformat='file';
my $cycleprefix='cycle';
my $verbose=1;
my $debug=0;
my $ignore_extra_rpms=0;
my $ignore_extra_srpms=1;
my $use_file_provides=1;
my $use_build_requires=1;
my $set_buildfrom_relation=1;
my $opt_buildfrom_if_srpm_marked=1;
my $opt_buildfrom_if_rpm_marked=0;
my $opt_ignore_debuginfo=1;
my $opt_ignore_core=1;
my ($opt_whobuildreq,$opt_mark_all_rpm_of_srpm,$opt_mark_all_srpm_of_rpm);
my ($opt_cycle_break_through_unmarked,$opt_ignore_ambiguous_provides);
my ($transaction_srpm_files,$transaction_srpm_names,
    $transaction_rpm_files,$transaction_rpm_names,
    $opt_add_textid_relation_file,$opt_del_textid_relation_file,
    $opt_add_filename_relation_file,$opt_del_filename_relation_file,
    $opt_ignore_missing_srpm_names,
#    @include_buildreq_regexp,@include_req_regexp,
    $mark_req_regexp,$outputfile,
    $opt_buildreq_if_srpm_marked,
);

my %CORE_SRPMNAMES=map {$_=>1} qw/
basesystem-sisyphus
bash
binutils
bzip2
common-licenses
filesystem
file
findutils
gawk
glibc
glibc-kernheaders
grep
gzip
util-linux
/;
my $srpms_to_ignore;

GetOptions (
    "debug+"  => \$debug,
    "help"  => \$help,
    "cycle-format=s" => \$cycleformat,
    "cycle-prefix=s" => \$cycleprefix,
    "cycle-break-through-unmarked!" => \$opt_cycle_break_through_unmarked,
    "format=s" => \$format,
    "ignore-extra-rpms!" => \$ignore_extra_rpms,
    "ignore-extra-srpms!" => \$ignore_extra_srpms,
    "ignore-debuginfo!" => \$opt_ignore_debuginfo,
    "ignore-ambiguous-provides!" => \$opt_ignore_ambiguous_provides,
    "ignore-core!" => \$opt_ignore_core,
    "ignore-missing-srpm-names!" => \$opt_ignore_missing_srpm_names,
# TODO: filter input
#    "include-buildreq-regexp=s" => \@include_buildreq_regexp,
#    "include-req-regexp=s" => \@include_req_regexp,
    "mark-req-regexp=s" => \$mark_req_regexp,
    "mark-all-rpm-of-srpm" => \$opt_mark_all_rpm_of_srpm,
    "mark-all-srpm-of-rpm" => \$opt_mark_all_srpm_of_rpm,
    "buildfrom-none" => sub {$set_buildfrom_relation=0},
    "buildfrom-srpm-marked!" => \$opt_buildfrom_if_srpm_marked,
    "buildfrom-rpm-marked!" => \$opt_buildfrom_if_rpm_marked,
    "buildreq-srpm-marked!" => \$opt_buildreq_if_srpm_marked,
    "rpm-files-transaction=s" => \$transaction_rpm_files,
    "rpm-names-transaction=s" => \$transaction_rpm_names,
    "srpm-files-transaction=s" => \$transaction_srpm_files,
    "srpm-names-transaction=s" => \$transaction_srpm_names,
    "add-textid-relations-from=s" => \$opt_add_textid_relation_file,
    "del-textid-relations-from=s" => \$opt_del_textid_relation_file,
    "add-filename-relations-from=s" => \$opt_add_filename_relation_file,
    "del-filename-relations-from=s" => \$opt_del_filename_relation_file,
    "use-file-provides!" => \$use_file_provides,
    "use-build-requires!" => \$use_build_requires,
    "verbose+"  => \$verbose,
    "whobuildreq"  => \$opt_whobuildreq,
    "output=s" => \$outputfile,
    'q|quiet'   => sub { $verbose = 0 },
    );
if ($help or not @ARGV) {
    #exec "pod2usage --exit=0 $0";
    pod2usage({ #-message => "the options below are package-specific:" ,
	-exitval => 0  ,
	-verbose => $verbose,
	#-output  => $filehandle
	      } );
}

$set_buildfrom_relation=0 if $opt_whobuildreq;

die "Context is non-empty (good) but transaction is empty (bad).
specify the transaction with one of the options
    --mark-req-regexp
    --rpm-files-transaction
    --rpm-names-transaction
    --srpm-files-transaction
    --srpm-names-transaction

for example,
--mark-req-regexp='^libglib-2\\.0\\.so\\.0' will mark packages that require libglib-2.0.so.0.
--mark-req-regexp=. will mark ALL packages in the context.
" unless $transaction_srpm_files or $transaction_srpm_names
  or $transaction_rpm_files or $transaction_rpm_names or $mark_req_regexp;

my @found_cycle_files=glob($cycleprefix.'[0-9][0-9][0-9]*');
die "Found cycle files from previous run: ",
    join(' ',@found_cycle_files),"\nRemove and try again.\n" if @found_cycle_files;

my (@srpms, @rpms);
sub _add_rpm_file {
    my ($arg)=@_;
    if ($arg=~/.*\.src\.rpm$/) {
	push @srpms, $arg;
    } elsif ($arg=~/.*\.rpm$/) {
	push @rpms, $arg;
    } else {
	die "Invalid argument - not a rpm file: $arg";
    }
}

foreach my $rpmarg (@ARGV) {
    if ( -d "$rpmarg") {
	open (RPMARGS_FILES, "ls -1 $rpmarg |grep -E -x '[^.].*[.]rpm' |") || die $!;
	my $f;
	while ($f=<RPMARGS_FILES>) {
	    chomp $f;
	    &_add_rpm_file("$rpmarg/$f") if -e "$rpmarg/$f";
	}
	close RPMARGS_FILES;
    } else {
	&_add_rpm_file($rpmarg);
    }
}

my %pkg_by_srpm;
my %pkg_by_rpm;
my $srpm_by_input=[];

my ($i,$j);
for ($i=0;$i<@srpms;$i++) {
    my $rhref = new RPM::Header $srpms[$i];
    my $pkg={
	NAME => $rhref->{NAME},
	TEXTID => $rhref->{NAME},
	PATH => $srpms[$i],
	FILE => basename($srpms[$i]),
	SRPMHDR => $rhref,
	RPMS => [],
    };
    if ($pkg_by_srpm{$pkg->{FILE}}) {
	die "$pkg->{FILE} is already listed.";
    }
    $pkg_by_srpm{$pkg->{FILE}}=$pkg;
    $srpm_by_input->[$i]=$pkg;
}
foreach my $rpmfile (@rpms) {
    my $rhref = new RPM::Header $rpmfile;
    my $rpm_name=$rhref->{NAME};
    next if $opt_ignore_debuginfo and $rpm_name =~/-debuginfo$/;
    my $srpmfile=$rhref->{SOURCERPM};
    unless ($pkg_by_srpm{$srpmfile}) {
	next if $ignore_extra_rpms;
	die "source rpm is not specified for rpm $rpmfile\n";
    }
    my $rpm = {
	NAME => $rpm_name,
	TEXTID => $rpm_name.'.'.$rhref->{ARCH},
	PATH => $rpmfile,
	FILE => basename($rpmfile),
	RPMHDR => $rhref,
    };
    if ($pkg_by_rpm{$rpm->{FILE}}) {
	die "$rpm->{FILE} is already listed.";
    }
    $pkg_by_rpm{$rpm->{FILE}}=$rpm;
    push @{$pkg_by_srpm{$srpmfile}->{RPMS}}, $rpm;
}

if ($opt_ignore_core and not $srpms_to_ignore) {
    $srpms_to_ignore=\%CORE_SRPMNAMES;
}

if ($srpms_to_ignore) {
    my $ii=0;
    while ($ii<= $#{$srpm_by_input}) {
	if ($srpms_to_ignore->{$srpm_by_input->[$ii]->{NAME}}) {
	    $srpm_by_input->[$ii]=$srpm_by_input->[$#{$srpm_by_input}];# (no need) if $ii != $#{$srpm_by_input};
	    pop @$srpm_by_input;
	}
	$ii++;
    }
}

my $idcounter=0;
my $pkg_by_id=[];
my %id_is_source;

foreach my $srpmentry (@$srpm_by_input) {
    $id_is_source{$idcounter}=1;
    $srpmentry->{ID}=$idcounter++;
    push @$pkg_by_id, $srpmentry;
    foreach my $rpmentry (@{$srpmentry->{RPMS}}) {
	$rpmentry->{ID}=$idcounter++;
	push @$pkg_by_id, $rpmentry;
    }
}

if ($opt_del_textid_relation_file) {
    open (my $dr1, '<', $opt_del_textid_relation_file) || die "$!: can't open $opt_del_textid_relation_file";
    while (<$dr1>) {
	next if /^\s*$/ or /^\s*#/;
	my ($textid1,$textid2)=split(/\s+/,$_);
	die "$opt_del_textid_relation_file: bad line: $_" unless $textid1 and $textid2;
	$relations_to_drop{&__find_id_by_textid($textid1).':'.&__find_id_by_textid($textid2)}=1;
    }
    close($dr1);
}
if ($opt_del_filename_relation_file) {
    open (my $dr1, '<', $opt_del_filename_relation_file) || die "$!: can't open $opt_del_filename_relation_file";
    while (<$dr1>) {
	next if /^\s*$/ or /^\s*#/;
	my ($filename1,$filename2)=split(/\s+/,$_);
	die "$opt_del_filename_relation_file: bad line: $_" unless $filename1 and $filename2;
	my ($id1,$textid1)=&__find_ids_by_filename($filename1);
	my ($id2,$textid2)=&__find_ids_by_filename($filename2);
	$relations_to_drop{$id1.':'.$id2}=1;
    }
    close($dr1);
}

my $tmpdir;
unless ($debug) {
    $tmpdir = tempdir( CLEANUP => 1 );
} else {
    $tmpdir = '.';
}

my $infile = $tmpdir.'/infile';
my $srpmmarkfile = $tmpdir.'/srpmmarkfile';
my $rpmfile = $tmpdir.'/rpmlistfile';
my $rpmmarkfile = $tmpdir.'/rpmmarkfile';
my $namemapfile = $tmpdir.'/namemapfile';
my $filemapfile = $tmpdir.'/filemapfile';
my $pathmapfile = $tmpdir.'/filemapfile';

my @srpm_mark_flag;
my @rpm_mark_flag;
if ($transaction_srpm_files) {
    foreach my $tsrpm (@{&load_file($transaction_srpm_files)}) {
	my $tsfile=basename($tsrpm);
	my $pkg=$pkg_by_srpm{$tsfile};
	die "srpm files input error: can't find src.rpm $tsfile\n" unless $pkg;
	$srpm_mark_flag[$pkg->{ID}]=1;
    }
} elsif ($transaction_srpm_names) {
    my %srpm_by_name;
    foreach my $srpmpkg (@$srpm_by_input) {
	$srpm_by_name{$srpmpkg->{NAME}}=$srpmpkg;
    }
    foreach my $tsname (@{&load_file($transaction_srpm_names)}) {
	my $pkg=$srpm_by_name{$tsname};
	unless (defined $pkg) {
	    die "srpm names input error: can't find src.rpm for [$tsname]\n" if not $opt_ignore_missing_srpm_names;
	} else {
	    $srpm_mark_flag[$pkg->{ID}]=1;
	}
    }
}
if ($transaction_rpm_files) {
    foreach my $trpm (@{&load_file($transaction_rpm_files)}) {
	my $tfile=basename($trpm);
	my $pkg=$pkg_by_rpm{$tfile};
	die "rpm files input error: can't find rpm file $tfile\n" unless $pkg;
	$rpm_mark_flag[$pkg->{ID}]=1;
    }
} elsif ($transaction_rpm_names) {
    my %rpm_by_name;
    foreach my $rpmpkg (@$pkg_by_id) {
	$rpm_by_name{$rpmpkg->{NAME}}=$rpmpkg if !$id_is_source{$rpmpkg->{ID}};
    }
    foreach my $tname (@{&load_file($transaction_rpm_names)}) {
	my $pkg=$rpm_by_name{$tname};
	die "rpm names input error: can't find rpm name $tname\n" unless $pkg;
	$rpm_mark_flag[$pkg->{ID}]=1;
    }
}
if ($mark_req_regexp) {
    foreach my $srpmpkg (@$srpm_by_input) {
	my $srpmid=$srpmpkg->{ID};
	foreach my $rpm (@{$srpmpkg->{RPMS}}) {
	    foreach my $reqname (@{$rpm->{RPMHDR}->{REQUIRENAME}}) {
		if ($reqname=~/$mark_req_regexp/o) {
		    print STDERR "marked $srpmpkg->{TEXTID} due to $rpm->{TEXTID}\n" if $verbose>1;
		    $rpm_mark_flag[$rpm->{ID}]=1;
		    $srpm_mark_flag[$srpmid]=1;
		    last;
		}
	    }
	}
    }
}

# lazy markers by rpm
if ($opt_mark_all_srpm_of_rpm) {
    foreach my $srpmpkg (@$srpm_by_input) {
	my $srpmid=$srpmpkg->{ID};
	foreach my $rpm (@{$srpmpkg->{RPMS}}) {
	    if ($rpm_mark_flag[$rpm->{ID}]) {
		$srpm_mark_flag[$srpmid]=1;
		last;
	    }
	}
    }
}

# lazy markers by srpm
if ($opt_mark_all_rpm_of_srpm) {
    foreach my $srpmpkg (@$srpm_by_input) {
	my $srpmid=$srpmpkg->{ID};
	next if not $srpm_mark_flag[$srpmid];
	foreach my $rpm (@{$srpmpkg->{RPMS}}) {
	    $rpm_mark_flag[$rpm->{ID}]=1;
	}
    }
}

if ($opt_whobuildreq
    and ($transaction_srpm_names or $transaction_srpm_files)
    and not ($transaction_rpm_names or $transaction_rpm_files)) {
    for (my $srpmid=0; $srpmid<@srpm_mark_flag; $srpmid++) {
	next unless $srpm_mark_flag[$srpmid];
	my $srpmpkg=$pkg_by_id->[$srpmid];
	$srpm_mark_flag[$srpmid]=0;
	foreach my $rpm (@{$srpmpkg->{RPMS}}) {
	    $rpm_mark_flag[$rpm->{ID}]=1;
	}
    }
}


my $srpmdim=@$srpm_by_input;
my %global_filereq_cache;
my $global_provides_cache={};
my $global_fileprov_cache={};

for ($i=0;$i<$srpmdim;$i++) {
    &__add_providecache_srpm($srpm_by_input->[$i]);
}

my $fn;
if (@srpm_mark_flag) {
    open ($fn, '>', $srpmmarkfile) || die "$!: can't open $srpmmarkfile";
    for ($i=0;$i<@$pkg_by_id;$i++) {
	print $fn "$i\n" if $srpm_mark_flag[$i];
    }
    close($fn);
    if ($debug) {
	open (my $fn2, '>', $srpmmarkfile.'2') || die "$!: can't open ${srpmmarkfile}2";
	for ($i=0;$i<@$pkg_by_id;$i++) {
	    print $fn2 $pkg_by_id->[$i]->{TEXTID},"\n" if $srpm_mark_flag[$i];
	}
	close($fn2);
    }
}
if (@rpm_mark_flag) {
    open ($fn, '>', $rpmmarkfile) || die "$!: can't open $rpmmarkfile";
    for ($i=0;$i<@$pkg_by_id;$i++) {
	print $fn "$i\n" if $rpm_mark_flag[$i];
    }
    close($fn);
    if ($debug) {
	open (my $fn2, '>', $rpmmarkfile.'2') || die "$!: can't open ${rpmmarkfile}2";
	for ($i=0;$i<@$pkg_by_id;$i++) {
	    print $fn2 $pkg_by_id->[$i]->{TEXTID},"\n" if $rpm_mark_flag[$i];
	}
	close($fn2);
    }
}
if (@rpm_mark_flag) {
    open ($fn, '>', $rpmfile) || die "$!: can't open $rpmfile";
    for ($i=0;$i<@$pkg_by_id;$i++) {
	print $fn "$i\n" if (!$id_is_source{$i});
    }
    close($fn);
}

# relations:
# is builds < [natural order]
# is buildrequires < [natural order]
# is requires < [natural order]

# reversed order is order in which transaction is formed.
# is built from < [reversed order]
# is buildrequired < [reversed order]
# is required < [reversed order]
my $reversed_order=1;

open (my $in, '>', $infile) || die "$!: can't open $infile";
my ($in2,$amb2);
if ($debug) {
    open ($in2, '>', $infile.'2') || die "$!: can't open ${infile}2";
    open ($amb2, '>', 'ambiguous2') || die "$!: can't open ambiguous2";
}
for ($i=0;$i<$srpmdim;$i++) {
    my $isrpm=$srpm_by_input->[$i];
    my $iid=$isrpm->{ID};
    my $iidname=$isrpm->{TEXTID};
    my $irpms=$isrpm->{RPMS};
    my ($req);

    # print dependencies rpms -> srpm "is build from" rpms
    if ($set_buildfrom_relation) {
	if (!$opt_buildfrom_if_srpm_marked or $srpm_mark_flag[$iid]) {
	    foreach my $ichildrpm (@{$irpms}) {
		my $childid=$ichildrpm->{ID};
		my $ichildname=$ichildrpm->{TEXTID};
		&output_buildsfrom_to_infile($iid,$childid,$iidname,$ichildname)
		    if !$opt_buildfrom_if_rpm_marked or $rpm_mark_flag[$childid];
	    }
	}
    }

    if ($use_build_requires and
	(!$opt_buildreq_if_srpm_marked or $srpm_mark_flag[$iid])
	) {
	foreach $req (&buildreq($isrpm)) {
	    my $val=$global_provides_cache->{$req};
	    &output_val_to_infile_check_ambiguous($val,$iid,$iidname,$req,'is buildrequired by','buildrequires');
	}
    }

    foreach my $ichildrpm (@$irpms) {
	my $ichildid=$ichildrpm->{ID};
	my $ichildname=$ichildrpm->{TEXTID};
	foreach $req (&requires($ichildrpm)) {
	    my $val=$global_provides_cache->{$req};
	    &output_val_to_infile_check_ambiguous($val,$ichildid,$ichildname,$req,'is required by','requires');
	}
    }
}

if ($use_file_provides) {
    # find fileprovides for all files in global_filereq_cache
    for ($i=0;$i<$srpmdim;$i++) {
	&__add_fileprovidecache_srpm($srpm_by_input->[$i]);
    }
    # resolve file requires
    for ($i=0;$i<$srpmdim;$i++) {
	my $isrpm=$srpm_by_input->[$i];
	my $iid=$isrpm->{ID};
	my $iidname=$isrpm->{TEXTID};
	my $irpms=$isrpm->{RPMS};
	my ($req);
	if ($use_build_requires and
	    (!$opt_buildreq_if_srpm_marked or $srpm_mark_flag[$iid])
	    ) {
	    foreach $req (&buildreq($isrpm)) {
		my $val=$global_fileprov_cache->{$req};
		&output_val_to_infile_check_ambiguous($val,$iid,$iidname,$req,'is buildrequired by','buildrequires');
	    }
	}

	foreach my $ichildrpm (@$irpms) {
	    my $ichildid=$ichildrpm->{ID};
	    my $ichildname=$ichildrpm->{TEXTID};
	    foreach $req (@{$ichildrpm->{FILEREQCACHE}}) {
		my $val=$global_fileprov_cache->{$req};
		&output_val_to_infile_check_ambiguous($val,$ichildid,$ichildname,$req,'is required by','requires');
	    }
	}
    }
}

if ($opt_add_textid_relation_file) {
    open (my $ar1, '<', $opt_add_textid_relation_file) || die "$!: can't open $opt_add_textid_relation_file";
    while (<$ar1>) {
	next if /^\s*$/ or /^\s*#/;
	my ($textid1,$textid2)=split(/\s+/,$_);
	die "$opt_add_textid_relation_file: bad line: $_" unless $textid1 and $textid2;
	&output_to_infile(&__find_id_by_textid($textid1),
			  &__find_id_by_textid($textid2),
			  $textid1,'manually added',$textid2);
    }
    close($ar1);
}

if ($opt_add_filename_relation_file) {
    open (my $ar1, '<', $opt_add_filename_relation_file) || die "$!: can't open $opt_add_filename_relation_file";
    while (<$ar1>) {
	next if /^\s*$/ or /^\s*#/;
	my ($filename1,$filename2)=split(/\s+/,$_);
	die "$opt_add_filename_relation_file: bad line: $_" unless $filename1 and $filename2;
	my ($id1,$textid1)=&__find_ids_by_filename($filename1);
	my ($id2,$textid2)=&__find_ids_by_filename($filename2);
	&output_to_infile($id1,$id2,$textid1,'manually added',$textid2);
    }
    close($ar1);
}

close($in);
close($in2) if $in2;


sub output_buildsfrom_to_infile {
    my ($iid,$childid,$iidname,$ichildname)=@_;
    if ($reversed_order) {
	&output_to_infile($iid,$childid,$iidname,'builds',$ichildname);
    } else {
	&output_to_infile($childid,$iid,$ichildname,'is built from', $iidname);
    }
}

sub output_val_to_infile_check_ambiguous {
    my ($val,$iid,$iidname,$req,$rev_msg,$other_msg)=@_;
    return unless $val;
    if ($amb2 and @$val>1) {
	print $amb2 $iidname.' '.$other_msg.' ['.join('|',map{ $pkg_by_id->[$_]->{TEXTID} } @$val),"]\n";
    }
    unless ($opt_ignore_ambiguous_provides and @$val>1) {
	foreach my $slaveid (@$val) {
	    if ($reversed_order) {
		&output_to_infile($slaveid,$iid,$pkg_by_id->[$slaveid]->{TEXTID}."(${req})",$rev_msg, $iidname);
	    } else {
		&output_to_infile($iid,$slaveid,$iidname,$other_msg,$pkg_by_id->[$slaveid]->{TEXTID}."(${req})");
	    }
	}
    }
}

sub output_to_infile {
    my ($id1,$id2,$textid1,$relation,$textid2)=@_;
    return if $relations_to_drop{"$id1:$id2"};
    print $in "$id1 $id2\n";
    print $in2 "$id1 $id2 $textid1 $relation $textid2\n" if $in2;
}

sub __find_id_by_textid {
    my ($textid,$inputfile)=@_;
    for(my $i=0; $i<@$pkg_by_id;$i++) {
	my $pkg=$pkg_by_id->[$i];
	return $pkg->{ID} if $pkg->{TEXTID} eq $textid;
    }
    die "id not found: $textid in file $inputfile";
}

sub __find_ids_by_filename {
    my ($filename,$inputfile)=@_;
    for(my $i=0; $i<@$pkg_by_id;$i++) {
	my $pkg=$pkg_by_id->[$i];
	return $pkg->{ID},$pkg->{TEXTID} if $pkg->{FILENAME} eq $filename;
    }
    die "filename not found: $filename in file $inputfile";
}


my ($fn1,$fn2,$fn3,$pkgi);
open ($fn1, '>', $namemapfile) || die "$!: can't open $namemapfile";
open ($fn2, '>', $filemapfile) || die "$!: can't open $filemapfile";
open ($fn3, '>', $pathmapfile) || die "$!: can't open $pathmapfile";
for ($i=0;$i<@$pkg_by_id;$i++) {
    $pkgi=$pkg_by_id->[$i];
    print $fn1 $pkgi->{TEXTID},"\n";
    print $fn2 $pkgi->{FILE},"\n";
    print $fn3 $pkgi->{PATH},"\n";
}
close($fn1);
close($fn2);
close($fn3);

if ($debug) {
    open ($fn1, '>', $namemapfile.'2') || die "$!: can't open ${namemapfile}2";
    open ($fn2, '>', $filemapfile.'2') || die "$!: can't open ${filemapfile}2";
    for ($i=0;$i<@$pkg_by_id;$i++) {
	$pkgi=$pkg_by_id->[$i];
	print $fn1 "$i $pkgi->{TEXTID}\n";
	print $fn2 "$i $pkgi->{FILE}\n";
    }
    close($fn1);
    close($fn2);
}

my @args=('girar-nmu-helper-pos-sort',
	  '-m',&get_format_file($format),
	  '-i',$infile
);

if ($opt_whobuildreq) {
    push @args, '-g';
} else {
    push @args, '-C',$cycleprefix;
    push @args, '-M',&get_format_file($cycleformat);
}
push @args, '-s', $srpmmarkfile if @srpm_mark_flag;
push @args, '-R', $rpmfile, '-r', $rpmmarkfile if @rpm_mark_flag;
push @args, '-o', $outputfile if $outputfile;
push @args, '-b' if $opt_cycle_break_through_unmarked;
push @args, scalar(@$pkg_by_id);

print STDERR join(' ',@args),"\n" if $verbose>1 or $debug;
die "debug mode: execute the command above manually.\n" if $debug;
system(@args)==0 or die "girar-nmu-helper-pos-sort failed";
unless ($debug) {
    unlink $infile,$namemapfile,$filemapfile,$pathmapfile;
    unlink $srpmmarkfile if @srpm_mark_flag;
    unlink $rpmfile, $rpmmarkfile if @rpm_mark_flag;
}

# destroy
$tmpdir=undef;

if ($verbose and not $opt_whobuildreq) {
    my @cycles=glob("$cycleprefix*");
    unless (@cycles) {
	warn "no cyclic dependencies detected.\n";
    } else {
	warn "cyclic dependencies detected:\n";
	foreach my $cyclefile (@cycles) {
	    warn basename($cyclefile).":\n";
	    &print_stderr($cyclefile);
	}
    }
}

sub get_format_file {
    my ($format)=@_;
    if ($format eq 'name') {
	return $namemapfile;
    } elsif ($format eq 'path') {
	return $pathmapfile;
    } elsif ($format eq 'file') {
	return $filemapfile;
    }
    warn "unknown format $format";
    return $filemapfile;
}

sub print_stderr {
    my ($file)=@_;
    open (my $out, '<', $file) || die "$!: can't open $file";
    local $/;
    print STDERR <$out>;
    close($out);
}

sub buildreq {
    my ($pkg)=@_;
    return @{$pkg->{SRPMHDR}->{REQUIRENAME}};
}

sub requires {
    my ($rpm)=@_;
    my @requirecache;
    my @filereq;
    my $provides=$rpm->{PROVIDECACHE};
    die "Oops! cache is lost" unless $rpm->{PROVIDECACHE};

    foreach my $req (@{$rpm->{RPMHDR}->{REQUIRENAME}}) {
	push @requirecache, $req unless $provides->{$req};
	if ('/' eq substr($req,0,1)) {
	    push @filereq, $req;
	    $global_filereq_cache{$req}=1;
	}
    }
    $rpm->{FILEREQCACHE}=\@filereq;
    return @requirecache;
}

sub load_file {
    my ($filename) = @_;
    Carp::confess 'Oops! internal error: filename not specified' unless defined $filename;
    my @out;
    open (my $fn, '<', $filename) || die "can't open $filename: $!";
    while (<$fn>) {
	chomp;
	s/\s*$//;
	s/^\s*//;
	push @out, $_ if /\S/;
    }
    return \@out;
}

sub __add_to_cache {
    my ($id, $name, $cache)=@_;
    my $val=$cache->{$name};
    if ($val) {
	# to avoid doubles
	push @$val,$id if not grep {$_ eq $id} @$val;
	#push @$val,$id;
    } else {
	$cache->{$name}=[$id];
    }
}

sub __add_providecache_srpm {
    my ($srpm)=@_;
    die "no binary rpms found for $srpm->{TEXTID}" unless @{$srpm->{RPMS}} or $ignore_extra_srpms;
    foreach my $rpm (@{$srpm->{RPMS}}) {
	my $id=$rpm->{ID};
	my %providecache;
	foreach my $providename (@{$rpm->{RPMHDR}->{PROVIDES}}) {
	    &__add_to_cache($id,$providename,$global_provides_cache);
	    $providecache{$providename}=1;
	}
	$rpm->{PROVIDECACHE}=\%providecache;
    }
}

sub __add_fileprovidecache_srpm {
    my ($srpm)=@_;
    # optimization: already dead
    #die "no binary rpms found for $srpm->{TEXTID}" unless @{$srpm->{RPMS}} or $ignore_extra_srpms;
    foreach my $rpm (@{$srpm->{RPMS}}) {
	my $id=$rpm->{ID};
	my %fileprovcache;
	my $filenamesref=$rpm->{RPMHDR}->filenames();
	if ($filenamesref) {
	    foreach my $filename (@$filenamesref) {
		&__add_to_cache($id,$filename,$global_fileprov_cache) if $global_filereq_cache{$filename};
		$fileprovcache{$filename}=1;
	    }
	}
	$rpm->{FILEPROVCACHE}=\%fileprovcache;
    }
}


=head1	NAME

girar-nmu-sort-transaction - calculate an order to build a transaction.

=head1	SYNOPSIS

B<girar-nmu-sort-transaction>
[B<-h, --help>]
[B<--cycle-format> I<name|file|path>]
[B<--cycle-prefix> I<string>]
[B<--debug>]
[B<--format> I<name|file|path>]
[B<--ignore-extra-rpms|--no-ignore-extra-rpms>]
[B<--ignore-extra-srpms|--no-ignore-extra-srpms>]
[B<--ignore-debuginfo|--no-ignore-debuginfo>]
[B<--ignore-core|--no-ignore-core>]
[B<--ignore-ambiguous-provides>]
[B<--ignore-missing-srpm-names>]
[B<--mark-req-regexp> I<pcre regexp>]
[B<--rpm-files-transaction> I<file>]
[B<--rpm-names-transaction> I<file>]
[B<--srpm-files-transaction> I<file>]
[B<--srpm-names-transaction> I<file>]
[B<--mark-all-rpm-of-srpm>]
[B<--mark-all-srpm-of-rpm>]
[B<--add-textid-relations-from> I<file>]
[B<--del-textid-relations-from> I<file>]
[B<--add-filename-relations-from> I<file>]
[B<--del-filename-relations-from> I<file>]
[B<--cycle-break-through-unmarked>]
[B<-q|--quiet>]
[B<-v|--verbose>]
[B<-o|--output> I<file>]
<context of the transaction: the list of src.rpms and binary rpms built
from the src.rpms or/and the list of directories containing those rpms>

=head1	DESCRIPTION

B<girar-nmu-sort-transaction> sorts packages of the transaction
into the build order according to their dependencies;
finds circular dependencies that are obstacles to replacement transaction.

The main arguments to the utility are transaction and context.
Context is the set of packages that provide the net of dependencies.
It is failsafe to provide the whole repository as the context.
Use the smaller contexts on your own risk.

Transaction is the subset of context that should be sorted according to
the order generated by BuildRequires and Requires.

Mark the transaction using B<--mark-req-regexp> option or
B<--(srpm|rpm)-(files|names)-transaction> I<file> option.

=head1	OPTIONS

=over

=item	B<-h, --help>

Display this help and exit.

=item	B<-v, --verbose>, B<-q, --quiet>

Verbosity level. Multiple -v increase the verbosity level, -q sets it to 0.
Default is 1.

=item	B<-o, --output> I<filename>

Output file.

=item	B<--format> I<name>

Output file format. One of: name, file, path.

name prints rpm name; file prints rpm filename; path prints rpm filename with path.

=item	B<--cycle-format> I<name>

Cycle information files format. One of: name, file, path.

=item	B<--cycle-prefix> I<name>

Prefix for cycle information files.

=item	B<--debug>

Debug mode. Data is collected, but data procession is left to user.
Also, auxilliary text files are created that help deciphering gathered data.
The auxilliary text files names end with '2'.

=item	B<--ignore-extra-rpms, --no-ignore-extra-rpms>

Ignore/do not ignore binary rpms that have no corresponding source counterparts.
Default is die on first occurence of mon-matched rpm (B<--no-ignore-extra-rpms>).

=item	B<--ignore-extra-srpms, --no-ignore-extra-srpms>

Ignore/do not ignore source rpms that have no corresponding binary counterparts.
Default is ignore.

=item	[B<--ignore-missing-srpm-names>]

Ignore srpm names not found in current reposit

=item B<--mark-req-regexp> I<pcre regexp>

Marks some binary and source rpms as part of the transaction according to I<regexp>.
For example, B<--mark-req-regexp '^libperl\.so\.5\.8'> will mark
binary rpms that depend on libperl.so.5.8* and their source rpms.
Beware of special symbols in regexp! you have to quote `.',`+', and so on.
For example, for libstdc++.so.6 regexp will be '^libstdc\+\+\.so\.6'.

Alternatively, use B<--(s)rpm-(files|names)-transaction> I<file> options
to mark transaction rpms and srpms explicitly.

=item B<--(s)rpm-(files|names)-transaction> I<file>

B<--rpm-files-transaction> I<file>
B<--rpm-names-transaction> I<file>
B<--srpm-files-transaction> I<file>
B<--srpm-names-transaction> I<file>

Alternatively, use B<--mark-req-regexp> I<pcre regexp> to mark transaction rpms
implicitly based on regexp on their Requires:.

=item	B<--mark-all-rpm-of-srpm, --mark-all-srpm-of-rpm>

Instead of providing both B<--rpm-(names|files)-transaction> and
B<--srpm-(names|files)-transaction> you can provide just one of them
and then B<--mark-all-rpm-of-srpm> or B<--mark-all-srpm-of-rpm>.

=item	B<--buildfrom-none>

Don not set "build from" relation.

=item	B<--buildfrom-srpm-marked, --no-buildfrom-srpm-marked>

If there are marked packages, set "build from" relation
only if src.rpm is marked (default).

=item	B<--buildfrom-rpm-marked, --no-buildfrom-rpm-marked>

If there are marked packages, set "build from" relation
only if binary rpm is marked.

=item	B<--buildreq-srpm-marked, --no-buildreq-srpm-marked>

If there are marked packages, set "buildrequires" relation
only if source rpm is marked.

=item	B<--use-file-provides, --no-use-file-provides>

Resolve/do not resolve requires using the list of files in the package.
Default is --use-file-provides.
Disable it if uou are sure it is harmless to save some time.

=item B<--add/del-textid/filename-relations-from> I<file>

Those options allow correction of the set of relations.
I<file> contains pairs of packages in relation, a pair per line.
B<--add-textid-relations-from> I<file>, B<--del-textid-relations-from> I<file> use
internal TextIds (%{NAME} for src.rpm and %{NAME}.%{ARCH} for binary rpms) and
B<--add-filename-relations-from> I<file>, B<--del-filename-relations-from> I<file>
use rpm file names (as returned by basename(1) ).

=item	B<--ignore-ambiguous-provides>

A hack; some 'Provides' are provided by multiple packages, so sometimes
ignoring them will improve build order.

=item	B<--cycle-break-through-unmarked>

A hack; try to resolve cycles assuming that cycle dependencies outside
of the transaction are harmless and are safe to drop.

=item	B<--ignore-core|--no-ignore-core>

A hack; ignore some basesystem srpms and rpms (glibc,binutils,etc...)
Enabed by default. Disable if you need to include them into transaction.

=item	B<--whobuildreq>

'whobuildreq' mode. Instead of sorting transaction, calculates packages
who BuildRequires marked package(s).

=back

=head1	AUTHOR

Written by Igor Vlasenko <viy@altlinux.org>.

=head1	COPYING

Copyright (c) 2010-2015 Igor Vlasenko, ALT Linux Team.

This 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 2 of the License, or (at your option) any later version.

=cut

