#!/usr/bin/perl use strict; use warnings; use URI::Escape; my $prgname = $0; #### find_setup_ini_file ############################################### # Parse Cygwin's setup.rc file to find the last setup.ini file it used. sub find_setup_ini_file { open my $rc, '<', '/etc/setup/setup.rc' or usage("could not read setup.rc file: $!"); my ($path, $mirror); while (<$rc>) { chomp; if ($_ eq 'last-cache') { $path = <$rc>; chomp $path; $path =~ s/^\s+//; open my $cp, '-|', "cygpath -u '$path'"; $path = <$cp>; chomp $path; close $cp; } elsif ($_ eq 'last-mirror') { $mirror = <$rc>; chomp $mirror; $mirror =~ s/^\s+//; $mirror = uri_escape($mirror); } } close $rc; usage("could not find last Cygwin cache dir") unless $path; usage("could not find last Cygwin DL mirror") unless $mirror; for my $parent (glob("$path/$mirror/x86*")) { my $path = "$parent/setup.ini"; return $path if -r $path; } usage("could not find setup.ini"); return; } #### get_installed_package_list ######################################## # Return a list of names of installed packages sub get_installed_package_list { open my $db, '<', '/etc/setup/installed.db' or usage("failed to read installed package DB file: $!"); my $header = <$db>; my @pkgnames; while (<$db>) { my ($name) = split; push @pkgnames, $name; } return \@pkgnames; } #### parse_cygwin_setup_ini_file ####################################### # Extract dependency info from the Cygwin setup.ini file. sub parse_cygwin_setup_ini_file { my ($inifile, $piref) = @_; open my $ini, '<', $inifile or die "Cannot read INI file $inifile: $!\n"; # Skip to first package entry while (<$ini>) { last if /^@/; } # Parse package entries my %deps; while (defined $_) { chomp; my $p = substr $_, 2; my $obs = 0; while (<$ini>) { if (/^@/) { # Found next package entry; restart outer loop last; } elsif (/^category: Base$/) { # Mark this one as a special sort of root package: one # we're going to install regardless of user selection, # so we need not list it in our output. $piref->{$p} = 2; } elsif (/^category: _obsolete$/) { # Select this package's replacement instead below. $piref->{$p} = 0; $obs = 1; } elsif (/^requires:/) { # Save this package's requirements as its dependents list. my ($junk, @deps) = split; $deps{$p} = \@deps; # If this package was marked obsolete above, select its # replacement as provisionally to-be-installed. That # package still might end up removed from our output list # if it in turn is a dependent of one of the packages we # consider a "root" package at the end. $piref->{$deps[0]} = 1 if $obs; } } } close $ini; return \%deps; } #### usage ############################################################# # Print usage message plus optional error string, then exit sub usage { my ($error) = @_; print "ERROR: $error\n\n" if length($error); print <<"USAGE"; usage: $prgname Finds the last-used Cygwin setup.ini file, then uses the package dependency info found within it to pare the list of currently-installed Cygwin packages down to a "root" set, being those that will implicitly install all of the others as dependencies. The output is a list suitable for passing to setup.exe -P. USAGE exit ($error ? 1 : 0); } #### main ############################################################## my $inifile = find_setup_ini_file; # Convert package list to a hash so we can mark them non-root by name my $pkgnames = get_installed_package_list; my %packages = map { $_ => 1 } @$pkgnames; my $deps = parse_cygwin_setup_ini_file($inifile, \%packages); # For each given package name, mark any of its dependencies also found # on the command line as as non-root. for my $p (@$pkgnames) { my $pdref = $deps->{$p}; for my $d (@$pdref) { $packages{$d} = 0; } } # Collect list of root packages and print it out print join ',', sort(grep { $packages{$_} == 1 } @$pkgnames);