]> Sergey Matveev's repositories - public-inbox.git/blob - ci/deps.perl
No ext_urls
[public-inbox.git] / ci / deps.perl
1 #!/usr/bin/perl -w
2 # Copyright (C) 2019-2021 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
7 use strict;
8 my $usage = "$0 PKG_FMT PROFILE [PROFILE_MOD]";
9 my $pkg_fmt = shift;
10 @ARGV or die $usage, "\n";
11
12 my @test_essential = qw(Test::Simple); # we actually use Test::More
13
14 # package profiles
15 my $profiles = {
16         # the smallest possible profile for testing
17         essential => [ qw(
18                 git
19                 perl
20                 Digest::SHA
21                 Encode
22                 ExtUtils::MakeMaker
23                 IO::Compress::Gzip
24                 URI
25                 ), @test_essential ],
26
27         # everything optional for normal use
28         optional => [ qw(
29                 Date::Parse
30                 BSD::Resource
31                 DBD::SQLite
32                 DBI
33                 Inline::C
34                 Net::Server
35                 Plack
36                 Plack::Test
37                 Plack::Middleware::ReverseProxy
38                 Search::Xapian
39                 Socket6
40                 highlight.pm
41                 xapian-compact
42                 ) ],
43
44         # optional developer stuff
45         devtest => [ qw(
46                 XML::TreePP
47                 curl
48                 w3m
49                 Plack::Test::ExternalServer
50                 ) ],
51 };
52
53 # account for granularity differences between package systems and OSes
54 my @precious;
55 if ($^O eq 'freebsd') {
56         @precious = qw(perl curl Socket6 IO::Compress::Gzip);
57 } elsif ($pkg_fmt eq 'rpm') {
58         @precious = qw(perl curl);
59 }
60
61 if (@precious) {
62         my $re = join('|', map { quotemeta($_) } @precious);
63         for my $list (values %$profiles) {
64                 @$list = grep(!/\A(?:$re)\z/, @$list);
65         }
66         push @{$profiles->{essential}}, @precious;
67 }
68
69
70 # bare minimum for v2
71 $profiles->{v2essential} = [ @{$profiles->{essential}}, qw(DBD::SQLite DBI) ];
72
73 # package names which can't be mapped automatically:
74 my $non_auto = {
75         'perl' => { pkg => 'perl5' },
76         'Date::Parse' => {
77                 deb => 'libtimedate-perl',
78                 pkg => 'p5-TimeDate',
79                 rpm => 'perl-TimeDate',
80         },
81         'Digest::SHA' => {
82                 deb => 'perl', # libperl5.XX, but the XX varies
83                 pkg => 'perl5',
84         },
85         'Encode' => {
86                 deb => 'perl', # libperl5.XX, but the XX varies
87                 pkg => 'perl5',
88                 rpm => 'perl-Encode',
89         },
90         'ExtUtils::MakeMaker' => {
91                 deb => 'perl', # perl-modules-5.xx
92                 pkg => 'perl5',
93                 rpm => 'perl-ExtUtils-MakeMaker',
94         },
95         'IO::Compress::Gzip' => {
96                 deb => 'perl', # perl-modules-5.xx
97                 pkg => 'perl5',
98                 rpm => 'perl-IO-Compress',
99         },
100         'DBD::SQLite' => { deb => 'libdbd-sqlite3-perl' },
101         'Plack::Test' => {
102                 deb => 'libplack-perl',
103                 pkg => 'p5-Plack',
104                 rpm => 'perl-Plack-Test',
105         },
106         'URI' => {
107                 deb => 'liburi-perl',
108                 pkg => 'p5-URI',
109                 rpm => 'perl-URI',
110         },
111         'Test::Simple' => {
112                 deb => 'perl', # perl-modules-5.XX, but the XX varies
113                 pkg => 'perl5',
114                 rpm => 'perl-Test-Simple',
115         },
116         'highlight.pm' => {
117                 deb => 'libhighlight-perl',
118                 pkg => [],
119                 rpm => [],
120         },
121
122         # we call xapian-compact(1) in public-inbox-compact(1)
123         'xapian-compact' => {
124                 deb => 'xapian-tools',
125                 pkg => 'xapian-core',
126                 rpm => 'xapian-core', # ???
127         },
128
129         # OS-specific
130         'IO::KQueue' => {
131                 deb => [],
132                 pkg => 'p5-IO-KQueue',
133                 rpm => [],
134         },
135 };
136
137 my (@pkg_install, @pkg_remove, %all);
138 for my $ary (values %$profiles) {
139         $all{$_} = \@pkg_remove for @$ary;
140 }
141 if ($^O eq 'freebsd') {
142         $all{'IO::KQueue'} = \@pkg_remove;
143 }
144 $profiles->{all} = [ keys %all ]; # pseudo-profile for all packages
145
146 # parse the profile list from the command-line
147 for my $profile (@ARGV) {
148         if ($profile =~ s/-\z//) {
149                 # like apt-get, trailing "-" means remove
150                 profile2dst($profile, \@pkg_remove);
151         } else {
152                 profile2dst($profile, \@pkg_install);
153         }
154 }
155
156 # fill in @pkg_install and @pkg_remove:
157 while (my ($pkg, $dst_pkg_list) = each %all) {
158         push @$dst_pkg_list, list(pkg2ospkg($pkg, $pkg_fmt));
159 }
160
161 my @apt_opts =
162         qw(-o APT::Install-Recommends=false -o APT::Install-Suggests=false);
163
164 # OS-specific cleanups appreciated
165
166 if ($pkg_fmt eq 'deb') {
167         my @quiet = $ENV{V} ? () : ('-q');
168         root('apt-get', @apt_opts, qw(install --purge -y), @quiet,
169                 @pkg_install,
170                 # apt-get lets you suffix a package with "-" to
171                 # remove it in an "install" sub-command:
172                 map { "$_-" } @pkg_remove);
173         root('apt-get', @apt_opts, qw(autoremove --purge -y), @quiet);
174 } elsif ($pkg_fmt eq 'pkg') {
175         my @quiet = $ENV{V} ? () : ('-q');
176         # FreeBSD, maybe other *BSDs are similar?
177
178         # don't remove stuff that isn't installed:
179         exclude_uninstalled(\@pkg_remove);
180         root(qw(pkg remove -y), @quiet, @pkg_remove) if @pkg_remove;
181         root(qw(pkg install -y), @quiet, @pkg_install) if @pkg_install;
182         root(qw(pkg autoremove -y), @quiet);
183 # TODO: yum / rpm support
184 } elsif ($pkg_fmt eq 'rpm') {
185         my @quiet = $ENV{V} ? () : ('-q');
186         exclude_uninstalled(\@pkg_remove);
187         root(qw(yum remove -y), @quiet, @pkg_remove) if @pkg_remove;
188         root(qw(yum install -y), @quiet, @pkg_install) if @pkg_install;
189 } else {
190         die "unsupported package format: $pkg_fmt\n";
191 }
192 exit 0;
193
194
195 # map a generic package name to an OS package name
196 sub pkg2ospkg {
197         my ($pkg, $fmt) = @_;
198
199         # check explicit overrides, first:
200         if (my $ospkg = $non_auto->{$pkg}->{$fmt}) {
201                 return $ospkg;
202         }
203
204         # check common Perl module name patterns:
205         if ($pkg =~ /::/ || $pkg =~ /\A[A-Z]/) {
206                 if ($fmt eq 'deb') {
207                         $pkg =~ s/::/-/g;
208                         $pkg =~ tr/A-Z/a-z/;
209                         return "lib$pkg-perl";
210                 } elsif ($fmt eq 'rpm') {
211                         $pkg =~ s/::/-/g;
212                         return "perl-$pkg"
213                 } elsif ($fmt eq 'pkg') {
214                         $pkg =~ s/::/-/g;
215                         return "p5-$pkg"
216                 } else {
217                         die "unsupported package format: $fmt for $pkg\n"
218                 }
219         }
220
221         # use package name as-is (e.g. 'curl' or 'w3m')
222         $pkg;
223 }
224
225 # maps a install profile to a package list (@pkg_remove or @pkg_install)
226 sub profile2dst {
227         my ($profile, $dst_pkg_list) = @_;
228         if (my $pkg_list = $profiles->{$profile}) {
229                 $all{$_} = $dst_pkg_list for @$pkg_list;
230         } elsif ($all{$profile}) { # $profile is just a package name
231                 $all{$profile} = $dst_pkg_list;
232         } else {
233                 die "unrecognized profile or package: $profile\n";
234         }
235 }
236
237 sub exclude_uninstalled {
238         my ($list) = @_;
239         my %inst_check = (
240                 pkg => sub { system(qw(pkg info -q), $_[0]) == 0 },
241                 deb => sub { system("dpkg -s $_[0] >/dev/null 2>&1") == 0 },
242                 rpm => sub { system("rpm -qs $_[0] >/dev/null 2>&1") == 0 },
243         );
244
245         my $cb = $inst_check{$pkg_fmt} || die <<"";
246 don't know how to check install status for $pkg_fmt
247
248         my @tmp;
249         for my $pkg (@$list) {
250                 push @tmp, $pkg if $cb->($pkg);
251         }
252         @$list = @tmp;
253 }
254
255 sub root {
256         print join(' ', @_), "\n";
257         return if $ENV{DRY_RUN};
258         return if system(@_) == 0;
259         warn 'command failed: ', join(' ', @_), "\n";
260         exit($? >> 8);
261 }
262
263 # ensure result can be pushed into an array:
264 sub list {
265         my ($pkg) = @_;
266         ref($pkg) eq 'ARRAY' ? @$pkg : $pkg;
267 }