summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Debian/Debhelper/Buildsystem.pm203
-rw-r--r--lib/Debian/Debhelper/Buildsystem/autoconf.pm4
-rw-r--r--lib/Debian/Debhelper/Buildsystem/cmake.pm61
-rw-r--r--lib/Debian/Debhelper/Buildsystem/makefile.pm17
-rw-r--r--lib/Debian/Debhelper/Buildsystem/meson.pm38
-rw-r--r--lib/Debian/Debhelper/Buildsystem/ninja.pm44
-rw-r--r--lib/Debian/Debhelper/Buildsystem/python_distutils.pm7
-rw-r--r--lib/Debian/Debhelper/Buildsystem/qmake.pm64
-rw-r--r--lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm6
-rw-r--r--lib/Debian/Debhelper/Dh_Buildsystems.pm80
-rw-r--r--lib/Debian/Debhelper/Dh_Lib.pm761
-rw-r--r--lib/Debian/Debhelper/Sequence/dwz.pm6
-rw-r--r--lib/Debian/Debhelper/Sequence/installinitramfs.pm14
-rw-r--r--lib/Debian/Debhelper/SequencerUtil.pm152
14 files changed, 1132 insertions, 325 deletions
diff --git a/lib/Debian/Debhelper/Buildsystem.pm b/lib/Debian/Debhelper/Buildsystem.pm
index 4d2ee728..8176c7bf 100644
--- a/lib/Debian/Debhelper/Buildsystem.pm
+++ b/lib/Debian/Debhelper/Buildsystem.pm
@@ -16,10 +16,22 @@ use Debian::Debhelper::Dh_Lib;
# name. Do not override this method unless you know what you are
# doing.
sub NAME {
- my $this=shift;
- my $class = ref($this) || $this;
+ my ($this) = @_;
+ my $class = ref($this);
+ my $target_name;
+ if ($class) {
+ # Do not assume that the target buildsystem has been provided.
+ # NAME could be called during an error in the constructor.
+ if ($this->IS_GENERATOR_BUILD_SYSTEM and $this->get_targetbuildsystem) {
+ $target_name = $this->get_targetbuildsystem->NAME;
+ }
+ } else {
+ $class = $this;
+ }
if ($class =~ m/^.+::([^:]+)$/) {
- return $1;
+ my $name = $1;
+ return "${name}+${target_name}" if defined($target_name);
+ return $name;
}
else {
error("ınvalid build system class name: $class");
@@ -37,6 +49,43 @@ sub DEFAULT_BUILD_DIRECTORY {
"obj-" . dpkg_architecture_value("DEB_HOST_GNU_TYPE");
}
+# Return 1 if the build system generator
+sub IS_GENERATOR_BUILD_SYSTEM {
+ return 0;
+}
+
+# Generator build-systems only
+# The name of the supported target systems. The first one is
+# assumed to be the default if DEFAULT_TARGET_BUILD_SYSTEM is
+# not overridden.
+sub SUPPORTED_TARGET_BUILD_SYSTEMS {
+ error("class lacking SUPPORTED_TARGET_BUILD_SYSTEMS");
+}
+
+# Generator build-systems only
+# Name of default target build system if target is unspecified
+# (e.g. --buildsystem=cmake instead of cmake+makefile).
+sub DEFAULT_TARGET_BUILD_SYSTEM {
+ my ($this) = @_;
+ my @targets = $this->SUPPORTED_TARGET_BUILD_SYSTEMS;
+ # Assume they are listed in order.
+ return $targets[0];
+}
+
+# For regular build systems, the same as DESCRIPTION
+# For generator based build systems, the DESCRIPTION of the generator build
+# system + the target build system. Do not override this method unless you
+# know what you are doing.
+sub FULL_DESCRIPTION {
+ my ($this) = @_;
+ my $description = $this->DESCRIPTION;
+ return $description if not exists($this->{'targetbuildsystem'});
+ my $target_build_system = $this->{'targetbuildsystem'};
+ return $description if not defined($target_build_system);
+ my $target_desc = $target_build_system->FULL_DESCRIPTION;
+ return "${description} combined with ${target_desc}";
+}
+
# Constructs a new build system object. Named parameters:
# - sourcedir- specifies source directory (relative to the current (top)
# directory) where the sources to be built live. If not
@@ -46,6 +95,8 @@ sub DEFAULT_BUILD_DIRECTORY {
# DEFAULT_BUILD_DIRECTORY directory will be used.
# - parallel - max number of parallel processes to be spawned for building
# sources (-1 = unlimited; 1 = no parallel)
+# - targetbuildsystem - The target build system for generator based build
+# systems. Only set for generator build systems.
# Derived class can override the constructor to initialize common object
# parameters. Do NOT use constructor to execute commands or otherwise
# configure/setup build environment. There is absolutely no guarantee the
@@ -59,6 +110,23 @@ sub new {
parallel => undef,
cwd => Cwd::getcwd() }, $class);
+ # Setup the target buildsystem early, so e.g. _set_builddir also
+ # applies to the target build system. Useful if the generator
+ # and target does not agree on (e.g.) the default build dir.
+ my $target_bs_name;
+ if (exists $opts{targetbuildsystem}) {
+ $target_bs_name = $opts{targetbuildsystem};
+ }
+
+ $target_bs_name //= $this->DEFAULT_TARGET_BUILD_SYSTEM if $this->IS_GENERATOR_BUILD_SYSTEM;
+
+ if (defined($target_bs_name)) {
+ my %target_opts = %opts;
+ delete($target_opts{'targetbuildsystem'});
+ my $target_system =_create_buildsystem_instance($target_bs_name, 1, %target_opts);
+ $this->set_targetbuildsystem($target_system);
+ }
+
if (exists $opts{sourcedir}) {
# Get relative sourcedir abs_path (without symlinks)
my $abspath = Cwd::abs_path($opts{sourcedir});
@@ -73,6 +141,7 @@ sub new {
if (defined $opts{parallel}) {
$this->{parallel} = $opts{parallel};
}
+
return $this;
}
@@ -101,9 +170,42 @@ sub _set_builddir {
}
}
$this->{builddir} = $builddir;
+ # Use get as guard because this method is (also) called from the
+ # constructor before the target build system is setup.
+ if ($this->get_targetbuildsystem) {
+ $this->get_targetbuildsystem->{builddir} = $builddir;
+ };
return $builddir;
}
+sub set_targetbuildsystem {
+ my ($this, $target_system) = @_;
+ my $ok = 0;
+ my $target_bs_name = $target_system->NAME;
+ if (not $this->IS_GENERATOR_BUILD_SYSTEM) {
+ my $name = $this->NAME;
+ error("Cannot set a target build system: Buildsystem ${name} is not a generator build system");
+ }
+ for my $supported_bs_name ($this->SUPPORTED_TARGET_BUILD_SYSTEMS) {
+ if ($supported_bs_name eq $target_bs_name) {
+ $ok = 1;
+ last;
+ }
+ }
+ if (not $ok) {
+ my $name = $this->NAME;
+ error("Buildsystem ${name} does not support ${target_bs_name} as target build system.");
+ }
+ $this->{'targetbuildsystem'} = $target_system
+}
+
+# Returns the target build system if it is provided
+sub get_targetbuildsystem {
+ my $this = shift;
+ return if not exists($this->{'targetbuildsystem'});
+ return $this->{'targetbuildsystem'};
+}
+
# This instance method is called to check if the build system is able
# to build a source package. It will be called during the build
# system auto-selection process, inside the root directory of the debian
@@ -137,6 +239,11 @@ sub enforce_in_source_building {
$this->{warn_insource} = 1;
$this->{builddir} = undef;
}
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->enforce_in_source_building(@_);
+ # Only warn in one build system.
+ delete($this->{warn_insource});
+ }
}
# Derived class can call this method in its constructor to *prefer*
@@ -155,6 +262,9 @@ sub prefer_out_of_source_building {
error("default build directory is the same as the source directory." .
" Please specify a custom build directory");
}
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->prefer_out_of_source_building(@_);
+ }
}
}
@@ -263,6 +373,9 @@ sub get_parallel {
sub disable_parallel {
my ($this) = @_;
$this->{parallel} = 1;
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->disable_parallel;
+ }
}
# When given a relative path to the build directory, converts it
@@ -291,6 +404,17 @@ sub mkdir_builddir {
}
}
+sub check_auto_buildable_clean_oos_buildir {
+ my $this = shift;
+ my ($step) = @_;
+ # This only applies to clean
+ return 0 if $step ne 'clean';
+ my $builddir = $this->get_builddir;
+ # If there is no builddir, then this rule does not apply.
+ return 0 if not defined($builddir) or not -d $builddir;
+ return 1;
+}
+
sub _cd {
my ($this, $dir)=@_;
verbose_print("cd $dir");
@@ -315,12 +439,22 @@ sub _in_dir {
return $code->(@args);
}
+sub _generic_doit_in_dir {
+ my ($this, $dir, $sub, @args) = @_;
+ my %args;
+ if (ref($args[0])) {
+ %args = %{shift(@args)};
+ }
+ $args{chdir} = $dir;
+ return $sub->(\%args, @args);
+}
+
# Changes working directory to the source directory (if needed),
# calls print_and_doit(@_) and changes working directory back to the
# top directory.
sub doit_in_sourcedir {
my ($this, @args) = @_;
- print_and_doit({ chdir => $this->get_sourcedir }, @args);
+ $this->_generic_doit_in_dir($this->get_sourcedir, \&print_and_doit, @args);
return 1;
}
@@ -329,7 +463,7 @@ sub doit_in_sourcedir {
# top directory. Errors are ignored.
sub doit_in_sourcedir_noerror {
my ($this, @args) = @_;
- return print_and_doit_noerror({ chdir => $this->get_sourcedir }, @args);
+ return $this->_generic_doit_in_dir($this->get_sourcedir, \&print_and_doit_noerror, @args);
}
# Changes working directory to the build directory (if needed),
@@ -337,7 +471,7 @@ sub doit_in_sourcedir_noerror {
# top directory.
sub doit_in_builddir {
my ($this, @args) = @_;
- print_and_doit({ chdir => $this->get_buildpath }, @args);
+ $this->_generic_doit_in_dir($this->get_buildpath, \&print_and_doit, @args);
return 1;
}
@@ -346,15 +480,7 @@ sub doit_in_builddir {
# top directory. Errors are ignored.
sub doit_in_builddir_noerror {
my ($this, @args) = @_;
- return print_and_doit_noerror({ chdir => $this->get_buildpath }, @args);
-}
-
-# Changes working directory to the build directory (if needed),
-# calls print_and_complex_doit(@_) and changes working directory back to the
-# top directory.
-sub complex_doit_in_builddir {
- my ($this, @args) = @_;
- return $this->_in_dir($this->get_buildpath, \&print_and_complex_doit, @args);
+ return $this->_generic_doit_in_dir($this->get_buildpath, \&print_and_doit_noerror, @args);
}
# In case of out of source tree building, whole build directory
@@ -402,6 +528,9 @@ sub pre_building_step {
" does not support building out of source tree. In source building enforced.");
delete $this->{warn_insource};
}
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->pre_building_step(@_);
+ }
}
# Instance method that is called after performing any step (see below).
@@ -410,6 +539,9 @@ sub pre_building_step {
sub post_building_step {
my $this=shift;
my ($step)=@_;
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->post_building_step(@_);
+ }
}
# The instance methods below provide support for configuring,
@@ -420,26 +552,65 @@ sub post_building_step {
# implement build system specific steps needed to build the
# source. Arbitrary number of custom step arguments might be
# passed. Default implementations do nothing.
+#
+# Note: For generator build systems, the default is to
+# delegate the step to the target build system for all
+# steps except configure.
sub configure {
my $this=shift;
}
sub build {
my $this=shift;
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->build(@_);
+ }
}
sub test {
my $this=shift;
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->test(@_);
+ }
}
# destdir parameter specifies where to install files.
sub install {
my $this=shift;
- my $destdir=shift;
+ my ($destdir) = @_;
+
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->install(@_);
+ }
}
sub clean {
my $this=shift;
+
+ if ($this->IS_GENERATOR_BUILD_SYSTEM) {
+ $this->get_targetbuildsystem->clean(@_);
+ }
+}
+
+
+sub _create_buildsystem_instance {
+ my ($full_name, $required, %bsopts) = @_;
+ my @parts = split(m{[+]}, $full_name, 2);
+ my $name = $parts[0];
+ my $module = "Debian::Debhelper::Buildsystem::$name";
+ if (@parts > 1) {
+ if (exists($bsopts{'targetbuildsystem'})) {
+ error("Conflicting target buildsystem for ${name} (load as ${full_name}, but target configured in bsopts)");
+ }
+ $bsopts{'targetbuildsystem'} = $parts[1];
+ }
+
+ eval "use $module";
+ if ($@) {
+ return if not $required;
+ error("unable to load build system class '$name': $@");
+ }
+ return $module->new(%bsopts);
}
1
diff --git a/lib/Debian/Debhelper/Buildsystem/autoconf.pm b/lib/Debian/Debhelper/Buildsystem/autoconf.pm
index e31951b0..142c27ea 100644
--- a/lib/Debian/Debhelper/Buildsystem/autoconf.pm
+++ b/lib/Debian/Debhelper/Buildsystem/autoconf.pm
@@ -47,10 +47,10 @@ sub configure {
if (! compat(8)) {
if (defined $multiarch) {
push @opts, "--libdir=\${prefix}/lib/$multiarch";
- push @opts, "--libexecdir=\${prefix}/lib/$multiarch";
+ push(@opts, "--libexecdir=\${prefix}/lib/$multiarch") if not compat(11);
}
else {
- push @opts, "--libexecdir=\${prefix}/lib";
+ push(@opts, "--libexecdir=\${prefix}/lib") if not compat(11);
}
}
else {
diff --git a/lib/Debian/Debhelper/Buildsystem/cmake.pm b/lib/Debian/Debhelper/Buildsystem/cmake.pm
index 5e3ce082..c732ba41 100644
--- a/lib/Debian/Debhelper/Buildsystem/cmake.pm
+++ b/lib/Debian/Debhelper/Buildsystem/cmake.pm
@@ -9,7 +9,7 @@ package Debian::Debhelper::Buildsystem::cmake;
use strict;
use warnings;
use Debian::Debhelper::Dh_Lib qw(compat dpkg_architecture_value error is_cross_compiling);
-use parent qw(Debian::Debhelper::Buildsystem::makefile);
+use parent qw(Debian::Debhelper::Buildsystem);
my @STANDARD_CMAKE_FLAGS = qw(
-DCMAKE_INSTALL_PREFIX=/usr
@@ -27,16 +27,35 @@ my %DEB_HOST2CMAKE_SYSTEM = (
'hurd' => 'GNU',
);
+my %TARGET_BUILD_SYSTEM2CMAKE_GENERATOR = (
+ 'makefile' => 'Unix Makefiles',
+ 'ninja' => 'Ninja',
+);
+
sub DESCRIPTION {
"CMake (CMakeLists.txt)"
}
+sub IS_GENERATOR_BUILD_SYSTEM {
+ return 1;
+}
+
+sub SUPPORTED_TARGET_BUILD_SYSTEMS {
+ return qw(makefile ninja);
+}
+
sub check_auto_buildable {
my $this=shift;
my ($step)=@_;
if (-e $this->get_sourcepath("CMakeLists.txt")) {
my $ret = ($step eq "configure" && 1) ||
- $this->SUPER::check_auto_buildable(@_);
+ $this->get_targetbuildsystem->check_auto_buildable(@_);
+ if ($this->check_auto_buildable_clean_oos_buildir(@_)) {
+ # Assume that the package can be cleaned (i.e. the build directory can
+ # be removed) as long as it is built out-of-source tree and can be
+ # configured.
+ $ret++ if not $ret;
+ }
# Existence of CMakeCache.txt indicates cmake has already
# been used by a prior build step, so should be used
# instead of the parent makefile class.
@@ -57,11 +76,22 @@ sub configure {
my $this=shift;
# Standard set of cmake flags
my @flags = @STANDARD_CMAKE_FLAGS;
+ my $backend = $this->get_targetbuildsystem->NAME;
if (not compat(10)) {
push(@flags, '-DCMAKE_INSTALL_RUNSTATEDIR=/run');
}
+ if (exists($TARGET_BUILD_SYSTEM2CMAKE_GENERATOR{$backend})) {
+ my $generator = $TARGET_BUILD_SYSTEM2CMAKE_GENERATOR{$backend};
+ push(@flags, "-G${generator}");
+ }
+ if ($ENV{CC}) {
+ push @flags, "-DCMAKE_C_COMPILER=" . $ENV{CC};
+ }
+ if ($ENV{CXX}) {
+ push @flags, "-DCMAKE_CXX_COMPILER=" . $ENV{CXX};
+ }
if (is_cross_compiling()) {
my $deb_host = dpkg_architecture_value("DEB_HOST_ARCH_OS");
if (my $cmake_system = $DEB_HOST2CMAKE_SYSTEM{$deb_host}) {
@@ -70,20 +100,16 @@ sub configure {
error("Cannot cross-compile - CMAKE_SYSTEM_NAME not known for ${deb_host}");
}
push @flags, "-DCMAKE_SYSTEM_PROCESSOR=" . dpkg_architecture_value("DEB_HOST_GNU_CPU");
- if ($ENV{CC}) {
- push @flags, "-DCMAKE_C_COMPILER=" . $ENV{CC};
- } else {
+ if (not $ENV{CC}) {
push @flags, "-DCMAKE_C_COMPILER=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-gcc";
}
- if ($ENV{CXX}) {
- push @flags, "-DCMAKE_CXX_COMPILER=" . $ENV{CXX};
- } else {
+ if (not $ENV{CXX}) {
push @flags, "-DCMAKE_CXX_COMPILER=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-g++";
}
push(@flags, "-DPKG_CONFIG_EXECUTABLE=/usr/bin/" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-pkg-config");
push(@flags, "-DPKGCONFIG_EXECUTABLE=/usr/bin/" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-pkg-config");
- push(@flags, "-DCMAKE_INSTALL_LIBDIR=lib/" . dpkg_architecture_value("DEB_HOST_MULTIARCH"));
}
+ push(@flags, "-DCMAKE_INSTALL_LIBDIR=lib/" . dpkg_architecture_value("DEB_HOST_MULTIARCH"));
# CMake doesn't respect CPPFLAGS, see #653916.
if ($ENV{CPPFLAGS} && ! compat(8)) {
@@ -93,7 +119,7 @@ sub configure {
$this->mkdir_builddir();
eval {
- $this->doit_in_builddir("cmake", $this->get_source_rel2builddir(), @flags, @_);
+ $this->doit_in_builddir("cmake", @flags, @_, $this->get_source_rel2builddir());
};
if (my $err = $@) {
if (-e $this->get_buildpath("CMakeCache.txt")) {
@@ -111,13 +137,16 @@ sub configure {
sub test {
my $this=shift;
-
- # Unlike make, CTest does not have "unlimited parallel" setting (-j implies
- # -j1). So in order to simulate unlimited parallel, allow to fork a huge
- # number of threads instead.
- my $parallel = ($this->get_parallel() > 0) ? $this->get_parallel() : 999;
+ my $target = $this->get_targetbuildsystem;
$ENV{CTEST_OUTPUT_ON_FAILURE} = 1;
- return $this->SUPER::test(@_, "ARGS+=-j$parallel");
+ if ($target->NAME eq 'makefile') {
+ # Unlike make, CTest does not have "unlimited parallel" setting (-j implies
+ # -j1). So in order to simulate unlimited parallel, allow to fork a huge
+ # number of threads instead.
+ my $parallel = ($this->get_parallel() > 0) ? $this->get_parallel() : 999;
+ push(@_, "ARGS+=-j$parallel")
+ }
+ return $this->SUPER::test(@_);
}
1
diff --git a/lib/Debian/Debhelper/Buildsystem/makefile.pm b/lib/Debian/Debhelper/Buildsystem/makefile.pm
index 49a368db..052af0b4 100644
--- a/lib/Debian/Debhelper/Buildsystem/makefile.pm
+++ b/lib/Debian/Debhelper/Buildsystem/makefile.pm
@@ -80,7 +80,14 @@ sub do_make {
clean_jobserver_makeflags();
# Note that this will override any -j settings in MAKEFLAGS.
- unshift @_, "-j" . ($this->get_parallel() > 0 ? $this->get_parallel() : "");
+ my $parallel = $this->get_parallel();
+ if ($parallel == 0 or $parallel > 1) {
+ # We have to use the empty string for "unlimited"
+ $parallel = '' if $parallel == 0;
+ unshift(@_, "-j${parallel}");
+ } else {
+ unshift(@_, '-j1');
+ }
my @root_cmd;
if (exists($this->{_run_make_as_root}) and $this->{_run_make_as_root}) {
@@ -124,9 +131,8 @@ sub check_auto_buildable {
# This is always called in the source directory, but generally
# Makefiles are created (or live) in the build directory.
return 1;
- } elsif ($step eq "clean" && defined $this->get_builddir() &&
- $this->check_auto_buildable("configure"))
- {
+ } elsif ($this->check_auto_buildable_clean_oos_buildir(@_)
+ and $this->check_auto_buildable('configure')) {
# Assume that the package can be cleaned (i.e. the build directory can
# be removed) as long as it is built out-of-source tree and can be
# configured. This is useful for derivative buildsystems which
@@ -139,7 +145,8 @@ sub check_auto_buildable {
sub build {
my $this=shift;
if (ref($this) eq 'Debian::Debhelper::Buildsystem::makefile' and is_cross_compiling()) {
- while (my ($var, $tool) = each %DEB_DEFAULT_TOOLS) {
+ for my $var (sort(keys(%DEB_DEFAULT_TOOLS))) {
+ my $tool = $DEB_DEFAULT_TOOLS{$var};
if ($ENV{$var}) {
unshift @_, $var . "=" . $ENV{$var};
} else {
diff --git a/lib/Debian/Debhelper/Buildsystem/meson.pm b/lib/Debian/Debhelper/Buildsystem/meson.pm
index dcad89f9..890112bd 100644
--- a/lib/Debian/Debhelper/Buildsystem/meson.pm
+++ b/lib/Debian/Debhelper/Buildsystem/meson.pm
@@ -8,12 +8,21 @@ package Debian::Debhelper::Buildsystem::meson;
use strict;
use warnings;
use Debian::Debhelper::Dh_Lib qw(dpkg_architecture_value is_cross_compiling doit warning error generated_file);
-use parent qw(Debian::Debhelper::Buildsystem::ninja);
+use parent qw(Debian::Debhelper::Buildsystem);
sub DESCRIPTION {
"Meson (meson.build)"
}
+sub IS_GENERATOR_BUILD_SYSTEM {
+ return 1;
+}
+
+sub SUPPORTED_TARGET_BUILD_SYSTEMS {
+ return qw(ninja);
+}
+
+
sub check_auto_buildable {
my $this=shift;
my ($step)=@_;
@@ -22,7 +31,14 @@ sub check_auto_buildable {
# Handle configure explicitly; inherit the rest
return 1 if $step eq "configure";
- return $this->SUPER::check_auto_buildable(@_);
+ my $ret = $this->get_targetbuildsystem->check_auto_buildable(@_);
+ if ($ret == 0 and $this->check_auto_buildable_clean_oos_buildir(@_)) {
+ # Assume that the package can be cleaned (i.e. the build directory can
+ # be removed) as long as it is built out-of-source tree and can be
+ # configured.
+ $ret++;
+ }
+ return $ret;
}
sub new {
@@ -45,7 +61,7 @@ sub configure {
push @opts, "--localstatedir=/var";
my $multiarch=dpkg_architecture_value("DEB_HOST_MULTIARCH");
push @opts, "--libdir=lib/$multiarch";
- push @opts, "--libexecdir=lib/$multiarch";
+ push(@opts, "--libexecdir=lib/$multiarch") if not compat(11);
if (is_cross_compiling()) {
# http://mesonbuild.com/Cross-compilation.html
@@ -57,7 +73,11 @@ sub configure {
error("Cannot cross-compile: Please use meson (>= 0.42.1) or provide a cross file via DH_MESON_CROSS_FILE");
}
my $filename = generated_file('_source', 'meson-cross-file.conf');
- doit({ stdout => '/dev/null' }, $debcrossgen, "-o${filename}");
+ my %options = (
+ stdout => '/dev/null',
+ update_env => { LC_ALL => 'C.UTF-8'},
+ );
+ doit(\%options, $debcrossgen, "-o${filename}");
$cross_file = $filename;
}
if ($cross_file !~ m{^/}) {
@@ -71,7 +91,10 @@ sub configure {
$this->mkdir_builddir();
eval {
- $this->doit_in_builddir("meson", $this->get_source_rel2builddir(), @opts, @_);
+ my %options = (
+ update_env => { LC_ALL => 'C.UTF-8'},
+ );
+ $this->doit_in_builddir(\%options, "meson", $this->get_source_rel2builddir(), @opts, @_);
};
if ($@) {
if (-e $this->get_buildpath("meson-logs/meson-log.txt")) {
@@ -81,9 +104,4 @@ sub configure {
}
}
-sub test {
- my $this=shift;
- return $this->SUPER::test(@_);
-}
-
1
diff --git a/lib/Debian/Debhelper/Buildsystem/ninja.pm b/lib/Debian/Debhelper/Buildsystem/ninja.pm
index 5d6c874d..c08ff166 100644
--- a/lib/Debian/Debhelper/Buildsystem/ninja.pm
+++ b/lib/Debian/Debhelper/Buildsystem/ninja.pm
@@ -30,52 +30,60 @@ sub check_auto_buildable {
# This is always called in the source directory, but generally
# Ninja files are created (or live) in the build directory.
return 1;
- } elsif ($step eq "clean" && defined $this->get_builddir() &&
- $this->check_auto_buildable("configure"))
- {
- # Assume that the package can be cleaned (i.e. the build directory can
- # be removed) as long as it is built out-of-source tree and can be
- # configured. This is useful for derivative buildsystems which
- # generate Ninja files.
- return 1;
}
return 0;
}
sub build {
my $this=shift;
-
+ my %options = (
+ update_env => {
+ 'LC_ALL' => 'C.UTF-8',
+ }
+ );
if (!$dh{QUIET}) {
unshift @_, "-v";
}
if ($this->get_parallel() > 0) {
unshift @_, "-j" . $this->get_parallel();
}
- $this->doit_in_builddir($this->{buildcmd}, @_);
+ $this->doit_in_builddir(\%options, $this->{buildcmd}, @_);
}
sub test {
my $this=shift;
-
+ my %options = (
+ update_env => {
+ 'LC_ALL' => 'C.UTF-8',
+ }
+ );
if ($this->get_parallel() > 0) {
- $ENV{MESON_TESTTHREADS}=$this->get_parallel();
+ $options{update_env}{MESON_TESTTHREADS} = $this->get_parallel();
}
- $this->doit_in_builddir($this->{buildcmd}, "test", @_);
+ $this->doit_in_builddir(\%options, $this->{buildcmd}, "test", @_);
}
sub install {
my $this=shift;
my $destdir=shift;
-
- $ENV{DESTDIR}=$destdir;
- $this->doit_in_builddir($this->{buildcmd}, "install", @_);
+ my %options = (
+ update_env => {
+ 'LC_ALL' => 'C.UTF-8',
+ 'DESTDIR' => $destdir,
+ }
+ );
+ $this->doit_in_builddir(\%options, $this->{buildcmd}, "install", @_);
}
sub clean {
my $this=shift;
-
if (!$this->rmdir_builddir()) {
- $this->doit_in_builddir($this->{buildcmd}, "clean", @_);
+ my %options = (
+ update_env => {
+ 'LC_ALL' => 'C.UTF-8',
+ }
+ );
+ $this->doit_in_builddir(\%options, $this->{buildcmd}, "clean", @_);
}
}
diff --git a/lib/Debian/Debhelper/Buildsystem/python_distutils.pm b/lib/Debian/Debhelper/Buildsystem/python_distutils.pm
index 4c2a5f8d..e5fe7edc 100644
--- a/lib/Debian/Debhelper/Buildsystem/python_distutils.pm
+++ b/lib/Debian/Debhelper/Buildsystem/python_distutils.pm
@@ -10,11 +10,11 @@ package Debian::Debhelper::Buildsystem::python_distutils;
use strict;
use warnings;
use Cwd ();
-use Debian::Debhelper::Dh_Lib qw(error);
+use Debian::Debhelper::Dh_Lib qw(error deprecated_functionality);
use parent qw(Debian::Debhelper::Buildsystem);
sub DESCRIPTION {
- "Python Distutils (setup.py)"
+ "Python Distutils (setup.py) [DEPRECATED]"
}
sub DEFAULT_BUILD_DIRECTORY {
@@ -60,6 +60,9 @@ sub pre_building_step {
my $this=shift;
my $step=shift;
+ deprecated_functionality('Please use the third-party "pybuild" build system instead of python-distutils',
+ 12);
+
return unless grep /$step/, qw(build install clean);
if ($this->get_buildpath() ne $this->DEFAULT_BUILD_DIRECTORY()) {
diff --git a/lib/Debian/Debhelper/Buildsystem/qmake.pm b/lib/Debian/Debhelper/Buildsystem/qmake.pm
index 6e7f87f1..18b896d8 100644
--- a/lib/Debian/Debhelper/Buildsystem/qmake.pm
+++ b/lib/Debian/Debhelper/Buildsystem/qmake.pm
@@ -8,11 +8,9 @@ package Debian::Debhelper::Buildsystem::qmake;
use strict;
use warnings;
-use Debian::Debhelper::Dh_Lib qw(dpkg_architecture_value error generated_file is_cross_compiling);
+use Debian::Debhelper::Dh_Lib qw(dpkg_architecture_value error is_cross_compiling);
use parent qw(Debian::Debhelper::Buildsystem::makefile);
-our $qmake="qmake";
-
my %OS_MKSPEC_MAPPING = (
'linux' => 'linux-g++',
'kfreebsd' => 'gnukfreebsd-g++',
@@ -65,38 +63,6 @@ sub configure {
} else {
error("Cannot cross-compile: Missing entry for HOST OS ${host_os} for qmake's -spec option");
}
-
- my $filename = generated_file('_source', 'qmake-cross.conf');
- my $host_multiarch = dpkg_architecture_value("DEB_HOST_MULTIARCH");
- open(my $fh, '>', $filename) or error("open($filename) failed: $!");
-
- $fh->print("[Paths]\n");
- $fh->print("Prefix=/usr\n");
- $fh->print("ArchData=lib/$host_multiarch/qt5\n");
- $fh->print("Binaries=lib/qt5/bin\n");
- $fh->print("Data=share/qt5\n");
- $fh->print("Documentation=share/qt5/doc\n");
- $fh->print("Examples=lib/$host_multiarch/qt5/examples\n");
- $fh->print("Headers=include/$host_multiarch/qt5\n");
- $fh->print("HostBinaries=lib/qt5/bin\n");
- $fh->print("HostData=lib/$host_multiarch/qt5\n");
- $fh->print("HostLibraries=lib/$host_multiarch\n");
- $fh->print("Imports=lib/$host_multiarch/qt5/imports\n");
- $fh->print("Libraries=lib/$host_multiarch\n");
- $fh->print("LibraryExecutables=lib/$host_multiarch/qt5/libexec\n");
- $fh->print("Plugins=lib/$host_multiarch/qt5/plugins\n");
- $fh->print("Qml2Imports=lib/$host_multiarch/qt5/qml\n");
- $fh->print("Settings=/etc/xdg\n");
- $fh->print("Translations=share/qt5/translations\n");
-
- close($fh) or error("close($filename) failed: $!");
- if ($filename !~ m{^/}) {
- # Make the file name absolute (just in case qmake cares).
- require Cwd;
- $filename =~ s{^\./}{};
- $filename = Cwd::cwd() . "/${filename}";
- }
- push @options, ("-qtconf", $filename);
}
if ($ENV{CFLAGS}) {
@@ -114,27 +80,8 @@ sub configure {
push @flags, "QMAKE_STRIP=:";
push @flags, "PREFIX=/usr";
- if (is_cross_compiling()) {
- # qmake calls $$QMAKE_CXX in toolchain.prf to get a list of library/include paths,
- # we need -early flag to make sure $$QMAKE_CXX is already properly set on that step.
- push @flags, "-early";
- if ($ENV{CC}) {
- push @flags, "QMAKE_CC=" . $ENV{CC};
- } else {
- push @flags, "QMAKE_CC=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-gcc";
- }
- if ($ENV{CXX}) {
- push @flags, "QMAKE_CXX=" . $ENV{CXX};
- push @flags, "QMAKE_LINK=" . $ENV{CXX};
- } else {
- push @flags, "QMAKE_CXX=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-g++";
- push @flags, "QMAKE_LINK=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-g++";
- }
- push @flags, "PKG_CONFIG=" . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-pkg-config";
- }
-
$this->mkdir_builddir();
- $this->doit_in_builddir($qmake, @options, @flags, @_);
+ $this->doit_in_builddir($this->_qmake(), @options, @flags, @_);
}
sub install {
@@ -146,4 +93,11 @@ sub install {
$this->SUPER::install($destdir, "INSTALL_ROOT=$destdir", @_);
}
+sub _qmake {
+ if (is_cross_compiling()) {
+ return dpkg_architecture_value("DEB_HOST_GNU_TYPE") . "-qmake";
+ }
+ return 'qmake';
+}
+
1
diff --git a/lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm b/lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm
index ac7ce98a..60d9084c 100644
--- a/lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm
+++ b/lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm
@@ -8,10 +8,8 @@ sub DESCRIPTION {
"qmake for QT 4 (*.pro)";
}
-sub configure {
- my $this=shift;
- $Debian::Debhelper::Buildsystem::qmake::qmake="qmake-qt4";
- $this->SUPER::configure(@_);
+sub _qmake {
+ return 'qmake-qt4';
}
1
diff --git a/lib/Debian/Debhelper/Dh_Buildsystems.pm b/lib/Debian/Debhelper/Dh_Buildsystems.pm
index 7d4b421c..a386507c 100644
--- a/lib/Debian/Debhelper/Dh_Buildsystems.pm
+++ b/lib/Debian/Debhelper/Dh_Buildsystems.pm
@@ -8,6 +8,7 @@ package Debian::Debhelper::Dh_Buildsystems;
use strict;
use warnings;
+use Debian::Debhelper::Buildsystem;
use Debian::Debhelper::Dh_Lib;
use File::Spec;
@@ -25,11 +26,12 @@ our @BUILDSYSTEMS = (
"makefile",
"python_distutils",
(compat(7) ? "perl_build" : ()),
- "cmake",
+ "cmake+makefile",
+ "cmake+ninja",
"ant",
"qmake",
"qmake_qt4",
- "meson",
+ "meson+ninja",
"ninja",
);
@@ -44,16 +46,10 @@ my $opt_builddir;
my $opt_list;
my $opt_parallel;
-sub create_buildsystem_instance {
- my ($system, $required, %bsopts) = @_;
- my $module = "Debian::Debhelper::Buildsystem::$system";
-
- eval "use $module";
- if ($@) {
- return if not $required;
- error("unable to load build system class '$system': $@");
- }
+*create_buildsystem_instance = \&Debian::Debhelper::Buildsystem::_create_buildsystem_instance;
+sub _insert_cmd_opts {
+ my (%bsopts) = @_;
if (!exists $bsopts{builddir} && defined $opt_builddir) {
$bsopts{builddir} = ($opt_builddir eq "") ? undef : $opt_builddir;
}
@@ -63,7 +59,7 @@ sub create_buildsystem_instance {
if (!exists $bsopts{parallel}) {
$bsopts{parallel} = $opt_parallel;
}
- return $module->new(%bsopts);
+ return %bsopts;
}
# Autoselect a build system from the list of instances
@@ -73,9 +69,15 @@ sub autoselect_buildsystem {
my $selected_level = 0;
foreach my $inst (@_) {
- # Only derived (i.e. more specific) build system can be
- # considered beyond the currently selected one.
- next if defined $selected && !$inst->isa(ref $selected);
+ # Only more specific build system can be considered beyond
+ # the currently selected one.
+ if (defined($selected)) {
+ my $ok = $inst->isa(ref($selected)) ? 1 : 0;
+ if (not $ok and $inst->IS_GENERATOR_BUILD_SYSTEM) {
+ $ok = 1 if $inst->get_targetbuildsystem->NAME eq $selected->NAME;
+ }
+ next if not $ok;
+ }
# If the build system says it is auto-buildable at the current
# step and it can provide more specific information about its
@@ -95,24 +97,25 @@ sub autoselect_buildsystem {
sub load_buildsystem {
my $system=shift;
my $step=shift;
+ my %opts = _insert_cmd_opts(@_);
my $system_options;
if (defined($system) && ref($system) eq 'HASH') {
$system_options = $system;
$system = $system_options->{'system'};
}
if (defined $system) {
- my $inst = create_buildsystem_instance($system, 1, @_);
+ my $inst = create_buildsystem_instance($system, 1, %opts);
return $inst;
}
else {
# Try to determine build system automatically
my @buildsystems;
foreach $system (@BUILDSYSTEMS) {
- push @buildsystems, create_buildsystem_instance($system, 1, @_);
+ push @buildsystems, create_buildsystem_instance($system, 1, %opts);
}
if (!$system_options || $system_options->{'enable-thirdparty'}) {
foreach $system (@THIRD_PARTY_BUILDSYSTEMS) {
- push @buildsystems, create_buildsystem_instance($system, 0, @_);
+ push @buildsystems, create_buildsystem_instance($system, 0, %opts);
}
}
return autoselect_buildsystem($step, @buildsystems);
@@ -121,7 +124,8 @@ sub load_buildsystem {
sub load_all_buildsystems {
my $incs=shift || \@INC;
- my (%buildsystems, @buildsystems);
+ my %opts = _insert_cmd_opts(@_);
+ my (%buildsystems, %genbuildsystems, @buildsystems);
foreach my $inc (@$incs) {
my $path = File::Spec->catdir($inc, "Debian/Debhelper/Buildsystem");
@@ -129,8 +133,19 @@ sub load_all_buildsystems {
foreach my $module_path (glob "$path/*.pm") {
my $name = basename($module_path);
$name =~ s/\.pm$//;
- next if exists $buildsystems{$name};
- $buildsystems{$name} = create_buildsystem_instance($name, 1, @_);
+ next if exists $buildsystems{$name} or exists $genbuildsystems{$name};
+ my $system = create_buildsystem_instance($name, 1, %opts);
+ if ($system->IS_GENERATOR_BUILD_SYSTEM) {
+ $genbuildsystems{$name} = 1;
+ for my $target_name ($system->SUPPORTED_TARGET_BUILD_SYSTEMS) {
+ my $full_name = "${name}+${target_name}";
+ my $full_system = create_buildsystem_instance($name, 1, %opts,
+ 'targetbuildsystem' => $target_name);
+ $buildsystems{$full_name} = $full_system;
+ }
+ } else {
+ $buildsystems{$name} = $system;
+ }
}
}
}
@@ -209,22 +224,31 @@ sub buildsystems_list {
my @buildsystems = load_all_buildsystems();
my %auto_selectable = map { $_ => 1 } @THIRD_PARTY_BUILDSYSTEMS;
my $auto = autoselect_buildsystem($step, grep { ! $_->{thirdparty} || $auto_selectable{$_->NAME} } @buildsystems);
- my $specified;
+ my $specified_text;
+
+ if ($opt_buildsys) {
+ for my $inst (@buildsystems) {
+ my $full_name = $inst->NAME;
+ if ($full_name eq $opt_buildsys) {
+ $specified_text = $full_name;
+ } elsif ($inst->IS_GENERATOR_BUILD_SYSTEM and ref($inst)->NAME eq $opt_buildsys) {
+ my $default = $inst->DEFAULT_TARGET_BUILD_SYSTEM;
+ $specified_text = "${opt_buildsys}+${default} (default for ${opt_buildsys})";
+ }
+ }
+ }
# List build systems (including auto and specified status)
foreach my $inst (@buildsystems) {
- if (! defined $specified && defined $opt_buildsys && $opt_buildsys eq $inst->NAME()) {
- $specified = $inst;
- }
- printf("%-20s %s", $inst->NAME(), $inst->DESCRIPTION());
+ printf("%-20s %s", $inst->NAME(), $inst->FULL_DESCRIPTION());
print " [3rd party]" if $inst->{thirdparty};
print "\n";
}
print "\n";
print "Auto-selected: ", $auto->NAME(), "\n" if defined $auto;
- print "Specified: ", $specified->NAME(), "\n" if defined $specified;
+ print "Specified: ", $specified_text, "\n" if defined $specified_text;
print "No system auto-selected or specified\n"
- if ! defined $auto && ! defined $specified;
+ if ! defined $auto && ! defined $specified_text;
}
sub buildsystems_do {
diff --git a/lib/Debian/Debhelper/Dh_Lib.pm b/lib/Debian/Debhelper/Dh_Lib.pm
index 37be1cc2..8d9f3476 100644
--- a/lib/Debian/Debhelper/Dh_Lib.pm
+++ b/lib/Debian/Debhelper/Dh_Lib.pm
@@ -7,6 +7,7 @@
package Debian::Debhelper::Dh_Lib;
use strict;
use warnings;
+use utf8;
use constant {
# Lowest compat level supported
@@ -14,9 +15,6 @@ use constant {
# Lowest compat level that does *not* cause deprecation
# warnings
'LOWEST_NON_DEPRECATED_COMPAT_LEVEL' => 9,
- # Highest "open-beta" compat level. Remember to notify
- # debian-devel@l.d.o before bumping this.
- 'BETA_TESTER_COMPAT' => 10,
# Highest compat level permitted
'MAX_COMPAT_LEVEL' => 12,
# Magic value for xargs
@@ -25,9 +23,6 @@ use constant {
'DH_BUILTIN_VERSION' => \'<DH_LIB_VERSION>', #'# Hi emacs.
# Default Package-Type / extension (must be aligned with dpkg)
'DEFAULT_PACKAGE_TYPE' => 'deb',
-
- # Kill-switch for R³ (for backports)
- 'DH_ENABLE_RRR_SUPPORT' => 1,
};
use constant {
@@ -37,40 +32,142 @@ use constant {
'DBGSYM_PACKAGE_TYPE' => DEFAULT_PACKAGE_TYPE,
};
-use Errno qw(ENOENT);
+use Errno qw(ENOENT EXDEV);
use Exporter qw(import);
use File::Glob qw(bsd_glob GLOB_CSH GLOB_NOMAGIC GLOB_TILDE);
our (@EXPORT, %dh);
-@EXPORT=qw(&init &doit &doit_noerror &complex_doit &verbose_print &error
- &nonquiet_print &print_and_doit &print_and_doit_noerror
- &warning &tmpdir &pkgfile &pkgext &pkgfilename &isnative
- &autoscript &filearray &filedoublearray &is_build_profile_active
- &getpackages &basename &dirname &xargs %dh &process_pkg
- &compat &addsubstvar &delsubstvar &excludefile &package_arch
- &package_is_arch_all &package_binary_arch &package_declared_arch
- &is_udeb &debhelper_script_subst &escape_shell
- &inhibit_log &load_log &write_log &commit_override_log
- &dpkg_architecture_value &sourcepackage &make_symlink
- &is_make_jobserver_unavailable &clean_jobserver_makeflags
- &cross_command &set_buildflags &get_buildoption
- &install_dh_config_file &error_exitcode &package_multiarch
- &install_file &install_prog &install_lib &install_dir
- &get_source_date_epoch &is_cross_compiling
- &generated_file &autotrigger &package_section
- &restore_file_on_clean &restore_all_files
- &open_gz &reset_perm_and_owner &deprecated_functionality
- &log_installed_files &buildarch &rename_path
- &on_pkgs_in_parallel &on_selected_pkgs_in_parallel
- &rm_files &make_symlink_raw_target &on_items_in_parallel
- XARGS_INSERT_PARAMS_HERE &glob_expand_error_handler_reject
- &glob_expand_error_handler_warn_and_discard &glob_expand
- &glob_expand_error_handler_silently_ignore DH_BUILTIN_VERSION
- &print_and_complex_doit &default_sourcedir &qx_cmd
- &compute_doc_main_package &is_so_or_exec_elf_file &hostarch
- &assert_opt_is_known_package &dbgsym_tmpdir &find_hardlinks
- &should_use_root &gain_root_cmd DEFAULT_PACKAGE_TYPE
- DBGSYM_PACKAGE_TYPE
-);
+@EXPORT = (
+ # debhelper basis functionality
+qw(
+ init
+ %dh
+ compat
+),
+ # External command tooling API
+qw(
+ doit
+ doit_noerror
+ qx_cmd
+ xargs
+ XARGS_INSERT_PARAMS_HERE
+ print_and_doit
+ print_and_doit_noerror
+
+ complex_doit
+ escape_shell
+),
+ # Logging/messaging/error handling
+qw(
+ error
+ error_exitcode
+ warning
+ verbose_print
+ nonquiet_print
+),
+ # Package related actions
+qw(
+ getpackages
+ sourcepackage
+ tmpdir
+ dbgsym_tmpdir
+ default_sourcedir
+ pkgfile
+ pkgext
+ pkgfilename
+ package_is_arch_all
+ package_binary_arch
+ package_declared_arch
+ package_multiarch
+ package_section
+ package_arch
+ process_pkg
+ compute_doc_main_package
+ isnative
+ is_udeb
+),
+ # File/path related actions
+qw(
+ basename
+ dirname
+ install_file
+ install_prog
+ install_lib
+ install_dir
+ install_dh_config_file
+ make_symlink
+ make_symlink_raw_target
+ rename_path
+ find_hardlinks
+ rm_files
+ excludefile
+ is_so_or_exec_elf_file
+ is_empty_dir
+ reset_perm_and_owner
+ log_installed_files
+
+ filearray
+ filedoublearray
+ glob_expand
+ glob_expand_error_handler_reject
+ glob_expand_error_handler_warn_and_discard
+ glob_expand_error_handler_silently_ignore
+ glob_expand_error_handler_reject_nomagic_warn_discard
+),
+ # Generate triggers, substvars, maintscripts, build-time temporary files
+qw(
+ autoscript
+ autotrigger
+ addsubstvar
+ delsubstvar
+
+ generated_file
+ restore_file_on_clean
+),
+ # Split tasks among different cores
+qw(
+ on_pkgs_in_parallel
+ on_items_in_parallel
+ on_selected_pkgs_in_parallel
+),
+ # R³ framework
+qw(
+ should_use_root
+ gain_root_cmd
+
+),
+ # Architecture, cross-tooling, build options and profiles
+qw(
+ dpkg_architecture_value
+ hostarch
+ cross_command
+ is_cross_compiling
+ is_build_profile_active
+ get_buildoption
+),
+ # Other
+qw(
+ open_gz
+ get_source_date_epoch
+ deprecated_functionality
+),
+ # Special-case functionality (e.g. tool specific), debhelper(-core) functionality and deprecated functions
+qw(
+ inhibit_log
+ load_log
+ write_log
+ commit_override_log
+ debhelper_script_subst
+ is_make_jobserver_unavailable
+ clean_jobserver_makeflags
+ set_buildflags
+ DEFAULT_PACKAGE_TYPE
+ DBGSYM_PACKAGE_TYPE
+ DH_BUILTIN_VERSION
+ assert_opt_is_known_package
+ restore_all_files
+
+ buildarch
+));
# The Makefile changes this if debhelper is installed in a PREFIX.
my $prefix="/usr";
@@ -78,6 +175,25 @@ my $prefix="/usr";
my $MAX_PROCS = get_buildoption("parallel") || 1;
my $DH_TOOL_VERSION;
+our $PKGNAME_REGEX = qr/[a-z0-9][-+\.a-z0-9]+/o;
+our $PKGVERSION_REGEX = qr/
+ (?: \d+ : )? # Optional epoch
+ [0-9][0-9A-Za-z.+:~]* # Upstream version (with no hyphens)
+ (?: - [0-9A-Za-z.+:~]+ )* # Optional debian revision (+ upstreams versions with hyphens)
+ /xoa;
+
+# From Policy 5.1:
+#
+# The field name is composed of US-ASCII characters excluding control
+# characters, space, and colon (i.e., characters in the ranges U+0021
+# (!) through U+0039 (9), and U+003B (;) through U+007E (~),
+# inclusive). Field names must not begin with the comment character
+# (U+0023 #), nor with the hyphen character (U+002D -).
+our $DEB822_FIELD_REGEX = qr/
+ [\x21\x22\x24-\x2C\x2F-\x39\x3B-\x7F] # First character
+ [\x21-\x39\x3B-\x7F]* # Subsequent characters (if any)
+ /xoa;
+
sub init {
my %params=@_;
@@ -177,7 +293,7 @@ sub init {
# If no error handling function was specified, just propagate
# errors out.
if (! exists $dh{ERROR_HANDLER} || ! defined $dh{ERROR_HANDLER}) {
- $dh{ERROR_HANDLER}='exit \$?';
+ $dh{ERROR_HANDLER}='exit 1';
}
$dh{U_PARAMS} //= [];
@@ -187,6 +303,10 @@ sub init {
# on, if it's exiting successfully.
my $write_log=1;
sub END {
+ # If there is no 'debian/control', then we are not being run from
+ # a package directory and then the write_log will not do what we
+ # expect.
+ return if not -f 'debian/control';
if ($? == 0 && $write_log && (compat(9, 1) || $ENV{DH_INTERNAL_OVERRIDE})) {
write_log(basename($0), @{$dh{DOPACKAGES}});
}
@@ -336,6 +456,15 @@ sub _doit {
if (defined(my $output = $options->{stdout})) {
open(STDOUT, '>', $output) or error("redirect STDOUT failed: $!");
}
+ if (defined(my $update_env = $options->{update_env})) {
+ while (my ($k, $v) = each(%{$update_env})) {
+ if (defined($v)) {
+ $ENV{$k} = $v;
+ } else {
+ delete($ENV{$k});
+ }
+ }
+ }
}
# Force execvp call to avoid shell. Apparently, even exec can
# involve a shell if you don't do this.
@@ -348,6 +477,25 @@ sub _format_cmdline {
my (@cmd) = @_;
my $options = ref($cmd[0]) ? shift(@cmd) : {};
my $cmd_line = escape_shell(@cmd);
+ if (defined(my $update_env = $options->{update_env})) {
+ my $need_env = 0;
+ my @params;
+ for my $key (sort(keys(%{$update_env}))) {
+ my $value = $update_env->{$key};
+ if (defined($value)) {
+ my $quoted_key = escape_shell($key);
+ push(@params, join('=', $quoted_key, escape_shell($value)));
+ # shell does not like: "FU BAR"=1 cmd
+ # if the ENV key has weird symbols, the best bet is to use env
+ $need_env = 1 if $quoted_key ne $key;
+ } else {
+ $need_env = 1;
+ push(@params, escape_shell("--unset=${key}"));
+ }
+ }
+ unshift(@params, 'env', '--') if $need_env;
+ $cmd_line = join(' ', @params, $cmd_line);
+ }
if (defined(my $dir = $options->{chdir})) {
$cmd_line = join(' ', 'cd', escape_shell($dir), '&&', $cmd_line) if $dir ne '.';
}
@@ -388,19 +536,6 @@ sub complex_doit {
}
}
-# Run a command and display the command to stdout except when quiet
-# Use print_and_doit() if you can, instead of this function, because
-# this function forks a shell. However, this function can handle more
-# complicated stuff like redirection.
-sub print_and_complex_doit {
- nonquiet_print(join(" ",@_));
-
- if (! $dh{NO_ACT}) {
- # The join makes system get a scalar so it forks off a shell.
- system(join(" ", @_)) == 0 || error_exitcode(join(" ", @_))
- }
-}
-
sub error_exitcode {
my $command=shift;
@@ -502,8 +637,16 @@ sub rename_path {
}
return 1 if $dh{NO_ACT};
if (not rename($source, $dest)) {
- my $files = escape_shell($source, $dest);
- error("mv $files: $!")
+ my $ok = 0;
+ if ($! == EXDEV) {
+ # Replay with a fork+exec to handle crossing two mount
+ # points (See #897569)
+ $ok = _doit('mv', $source, $dest);
+ }
+ if (not $ok) {
+ my $files = escape_shell($source, $dest);
+ error("mv $files: $!");
+ }
}
return 1;
}
@@ -620,7 +763,8 @@ sub error {
# Output a warning.
sub warning {
- my $message=shift;
+ my ($message) = @_;
+ $message //= '';
print STDERR basename($0).": $message\n";
}
@@ -645,13 +789,22 @@ sub dirname {
# Pass in a number, will return true iff the current compatibility level
# is less than or equal to that number.
+my $compat_from_bd;
{
my $warned_compat = $ENV{DH_INTERNAL_TESTSUITE_SILENT_WARNINGS} ? 1 : 0;
my $c;
+ # Used mainly for testing
+ sub resetcompat {
+ undef $c;
+ undef $compat_from_bd;
+ }
+
sub compat {
my $num=shift;
my $nowarn=shift;
+
+ getpackages() if not defined($compat_from_bd);
if (! defined $c) {
$c=1;
@@ -665,15 +818,23 @@ sub dirname {
}
else {
chomp $l;
- $c=$l;
- $c =~ s/^\s*+//;
- $c =~ s/\s*+$//;
- if ($c !~ m/^\d+$/) {
- error("debian/compat must contain a positive number (found: \"$c\")");
+ my $new_compat = $l;
+ $new_compat =~ s/^\s*+//;
+ $new_compat =~ s/\s*+$//;
+ if ($new_compat !~ m/^\d+$/) {
+ error("debian/compat must contain a positive number (found: \"${new_compat}\")");
}
+ if (defined($compat_from_bd) and $compat_from_bd != -1) {
+ warning("Please specify the debhelper compat level exactly once.");
+ warning(" * debian/compat requests compat ${new_compat}.");
+ warning(" * debian/control requests compat ${compat_from_bd} via \"debhelper-compat (= ${compat_from_bd})\"");
+ error("debhelper compat level specified both in debian/compat and via build-dependency on debhelper-compat");
+ }
+ $c = $new_compat;
}
- }
- elsif (not $nowarn) {
+ } elsif ($compat_from_bd != -1) {
+ $c = $compat_from_bd;
+ } elsif (not $nowarn) {
error("Please specify the compatibility level in debian/compat");
}
@@ -777,9 +938,10 @@ sub default_sourcedir {
for my $pkg (@{$package}) {
push(@try, "debian/${pkg}.${filename}");
if ($check_expensive) {
+ my $cross_type = uc(package_cross_type($pkg));
push(@try,
- "debian/${pkg}.${filename}.".hostarch(),
- "debian/${pkg}.${filename}.".dpkg_architecture_value("DEB_HOST_ARCH_OS"),
+ "debian/${pkg}.${filename}.".dpkg_architecture_value("DEB_${cross_type}_ARCH"),
+ "debian/${pkg}.${filename}.".dpkg_architecture_value("DEB_${cross_type}_ARCH_OS"),
);
}
}
@@ -787,9 +949,10 @@ sub default_sourcedir {
# Avoid checking for hostarch+hostos unless we have reason
# to believe that they exist.
if ($check_expensive) {
+ my $cross_type = uc(package_cross_type($package));
push(@try,
- "debian/${package}.${filename}.".hostarch(),
- "debian/${package}.${filename}.".dpkg_architecture_value("DEB_HOST_ARCH_OS"),
+ "debian/${package}.${filename}.".dpkg_architecture_value("DEB_${cross_type}_ARCH"),
+ "debian/${package}.${filename}.".dpkg_architecture_value("DEB_${cross_type}_ARCH_OS"),
);
}
push(@try, "debian/$package.$filename");
@@ -861,12 +1024,13 @@ sub pkgfilename {
if (not defined($res)) {
error("No changelog entries for $package!? (changelog file: ${isnative_changelog})");
}
- # Get the package version.
- $dh{VERSION} = $res->{'Version'};
- # Did the changelog parse fail?
- if ($dh{VERSION} eq q{}) {
- error("changelog parse failure");
+ my $version = $res->{'Version'};
+ # Do we have a valid version?
+ if (not defined($version) or not $version->is_valid) {
+ error("changelog parse failure; invalid or missing version");
}
+ # Get the package version.
+ $dh{VERSION} = $version->as_string;
# Is this a native Debian package?
if (index($dh{VERSION}, '-') > -1) {
@@ -905,12 +1069,20 @@ sub _tool_version {
# 3: filename of snippet
# 4: either text: shell-quoted sed to run on the snippet. Ie, 's/#PACKAGE#/$PACKAGE/'
# or a sub to run on each line of the snippet. Ie sub { s/#PACKAGE#/$PACKAGE/ }
+# or a hashref with keys being variables and values being their replacement. Ie. { PACKAGE => $PACKAGE }
+# 5: Internal usage only
sub autoscript {
- my ($package, $script, $filename, $sed) = @_;
+ my ($package, $script, $filename, $sed, $extra_options) = @_;
my $tool_version = _tool_version();
# This is the file we will modify.
my $outfile="debian/".pkgext($package)."$script.debhelper";
+ if ($extra_options && exists($extra_options->{'snippet-order'})) {
+ my $order = $extra_options->{'snippet-order'};
+ error("Internal error - snippet order set to unknown value: \"${order}\"")
+ if $order ne 'service';
+ $outfile = generated_file($package, "${script}.${order}");
+ }
# Figure out what shell script snippet to use.
my $infile;
@@ -1133,6 +1305,20 @@ sub glob_expand_error_handler_warn_and_discard {
return;
}
+# Emulates the "old" glob mechanism; not recommended for new code as
+# it permits some globs expand to nothing with only a warning.
+sub glob_expand_error_handler_reject_nomagic_warn_discard {
+ my ($pattern, $dir_ref) = @_;
+ for my $dir (@{$dir_ref}) {
+ my $full_pattern = "$dir/$pattern";
+ my @matches = bsd_glob($full_pattern, GLOB_CSH & ~(GLOB_TILDE));
+ if (@matches) {
+ goto \&glob_expand_error_handler_reject;
+ }
+ }
+ goto \&glob_expand_error_handler_warn_and_discard;
+}
+
sub glob_expand_error_handler_silently_ignore {
return;
}
@@ -1144,8 +1330,14 @@ sub glob_expand {
for my $pattern (@patterns) {
my @m;
for my $dir (@dirs) {
- @m = bsd_glob("$dir/$pattern", GLOB_CSH & ~(GLOB_NOMAGIC|GLOB_TILDE));
- last if @m;# > 1 or (@m and (-l $m[0] or -e _));
+ my $full_pattern = "$dir/$pattern";
+ @m = bsd_glob($full_pattern, GLOB_CSH & ~(GLOB_NOMAGIC|GLOB_TILDE));
+ last if @m;
+ # Handle "foo{bar}" pattern (#888251)
+ if (-l $full_pattern or -e _) {
+ push(@m, $full_pattern);
+ last;
+ }
}
if (not @m) {
$error_handler //= \&glob_expand_error_handler_reject;
@@ -1170,7 +1362,7 @@ sub filedoublearray {
require Cwd;
my $cmd=Cwd::abs_path($file);
$ENV{"DH_CONFIG_ACT_ON_PACKAGES"} = join(",", @{$dh{"DOPACKAGES"}});
- open (DH_FARRAY_IN, "$cmd |") || error("cannot run $file: $!");
+ open(DH_FARRAY_IN, '-|', $cmd) || error("cannot run $file: $!");
delete $ENV{"DH_CONFIG_ACT_ON_PACKAGES"};
}
else {
@@ -1209,7 +1401,22 @@ sub filedoublearray {
if (!close(DH_FARRAY_IN)) {
if ($x) {
- error("Error closing fd/process for $file: $!") if $!;
+ my ($err, $proc_err) = ($!, $?);
+ error("Error closing fd/process for $file: $err") if $err;
+ # The interpreter did not like the file for some reason.
+ # Lets check if the maintainer intended it to be
+ # executable.
+ if (not is_so_or_exec_elf_file($file) and not _has_shbang_line($file)) {
+ warning("$file is marked executable but does not appear to an executable config.");
+ warning();
+ warning("If $file is intended to be an executable config file, please ensure it can");
+ warning("be run as a stand-alone script/program (e.g. \"./${file}\")");
+ warning("Otherwise, please remove the executable bit from the file (e.g. chmod -x \"${file}\")");
+ warning();
+ warning('Please see "Executable debhelper config files" in debhelper(7) for more information.');
+ warning();
+ }
+ $? = $proc_err;
error_exitcode("$file (executable config)");
} else {
error("problem reading $file: $!");
@@ -1317,7 +1524,15 @@ sub is_cross_compiling {
# As a side effect, populates %package_arches and %package_types
# with the types of all packages (not only those returned).
my (%package_types, %package_arches, %package_multiarches, %packages_by_type,
- %package_sections, $sourcepackage, %rrr, %package_cross_type);
+ %package_sections, $sourcepackage, %package_cross_type, %dh_bd_sequences);
+
+# Resets the arrays; used mostly for testing
+sub resetpackages {
+ undef $sourcepackage;
+ %package_types = %package_arches = %package_multiarches =
+ %packages_by_type = %package_sections = %package_cross_type = ();
+ %dh_bd_sequences = ();
+}
# Returns source package name
sub sourcepackage {
@@ -1340,8 +1555,10 @@ sub getpackages {
my $package="";
my $arch="";
my $section="";
+ my $valid_pkg_re = qr{^${PKGNAME_REGEX}$}o;
my ($package_type, $multiarch, %seen, @profiles, $source_section,
- $included_in_build_profile, $cross_type, $cross_target_arch);
+ $included_in_build_profile, $cross_type, $cross_target_arch,
+ %bd_fields, $bd_field_value, %seen_fields);
if (exists $ENV{'DEB_BUILD_PROFILES'}) {
@profiles=split /\s+/, $ENV{'DEB_BUILD_PROFILES'};
}
@@ -1357,68 +1574,197 @@ sub getpackages {
s/\s+$//;
next if m/^\s*+\#/;
- if (/^Source:\s*(.*)/i) {
- $sourcepackage = $1;
- next;
- } elsif (/^Rules-Requires-Root:\s*(.*)/i) {
- for my $keyword (split(' ', $1)) {
- $rrr{$keyword} = 1;
+ if (/^\s/) {
+ if (not %seen_fields) {
+ error("Continuation line seen before first stanza in debian/control (line $.)");
}
+ # Continuation line
+ push(@{$bd_field_value}, $_) if $bd_field_value;
+ } elsif (not $_ and not %seen_fields) {
+ # Ignore empty lines before first stanza
next;
- } elsif (/^Section:\s(.*)$/i) {
- $source_section = $1;
- next;
+ } elsif ($_) {
+ my ($field_name, $value);
+
+ if (m/^($DEB822_FIELD_REGEX):\s*(.*)/o) {
+ ($field_name, $value) = (lc($1), $2);
+ if (exists($seen_fields{$field_name})) {
+ my $first_time = $seen_fields{$field_name};
+ error("${field_name}-field appears twice in the same stanza of debian/control. " .
+ "First time on line $first_time, second time: $.");
+ }
+ $seen_fields{$field_name} = $.;
+ $bd_field_value = undef;
+ } else {
+ # Invalid file
+ error("Parse error in debian/control, line $., read: $_");
+ }
+ if ($field_name eq 'source') {
+ $sourcepackage = $value;
+ if ($sourcepackage !~ $valid_pkg_re) {
+ error('Source-field must be a valid package name, ' .
+ "got: \"${sourcepackage}\", should match \"${valid_pkg_re}\"");
+ }
+ next;
+ } elsif ($field_name eq 'section') {
+ $source_section = $value;
+ next;
+ } elsif ($field_name =~ /^(?:build-depends(?:-arch|-indep)?)$/) {
+ $bd_field_value = [$value];
+ $bd_fields{$field_name} = $bd_field_value;
+ }
}
- next if not $_ and not defined($sourcepackage);
- last if (!$_ or eof); # end of stanza.
+ last if not $_ or eof;
}
error("could not find Source: line in control file.") if not defined($sourcepackage);
- $rrr{'binary-targets'} = 1 if not %rrr;
+ if (%bd_fields) {
+ my ($dh_compat_bd, $final_level);
+ for my $field (sort(keys(%bd_fields))) {
+ my $value = join(' ', @{$bd_fields{$field}});
+ $value =~ s/^\s*//;
+ $value =~ s/\s*(?:,\s*)?$//;
+ for my $dep (split(/\s*,\s*/, $value)) {
+ if ($dep =~ m/^debhelper-compat\s*[(]\s*=\s*(${PKGVERSION_REGEX})\s*[)]$/) {
+ my $version = $1;
+ if ($version =~m/^(\d+)\D.*$/) {
+ my $guessed_compat = $1;
+ warning("Please use the compat level as the exact version rather than the full version.");
+ warning(" Perhaps you meant: debhelper-compat (= ${guessed_compat})");
+ if ($field ne 'build-depends') {
+ warning(" * Also, please move the declaration to Build-Depends (it was found in ${field})");
+ }
+ error("Invalid compat level ${version}, derived from relation: ${dep}");
+ }
+ $final_level = $version;
+ error("Duplicate debhelper-compat build-dependency: ${dh_compat_bd} vs. ${dep}") if $dh_compat_bd;
+ error("The debhelper-compat build-dependency must be in the Build-Depends field (not $field)")
+ if $field ne 'build-depends';
+ $dh_compat_bd = $dep;
+ } elsif ($dep =~ m/^debhelper-compat\s*(?:\S.*)?$/) {
+ my $clevel = "${\MAX_COMPAT_LEVEL}";
+ eval {
+ require Debian::Debhelper::Dh_Version;
+ $clevel = $Debian::Debhelper::Dh_Version::version;
+ };
+ $clevel =~ s/^\d+\K\D.*$//;
+ warning("Found invalid debhelper-compat relation: ${dep}");
+ warning(" * Please format the relation as (example): debhelper-compat (= ${clevel})");
+ warning(" * Note that alternatives, architecture restrictions, build-profiles etc. are not supported.");
+ if ($field ne 'build-depends') {
+ warning(" * Also, please move the declaration to Build-Depends (it was found in ${field})");
+ }
+ warning(" * If this is not possible, then please remove the debhelper-compat relation and insert the");
+ warning(" compat level into the file debian/compat. (E.g. \"echo ${clevel} > debian/compat\")");
+ error("Could not parse desired debhelper compat level from relation: $dep");
+ }
+ # Build-Depends on dh-sequence-<foo> OR dh-sequence-<foo> (<op> <version>)
+ if ($dep =~ m/^dh-sequence-(${PKGNAME_REGEX})\s*(?:[(]\s*(?:[<>]?=|<<|>>)\s*(${PKGVERSION_REGEX})\s*[)])?$/) {
+ my $sequence = $1;
+ if ($field ne 'build-depends') {
+ warning("Ignoring dh sequence add-on request for sequenece ${sequence} via ${field}: Please move it to the Build-Depends field");
+ warning("The relation that triggered this warning was: ${dep} (from the ${field} field)");
+ next;
+ }
+ $dh_bd_sequences{$sequence} = 1;
+ }
+ }
+ }
+ $compat_from_bd = $final_level // -1;
+ } else {
+ $compat_from_bd = -1;
+ }
+
+ %seen_fields = ();
while (<$fd>) {
chomp;
s/\s+$//;
- if (/^Package:\s*(.*)/i) {
- $package=$1;
- # Detect duplicate package names in the same control file.
- if (! $seen{$package}) {
- $seen{$package}=1;
- }
- else {
- error("debian/control has a duplicate entry for $package");
+ if (m/^\#/) {
+ # Skip unless EOF for the special case where the last line
+ # is a comment line directly after the last stanza. In
+ # that case we need to "commit" the last stanza as well or
+ # we end up omitting the last package.
+ next if not eof;
+ $_ = '';
+ }
+
+
+ if (/^\s/) {
+ # Continuation line
+ if (not %seen_fields) {
+ error("Continuation line seen outside stanza in debian/control (line $.)");
}
- $included_in_build_profile=1;
- } elsif (/^Section:\s(.*)$/i) {
- $section = $1;
- } elsif (/^Architecture:\s*(.*)/i) {
- $arch=$1;
- } elsif (/^(?:X[BC]*-)?Package-Type:\s*(.*)/i) {
- $package_type=$1;
- } elsif (/^Multi-Arch:\s*(.*)/i) {
- $multiarch = $1;
- } elsif (/^X-DH-Build-For-Type:\s*(.*)/i) {
- $cross_type = $1;
- if ($cross_type ne 'host' and $cross_type ne 'target') {
- error("Unknown value of X-DH-Build-For-Type \"$cross_type\" at debian/control:$.");
+ } elsif (not $_ and not %seen_fields) {
+ # Ignore empty lines before first stanza
+ next;
+ } elsif ($_) {
+ my ($field_name, $value);
+
+ if (m/^($DEB822_FIELD_REGEX):\s*(.*)/o) {
+ ($field_name, $value) = (lc($1), $2);
+ if (exists($seen_fields{$field_name})) {
+ my $first_time = $seen_fields{$field_name};
+ error("${field_name}-field appears twice in the same stanza of debian/control. " .
+ "First time on line $first_time, second time: $.");
+ }
+ $seen_fields{$field_name} = $.;
+ $bd_field_value = undef;
+ } else {
+ # Invalid file
+ error("Parse error in debian/control, line $., read: $_");
}
- } elsif (/^Build-Profiles:\s*(.*)/i) {
- # rely on libdpkg-perl providing the parsing functions
- # because if we work on a package with a Build-Profiles
- # field, then a high enough version of dpkg-dev is needed
- # anyways
- my $build_profiles=$1;
- eval {
- require Dpkg::BuildProfiles;
- my @restrictions=Dpkg::BuildProfiles::parse_build_profiles($build_profiles);
- if (@restrictions) {
- $included_in_build_profile=Dpkg::BuildProfiles::evaluate_restriction_formula(\@restrictions, \@profiles);
+
+ if ($field_name eq 'package') {
+ $package = $value;
+ # Detect duplicate package names in the same control file.
+ if (! $seen{$package}) {
+ $seen{$package}=1;
+ } else {
+ error("debian/control has a duplicate entry for $package");
+ }
+ if ($package !~ $valid_pkg_re) {
+ error('Package-field must be a valid package name, ' .
+ "got: \"${package}\", should match \"${valid_pkg_re}\"");
+ }
+ $included_in_build_profile=1;
+ } elsif ($field_name eq 'section') {
+ $section = $value;
+ } elsif ($field_name eq 'architecture') {
+ $arch = $value;
+ } elsif ($field_name =~ m/^(?:x[bc]*-)?package-type$/) {
+ if (defined($package_type)) {
+ my $help = "(issue seen prior \"Package\"-field)";
+ $help = "for package ${package}" if $package;
+ error("Multiple definitions of (X-)Package-Type in line $. ${help}");
+ }
+ $package_type = $value;
+ } elsif ($field_name eq 'multi-arch') {
+ $multiarch = $value;
+ } elsif ($field_name eq 'x-dh-build-for-type') {
+ $cross_type = $value;
+ if ($cross_type ne 'host' and $cross_type ne 'target') {
+ error("Unknown value of X-DH-Build-For-Type \"$cross_type\" at debian/control:$.");
+ }
+ } elsif ($field_name eq 'build-profiles') {
+ # rely on libdpkg-perl providing the parsing functions
+ # because if we work on a package with a Build-Profiles
+ # field, then a high enough version of dpkg-dev is needed
+ # anyways
+ my $build_profiles = $value;
+ eval {
+ require Dpkg::BuildProfiles;
+ my @restrictions=Dpkg::BuildProfiles::parse_build_profiles($build_profiles);
+ if (@restrictions) {
+ $included_in_build_profile = Dpkg::BuildProfiles::evaluate_restriction_formula(
+ \@restrictions,
+ \@profiles);
+ }
+ };
+ if ($@) {
+ error("The control file has a Build-Profiles field. Requires libdpkg-perl >= 1.17.14");
}
- };
- if ($@) {
- error("The control file has a Build-Profiles field. Requires libdpkg-perl >= 1.17.14");
}
}
-
if (!$_ or eof) { # end of stanza.
if ($package) {
$package_types{$package}=$package_type // 'deb';
@@ -1444,8 +1790,8 @@ sub getpackages {
$included = 1 if samearch($desired_arch, $arch);
}
if ($included) {
- push(@{$packages_by_type{'arch'}}, $package);
- push(@{$packages_by_type{'both'}}, $package);
+ push(@{$packages_by_type{'arch'}}, $package);
+ push(@{$packages_by_type{'both'}}, $package);
}
}
}
@@ -1455,6 +1801,7 @@ sub getpackages {
$cross_type = undef;
$arch='';
$section='';
+ %seen_fields = ();
}
}
close($fd);
@@ -1466,32 +1813,36 @@ sub getpackages {
# - Takes an optional keyword; if passed, this will return true if the keyword is listed in R^3 (Rules-Requires-Root)
# - If the optional keyword is omitted or not present in R^3 and R^3 is not 'binary-targets', then returns false
# - Returns true otherwise (i.e. keyword is in R^3 or R^3 is 'binary-targets')
-sub should_use_root {
- my ($keyword) = @_;
- return 1 if not DH_ENABLE_RRR_SUPPORT;
- getpackages() if not %rrr;
-
- return 0 if exists($rrr{'no'});
- return 1 if exists($rrr{'binary-targets'});
- return 0 if not defined($keyword);
- return 1 if exists($rrr{$keyword});
- return 0;
+{
+ my %rrr;
+ sub should_use_root {
+ my ($keyword) = @_;
+ my $rrr_env = $ENV{'DEB_RULES_REQUIRES_ROOT'} // 'binary-targets';
+ $rrr_env =~ s/^\s++//;
+ $rrr_env =~ s/\s++$//;
+ return 0 if $rrr_env eq 'no';
+ return 1 if $rrr_env eq 'binary-targets';
+ return 0 if not defined($keyword);
+
+ %rrr = map { $_ => 1 } split(' ', $rrr_env) if not %rrr;
+ return 1 if exists($rrr{$keyword});
+ return 0;
+ }
}
# Returns the "gain root command" as a list suitable for passing as a part of the command to "doit()"
sub gain_root_cmd {
- my $raw_cmd = $ENV{DPKG_GAIN_ROOT_CMD};
+ my $raw_cmd = $ENV{DEB_GAIN_ROOT_CMD};
return if not defined($raw_cmd) or $raw_cmd =~ m/^\s*+$/;
return split(' ', $raw_cmd);
}
sub root_requirements {
- return 'legacy-root' if not DH_ENABLE_RRR_SUPPORT;
-
- getpackages() if not %rrr;
-
- return 'none' if exists($rrr{'no'});
- return 'legacy-root' if exists($rrr{'binary-targets'});
+ my $rrr_env = $ENV{'DEB_RULES_REQUIRES_ROOT'} // 'binary-targets';
+ $rrr_env =~ s/^\s++//;
+ $rrr_env =~ s/\s++$//;
+ return 'none' if $rrr_env eq 'no';
+ return 'legacy-root' if $rrr_env eq 'binary-targets';
return 'targeted-promotion';
}
@@ -1602,6 +1953,26 @@ sub is_udeb {
}
}
+# Only useful for dh(1)
+sub bd_dh_sequences {
+ # Use $sourcepackage as check because %dh_bd_sequence can be empty
+ # after running getpackages().
+ getpackages() if not defined($sourcepackage);
+ return sort(keys(%dh_bd_sequences));
+}
+
+sub _concat_slurp_script_files {
+ my (@files) = @_;
+ my $res = '';
+ for my $file (@files) {
+ open(my $fd, '<', $file) or error("open($file) failed: $!");
+ my $f = join('', <$fd>);
+ close($fd);
+ $res .= $f;
+ }
+ return $res;
+}
+
# Handles #DEBHELPER# substitution in a script; also can generate a new
# script from scratch if none exists but there is a .debhelper file for it.
sub debhelper_script_subst {
@@ -1611,34 +1982,56 @@ sub debhelper_script_subst {
my $tmp=tmpdir($package);
my $ext=pkgext($package);
my $file=pkgfile($package,$script);
+ my $service_script = generated_file($package, "${script}.service", 0);
+ my @generated_scripts = ("debian/$ext$script.debhelper", $service_script);
+ if ($script eq 'prerm' or $script eq 'postrm') {
+ @generated_scripts = reverse(@generated_scripts);
+ }
+ @generated_scripts = grep { -f } @generated_scripts;
if ($file ne '') {
- if (-f "debian/$ext$script.debhelper") {
+ if (@generated_scripts) {
+ if ($dh{VERBOSE}) {
+ verbose_print('cp -f ' . escape_shell($file) . " $tmp/DEBIAN/$script");
+ verbose_print("perl -p -i -e \"s~#DEBHELPER#~qx{cat @generated_scripts}~eg\" $tmp/DEBIAN/$script");
+ }
# Add this into the script, where it has #DEBHELPER#
- doit({ stdout => "$tmp/DEBIAN/$script" }, 'perl', '-pe',
- "s~#DEBHELPER#~qx{cat debian/$ext$script.debhelper}~eg", $file);
- }
- else {
+ my $text = _concat_slurp_script_files(@generated_scripts);
+ if (not $dh{NO_ACT}) {
+ open(my $out_fd, '>', "$tmp/DEBIAN/$script") or error("open($tmp/DEBIAN/$script) failed: $!");
+ open(my $in_fd, '<', $file) or error("open($file) failed: $!");
+ while (my $line = <$in_fd>) {
+ $line =~ s/#DEBHELPER#/$text/g;
+ print {$out_fd} $line;
+ }
+ close($in_fd);
+ close($out_fd) or error("close($tmp/DEBIAN/$script) failed: $!");
+ }
+ } else {
# Just get rid of any #DEBHELPER# in the script.
doit({ stdout => "$tmp/DEBIAN/$script" }, 'sed', 's/#DEBHELPER#//', $file);
}
reset_perm_and_owner('0755', "$tmp/DEBIAN/$script");
}
- elsif ( -f "debian/$ext$script.debhelper" ) {
+ elsif (@generated_scripts) {
if ($dh{VERBOSE}) {
verbose_print(q{printf '#!/bin/sh\nset -e\n' > } . "$tmp/DEBIAN/$script");
- verbose_print("cat debian/$ext$script.debhelper >> $tmp/DEBIAN/$script");
- }
- open(my $out_fd, '>', "$tmp/DEBIAN/$script") or error("open($tmp/DEBIAN/$script): $!");
- print {$out_fd} "#!/bin/sh\n";
- print {$out_fd} "set -e\n";
- open(my $in_fd, '<', "debian/$ext$script.debhelper")
- or error("open(debian/$ext$script.debhelper): $!");
- while (my $line = <$in_fd>) {
- print {$out_fd} $line;
- }
- close($in_fd);
- close($out_fd) or error("close($tmp/DEBIAN/$script): $!");
+ verbose_print("cat @generated_scripts >> $tmp/DEBIAN/$script");
+ }
+ if (not $dh{NO_ACT}) {
+ open(my $out_fd, '>', "$tmp/DEBIAN/$script") or error("open($tmp/DEBIAN/$script): $!");
+ print {$out_fd} "#!/bin/sh\n";
+ print {$out_fd} "set -e\n";
+ for my $generated_script (@generated_scripts) {
+ open(my $in_fd, '<', $generated_script)
+ or error("open($generated_script) failed: $!");
+ while (my $line = <$in_fd>) {
+ print {$out_fd} $line;
+ }
+ close($in_fd);
+ }
+ close($out_fd) or error("close($tmp/DEBIAN/$script) failed: $!");
+ }
reset_perm_and_owner('0755', "$tmp/DEBIAN/$script");
}
}
@@ -1674,9 +2067,17 @@ sub make_symlink{
my $dest = shift;
my $src = _expand_path(shift);
my $tmp = shift;
- $tmp = '' if not defined($tmp);
- $src=~s:^/::;
- $dest=~s:^/::;
+ $tmp = '' if not defined($tmp);
+
+ if ($dest =~ m{(?:^|/)*[.]{2}(?:/|$)}) {
+ error("Invalid destination/link name (contains \"..\"-segments): $dest");
+ }
+
+ $src =~ s{^(?:[.]/+)++}{};
+ $dest =~ s{^(?:[.]/+)++}{};
+
+ $src=~s:^/++::;
+ $dest=~s:^/++::;
if ($src eq $dest) {
warning("skipping link from $src to self");
@@ -1688,8 +2089,8 @@ sub make_symlink{
# Policy says that if the link is all within one toplevel
# directory, it should be relative. If it's between
# top level directories, leave it absolute.
- my @src_dirs=split(m:/+:,$src);
- my @dest_dirs=split(m:/+:,$dest);
+ my @src_dirs = grep { $_ ne '.' } split(m:/+:,$src);
+ my @dest_dirs = grep { $_ ne '.' } split(m:/+:,$dest);
if (@src_dirs > 0 && $src_dirs[0] eq $dest_dirs[0]) {
# Figure out how much of a path $src and $dest
# share in common.
@@ -2089,6 +2490,32 @@ sub is_so_or_exec_elf_file {
return 1;
}
+sub _has_shbang_line {
+ my ($file) = @_;
+ open(my $fd, '<', $file) or error("open $file: $!");
+ my $line = <$fd>;
+ close($fd);
+ return 1 if (defined($line) and substr($line, 0, 2) eq '#!');
+ return 0;
+}
+
+# Returns true iff the given argument is an empty directory.
+# Corner-cases:
+# - false if not a directory
+sub is_empty_dir {
+ my ($dir) = @_;
+ return 0 if not -d $dir;
+ my $ret = 1;
+ opendir(my $dir_fd, $dir) or error("opendir($dir) failed: $!");
+ while (defined(my $entry = readdir($dir_fd))) {
+ next if $entry eq '.' or $entry eq '..';
+ $ret = 0;
+ last;
+ }
+ closedir($dir_fd);
+ return $ret;
+}
+
sub on_pkgs_in_parallel(&) {
unshift(@_, $dh{DOPACKAGES});
goto \&on_items_in_parallel;
diff --git a/lib/Debian/Debhelper/Sequence/dwz.pm b/lib/Debian/Debhelper/Sequence/dwz.pm
index 3db77561..eb8ef13e 100644
--- a/lib/Debian/Debhelper/Sequence/dwz.pm
+++ b/lib/Debian/Debhelper/Sequence/dwz.pm
@@ -3,9 +3,11 @@
use strict;
use warnings;
-use Debian::Debhelper::Dh_Lib qw(warning);
+use Debian::Debhelper::Dh_Lib;
-warning('The "dwz"-sequence is experimental and may change (or be retired) without any notice');
+if (not compat(11)) {
+ error("In compat 12, dh_dwz is run by default and the dwz-sequence is no longer required.");
+}
insert_before('dh_strip', 'dh_dwz');
diff --git a/lib/Debian/Debhelper/Sequence/installinitramfs.pm b/lib/Debian/Debhelper/Sequence/installinitramfs.pm
new file mode 100644
index 00000000..3c7b2b81
--- /dev/null
+++ b/lib/Debian/Debhelper/Sequence/installinitramfs.pm
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+# Enable dh_installinitramfs
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Lib;
+
+if (not compat(11)) {
+ error("In compat 12, dh_installinitramfs is run by default and the installinitramfs-sequence is no longer required.");
+}
+
+insert_after('dh_installgsettings', 'dh_installinitramfs');
+
+1;
diff --git a/lib/Debian/Debhelper/SequencerUtil.pm b/lib/Debian/Debhelper/SequencerUtil.pm
new file mode 100644
index 00000000..9a9ce2bf
--- /dev/null
+++ b/lib/Debian/Debhelper/SequencerUtil.pm
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+#
+# Internal library functions for the dh(1) command
+
+package Debian::Debhelper::SequencerUtil;
+use strict;
+use warnings;
+use constant DUMMY_TARGET => 'debhelper-fail-me';
+
+use Exporter qw(import);
+
+our @EXPORT = qw(
+ extract_rules_target_name
+ to_rules_target
+ unpack_sequence
+ rules_explicit_target
+ extract_skipinfo
+ DUMMY_TARGET
+);
+
+our (%EXPLICIT_TARGETS, $RULES_PARSED);
+
+sub extract_rules_target_name {
+ my ($command) = @_;
+ if ($command =~ m{^debian/rules\s++(.++)}) {
+ return $1
+ }
+ return;
+}
+
+sub to_rules_target {
+ return 'debian/rules '.join(' ', @_);
+}
+
+sub unpack_sequence {
+ my ($sequences, $sequence_name, $always_inline, $completed_sequences) = @_;
+ my (@sequence, @targets, %seen, %non_inlineable_targets, @stack);
+ # Walk through the sequence effectively doing a DFS of the rules targets
+ # (when we are allowed to inline them).
+ push(@stack, [@{$sequences->{$sequence_name}}]);
+ while (@stack) {
+ my $current_sequence = pop(@stack);
+ COMMAND:
+ while (@{$current_sequence}) {
+ my $command = shift(@{$current_sequence});
+ my $rules_target=extract_rules_target_name($command);
+ next if (defined($rules_target) and exists($completed_sequences->{$rules_target}));
+ if (defined($rules_target) && ($always_inline ||
+ ! exists($non_inlineable_targets{$rules_target}) &&
+ ! defined(rules_explicit_target($rules_target)))) {
+
+ # inline the sequence for this implicit target.
+ push(@stack, $current_sequence);
+ $current_sequence = [@{$sequences->{$rules_target}}];
+ next COMMAND;
+ } else {
+ if (defined($rules_target) and not $always_inline) {
+ next COMMAND if exists($non_inlineable_targets{$rules_target});
+ my @opaque_targets = ($rules_target);
+ while (my $opaque_target = pop(@opaque_targets)) {
+ for my $c (@{$sequences->{$opaque_target}}) {
+ my $subtarget = extract_rules_target_name($c);
+ next if not defined($subtarget);
+ next if exists($non_inlineable_targets{$subtarget});
+ $non_inlineable_targets{$subtarget} = $rules_target;
+ }
+ }
+ push(@targets, $command) if not $seen{$command}++;
+ } elsif (! $seen{$command}) {
+ $seen{$command} = 1;
+ push(@sequence, $command);
+ }
+ }
+ }
+ }
+ return (\@targets, \@sequence);
+}
+
+
+sub rules_explicit_target {
+ # Checks if a specified target exists as an explicit target
+ # in debian/rules.
+ # undef is returned if target does not exist, 0 if target is noop
+ # and 1 if target has dependencies or executes commands.
+ my ($target) = @_;
+
+ if (! $RULES_PARSED) {
+ my $processing_targets = 0;
+ my $not_a_target = 0;
+ my $current_target;
+ open(MAKE, "LC_ALL=C make -Rrnpsf debian/rules ${\DUMMY_TARGET} 2>/dev/null |");
+ while (<MAKE>) {
+ if ($processing_targets) {
+ if (/^# Not a target:/) {
+ $not_a_target = 1;
+ } else {
+ if (!$not_a_target && m/^([^#:]+)::?\s*(.*)$/) {
+ # Target is defined. NOTE: if it is a dependency of
+ # .PHONY it will be defined too but that's ok.
+ # $2 contains target dependencies if any.
+ $current_target = $1;
+ $EXPLICIT_TARGETS{$current_target} = ($2) ? 1 : 0;
+ } else {
+ if (defined($current_target)) {
+ if (m/^#/) {
+ # Check if target has commands to execute
+ if (m/^#\s*(commands|recipe) to execute/) {
+ $EXPLICIT_TARGETS{$current_target} = 1;
+ }
+ } else {
+ # Target parsed.
+ $current_target = undef;
+ }
+ }
+ }
+ # "Not a target:" is always followed by
+ # a target name, so resetting this one
+ # here is safe.
+ $not_a_target = 0;
+ }
+ } elsif (m/^# Files$/) {
+ $processing_targets = 1;
+ }
+ }
+ close MAKE;
+ $RULES_PARSED = 1;
+ }
+
+ return $EXPLICIT_TARGETS{$target};
+}
+
+sub extract_skipinfo {
+ my ($command) = @_;
+
+ foreach my $dir (split(':', $ENV{PATH})) {
+ if (open (my $h, "<", "$dir/$command")) {
+ while (<$h>) {
+ if (m/PROMISE: DH NOOP( WITHOUT\s+(.*))?\s*$/) {
+ close $h;
+ return split(' ', $2) if defined($2);
+ return ('always-skip');
+ }
+ }
+ close $h;
+ return;
+ }
+ }
+ return;
+}
+
+
+1;