# Defines dh sequence state variables # # License: GPL-2+ package Debian::Debhelper::DH::AddonAPI; use strict; use warnings; use Debian::Debhelper::Dh_Lib qw(warning error); use Debian::Debhelper::Sequence; use Debian::Debhelper::SequencerUtil; use Debian::Debhelper::DH::SequenceState; our ($DH_INTERNAL_ADDON_TYPE, $DH_INTERNAL_ADDON_NAME); sub _add_sequence { my @args = @_; my $seq = Debian::Debhelper::Sequence->new(@args); my $name = $seq->name; $Debian::Debhelper::DH::SequenceState::sequences{$name} = $seq; if ($seq->allowed_subsequences eq SEQUENCE_ARCH_INDEP_SUBSEQUENCES) { for my $subseq ((SEQUENCE_TYPE_ARCH_ONLY, SEQUENCE_TYPE_INDEP_ONLY)) { my $subname = "${name}-${subseq}"; $Debian::Debhelper::DH::SequenceState::sequences{$subname} = $seq; } } return; } sub _skip_cmd_if_deb_build_options_contains { my ($command, $flag) = @_; push(@{$Debian::Debhelper::DH::SequenceState::commands_skippable_via_deb_build_options{$command}}, $flag); return; } sub _assert_not_conditional_sequence_addon { my ($feature) = @_; return if $DH_INTERNAL_ADDON_TYPE eq 'both'; warning("The add-on ${DH_INTERNAL_ADDON_NAME} relies on a feature (${feature}) (possibly indirectly), which is " . 'not supported for conditional debhelper sequence add-ons.'); warning("Hint: You may have to move the build-dependency for dh-sequence-${DH_INTERNAL_ADDON_NAME} to " . 'Build-Depends to avoid this error assuming it is possible to use the sequence unconditionally.'); error("${feature} is not supported for conditional dh sequence add-ons.\n"); } sub _filter_sequences_for_conditional_add_ons { my @sequences = @_; # If it is unconditional, then there is no issues. return @sequences if $DH_INTERNAL_ADDON_TYPE eq 'both' or not @sequences; for my $seq (@sequences) { # Typically, if you add a command to a sequence, then you will in fact add it to two. E.g. # Adding dh_foo after dh_installdocs will affect both install-arch AND install-indep. We want # this to "just work(tm)" with a conditional add-on to avoid too much hassle (i.e. only affect # the relevant sequence). At the same time, we must abort if a sequence like "clean" is # affected. # # We solve the above by checking if the sequence has an -arch + an -indep variant and then # insert the command only for that sequence variant. if ($seq->allowed_subsequences ne SEQUENCE_ARCH_INDEP_SUBSEQUENCES) { my $sequence_name = $seq->name; warning("The add-on ${DH_INTERNAL_ADDON_NAME} attempted to modify the sequence ${sequence_name} (possibly " . "indirectly) but the add-on is conditional for \"*-${DH_INTERNAL_ADDON_TYPE}\" targets"); warning("Hint: You may have to move the build-dependency for dh-sequence-${DH_INTERNAL_ADDON_NAME} to " . 'Build-Depends to avoid this error assuming it is possible to use the sequence unconditionally.'); error("The add-on ${DH_INTERNAL_ADDON_NAME} cannot be use conditionally for \"*-${DH_INTERNAL_ADDON_TYPE}\"" . " targets\n"); } } return @sequences; } sub _register_cmd_added_by_addon { my ($cmd) = @_; my $existing = $Debian::Debhelper::DH::SequenceState::commands_added_by_addon{$cmd}; if ($existing) { if ($existing->{'addon-type'} ne $DH_INTERNAL_ADDON_TYPE) { my $old_addon_name = $existing->{'addon-name'}; my $old_addon_type = $existing->{'addon-type'}; # Technically, "both" could be made compatible with "indep" OR "arch" (but not both at the same time). # Implement if it turns out to be relevant. warning("Both dh sequence add-ons ${DH_INTERNAL_ADDON_NAME} and ${old_addon_name} have attempted to add " . "the command $cmd (possibly indirectly)."); warning("However, the two add-ons do not have compatible constraints (${DH_INTERNAL_ADDON_TYPE} vs. " . "${old_addon_type})."); warning("Hint: You may have to move the build-dependency for dh-sequence- to " . ' the same build-dependency field to avoid this error assuming it is possible.'); error("Multiple sequences have conflicting requests for $cmd.\n"); } return; } $Debian::Debhelper::DH::SequenceState::commands_added_by_addon{$cmd} = { 'addon-name' => $DH_INTERNAL_ADDON_NAME, 'addon-type' => $DH_INTERNAL_ADDON_TYPE, }; return; } sub _sequences_containing_cmd { my ($cmd) = @_; my @sequences; foreach my $sequence_name (keys(%Debian::Debhelper::DH::SequenceState::sequences)) { my $seq = $Debian::Debhelper::DH::SequenceState::sequences{$sequence_name}; for my $scmd (@{$seq->{'_cmds'}}) { if ($scmd->{'command'} eq $cmd) { push(@sequences, $seq); last; } } } return @sequences; } sub _seq_cmd { my ($cmd_name) = @_; return { 'command' => $cmd_name, 'command-options' => [], 'sequence-limitation' => $DH_INTERNAL_ADDON_TYPE, }; } # sequence addon interface sub _insert { my ($offset, $existing, $new) = @_; my @affected_sequences = _sequences_containing_cmd($existing); @affected_sequences = _filter_sequences_for_conditional_add_ons(@affected_sequences); return if not @affected_sequences; _register_cmd_added_by_addon($new); for my $seq (@affected_sequences) { $seq->_insert($offset, $existing, _seq_cmd($new)); } return 1; } sub insert_before { return _insert(-1, @_); } sub insert_after { return _insert(1, @_); } sub remove_command { my ($command) = @_; # Implement if actually needed (I *think* it basically means to transform dh_foo to dh_foo -a/-i) _assert_not_conditional_sequence_addon('remove_command'); my @affected_sequences = _sequences_containing_cmd($command); @affected_sequences = _filter_sequences_for_conditional_add_ons(@affected_sequences); return 1 if not @affected_sequences; for my $seq (@affected_sequences) { $seq->remove_command($command); } return 1; } sub add_command { my ($command, $sequence) = @_; _assert_not_conditional_sequence_addon('add_command'); _register_cmd_added_by_addon($command); if (not exists($Debian::Debhelper::DH::SequenceState::sequences{$sequence})) { _add_sequence($sequence, SEQUENCE_NO_SUBSEQUENCES, _seq_cmd($command)); } else { my $seq = $Debian::Debhelper::DH::SequenceState::sequences{$sequence}; _filter_sequences_for_conditional_add_ons($seq); $seq->add_command_at_start(_seq_cmd($command)) } return 1; } sub add_command_at_end { my ($command, $sequence) = @_; _assert_not_conditional_sequence_addon('add_command'); _register_cmd_added_by_addon($command); if (not exists($Debian::Debhelper::DH::SequenceState::sequences{$sequence})) { _add_sequence($sequence, SEQUENCE_NO_SUBSEQUENCES, _seq_cmd($command)); } else { my $seq = $Debian::Debhelper::DH::SequenceState::sequences{$sequence}; _filter_sequences_for_conditional_add_ons($seq); $seq->add_command_at_end(_seq_cmd($command)) } return 1; } sub add_command_options { my $command=shift; # Implement if actually needed (Complicated as dh_foo becomes dh_foo -a && dh_foo -i # and that implies smarter deduplication logic) _assert_not_conditional_sequence_addon('add_command_options'); push(@{$Debian::Debhelper::DH::SequenceState::command_opts{$command}}, @_); return 1; } sub remove_command_options { my ($command, @cmd_options) = @_; # Implement if actually needed (Complicated as dh_foo becomes # dh_foo -a && dh_foo -i and that implies smarter deduplication logic) _assert_not_conditional_sequence_addon('remove_command_options'); if (@cmd_options) { # Remove only specified options if (my $opts = $Debian::Debhelper::DH::SequenceState::command_opts{$command}) { foreach my $opt (@cmd_options) { $opts = [ grep { $_ ne $opt } @$opts ]; } $Debian::Debhelper::DH::SequenceState::command_opts{$command} = $opts; } } else { # Clear all additional options delete($Debian::Debhelper::DH::SequenceState::command_opts{$command}); } return 1; } sub declare_command_obsolete { my ($error_compat, $command) = @_; if (not defined($command) and defined($error_compat)) { # Backwards compat - originally this only accepted one command. $command = $error_compat; $error_compat = 13; } if ($error_compat < 13) { error("Minimum error compat is 13 (got ${error_compat} for command: ${command})"); } _assert_not_conditional_sequence_addon('declare_command_obsolete'); $Debian::Debhelper::DH::SequenceState::obsolete_command{$command} = [$DH_INTERNAL_ADDON_NAME, $error_compat]; return 1; } 1;