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