summaryrefslogtreecommitdiffstats
path: root/lib/App/ArchLinux/PackagerTools/Pacman.pm
blob: 287d2a9d77129c55cb625a67758103933701dd60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package App::ArchLinux::PackagerTools::Pacman;
use strictures;

use autodie;
use Function::Parameters;
use Log::Any qw($log);

use App::ArchLinux::PackagerTools::CPAN;


# TODO: Split this class so that the external calls (to expac) are in a
# dedicated class and this class becomes testable. Also update
# t/PackagerTools.t accordingly afterwards.
=head1 NAME

App::ArchLinux::PackagerTools::Pacman - Methods to interact with pacman

=head1 SYNOPSIS

  use App::ArchLinux::PackagerTools::Pacman;

  # synopsis...

=head1 DESCRIPTION

This module allows to query pacman for a list of packages in the repository and
extract a list of CPAN distributions that are part of the repository.

=head1 METHODS

=head2 Constructors

=head3 new

 my $pacman = App::ArchLinux::PackagerTools::Pacman->new();

Returns a new instance.

=cut


method new($class: $context, $deps = {}) {
	$deps->{config} //= App::ArchLinux::PackagerTools::Config->new($context);
	$deps->{cpan} //= App::ArchLinux::PackagerTools::CPAN->new($context);

	my $conf = $deps->{config}->get_config();
	$deps->{pkgname_cpan_name_map} //= $conf->{pacman}->{pkgname_to_cpan_dist_map};

	return $class->new_no_defaults($context, $deps);
}

method new_no_defaults($class: $context, $deps = {}) {
	my $self = {};
	bless $self, $class;
	$deps->{cpan_name_pkgname_map} = {reverse $deps->{pkgname_cpan_name_map}->%*};
	$self->{deps} = $deps;
	return $self;
}

=head2 Public Methods

=head3 get_packages

 my $packages = $pacman->get_packages();

Returns an arrayref of package hashes. Each package hash contains at least the
pkgname and pkgver keys.

=cut

method get_packages() {
	my @packages;
	open my $fh, '-|', 'expac -S \'%n %v\'';
	my %seen;
	while (<$fh>) {
		if (m/^(?<pkgname>[^\s]+) (?<pkgver>.*)$/) {
			push @packages, {%+};
		}
	}
	return \@packages;
}

=head3 get_perl_packages

 my $packages = $pacman->get_perl_packages();

Similar to get_packages(), but returns a list filtered to perl packages.

=cut

method get_perl_packages() {
	my @packages = grep {$_->{pkgname} =~ /^perl-/} $self->get_packages->@*;
	$log->debugf("Found %d perl packages", scalar(@packages));
	return \@packages;
}

=head3 get_perl_distributions

 my $dists = $pacman->get_perl_distributions();

Returns a list of CPAN distributions that are in the pacman repo. Returns an
arrayref of hashrefs that contain dist_name and version keys.

=cut


method get_perl_distributions() {
	my @distributions;

	for my $package ($self->get_perl_packages()->@*) {
		my $cleaned_name = $package->{pkgname} =~ s/^perl-//r;
		my $dist;
		if (defined $self->{deps}->{pkgname_cpan_name_map}->{$package->{pkgname}}) {
			$dist = $self->{deps}->{cpan}->get_dist($self->{deps}->{pkgname_cpan_name_map}->{$package->{pkgname}});
		} else {
			$dist = $self->{deps}->{cpan}->search_dist($cleaned_name);
		}
		push @distributions, $dist if defined $dist;
		$log->warningf("Failed to find distribution for package '%s'", $package) if !defined $dist;
	}
	return \@distributions;
}

=head3 get_package_version

 my $version = $pacman->get_package_version($pkgname);

Return the version of a package in the pacman package repositories.

=cut

method get_package_version($pkgname) {
	# TODO do not fork expac for each package here! works for now, but it's sloooow
	my $version = `expac -S -1 '%v' -- '$pkgname'`;
	chomp $version;
	return $version;
}

=head3 get_packages_versions

 my $versions = $pacman->get_packages_versions($package_names);

Return a hash that maps each package name to the version of that package in the
pacman repository.

=cut

method get_packages_versions($package_names) {
	my %versions;

	open my $fh, '-|', qw(expac -S -1), '%n %v', '--', $package_names->@*;
	while (<$fh>) {
		my ($pkgname, $version) = split /\s/;
		$versions{$pkgname} = $version;
		$log->debugf("Package '%s' has version '%s' in repository", $pkgname, $version);
	}
	if ($package_names->@* != keys %versions) {
		croak $log->error("Failed to find version for all packages. Got %d packages and %d versions were detected", scalar($package_names->@*), scalar(keys %versions));
	}
	return \%versions;
}

=head3 get_perl_pkgname

 my $pkgname = $pacman->get_perl_pkgname($dist_name);

Return the pacman package name for a CPAN distribution. In most cases, this
prefixes the distribution with "perl-" and replaces "::" with "-", but it may
also return other names if they are mapped differently.

=cut

method get_perl_pkgname($dist_name) {
	if (defined $self->{deps}->{cpan_name_pkgname_map}->{$dist_name}) {
		return $self->{deps}->{cpan_name_pkgname_map}->{$dist_name};
	}
	my $pkgname = lc($dist_name);
	$pkgname =~ s/::/-/g;
	$pkgname = "perl-$pkgname";
	return $pkgname;
}

=head3 get_perl_distribution_version

 my $version = $pacman->get_perl_distribution_version($dist_name);

Return the version of a CPAN distribution's pacman package. This removes
pacman-specific information from the pacman's package version.

=cut


method get_perl_distribution_version($dist_name) {
	my $pkgname = $self->get_perl_pkgname($dist_name);
	my $version = $self->get_package_version($pkgname);
	$version = $self->_clean_version($version);

	$log->tracef("Found version '%s' for package '%s'", $version, $pkgname);
	return $version;
}

=head3 get_perl_distribution_versions

 my $distribution_names = ['App::ArchLinux::PackagerTools', 'DBI'];
 my $versions = $pacman->get_perl_distribution_versions($distribution_names);
 print $versions->{'DBI'}; # prints "1.642"

Return a hash that maps a CPAN distribution name to the respective version of
the CPAN distribution of the pacman repository package.

=cut

method get_perl_distribution_versions($distribution_names) {
	my %pkgname_dist;
	for my $dist_name ($distribution_names->@*) {
		$pkgname_dist{$self->get_perl_pkgname($dist_name)} = $dist_name;
	}

	my $versions = $self->get_packages_versions([keys %pkgname_dist]);
	my %dist_versions;
	for my $key (keys $versions->%*) {
		$dist_versions{$pkgname_dist{$key}} = $self->_clean_version($versions->{$key});
		$log->debugf("Cleaned version of package '%s' is now '%s'", $key, $versions->{$key});
	}
	return \%dist_versions;

}

=head3 _clean_version

 my $version = $self->_clean_version($version);

Strip pacman-specific information (pkgrel, epoch) from a version string.

=cut

method _clean_version($version) {
	# remove pkgrel
	$version =~ s/-\d+(\.\d+)?$//;
	# remove epoch
	$version =~ s/^\d+://;
	return $version;
}

1;

__END__