dpkg (1.18.25) stretch; urgency=medium
[dpkg] / scripts / dpkg-genbuildinfo.pl
CommitLineData
1479465f
GJ
1#!/usr/bin/perl
2#
3# dpkg-genbuildinfo
4#
5# Copyright © 1996 Ian Jackson
6# Copyright © 2000,2001 Wichert Akkerman
7# Copyright © 2003-2013 Yann Dirson <dirson@debian.org>
8# Copyright © 2006-2016 Guillem Jover <guillem@debian.org>
9# Copyright © 2014 Niko Tyni <ntyni@debian.org>
10# Copyright © 2014-2015 Jérémy Bobbio <lunar@debian.org>
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program. If not, see <https://www.gnu.org/licenses/>.
24
25use strict;
26use warnings;
27
28use Cwd;
29use File::Basename;
30use POSIX qw(:fcntl_h :locale_h strftime);
31
32use Dpkg ();
33use Dpkg::Gettext;
34use Dpkg::Checksums;
35use Dpkg::ErrorHandling;
36use Dpkg::Arch qw(get_build_arch get_host_arch debarch_eq);
37use Dpkg::Build::Types;
38use Dpkg::Build::Info qw(get_build_env_whitelist);
39use Dpkg::BuildOptions;
40use Dpkg::BuildFlags;
41use Dpkg::BuildProfiles qw(get_build_profiles);
42use Dpkg::Control::Info;
43use Dpkg::Control::Fields;
44use Dpkg::Control;
45use Dpkg::Changelog::Parse;
46use Dpkg::Deps;
47use Dpkg::Dist::Files;
48use Dpkg::Util qw(:list);
49use Dpkg::File;
50use Dpkg::Version;
51use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
52
53textdomain('dpkg-dev');
54
55my $controlfile = 'debian/control';
56my $changelogfile = 'debian/changelog';
57my $changelogformat;
58my $fileslistfile = 'debian/files';
59my $uploadfilesdir = '..';
60my $outputfile;
61my $stdout = 0;
62my $admindir = $Dpkg::ADMINDIR;
63my %use_feature = (
64 path => 0,
65);
66my @build_profiles = get_build_profiles();
67my $buildinfo_format = '1.0';
68my $buildinfo;
69
70my $checksums = Dpkg::Checksums->new();
71my %archadded;
72my @archvalues;
73
74sub get_build_date {
75 my $date;
76
77 setlocale(LC_TIME, 'C');
78 $date = strftime('%a, %d %b %Y %T %z', localtime);
79 setlocale(LC_TIME, '');
80
81 return $date;
82}
83
84# There is almost the same function in dpkg-checkbuilddeps, they probably
85# should be factored out.
86sub parse_status {
87 my $status = shift;
88
89 my $facts = Dpkg::Deps::KnownFacts->new();
90 my %depends;
91 my @essential_pkgs;
92
93 local $/ = '';
94 open my $status_fh, '<', $status or syserr(g_('cannot open %s'), $status);
95 while (<$status_fh>) {
96 next unless /^Status: .*ok installed$/m;
97
98 my ($package) = /^Package: (.*)$/m;
99 my ($version) = /^Version: (.*)$/m;
100 my ($arch) = /^Architecture: (.*)$/m;
101 my ($multiarch) = /^Multi-Arch: (.*)$/m;
102
103 $facts->add_installed_package($package, $version, $arch, $multiarch);
104
105 if (/^Essential: yes$/m) {
106 push @essential_pkgs, $package;
107 }
108
109 if (/^Provides: (.*)$/m) {
110 my $provides = deps_parse($1, reduce_arch => 1, union => 1);
111
112 next if not defined $provides;
113
114 deps_iterate($provides, sub {
115 my $dep = shift;
116 $facts->add_provided_package($dep->{package}, $dep->{relation},
117 $dep->{version}, $package);
118 });
119 }
120
121 foreach my $deptype (qw(Pre-Depends Depends)) {
122 next unless /^$deptype: (.*)$/m;
123
124 my $depends = $1;
125 foreach (split /,\s*/, $depends) {
126 push @{$depends{"$package:$arch"}}, $_;
127 }
128 }
129 }
130 close $status_fh;
131
132 return ($facts, \%depends, \@essential_pkgs);
133}
134
135sub append_deps {
136 my $pkgs = shift;
137
138 foreach my $dep_str (@_) {
139 next unless $dep_str;
140
141 my $deps = deps_parse($dep_str, reduce_restrictions => 1,
142 build_dep => 1,
143 build_profiles => \@build_profiles);
144
145 # We add every sub-dependencies as we cannot know which package in
146 # an OR dependency has been effectively used.
147 deps_iterate($deps, sub {
148 push @{$pkgs},
149 $_[0]->{package} . (defined $_[0]->{archqual} ? ':' . $_[0]->{archqual} : '');
150 1
151 });
152 }
153}
154
155sub collect_installed_builddeps {
156 my $control = shift;
157
158 my ($facts, $depends, $essential_pkgs) = parse_status("$admindir/status");
159 my %seen_pkgs;
160 my @unprocessed_pkgs;
161
162 # Parse essential packages list.
163 append_deps(\@unprocessed_pkgs,
164 @{$essential_pkgs},
165 run_vendor_hook('builtin-build-depends'),
166 $control->get_source->{'Build-Depends'});
167
168 if (build_has_any(BUILD_ARCH_DEP)) {
169 append_deps(\@unprocessed_pkgs,
170 $control->get_source->{'Build-Depends-Arch'});
171 }
172
173 if (build_has_any(BUILD_ARCH_INDEP)) {
174 append_deps(\@unprocessed_pkgs,
175 $control->get_source->{'Build-Depends-Indep'});
176 }
177
178 my $installed_deps = Dpkg::Deps::AND->new();
179
180 while (my $pkg_name = shift @unprocessed_pkgs) {
181 next if $seen_pkgs{$pkg_name};
182 $seen_pkgs{$pkg_name} = 1;
183
184 my $required_architecture;
185 if ($pkg_name =~ /\A(.*):(.*)\z/) {
186 $pkg_name = $1;
187 my $arch = $2;
188 $required_architecture = $arch if $arch !~ /\A(?:all|any|native)\Z/
189 }
190 my $pkg;
191 my $qualified_pkg_name;
192 foreach my $installed_pkg (@{$facts->{pkg}->{$pkg_name}}) {
193 if (!defined $required_architecture ||
194 $required_architecture eq $installed_pkg->{architecture}) {
195 $pkg = $installed_pkg;
196 $qualified_pkg_name = $pkg_name . ':' . $installed_pkg->{architecture};
197 last;
198 }
199 }
200 if (defined $pkg) {
201 my $version = $pkg->{version};
202 my $architecture = $pkg->{architecture};
203 my $new_deps_str = defined $depends->{$qualified_pkg_name} ? deps_concat(@{$depends->{$qualified_pkg_name}}) : '';
204 my $new_deps = deps_parse($new_deps_str);
205 if (!defined $required_architecture) {
206 $installed_deps->add(Dpkg::Deps::Simple->new("$pkg_name (= $version)"));
207 } else {
208 $installed_deps->add(Dpkg::Deps::Simple->new("$qualified_pkg_name (= $version)"));
209
210 # Dependencies of foreign packages are also foreign packages
211 # (or Arch:all) so we need to qualify them as well. We figure
212 # out if the package is actually foreign by searching for an
213 # installed package of the right architecture.
214 deps_iterate($new_deps, sub {
215 my $dep = shift;
216 return unless defined $facts->{pkg}->{$dep->{package}};
217 $dep->{archqual} //= $architecture
218 if any { $_[0]->{architecture} eq $architecture }, @{$facts->{pkg}->{$dep->{package}}};
219 1;
220 });
221 }
222
223 # We add every sub-dependencies as we cannot know which package
224 # in an OR dependency has been effectively used.
225 deps_iterate($new_deps, sub {
226 push @unprocessed_pkgs,
227 $_[0]->{package} . (defined $_[0]->{archqual} ? ':' . $_[0]->{archqual} : '');
228 1
229 });
230 } elsif (defined $facts->{virtualpkg}->{$pkg_name}) {
231 # virtual package: we cannot know for sure which implementation
232 # is the one that has been used, so let's add them all...
233 foreach my $provided (@{$facts->{virtualpkg}->{$pkg_name}}) {
234 my ($provided_by, $provided_rel, $provided_ver) = @{$provided};
235 push @unprocessed_pkgs, $provided_by;
236 }
237 }
238 # else: it is a package in an OR dependency that has been otherwise
239 # satisfied.
240 }
241 $installed_deps->simplify_deps(Dpkg::Deps::KnownFacts->new());
242 $installed_deps->sort();
243 $installed_deps = "\n" . $installed_deps->output();
244 $installed_deps =~ s/, /,\n/g;
245
246 return $installed_deps;
247}
248
249sub cleansed_environment {
250 # Consider only whitelisted variables which are not supposed to leak
251 # local user information.
252 my %env = map {
253 $_ => $ENV{$_}
254 } grep {
255 exists $ENV{$_}
256 } get_build_env_whitelist();
257
258 # Record flags from dpkg-buildflags.
259 my $bf = Dpkg::BuildFlags->new();
260 $bf->load_system_config();
261 $bf->load_user_config();
262 $bf->load_environment_config();
263 foreach my $flag ($bf->list()) {
264 next if $bf->get_origin($flag) eq 'vendor';
265
266 # We do not need to record *_{STRIP,APPEND,PREPEND} as they
267 # have been used already to compute the above value.
268 $env{"DEB_${flag}_SET"} = $bf->get($flag);
269 }
270
271 return join "\n", map { $_ . '="' . ($env{$_} =~ s/"/\\"/gr) . '"' }
272 sort keys %env;
273}
274
275sub version {
276 printf g_("Debian %s version %s.\n"), $Dpkg::PROGNAME, $Dpkg::PROGVERSION;
277
278 printf g_('
279This is free software; see the GNU General Public License version 2 or
280later for copying conditions. There is NO warranty.
281');
282}
283
284sub usage {
285 printf g_(
286'Usage: %s [<option>...]')
287 . "\n\n" . g_(
288"Options:
289 --build=<type>[,...] specify the build <type>: full, source, binary,
290 any, all (default is \'full\').
291 -c<control-file> get control info from this file.
292 -l<changelog-file> get per-version info from this file.
293 -f<files-list-file> get .deb files list from this file.
294 -F<changelog-format> force changelog format.
295 -O[<buildinfo-file>] write to stdout (or <buildinfo-file>).
296 -u<upload-files-dir> directory with files (default is '..').
297 --always-include-path always include Build-Path.
298 --admindir=<directory> change the administrative directory.
299 -?, --help show this help message.
300 --version show the version.
301"), $Dpkg::PROGNAME;
302}
303
304my $build_opts = Dpkg::BuildOptions->new();
305$build_opts->parse_features('buildinfo', \%use_feature);
306
307while (@ARGV) {
308 $_ = shift @ARGV ;
309 if (m/^--build=(.*)$/) {
310 set_build_type_from_options($1, $_);
311 } elsif (m/^-c(.*)$/) {
312 $controlfile = $1;
313 } elsif (m/^-l(.*)$/) {
314 $changelogfile = $1;
315 } elsif (m/^-f(.*)$/) {
316 $fileslistfile = $1;
317 } elsif (m/^-F([0-9a-z]+)$/) {
318 $changelogformat = $1;
319 } elsif (m/^-u(.*)$/) {
320 $uploadfilesdir = $1;
321 } elsif (m/^-O$/) {
322 $stdout = 1;
323 } elsif (m/^-O(.*)$/) {
324 $outputfile = $1;
325 } elsif (m/^--buildinfo-id=.*$/) {
326 # Deprecated option
327 warning('--buildinfo-id is deprecated, it is without effect');
328 } elsif (m/^--always-include-path$/) {
329 $use_feature{path} = 1;
330 } elsif (m/^--admindir=(.*)$/) {
331 $admindir = $1;
332 } elsif (m/^-(?:\?|-help)$/) {
333 usage();
334 exit(0);
335 } elsif (m/^--version$/) {
336 version();
337 exit(0);
338 } else {
339 usageerr(g_("unknown option '%s'"), $_);
340 }
341}
342
343my $control = Dpkg::Control::Info->new($controlfile);
344my $fields = Dpkg::Control->new(type => CTRL_FILE_BUILDINFO);
345my $dist = Dpkg::Dist::Files->new();
346
347# Retrieve info from the current changelog entry.
348my %options = (file => $changelogfile);
349$options{changelogformat} = $changelogformat if $changelogformat;
350my $changelog = changelog_parse(%options);
351
352# Retrieve info from the former changelog entry to handle binNMUs.
353$options{count} = 1;
354$options{offset} = 1;
355my $prev_changelog = changelog_parse(%options);
356
357my $sourceversion = $changelog->{'Binary-Only'} ?
358 $prev_changelog->{'Version'} : $changelog->{'Version'};
359my $binaryversion = Dpkg::Version->new($changelog->{'Version'});
360
361# Include .dsc if available.
362my $spackage = $changelog->{'Source'};
363(my $sversion = $sourceversion) =~ s/^\d+://;
364
365if (build_has_any(BUILD_SOURCE)) {
366 my $dsc = "${spackage}_${sversion}.dsc";
367
368 $checksums->add_from_file("$uploadfilesdir/$dsc", key => $dsc);
369
370 push @archvalues, 'source';
371}
372
373my $dist_count = 0;
374
375$dist_count = $dist->load($fileslistfile) if -e $fileslistfile;
376
377if (build_has_any(BUILD_BINARY)) {
378 error(g_('binary build with no binary artifacts found; .buildinfo is meaningless'))
379 if $dist_count == 0;
380
381 foreach my $file ($dist->get_files()) {
382 # Make us a bit idempotent.
383 next if $file->{filename} =~ m/\.buildinfo$/;
384
385 my $path = "$uploadfilesdir/$file->{filename}";
386 $checksums->add_from_file($path, key => $file->{filename});
387
388 if (defined $file->{package_type} and $file->{package_type} =~ m/^u?deb$/) {
389 push @archvalues, $file->{arch}
390 if defined $file->{arch} and not $archadded{$file->{arch}}++;
391 }
392 }
393}
394
395$fields->{'Format'} = $buildinfo_format;
396$fields->{'Source'} = $spackage;
397$fields->{'Binary'} = join(' ', map { $_->{'Package'} } $control->get_packages());
398# Avoid overly long line by splitting over multiple lines.
399if (length($fields->{'Binary'}) > 980) {
400 $fields->{'Binary'} =~ s/(.{0,980}) /$1\n/g;
401}
402
403$fields->{'Architecture'} = join ' ', sort @archvalues;
404$fields->{'Version'} = $binaryversion;
405
406if ($changelog->{'Binary-Only'}) {
407 $fields->{'Source'} .= ' (' . $sourceversion . ')';
408 $fields->{'Binary-Only-Changes'} =
409 $changelog->{'Changes'} . "\n\n"
410 . ' -- ' . $changelog->{'Maintainer'}
411 . ' ' . $changelog->{'Date'};
412}
413
414$fields->{'Build-Origin'} = get_current_vendor();
415$fields->{'Build-Architecture'} = get_build_arch();
416$fields->{'Build-Date'} = get_build_date();
417
418my $cwd = cwd();
419if ($use_feature{path}) {
420 $fields->{'Build-Path'} = $cwd;
421} else {
422 # Only include the build path if its root path is considered acceptable
423 # by the vendor.
424 foreach my $root_path (run_vendor_hook('builtin-system-build-paths')) {
425 if (index($cwd, $root_path) == 0) {
426 $fields->{'Build-Path'} = $cwd;
427 last;
428 }
429 }
430}
431
432$checksums->export_to_control($fields);
433
434$fields->{'Installed-Build-Depends'} = collect_installed_builddeps($control);
435
436$fields->{'Environment'} = "\n" . cleansed_environment();
437
438# Generate the buildinfo filename.
439if ($stdout) {
440 # Nothing to do.
441} elsif (defined $outputfile) {
442 $buildinfo = basename($outputfile);
443} else {
444 my $arch;
445
446 if (build_has_any(BUILD_ARCH_DEP)) {
447 $arch = get_host_arch();
448 } elsif (build_has_any(BUILD_ARCH_INDEP)) {
449 $arch = 'all';
450 } elsif (build_has_any(BUILD_SOURCE)) {
451 $arch = 'source';
452 }
453
454 my $bversion = $binaryversion->as_string(omit_epoch => 1);
455 $buildinfo = "${spackage}_${bversion}_${arch}.buildinfo";
456 $outputfile = "$uploadfilesdir/$buildinfo";
457}
458
459# Write out the generated .buildinfo file.
460
461if ($stdout) {
462 $fields->output(\*STDOUT);
463} else {
464 my $section = $control->get_source->{'Section'} || '-';
465 my $priority = $control->get_source->{'Priority'} || '-';
466
467 # Obtain a lock on debian/control to avoid simultaneous updates
468 # of debian/files when parallel building is in use
469 my $lockfh;
470 my $lockfile = 'debian/control';
471 $lockfile = $controlfile if not -e $lockfile;
472
473 sysopen $lockfh, $lockfile, O_WRONLY
474 or syserr(g_('cannot write %s'), $lockfile);
475 file_lock($lockfh, $lockfile);
476
477 $dist = Dpkg::Dist::Files->new();
478 $dist->load($fileslistfile) if -e $fileslistfile;
479
480 foreach my $file ($dist->get_files()) {
481 if (defined $file->{package} &&
482 $file->{package} eq $spackage &&
483 $file->{package_type} eq 'buildinfo' &&
484 (debarch_eq($file->{arch}, $fields->{'Architecture'}) ||
485 debarch_eq($file->{arch}, 'all') ||
486 debarch_eq($file->{arch}, 'source'))) {
487 $dist->del_file($file->{filename});
488 }
489 }
490
491 $dist->add_file($buildinfo, $section, $priority);
492 $dist->save("$fileslistfile.new");
493
494 rename "$fileslistfile.new", $fileslistfile
495 or syserr(g_('install new files list file'));
496
497 # Release the lock
498 close $lockfh or syserr(g_('cannot close %s'), $lockfile);
499
500 $fields->save("$outputfile.new");
501
502 rename "$outputfile.new", $outputfile
503 or syserr(g_("cannot install output buildinfo file '%s'"), $outputfile);
504}
505
5061;