2 # Copyright (C) 2019 all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # Helper script for installing/uninstalling packages for CI use
5 # Intended for use on non-production chroots or VMs since it
6 # changes installed packages
8 my $usage = "$0 PKG_FMT PROFILE [PROFILE_MOD]";
10 @ARGV or die $usage, "\n";
12 my @test_essential = qw(Test::Simple Plack::Test);
16 # the smallest possible profile for testing
17 # TODO: trim this, Plack pulls in Filesys::Notify::Simple,
18 # and we don't need that for mda-only installs
27 Email::MIME::ContentType
30 Filesys::Notify::Simple
35 # everything optional for normal use
43 Plack::Middleware::Deflater
44 Plack::Middleware::ReverseProxy
51 # optional developer stuff
60 # account for granularity differences between package systems and OSes
62 if ($^O eq 'freebsd') {
63 @precious = qw(perl curl Socket6 IO::Compress::Gzip);
64 } elsif ($pkg_fmt eq 'rpm') {
65 @precious = qw(perl curl);
69 my $re = join('|', map { quotemeta($_) } @precious);
70 for my $list (values %$profiles) {
71 @$list = grep(!/\A(?:$re)\z/, @$list);
73 push @{$profiles->{essential}}, @precious;
78 $profiles->{v2essential} = [ @{$profiles->{essential}}, qw(DBD::SQLite DBI) ];
80 # package names which can't be mapped automatically:
82 'perl' => { pkg => 'perl5' },
84 deb => 'libtimedate-perl',
86 rpm => 'perl-TimeDate',
89 deb => 'perl', # libperl5.XX, but the XX varies
93 deb => 'perl', # libperl5.XX, but the XX varies
97 deb => 'perl', # libperl5.XX, but the XX varies
101 'ExtUtils::MakeMaker' => {
102 deb => 'perl', # perl-modules-5.xx
104 rpm => 'perl-ExtUtils-MakeMaker',
106 'IO::Compress::Gzip' => {
107 deb => 'perl', # perl-modules-5.xx
109 rpm => 'perl-IO-Compress',
111 'DBD::SQLite' => { deb => 'libdbd-sqlite3-perl' },
113 deb => 'libplack-perl',
115 rpm => 'perl-Plack-Test',
118 deb => 'liburi-perl',
123 deb => 'perl', # perl-modules-5.XX, but the XX varies
125 rpm => 'perl-Test-Simple',
128 deb => 'libhighlight-perl',
133 # we call xapian-compact(1) in public-inbox-compact(1)
134 'xapian-compact' => {
135 deb => 'xapian-tools',
136 pkg => 'xapian-core',
137 rpm => 'xapian-core', # ???
143 pkg => 'p5-IO-KQueue',
148 my (@pkg_install, @pkg_remove, %all);
149 for my $ary (values %$profiles) {
150 $all{$_} = \@pkg_remove for @$ary;
152 if ($^O eq 'freebsd') {
153 $all{'IO::KQueue'} = \@pkg_remove;
155 $profiles->{all} = [ keys %all ]; # pseudo-profile for all packages
157 # parse the profile list from the command-line
158 for my $profile (@ARGV) {
159 if ($profile =~ s/-\z//) {
160 # like apt-get, trailing "-" means remove
161 profile2dst($profile, \@pkg_remove);
163 profile2dst($profile, \@pkg_install);
167 # fill in @pkg_install and @pkg_remove:
168 while (my ($pkg, $dst_pkg_list) = each %all) {
169 push @$dst_pkg_list, list(pkg2ospkg($pkg, $pkg_fmt));
173 qw(-o APT::Install-Recommends=false -o APT::Install-Suggests=false);
175 # OS-specific cleanups appreciated
177 if ($pkg_fmt eq 'deb') {
178 my @quiet = $ENV{V} ? () : ('-q');
179 root('apt-get', @apt_opts, qw(install --purge -y), @quiet,
181 # apt-get lets you suffix a package with "-" to
182 # remove it in an "install" sub-command:
183 map { "$_-" } @pkg_remove);
184 root('apt-get', @apt_opts, qw(autoremove --purge -y), @quiet);
185 } elsif ($pkg_fmt eq 'pkg') {
186 my @quiet = $ENV{V} ? () : ('-q');
187 # FreeBSD, maybe other *BSDs are similar?
189 # don't remove stuff that isn't installed:
190 exclude_uninstalled(\@pkg_remove);
191 root(qw(pkg remove -y), @quiet, @pkg_remove) if @pkg_remove;
192 root(qw(pkg install -y), @quiet, @pkg_install) if @pkg_install;
193 root(qw(pkg autoremove -y), @quiet);
194 # TODO: yum / rpm support
195 } elsif ($pkg_fmt eq 'rpm') {
196 my @quiet = $ENV{V} ? () : ('-q');
197 exclude_uninstalled(\@pkg_remove);
198 root(qw(yum remove -y), @quiet, @pkg_remove) if @pkg_remove;
199 root(qw(yum install -y), @quiet, @pkg_install) if @pkg_install;
201 die "unsupported package format: $pkg_fmt\n";
206 # map a generic package name to an OS package name
208 my ($pkg, $fmt) = @_;
210 # check explicit overrides, first:
211 if (my $ospkg = $non_auto->{$pkg}->{$fmt}) {
215 # check common Perl module name patterns:
216 if ($pkg =~ /::/ || $pkg =~ /\A[A-Z]/) {
220 return "lib$pkg-perl";
221 } elsif ($fmt eq 'rpm') {
224 } elsif ($fmt eq 'pkg') {
228 die "unsupported package format: $fmt for $pkg\n"
232 # use package name as-is (e.g. 'curl' or 'w3m')
236 # maps a install profile to a package list (@pkg_remove or @pkg_install)
238 my ($profile, $dst_pkg_list) = @_;
239 if (my $pkg_list = $profiles->{$profile}) {
240 $all{$_} = $dst_pkg_list for @$pkg_list;
241 } elsif ($all{$profile}) { # $profile is just a package name
242 $all{$profile} = $dst_pkg_list;
244 die "unrecognized profile or package: $profile\n";
248 sub exclude_uninstalled {
251 pkg => sub { system(qw(pkg info -q), $_[0]) == 0 },
252 deb => sub { system("dpkg -s $_[0] >/dev/null 2>&1") == 0 },
253 rpm => sub { system("rpm -qs $_[0] >/dev/null 2>&1") == 0 },
256 my $cb = $inst_check{$pkg_fmt} || die <<"";
257 don't know how to check install status for $pkg_fmt
260 for my $pkg (@$list) {
261 push @tmp, $pkg if $cb->($pkg);
267 print join(' ', @_), "\n";
268 return if $ENV{DRY_RUN};
269 return if system(@_) == 0;
270 warn 'command failed: ', join(' ', @_), "\n";
274 # ensure result can be pushed into an array:
277 ref($pkg) eq 'ARRAY' ? @$pkg : $pkg;