summaryrefslogtreecommitdiffstats
path: root/scripts/dpkg-genbuildinfo.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/dpkg-genbuildinfo.pl')
-rwxr-xr-xscripts/dpkg-genbuildinfo.pl597
1 files changed, 597 insertions, 0 deletions
diff --git a/scripts/dpkg-genbuildinfo.pl b/scripts/dpkg-genbuildinfo.pl
new file mode 100755
index 0000000..ae0c125
--- /dev/null
+++ b/scripts/dpkg-genbuildinfo.pl
@@ -0,0 +1,597 @@
+#!/usr/bin/perl
+#
+# dpkg-genbuildinfo
+#
+# Copyright © 1996 Ian Jackson
+# Copyright © 2000,2001 Wichert Akkerman
+# Copyright © 2003-2013 Yann Dirson <dirson@debian.org>
+# Copyright © 2006-2016 Guillem Jover <guillem@debian.org>
+# Copyright © 2014 Niko Tyni <ntyni@debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+
+use List::Util qw(any);
+use Cwd;
+use File::Basename;
+use File::Temp;
+use POSIX qw(:fcntl_h :locale_h strftime);
+
+use Dpkg ();
+use Dpkg::Gettext;
+use Dpkg::Checksums;
+use Dpkg::ErrorHandling;
+use Dpkg::IPC;
+use Dpkg::Path qw(find_command);
+use Dpkg::Arch qw(
+ get_build_arch
+ get_host_arch
+ debarch_eq debarch_to_gnutriplet
+);
+use Dpkg::BuildTypes;
+use Dpkg::BuildOptions;
+use Dpkg::BuildFlags;
+use Dpkg::BuildProfiles qw(get_build_profiles);
+use Dpkg::BuildInfo qw(get_build_env_allowed);
+use Dpkg::Control::Info;
+use Dpkg::Control::Fields;
+use Dpkg::Control;
+use Dpkg::Changelog::Parse;
+use Dpkg::Deps;
+use Dpkg::Dist::Files;
+use Dpkg::Lock;
+use Dpkg::Version;
+use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
+
+textdomain('dpkg-dev');
+
+my $controlfile = 'debian/control';
+my $changelogfile = 'debian/changelog';
+my $changelogformat;
+my $fileslistfile = 'debian/files';
+my $uploadfilesdir = '..';
+my $outputfile;
+my $stdout = 0;
+my $admindir = $Dpkg::ADMINDIR;
+my %use_feature = (
+ kernel => 0,
+ path => 0,
+);
+my @build_profiles = get_build_profiles();
+my $buildinfo_format = '1.0';
+my $buildinfo;
+
+my $checksums = Dpkg::Checksums->new();
+my %distbinaries;
+my %archadded;
+my @archvalues;
+
+sub get_build_date {
+ my $date;
+
+ setlocale(LC_TIME, 'C');
+ $date = strftime('%a, %d %b %Y %T %z', localtime);
+ setlocale(LC_TIME, '');
+
+ return $date;
+}
+
+# There is almost the same function in dpkg-checkbuilddeps, they probably
+# should be factored out.
+sub parse_status {
+ my $status = shift;
+
+ my $facts = Dpkg::Deps::KnownFacts->new();
+ my %depends;
+ my @essential_pkgs;
+
+ local $/ = '';
+ open my $status_fh, '<', $status or syserr(g_('cannot open %s'), $status);
+ while (<$status_fh>) {
+ next unless /^Status: .*ok installed$/m;
+
+ my ($package) = /^Package: (.*)$/m;
+ my ($version) = /^Version: (.*)$/m;
+ my ($arch) = /^Architecture: (.*)$/m;
+ my ($multiarch) = /^Multi-Arch: (.*)$/m;
+
+ $facts->add_installed_package($package, $version, $arch, $multiarch);
+
+ if (/^Essential: yes$/m) {
+ push @essential_pkgs, $package;
+ }
+
+ if (/^Provides: (.*)$/m) {
+ my $provides = deps_parse($1, reduce_arch => 1, union => 1);
+
+ next if not defined $provides;
+
+ deps_iterate($provides, sub {
+ my $dep = shift;
+ $facts->add_provided_package($dep->{package}, $dep->{relation},
+ $dep->{version}, $package);
+ });
+ }
+
+ foreach my $deptype (qw(Pre-Depends Depends)) {
+ next unless /^$deptype: (.*)$/m;
+
+ my $depends = $1;
+ foreach (split /,\s*/, $depends) {
+ push @{$depends{"$package:$arch"}}, $_;
+ }
+ }
+ }
+ close $status_fh;
+
+ return ($facts, \%depends, \@essential_pkgs);
+}
+
+sub append_deps {
+ my ($pkgs, @deps) = @_;
+
+ foreach my $dep_str (@deps) {
+ next unless $dep_str;
+
+ my $deps = deps_parse($dep_str, reduce_restrictions => 1,
+ build_dep => 1,
+ build_profiles => \@build_profiles);
+
+ # We add every sub-dependencies as we cannot know which package in
+ # an OR dependency has been effectively used.
+ deps_iterate($deps, sub {
+ my $pkg = shift;
+
+ push @{$pkgs},
+ $pkg->{package} . (defined $pkg->{archqual} ? ':' . $pkg->{archqual} : '');
+ 1
+ });
+ }
+}
+
+sub collect_installed_builddeps {
+ my $control = shift;
+
+ my ($facts, $depends, $essential_pkgs) = parse_status("$admindir/status");
+ my %seen_pkgs;
+ my @unprocessed_pkgs;
+
+ # Parse essential packages list.
+ append_deps(\@unprocessed_pkgs,
+ @{$essential_pkgs},
+ run_vendor_hook('builtin-build-depends'),
+ $control->get_source->{'Build-Depends'});
+
+ if (build_has_any(BUILD_ARCH_DEP)) {
+ append_deps(\@unprocessed_pkgs,
+ $control->get_source->{'Build-Depends-Arch'});
+ }
+
+ if (build_has_any(BUILD_ARCH_INDEP)) {
+ append_deps(\@unprocessed_pkgs,
+ $control->get_source->{'Build-Depends-Indep'});
+ }
+
+ my $installed_deps = Dpkg::Deps::AND->new();
+
+ while (my $pkg_name = shift @unprocessed_pkgs) {
+ next if $seen_pkgs{$pkg_name};
+ $seen_pkgs{$pkg_name} = 1;
+
+ my $required_architecture;
+ if ($pkg_name =~ /\A(.*):(.*)\z/) {
+ $pkg_name = $1;
+ my $arch = $2;
+ $required_architecture = $arch if $arch !~ /\A(?:all|any|native)\Z/
+ }
+ my $pkg;
+ my $qualified_pkg_name;
+ foreach my $installed_pkg (@{$facts->{pkg}->{$pkg_name}}) {
+ if (!defined $required_architecture ||
+ $required_architecture eq $installed_pkg->{architecture}) {
+ $pkg = $installed_pkg;
+ $qualified_pkg_name = $pkg_name . ':' . $installed_pkg->{architecture};
+ last;
+ }
+ }
+ if (defined $pkg) {
+ my $version = $pkg->{version};
+ my $architecture = $pkg->{architecture};
+ my $new_deps_str = defined $depends->{$qualified_pkg_name} ? deps_concat(@{$depends->{$qualified_pkg_name}}) : '';
+ my $new_deps = deps_parse($new_deps_str);
+ if (!defined $required_architecture) {
+ $installed_deps->add(Dpkg::Deps::Simple->new("$pkg_name (= $version)"));
+ } else {
+ $installed_deps->add(Dpkg::Deps::Simple->new("$qualified_pkg_name (= $version)"));
+
+ # Dependencies of foreign packages are also foreign packages
+ # (or Arch:all) so we need to qualify them as well. We figure
+ # out if the package is actually foreign by searching for an
+ # installed package of the right architecture.
+ deps_iterate($new_deps, sub {
+ my $dep = shift;
+ return unless defined $facts->{pkg}->{$dep->{package}};
+ $dep->{archqual} //= $architecture
+ if any { $_[0]->{architecture} eq $architecture }, @{$facts->{pkg}->{$dep->{package}}};
+ 1;
+ });
+ }
+
+ # We add every sub-dependencies as we cannot know which package
+ # in an OR dependency has been effectively used.
+ deps_iterate($new_deps, sub {
+ push @unprocessed_pkgs,
+ $_[0]->{package} . (defined $_[0]->{archqual} ? ':' . $_[0]->{archqual} : '');
+ 1
+ });
+ } elsif (defined $facts->{virtualpkg}->{$pkg_name}) {
+ # virtual package: we cannot know for sure which implementation
+ # is the one that has been used, so let's add them all...
+ foreach my $provided (@{$facts->{virtualpkg}->{$pkg_name}}) {
+ push @unprocessed_pkgs, $provided->{provider};
+ }
+ }
+ # else: it is a package in an OR dependency that has been otherwise
+ # satisfied.
+ }
+ $installed_deps->simplify_deps(Dpkg::Deps::KnownFacts->new());
+ $installed_deps->sort();
+ $installed_deps = "\n" . $installed_deps->output();
+ $installed_deps =~ s/, /,\n/g;
+
+ return $installed_deps;
+}
+
+sub is_cross_executable {
+ my $host_arch = get_host_arch();
+ my $build_arch = get_build_arch();
+
+ return if $host_arch eq $build_arch;
+
+ # If we are cross-compiling, record whether it was possible to execute
+ # the host architecture by cross-compiling and executing a small
+ # host-arch binary.
+ my $CC = debarch_to_gnutriplet($host_arch) . '-gcc';
+
+ # If we do not have a cross-compiler, we might be in the process of
+ # building one or cross-compiling using a language other than C/C++,
+ # and aborting the build is then not very useful.
+ return if ! find_command($CC);
+
+ my $crossprog = <<~'CROSSPROG';
+ #include <unistd.h>
+ int main() { write(1, "ok", 2); return 0; }
+ CROSSPROG
+ my ($stdout, $stderr) = ('', '');
+ my $tmpfh = File::Temp->new();
+ spawn(
+ exec => [ $CC, '-w', '-x', 'c', '-o', $tmpfh->filename, '-' ],
+ from_string => \$crossprog,
+ to_string => \$stdout,
+ error_to_string => \$stderr,
+ wait_child => 1,
+ nocheck => 1,
+ );
+ if ($?) {
+ print { *STDOUT } $stdout;
+ print { *STDERR } $stderr;
+ eval {
+ subprocerr("$CC -w -x c -");
+ };
+ warning($@);
+ return;
+ }
+ close $tmpfh;
+ spawn(
+ exec => [ $tmpfh->filename ],
+ error_to_file => '/dev/null',
+ to_string => \$stdout,
+ wait_child => 1,
+ nocheck => 1,
+ );
+
+ return 1 if $? == 0 && $stdout eq 'ok';
+ return 0;
+}
+
+sub get_build_tainted_by {
+ my @tainted = run_vendor_hook('build-tainted-by');
+
+ if (is_cross_executable()) {
+ push @tainted, 'can-execute-cross-built-programs';
+ }
+
+ return @tainted;
+}
+
+sub cleansed_environment {
+ # Consider only allowed variables which are not supposed to leak
+ # local user information.
+ my %env = map {
+ $_ => $ENV{$_}
+ } grep {
+ exists $ENV{$_}
+ } get_build_env_allowed();
+
+ # Record flags from dpkg-buildflags.
+ my $bf = Dpkg::BuildFlags->new();
+ $bf->load_system_config();
+ $bf->load_user_config();
+ $bf->load_environment_config();
+ foreach my $flag ($bf->list()) {
+ next if $bf->get_origin($flag) eq 'vendor';
+
+ # We do not need to record *_{STRIP,APPEND,PREPEND} as they
+ # have been used already to compute the above value.
+ $env{"DEB_${flag}_SET"} = $bf->get($flag);
+ }
+
+ return join "\n", map { $_ . '="' . ($env{$_} =~ s/"/\\"/gr) . '"' }
+ sort keys %env;
+}
+
+sub version {
+ printf g_("Debian %s version %s.\n"), $Dpkg::PROGNAME, $Dpkg::PROGVERSION;
+
+ printf g_('
+This is free software; see the GNU General Public License version 2 or
+later for copying conditions. There is NO warranty.
+');
+}
+
+sub usage {
+ printf g_(
+'Usage: %s [<option>...]')
+ . "\n\n" . g_(
+"Options:
+ --build=<type>[,...] specify the build <type>: full, source, binary,
+ any, all (default is \'full\').
+ -c<control-file> get control info from this file.
+ -l<changelog-file> get per-version info from this file.
+ -f<files-list-file> get .deb files list from this file.
+ -F<changelog-format> force changelog format.
+ -O[<buildinfo-file>] write to stdout (or <buildinfo-file>).
+ -u<upload-files-dir> directory with files (default is '..').
+ --always-include-kernel always include Build-Kernel-Version.
+ --always-include-path always include Build-Path.
+ --admindir=<directory> change the administrative directory.
+ -?, --help show this help message.
+ --version show the version.
+"), $Dpkg::PROGNAME;
+}
+
+my $build_opts = Dpkg::BuildOptions->new();
+$build_opts->parse_features('buildinfo', \%use_feature);
+
+while (@ARGV) {
+ $_ = shift @ARGV ;
+ if (m/^--build=(.*)$/) {
+ set_build_type_from_options($1, $_);
+ } elsif (m/^-c(.*)$/) {
+ $controlfile = $1;
+ } elsif (m/^-l(.*)$/) {
+ $changelogfile = $1;
+ } elsif (m/^-f(.*)$/) {
+ $fileslistfile = $1;
+ } elsif (m/^-F([0-9a-z]+)$/) {
+ $changelogformat = $1;
+ } elsif (m/^-u(.*)$/) {
+ $uploadfilesdir = $1;
+ } elsif (m/^-O$/) {
+ $stdout = 1;
+ } elsif (m/^-O(.*)$/) {
+ $outputfile = $1;
+ } elsif (m/^(--buildinfo-id)=.*$/) {
+ # Deprecated option
+ warning(g_('%s is deprecated; it is without effect'), $1);
+ } elsif (m/^--always-include-kernel$/) {
+ $use_feature{kernel} = 1;
+ } elsif (m/^--always-include-path$/) {
+ $use_feature{path} = 1;
+ } elsif (m/^--admindir=(.*)$/) {
+ $admindir = $1;
+ } elsif (m/^-(?:\?|-help)$/) {
+ usage();
+ exit(0);
+ } elsif (m/^--version$/) {
+ version();
+ exit(0);
+ } else {
+ usageerr(g_("unknown option '%s'"), $_);
+ }
+}
+
+my $control = Dpkg::Control::Info->new($controlfile);
+my $fields = Dpkg::Control->new(type => CTRL_FILE_BUILDINFO);
+my $dist = Dpkg::Dist::Files->new();
+
+# Retrieve info from the current changelog entry.
+my %options = (file => $changelogfile);
+$options{changelogformat} = $changelogformat if $changelogformat;
+my $changelog = changelog_parse(%options);
+
+# Retrieve info from the former changelog entry to handle binNMUs.
+$options{count} = 1;
+$options{offset} = 1;
+my $prev_changelog = changelog_parse(%options);
+
+my $sourceversion = Dpkg::Version->new($changelog->{'Binary-Only'} ?
+ $prev_changelog->{'Version'} : $changelog->{'Version'});
+my $binaryversion = Dpkg::Version->new($changelog->{'Version'});
+
+# Include .dsc if available.
+my $spackage = $changelog->{'Source'};
+my $sversion = $sourceversion->as_string(omit_epoch => 1);
+
+if (build_has_any(BUILD_SOURCE)) {
+ my $dsc = "${spackage}_${sversion}.dsc";
+
+ $checksums->add_from_file("$uploadfilesdir/$dsc", key => $dsc);
+
+ push @archvalues, 'source';
+}
+
+my $dist_count = 0;
+
+$dist_count = $dist->load($fileslistfile) if -e $fileslistfile;
+
+if (build_has_any(BUILD_BINARY)) {
+ error(g_('binary build with no binary artifacts found; .buildinfo is meaningless'))
+ if $dist_count == 0;
+
+ foreach my $file ($dist->get_files()) {
+ # Make us a bit idempotent.
+ next if $file->{filename} =~ m/\.buildinfo$/;
+
+ if (defined $file->{arch}) {
+ my $arch_all = debarch_eq('all', $file->{arch});
+
+ next if build_has_none(BUILD_ARCH_INDEP) and $arch_all;
+ next if build_has_none(BUILD_ARCH_DEP) and not $arch_all;
+
+ $distbinaries{$file->{package}} = 1 if defined $file->{package};
+ }
+
+ my $path = "$uploadfilesdir/$file->{filename}";
+ $checksums->add_from_file($path, key => $file->{filename});
+
+ if (defined $file->{package_type} and $file->{package_type} =~ m/^u?deb$/) {
+ push @archvalues, $file->{arch}
+ if defined $file->{arch} and not $archadded{$file->{arch}}++;
+ }
+ }
+}
+
+$fields->{'Format'} = $buildinfo_format;
+$fields->{'Source'} = $spackage;
+$fields->{'Binary'} = join(' ', sort keys %distbinaries);
+# Avoid overly long line by splitting over multiple lines.
+if (length($fields->{'Binary'}) > 980) {
+ $fields->{'Binary'} =~ s/(.{0,980}) /$1\n/g;
+}
+
+$fields->{'Architecture'} = join ' ', sort @archvalues;
+$fields->{'Version'} = $binaryversion;
+
+if ($changelog->{'Binary-Only'}) {
+ $fields->{'Source'} .= ' (' . $sourceversion . ')';
+ $fields->{'Binary-Only-Changes'} =
+ $changelog->{'Changes'} . "\n\n"
+ . ' -- ' . $changelog->{'Maintainer'}
+ . ' ' . $changelog->{'Date'};
+}
+
+$fields->{'Build-Origin'} = get_current_vendor();
+$fields->{'Build-Architecture'} = get_build_arch();
+$fields->{'Build-Date'} = get_build_date();
+
+if ($use_feature{kernel}) {
+ my ($kern_rel, $kern_ver);
+
+ ((undef) x 2, $kern_rel, $kern_ver, undef) = POSIX::uname();
+ $fields->{'Build-Kernel-Version'} = "$kern_rel $kern_ver";
+}
+
+my $cwd = getcwd();
+if ($use_feature{path}) {
+ $fields->{'Build-Path'} = $cwd;
+} else {
+ # Only include the build path if its root path is considered acceptable
+ # by the vendor.
+ foreach my $root_path (run_vendor_hook('builtin-system-build-paths')) {
+ if (index($cwd, $root_path) == 0) {
+ $fields->{'Build-Path'} = $cwd;
+ last;
+ }
+ }
+}
+
+$fields->{'Build-Tainted-By'} = "\n" . join "\n", get_build_tainted_by();
+
+$checksums->export_to_control($fields);
+
+$fields->{'Installed-Build-Depends'} = collect_installed_builddeps($control);
+
+$fields->{'Environment'} = "\n" . cleansed_environment();
+
+# Generate the buildinfo filename.
+if ($stdout) {
+ # Nothing to do.
+} elsif (defined $outputfile) {
+ $buildinfo = basename($outputfile);
+} else {
+ my $arch;
+
+ if (build_has_any(BUILD_ARCH_DEP)) {
+ $arch = get_host_arch();
+ } elsif (build_has_any(BUILD_ARCH_INDEP)) {
+ $arch = 'all';
+ } elsif (build_has_any(BUILD_SOURCE)) {
+ $arch = 'source';
+ }
+
+ my $bversion = $binaryversion->as_string(omit_epoch => 1);
+ $buildinfo = "${spackage}_${bversion}_${arch}.buildinfo";
+ $outputfile = "$uploadfilesdir/$buildinfo";
+}
+
+# Write out the generated .buildinfo file.
+
+if ($stdout) {
+ $fields->output(\*STDOUT);
+} else {
+ my $section = $control->get_source->{'Section'} || '-';
+ my $priority = $control->get_source->{'Priority'} || '-';
+
+ # Obtain a lock on debian/control to avoid simultaneous updates
+ # of debian/files when parallel building is in use
+ my $lockfh;
+ my $lockfile = 'debian/control';
+ $lockfile = $controlfile if not -e $lockfile;
+
+ sysopen $lockfh, $lockfile, O_WRONLY
+ or syserr(g_('cannot write %s'), $lockfile);
+ file_lock($lockfh, $lockfile);
+
+ $dist = Dpkg::Dist::Files->new();
+ $dist->load($fileslistfile) if -e $fileslistfile;
+
+ foreach my $file ($dist->get_files()) {
+ if (defined $file->{package} &&
+ $file->{package} eq $spackage &&
+ $file->{package_type} eq 'buildinfo' &&
+ (debarch_eq($file->{arch}, $fields->{'Architecture'}) ||
+ debarch_eq($file->{arch}, 'all') ||
+ debarch_eq($file->{arch}, 'source'))) {
+ $dist->del_file($file->{filename});
+ }
+ }
+
+ $dist->add_file($buildinfo, $section, $priority);
+ $dist->save("$fileslistfile.new");
+
+ rename "$fileslistfile.new", $fileslistfile
+ or syserr(g_('install new files list file'));
+
+ # Release the lock
+ close $lockfh or syserr(g_('cannot close %s'), $lockfile);
+
+ $fields->save("$outputfile.new");
+
+ rename "$outputfile.new", $outputfile
+ or syserr(g_("cannot install output buildinfo file '%s'"), $outputfile);
+}