diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem.pm | 203 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/autoconf.pm | 4 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/cmake.pm | 61 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/makefile.pm | 17 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/meson.pm | 38 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/ninja.pm | 44 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/python_distutils.pm | 7 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/qmake.pm | 64 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Buildsystem/qmake_qt4.pm | 6 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Dh_Buildsystems.pm | 80 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Dh_Lib.pm | 761 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Sequence/dwz.pm | 6 | ||||
-rw-r--r-- | lib/Debian/Debhelper/Sequence/installinitramfs.pm | 14 | ||||
-rw-r--r-- | lib/Debian/Debhelper/SequencerUtil.pm | 152 |
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; |