Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package perl-Module-Pluggable for 
openSUSE:Factory checked in at 2026-08-22 21:34:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/perl-Module-Pluggable (Old)
 and      /work/SRC/openSUSE:Factory/.perl-Module-Pluggable.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "perl-Module-Pluggable"

Sat Aug 22 21:34:44 2026 rev:7 rq:1372839 version:6.400.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/perl-Module-Pluggable/perl-Module-Pluggable.changes  
    2025-01-17 18:38:01.390574335 +0100
+++ 
/work/SRC/openSUSE:Factory/.perl-Module-Pluggable.new.1258/perl-Module-Pluggable.changes
    2026-08-22 21:36:07.706166163 +0200
@@ -1,0 +2,6 @@
+Sun Jul 26 07:46:51 UTC 2026 - Tina Müller <[email protected]>
+
+- updated to 6.400.0 (6.4)
+   see /usr/share/doc/packages/perl-Module-Pluggable/Changes
+
+-------------------------------------------------------------------

Old:
----
  Module-Pluggable-6.3.tar.gz

New:
----
  Module-Pluggable-6.4.tar.gz
  README.md
  _scmsync.obsinfo
  build.specials.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ perl-Module-Pluggable.spec ++++++
--- /var/tmp/diff_new_pack.Xe3LzG/_old  2026-08-22 21:36:08.455192894 +0200
+++ /var/tmp/diff_new_pack.Xe3LzG/_new  2026-08-22 21:36:08.457192966 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package perl-Module-Pluggable
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,23 +18,25 @@
 
 %define cpan_name Module-Pluggable
 Name:           perl-Module-Pluggable
-Version:        6.300.0
+Version:        6.400.0
 Release:        0
-# 6.3 -> normalize -> 6.300.0
-%define cpan_version 6.3
+# 6.4 -> normalize -> 6.400.0
+%define cpan_version 6.4
 License:        Artistic-1.0 OR GPL-1.0-or-later
 Summary:        Automatically give your module the ability to have plugins
 URL:            https://metacpan.org/release/%{cpan_name}
 Source0:        
https://cpan.metacpan.org/authors/id/S/SI/SIMONW/%{cpan_name}-%{cpan_version}.tar.gz
 Source1:        cpanspec.yml
+Source100:      README.md
 BuildArch:      noarch
 BuildRequires:  perl
 BuildRequires:  perl-macros
-Provides:       perl(Devel::InnerPackage) = 0.4
+BuildRequires:  perl(parent)
+Provides:       perl(Devel::InnerPackage) = 0.400.0
 Provides:       perl(Module::Pluggable) = %{version}
-Provides:       perl(Module::Pluggable::Object) = 5.2
+Provides:       perl(Module::Pluggable::Object) = 5.300.0
 %undefine       __perllib_provides
-Recommends:     perl(Module::Runtime) >= 0.012
+Recommends:     perl(Module::Runtime) >= 0.12
 %{perl_requires}
 
 %description
@@ -48,7 +50,7 @@
 Optionally it instantiates those classes for you.
 
 %prep
-%autosetup  -n %{cpan_name}-%{cpan_version}
+%autosetup -n %{cpan_name}-%{cpan_version} -p1
 
 find . -type f ! -path "*/t/*" ! -name "*.pl" ! -path "*/bin/*" ! -path 
"*/script/*" ! -path "*/scripts/*" ! -name "configure" -print0 | xargs -0 chmod 
644
 

++++++ Module-Pluggable-6.3.tar.gz -> Module-Pluggable-6.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/Changes 
new/Module-Pluggable-6.4/Changes
--- old/Module-Pluggable-6.3/Changes    2025-01-10 20:58:39.298943793 +0100
+++ new/Module-Pluggable-6.4/Changes    2026-07-26 00:07:28.000000000 +0200
@@ -1,4 +1,15 @@
-2025-01-10 - 6.3 
+2026-07-25 - 6.4
+    Add a CI/CD pipeline for the first time in 26 years
+    Add the ability to make search search "strict" and only walk the given 
search_dirs (thanks @rjbs)
+    Add much more documentation to Module::Pluggable::Object (thanks Dave 
Rolsky)
+    Make it so that you can give each search_path its own custom options
+    Finally fix spurious test failure because of already installed modules I 
hope (thanks @SZREZIC, Andreas Koenig and @ZEFRAM)
+    Remove optional_features from Makefile.PL META_MERGE which caused 
installation confusion sometimes 
+    Add the ability to have different sorting algorithms (thanks @CHOHAG)
+    Fix the issues with OSX extended-attributes on files (thanks pgoyette99)
+    Clean up old stale core-lib (thanks @dolmen and @haarg)
+
+2025-01-10 - 6.3
     Fix make_path() in t/29unreadable.t. (thanks to Michal Josef Špaček 
@michal-josef-spacek)
     Fix t/29unreadable.t tests failing as root. (thanks to Michal Josef Špaček 
@michal-josef-spacek)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/MANIFEST 
new/Module-Pluggable-6.4/MANIFEST
--- old/Module-Pluggable-6.3/MANIFEST   2024-10-23 06:17:06.000000000 +0200
+++ new/Module-Pluggable-6.4/MANIFEST   2026-07-26 00:07:28.000000000 +0200
@@ -9,6 +9,7 @@
 META.json
 META.yml
 README
+installscripts/remove_stale_core_copies.pl
 t/01use.t
 t/02alsoworks.t
 t/02works.t
@@ -54,6 +55,13 @@
 t/27app_fatpacker.t
 t/28appledouble.t
 t/29unreadable.t
+<<<<<<< HEAD
+t/30search_dirs_strict.t
+t/31per_path_opts.t
+=======
+t/30per_path_opts.t
+t/31sort_results.t
+>>>>>>> 14fe60b (Add opt-in sort_results option.)
 t/acme/Acme/MyTest/Plugin/Foo.pm
 t/fp/app.pl
 t/fp/lib/App/TestMPFP/Plugin/A.pm
@@ -70,6 +78,7 @@
 t/lib/ExtTest/Plugin/Foo.plugin
 t/lib/ExtTest/Plugin/Quux/Foo.plugin
 t/lib/InnerTest/Plugin/Foo.pm
+t/lib/LocalIncTest/Plugin/Abbrev.pm
 t/lib/M/X.pm
 t/lib/MyOtherTest/Plugin/Bar.pm
 t/lib/MyOtherTest/Plugin/Foo.pm
@@ -82,10 +91,10 @@
 t/lib/No/Middle.pm
 t/lib/OddTest/Plugin/Foo.pm
 t/lib/TA/C/A/I.pm
-t/lib/Text/Abbrev.pm
 t/lib/TriggerTest/Plugin/After.pm
 t/lib/TriggerTest/Plugin/CallbackAllow.pm
 t/lib/TriggerTest/Plugin/CallbackDeny.pm
 t/lib/TriggerTest/Plugin/Deny.pm
 t/lib/TriggerTest/Plugin/Error.pm
 t/lib/Zot/.Zork.pm
+t/search_dirs_strict.t
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/MANIFEST.SKIP 
new/Module-Pluggable-6.4/MANIFEST.SKIP
--- old/Module-Pluggable-6.3/MANIFEST.SKIP      2012-08-10 21:04:30.000000000 
+0200
+++ new/Module-Pluggable-6.4/MANIFEST.SKIP      2026-07-26 00:07:28.000000000 
+0200
@@ -13,3 +13,6 @@
 
 ^MYMETA\.yml$
 ^MYMETA\.json$
+
+# macOS extended-attribute sidecar files
+(^|/)\._
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/META.json 
new/Module-Pluggable-6.4/META.json
--- old/Module-Pluggable-6.3/META.json  2025-01-10 21:05:27.000000000 +0100
+++ new/Module-Pluggable-6.4/META.json  2026-07-26 00:08:45.000000000 +0200
@@ -4,7 +4,7 @@
       "Simon Wistow <[email protected]>"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter 
version 2.150010",
+   "generated_by" : "ExtUtils::MakeMaker version 7.78, CPAN::Meta::Converter 
version 2.150010",
    "license" : [
       "perl_5"
    ],
@@ -19,18 +19,6 @@
          "inc"
       ]
    },
-   "optional_features" : {
-      "module_runtime" : {
-         "description" : "Optionally use Module::Runtime for requiring plugins 
rather than homegrown system",
-         "prereqs" : {
-            "runtime" : {
-               "requires" : {
-                  "Module::Runtime" : "0.012"
-               }
-            }
-         }
-      }
-   },
    "prereqs" : {
       "build" : {
          "requires" : {
@@ -70,6 +58,7 @@
             "FindBin" : "0",
             "Test::More" : "0",
             "base" : "0",
+            "parent" : "0",
             "strict" : "0",
             "warnings" : "0"
          },
@@ -90,6 +79,6 @@
          "web" : "https://github.com/simonwistow/Module-Pluggable";
       }
    },
-   "version" : "6.3",
-   "x_serialization_backend" : "JSON::PP version 4.06"
+   "version" : "6.4",
+   "x_serialization_backend" : "JSON::PP version 4.16"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/META.yml 
new/Module-Pluggable-6.4/META.yml
--- old/Module-Pluggable-6.3/META.yml   2025-01-10 21:05:27.000000000 +0100
+++ new/Module-Pluggable-6.4/META.yml   2026-07-26 00:08:45.000000000 +0200
@@ -14,12 +14,13 @@
   FindBin: '0'
   Test::More: '0'
   base: '0'
+  parent: '0'
   strict: '0'
   warnings: '0'
 configure_requires:
   ExtUtils::MakeMaker: '0'
 dynamic_config: 0
-generated_by: 'ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 
2.150010'
+generated_by: 'ExtUtils::MakeMaker version 7.78, CPAN::Meta::Converter version 
2.150010'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -29,11 +30,6 @@
   directory:
     - t
     - inc
-optional_features:
-  module_runtime:
-    description: 'Optionally use Module::Runtime for requiring plugins rather 
than homegrown system'
-    requires:
-      Module::Runtime: '0.012'
 recommends:
   Module::Runtime: '0.012'
 requires:
@@ -49,5 +45,5 @@
 resources:
   bugtracker: 
https://rt.cpan.org/Public/Dist/Display.html?Name=Module-Pluggable
   repository: https://github.com/simonwistow/Module-Pluggable.git
-version: '6.3'
+version: '6.4'
 x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/Makefile.PL 
new/Module-Pluggable-6.4/Makefile.PL
--- old/Module-Pluggable-6.3/Makefile.PL        2019-11-18 22:48:46.552465779 
+0100
+++ new/Module-Pluggable-6.4/Makefile.PL        2026-07-26 00:07:28.000000000 
+0200
@@ -61,6 +61,7 @@
         'File::Spec::Functions' => '0',
         'File::Temp'            => '0',
         'base'                  => '0',
+        'parent'                => '0',
         'FindBin'               => '0',
         'strict'                => '0',
         'warnings'              => '0',
@@ -92,18 +93,6 @@
                 },
             },
         },
-        optional_features => {
-            module_runtime => {
-                description => "Optionally use Module::Runtime for requiring 
plugins rather than homegrown system",
-                prereqs => {
-                    runtime => {
-                        requires => {
-                            'Module::Runtime' => '0.012',
-                        }
-                    }
-                }
-            }
-        }
     },
 );
 
@@ -121,4 +110,19 @@
     delete $WriteMakefileArgs{BUILD_REQUIRES};
 }
 
+
+# On `make install`, delete any copy of this module left behind in a core
+# Perl library directory by an old release that used INSTALLDIRS => 'perl'.
+# Such a copy would otherwise shadow the new 'site' install (#39). Only
+# needed when we're actually installing to 'site' this time around.
+my $installing_to_site = $WriteMakefileArgs{INSTALLDIRS} eq 'site';
+
+sub MY::postamble {
+    return '' unless $installing_to_site;
+    return <<'MAKE_FRAG';
+install ::
+       $(NOECHO) $(PERL) installscripts/remove_stale_core_copies.pl
+MAKE_FRAG
+}
+
 WriteMakefile(%WriteMakefileArgs);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/README 
new/Module-Pluggable-6.4/README
--- old/Module-Pluggable-6.3/README     2015-08-06 01:36:21.000000000 +0200
+++ new/Module-Pluggable-6.4/README     2026-07-26 00:07:28.000000000 +0200
@@ -46,7 +46,7 @@
     This can be trivally extended so that plugins could save the email
     somewhere and then no other plugin should try and do that. Simply have
     it so that the "examine" method returns 1 if it has saved the email
-    somewhere. You might also wnat to be paranoid and check to see if the
+    somewhere. You might also want to be paranoid and check to see if the
     plugin has an "examine" method.
 
             foreach my $plugin ($self->plugins) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Module-Pluggable-6.3/installscripts/remove_stale_core_copies.pl 
new/Module-Pluggable-6.4/installscripts/remove_stale_core_copies.pl
--- old/Module-Pluggable-6.3/installscripts/remove_stale_core_copies.pl 
1970-01-01 01:00:00.000000000 +0100
+++ new/Module-Pluggable-6.4/installscripts/remove_stale_core_copies.pl 
2026-07-26 00:07:28.000000000 +0200
@@ -0,0 +1,26 @@
+#!perl
+# Run by the 'install' target in the generated Makefile (see MY::postamble
+# in Makefile.PL). Deletes any copy of this module left behind in a core
+# Perl library directory by an old release that used INSTALLDIRS => 'perl'.
+# Those directories come before the site directories in @INC, so a stale
+# copy there silently shadows the newly installed 'site' copy (#39).
+use strict;
+use warnings;
+use Config;
+
+my @rel_paths = (
+    'Module/Pluggable.pm',
+    'Module/Pluggable/Object.pm',
+);
+
+my @core_dirs = grep { defined && length && -d $_ }
+    ($Config{installprivlib}, $Config{installarchlib});
+
+for my $dir (@core_dirs) {
+    for my $rel (@rel_paths) {
+        my $path = "$dir/$rel";
+        next unless -e $path;
+        print "Removing stale core copy: $path\n";
+        unlink $path or warn "Couldn't remove $path: $!\n";
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/lib/Module/Pluggable/Object.pm 
new/Module-Pluggable-6.4/lib/Module/Pluggable/Object.pm
--- old/Module-Pluggable-6.3/lib/Module/Pluggable/Object.pm     2024-10-23 
06:17:06.690657834 +0200
+++ new/Module-Pluggable-6.4/lib/Module/Pluggable/Object.pm     2026-07-26 
00:07:28.000000000 +0200
@@ -10,7 +10,7 @@
 
 use if $] > 5.017, 'deprecate';
 
-our $VERSION = '5.2';
+our $VERSION = '5.3';
 
 BEGIN {
     eval {  require Module::Runtime };
@@ -27,6 +27,61 @@
 }
 
 
+=head1 NAME
+
+Module::Pluggable::Object - the underlying plugin-finding object used by 
Module::Pluggable
+
+=head1 SYNOPSIS
+
+    package MyClass;
+    use Module::Pluggable::Object;
+
+    my $finder = Module::Pluggable::Object->new(
+        package     => __PACKAGE__,
+        search_path => ['MyClass::Plugin'],
+        require     => 1,
+    );
+
+    my @plugins = $finder->plugins;
+    print "Found: ", join(", ", @plugins), "\n";
+
+=head1 DESCRIPTION
+
+C<Module::Pluggable::Object> is the engine underneath L<Module::Pluggable>. It
+walks a set of search paths (namespaces and directories) looking for C<.pm>
+files, maps their paths to package names, and optionally C<require>s or
+instantiates them.
+
+Most of the time you should use L<Module::Pluggable> directly, which wraps
+this class and exports a C<plugins()> method into your namespace. Use this
+class directly when you need more control:
+
+=over 4
+
+=item * You want to create multiple independent finders in the same package.
+
+=item * You want to subclass or decorate the finder object.
+
+=item * You want to store the finder and call C<plugins()> on it explicitly
+rather than through an exported method.
+
+=item * You are building your own higher-level abstraction on top of the
+plugin-discovery machinery.
+
+=back
+
+=head1 CONSTRUCTOR
+
+=head2 new(%opts)
+
+    my $finder = Module::Pluggable::Object->new(%opts);
+
+Creates and returns a new finder object. All options are passed as a flat
+hash; they are stored on the object and consulted by L</plugins>. See
+L</OPTIONS> below for the full list.
+
+=cut
+
 sub new {
     my $class = shift;
     my %opts  = @_;
@@ -35,31 +90,98 @@
 
 }
 
+=head1 METHODS
+
+=head2 plugins
+
+    my @plugins = $finder->plugins;        # list of class names
+    my @objects = $finder->plugins(@args); # instances when 'instantiate' is 
set
+
+The main entry point. Walks C<search_path> across C<search_dirs> (and C<@INC>)
+and returns either a sorted list of fully-qualified package names or, if
+C<instantiate> is set, a list of objects created by calling the named method
+on each plugin class.
+
+Arguments passed to C<plugins()> are forwarded to the C<instantiate> method
+on each plugin class.
+
+The full search is repeated on every call, which allows plugins installed
+after startup to be discovered at run time. If you do not need dynamic
+discovery and want to pay the I/O cost only once, memoize the result yourself:
+
+    our @PLUGINS;
+    sub plugins { @PLUGINS ||= shift->_finder->plugins }
+
+=cut
+
 ### Eugggh, this code smells
 ### This is what happens when you keep adding patches
 ### *sigh*
 
+# Normalises the three supported forms of 'search_path' into an ordered
+# list of [$namespace, \%per_path_opts] pairs:
+#   "Foo::Plugin"                                  -> plain scalar
+#   ["Foo::Plugin", "Bar::Plugin"]                 -> plain arrayref
+#   { "Foo::Plugin" => { max_depth => 2 }, ... }    -> hashref, sorted for 
determinism
+#   ["Foo::Plugin", { "Bar::Plugin" => { ... } }]   -> mixed arrayref, 
preserves order
+sub _normalise_search_path {
+    my ($sp) = @_;
+    return () unless defined $sp;
+    return ([$sp, {}]) unless ref $sp;
+    if (ref($sp) eq 'HASH') {
+        return map { [$_, $sp->{$_} || {}] } sort keys %$sp;
+    }
+    if (ref($sp) eq 'ARRAY') {
+        my @out;
+        for my $item (@$sp) {
+            if (!ref($item)) {
+                push @out, [$item, {}];
+            } elsif (ref($item) eq 'HASH') {
+                push @out, map { [$_, $item->{$_} || {}] } sort keys %$item;
+            }
+        }
+        return @out;
+    }
+    return ();
+}
 
 sub plugins {
     my $self = shift;
     my @args = @_;
 
+    my $pkg = $self->{'package'};
+
+    # Normalise search_path, then apply the default
+    my @path_specs = _normalise_search_path($self->{'search_path'});
+    @path_specs = ([$pkg . '::Plugin', {}]) unless @path_specs;
+
+    # If any path carries per-path option overrides, dispatch each path through
+    # its own sub-finder and merge results in spec order, deduplicating class 
names.
+    if (grep { %{$_->[1]} } @path_specs) {
+        my @results;
+        for my $spec (@path_specs) {
+            my ($path, $per_path) = @$spec;
+            my $sub = ref($self)->new(%$self, %$per_path, search_path => 
[$path]);
+            push @results, $sub->plugins(@args);
+        }
+        my %seen;
+        return grep { ref($_) ? 1 : !$seen{$_}++ } @results;
+    }
+
+    # No per-path overrides: restore as a simple arrayref and continue as 
normal
+    $self->{'search_path'} = [map { $_->[0] } @path_specs];
+
     # override 'require'
     $self->{'require'} = 1 if $self->{'inner'};
 
     my $filename   = $self->{'filename'};
-    my $pkg        = $self->{'package'};
 
     # Get the exception params instantiated
     $self->_setup_exceptions;
 
-    # automatically turn a scalar search path or namespace into a arrayref
-    for (qw(search_path search_dirs)) {
-        $self->{$_} = [ $self->{$_} ] if exists $self->{$_} && 
!ref($self->{$_});
-    }
-
-    # default search path is '<Module>::<Name>::Plugin'
-    $self->{'search_path'} ||= ["${pkg}::Plugin"];
+    # automatically turn a scalar search_dirs into an arrayref
+    $self->{'search_dirs'} = [ $self->{'search_dirs'} ]
+        if exists $self->{'search_dirs'} && !ref($self->{'search_dirs'});
 
     # default error handler
     $self->{'on_require_error'} ||= sub { my ($plugin, $err) = @_; carp 
"Couldn't require $plugin : $err"; return 0 };
@@ -71,15 +193,22 @@
 
     # default whether to follow symlinks
     # because the default behavior is changed in the Perl-CORE module 
File::Find VERSION >= '1.39',
-    # in lower versions of File::Find, 'follow_symlinks' is (independent from 
the callers setting of 
+    # in lower versions of File::Find, 'follow_symlinks' is (independent from 
the callers setting of
     # 'follow_symlinks') hardcoded set to 0 on Windows so we force File::Find 
to fall back to the old
-    # behavior, if not otherwise told 
+    # behavior, if not otherwise told
     $self->{'follow_symlinks'} = 0 if ($File::Find::VERSION >= '1.39' && $^O 
eq 'MSWin32' && ! exists $self->{'follow_symlinks'} );
     $self->{'follow_symlinks'} = 1 unless exists $self->{'follow_symlinks'};
 
     # check to see if we're running under test
     my @SEARCHDIR = exists $INC{"blib.pm"} && defined $filename && $filename 
=~ m!(^|/)blib/! && !$self->{'force_search_all_paths'} ? grep {/blib/} @INC : 
@INC;
 
+    if ($self->{'search_dirs_strict'}) {
+        die "search_dirs_strict: makes no sense without setting search_dirs"
+            unless $self->{'search_dirs'};
+
+        @SEARCHDIR = ();
+    }
+
     # add any search_dir params
     unshift @SEARCHDIR, @{$self->{'search_dirs'}} if defined 
$self->{'search_dirs'};
 
@@ -95,19 +224,24 @@
     # return blank unless we've found anything
     return () unless @plugins;
 
-    # remove duplicates
-    # probably not necessary but hey ho
-    my %plugins;
+    # remove duplicates, keeping track of discovery order for 'sort_results'
+    my (@dedupe, %plugins);
     for(@plugins) {
         next unless $self->_is_legit($_);
-        $plugins{$_} = 1;
+        next if $plugins{$_}++;
+        push @dedupe, $_;
     }
 
+    my $sort_results = exists $self->{'sort_results'} ? 
$self->{'sort_results'} : 'alpha';
+    my @ordered = $sort_results eq 'path' ? @dedupe
+                : !$sort_results          ? keys %plugins
+                :                           sort keys %plugins;
+
     # are we instantiating or requiring?
     if (defined $self->{'instantiate'}) {
         my $method = $self->{'instantiate'};
         my @objs   = ();
-        foreach my $package (sort keys %plugins) {
+        foreach my $package (@ordered) {
             next unless $package->can($method);
             $self->{'before_instantiate'}->($package)
                 or next;
@@ -122,7 +256,7 @@
         return @objs;
     } else {
         # no? just return the names
-        my @objs = sort keys %plugins;
+        my @objs = @ordered;
         return @objs;
     }
 }
@@ -171,10 +305,10 @@
     my $except = $self->{_exceptions}->{except};
     my $depth  = () = split '::', $plugin, -1;
 
-    return 0 if     (keys %only   && !$only{$plugin}     );
-    return 0 unless (!defined $only || $plugin =~ m!$only!     );
+    return 0 if     (keys %only   && !$only{$plugin} );
+    return 0 unless (!defined $only || $plugin =~ m!$only! );
 
-    return 0 if     (keys %except &&  $except{$plugin}   );
+    return 0 if     (keys %except &&  $except{$plugin} );
     return 0 if     (defined $except &&  $plugin =~ m!$except! );
 
     return 0 if     defined $self->{max_depth} && $depth>$self->{max_depth};
@@ -185,6 +319,15 @@
     return 1;
 }
 
+=head2 search_directories(@dirs)
+
+    my @plugins = $finder->search_directories(@INC);
+
+Iterates over C<@dirs> and delegates to L</search_paths> for each one.
+Normally called internally by L</plugins>, but exposed so subclasses can
+override the top-level directory loop.
+=cut
+
 sub search_directories {
     my $self      = shift;
     my @SEARCHDIR = @_;
@@ -197,6 +340,18 @@
     return @plugins;
 }
 
+=head2 search_paths($dir)
+
+    my @plugins = $finder->search_paths($dir);
+
+For a single base directory, iterates over C</search_path> namespaces, locates
+C<.pm> files under the corresponding subdirectory, converts file paths to
+package names, and calls L</handle_finding_plugin> for each candidate.
+
+Handles case-insensitive filesystems by reading the C<package> declaration
+out of the file when the file name is all-lower or all-upper case.
+
+=cut
 
 sub search_paths {
     my $self = shift;
@@ -296,6 +451,20 @@
     return 0;
 }
 
+=head2 handle_finding_plugin($plugin, \@plugins, $no_require)
+
+    $finder->handle_finding_plugin('MyApp::Plugin::Foo', \@plugins);
+
+Called for each candidate package name discovered during the search. Applies
+the C<only>/C<except>/C<min_depth>/C<max_depth> filters, invokes
+C<before_require> and C<after_require> triggers, optionally C<require>s the
+module, and pushes it onto C<\@plugins> if everything succeeds.
+
+Pass a true value for C<$no_require> to skip the C<require> step (used when
+the module is known to already be loaded, e.g. for inner packages).
+
+=cut
+
 sub handle_finding_plugin {
     my $self    = shift;
     my $plugin  = shift;
@@ -326,6 +495,15 @@
     push @$plugins, $plugin;
 }
 
+=head2 find_files($search_path)
+
+    my @files = $finder->find_files($dir);
+
+Recursively finds all files matching C<file_regex> (default C<qr/\.pm$/>) under
+C<$dir> using L<File::Find>. Respects the C<follow_symlinks> option.
+
+=cut
+
 sub find_files {
     my $self         = shift;
     my $search_path  = shift;
@@ -353,6 +531,17 @@
 
 }
 
+=head2 handle_inc_hooks($search_path, @dirs)
+
+    my @plugins = $finder->handle_inc_hooks($path, @SEARCHDIR);
+
+Handles the case where an entry in C<@INC> is a blessed object with a
+C<files()> method (as used by L<App::FatPacker> and L<PAR>). Calls
+C<files()> on each such object, filters by C<$search_path>, and delegates
+to L</handle_finding_plugin>.
+
+=cut
+
 sub handle_inc_hooks {
     my $self      = shift;
     my $path      = shift;
@@ -372,6 +561,17 @@
     return @plugins;
 }
 
+=head2 handle_innerpackages($search_path)
+
+    my @plugins = $finder->handle_innerpackages('MyApp::Plugin');
+
+Uses L<Devel::InnerPackage> to find any package declared inside an already-
+loaded file whose name falls under C<$search_path>. This is how
+C<Something::Plugin::Bar> is discovered when it is defined inside
+F<Something/Plugin/Foo.pm>. Set C<< inner => 0 >> to disable this behaviour.
+
+=cut
+
 sub handle_innerpackages {
     my $self = shift;
     return () if (exists $self->{inner} && !$self->{inner});
@@ -388,41 +588,251 @@
 
 1;
 
-=pod
+=head1 OPTIONS
 
-=head1 NAME
+All options are passed to C<new> as a flat hash and may be set or overridden
+directly on the object before calling C<plugins>.
 
-Module::Pluggable::Object - automatically give your module the ability to have 
plugins
+=head2 package
 
-=head1 SYNOPSIS
+The package name used to build the default C<search_path>. When using
+L<Module::Pluggable> this is set automatically to the calling package. When
+using C<Module::Pluggable::Object> directly you should set it explicitly.
 
+=head2 search_path
 
-Simple use Module::Pluggable -
+An array ref (or a single string, which is promoted to an array ref) of
+namespace prefixes to search. Defaults to C<["${package}::Plugin"]>.
 
-    package MyClass;
-    use Module::Pluggable::Object;
+    search_path => ['MyApp::Plugin', 'MyApp::Extension']
 
-    my $finder = Module::Pluggable::Object->new(%opts);
-    print "My plugins are: ".join(", ", $finder->plugins)."\n";
+Alternatively, you can pass a hash ref where each key is a namespace prefix and
+each value is a hash ref of option overrides for that path. The per-path
+options are merged on top of the global options, so you only need to
+specify what differs.
 
-=head1 DESCRIPTION
+    search_path => {
+        'MyApp::Plugin'    => { max_depth => 2 },
+        'MyApp::Extension' => { max_depth => 3, instantiate => 'new' },
+    }
 
-Provides a simple but, hopefully, extensible way of having 'plugins' for
-your module. Obviously this isn't going to be the be all and end all of
-solutions but it works for me.
-
-Essentially all it does is export a method into your namespace that
-looks through a search path for .pm files and turn those into class names.
-
-Optionally it instantiates those classes for you.
-
-This object is wrapped by C<Module::Pluggable>. If you want to do something
-odd or add non-general special features you're probably best to wrap this
-and produce your own subclass.
+As another alternative, if you want plugins to be in a particular order, use
+an array ref of mixed plain strings and single-key hash refs. Order is
+preserved; only the hash ref entries carry per-path option overrides.
 
-=head1 OPTIONS
+    search_path => [
+        'MyApp::Plugin',
+        { 'MyApp::Extension' => { max_depth => 3, instantiate => 'new' } },
+    ]
+
+Results from all paths are combined and returned in a single list. Class
+names are deduplicated across paths; if any path uses C<instantiate> its
+objects are included as-is alongside any class names from other paths.
+
+=head2 search_dirs
+
+An array ref (or a single string) of filesystem directories to search
+I<before> C<@INC>. Use this when plugins live outside the normal include
+path.
+
+    search_dirs => ['/opt/myapp/plugins']
+
+=head2 search_dirs_strict
+
+When true, only the directories in C<search_dirs> are searched; C<@INC> is
+ignored entirely. Requires C<search_dirs> to also be set.
+
+    search_dirs_strict => 1
+
+=head2 instantiate
+
+Name of the method to call on each plugin class to create an instance.
+Typically C<'new'>. When set, C<plugins()> returns a list of objects instead
+of class names. Arguments passed to C<plugins()> are forwarded to this method.
+
+    instantiate => 'new'
+
+=head2 require
+
+When true, each plugin module is C<require>d but not instantiated. Overrides
+C<instantiate>. Also enables inner-package discovery (see L</inner>).
+
+    require => 1
+
+=head2 inner
+
+Controls whether inner packages (secondary C<package> declarations inside a
+single F<.pm> file) are discovered.
+
+=over 4
+
+=item * Defaults to true when C<require> or C<instantiate> is set.
+
+=item * Set to C<0> to disable even when C<require> is set.
+
+=back
+
+    inner => 0
+
+=head2 only
+
+Restricts the returned plugins to those matching this value. May be:
+
+=over 4
+
+=item * A string - only the exact named plugin is returned.
+
+=item * An array ref of strings - only those exact plugins are returned.
+
+=item * A compiled regex - only plugins whose name matches are returned.
+
+=back
+
+    only => qr/^MyApp::Plugin::Safe/
+
+=head2 except
+
+Excludes matching plugins from the results. Accepts the same forms as C<only>.
+
+    except => ['MyApp::Plugin::Broken', 'MyApp::Plugin::Deprecated']
+
+=head2 min_depth / max_depth
+
+Restrict discovery to plugins at a certain namespace depth (number of
+C<::>-separated components).
+
+For example, C<MyApp::Plugin::Foo> has depth 3 and
+C<MyApp::Plugin::Foo::Bar> has depth 4.
+
+    max_depth => 3   # only MyApp::Plugin::Foo, not MyApp::Plugin::Foo::Bar
+    min_depth => 4   # only MyApp::Plugin::Foo::Bar and deeper
+
+=head2 file_regex
+
+A compiled regex used to identify plugin files. Defaults to C<qr/\.pm$/>.
+
+    file_regex => qr/\.plugin$/
+
+=head2 follow_symlinks
+
+Whether to follow symbolic links when walking the filesystem. Defaults to
+C<1> (follow symlinks) on all platforms except Windows with
+L<File::Find> E<ge> 1.39, where it defaults to C<0> to preserve the
+historical behaviour.
+
+    follow_symlinks => 0
+
+=head2 include_editor_junk
 
-See the C<Module::Pluggable> docs.
+By default, files that look like editor artefacts are silently skipped:
+
+=over 4
+
+=item * Files ending in C<~> (Emacs / generic backup files).
+
+=item * Files beginning with C<.#> (Emacs lock files).
+
+=item * Files matching C<^[._].*\.s[a-w][a-z]$> (Vim swap files).
+
+=back
+
+Set C<include_editor_junk> to a true value to disable this filtering and
+include every file that matches C<file_regex>.
+
+    include_editor_junk => 1
+
+=head2 force_search_all_paths
+
+By default, when the module is loaded from inside a C<blib/> tree (i.e.
+during C<make test>), only C<@INC> entries that themselves contain C<blib>
+are searched. Set this option to search all of C<@INC> regardless.
+
+    force_search_all_paths => 1
+
+=head2 sort_results
+
+Controls the order plugin names (or objects) are returned in. One of:
+
+=over 4
+
+=item * C<'alpha'> (the default) - sorted alphabetically by package name.
+
+=item * C<'path'> - returned in search-path discovery order: all plugins
+under the first C<search_path> entry, then the second, and so on. Within a
+single path, order follows the underlying filesystem traversal.
+
+=item * C<0> - no sort is applied at all. Fastest option, for callers that
+will sort or otherwise order the results themselves.
+
+=back
+
+    sort_results => 'path'
+
+=head1 TRIGGERS
+
+Triggers are callbacks passed as options to C<new>. If a trigger returns
+C<0> (or any false value), the plugin is dropped at that point in the
+pipeline.
+
+=head2 before_require
+
+    before_require => sub { my ($plugin) = @_; ... }
+
+Called with the plugin package name before it is C<require>d. Return C<0> to
+skip both the C<require> and all subsequent steps for this plugin.
+
+=head2 on_require_error
+
+    on_require_error => sub { my ($plugin, $err) = @_; ... }
+
+Called when C<require> raises an exception. The default handler C<carp>s the
+error and returns C<0> (dropping the plugin). Return a true value to keep the
+plugin in the list despite the error.
+
+=head2 after_require
+
+    after_require => sub { my ($plugin) = @_; ... }
+
+Called after a successful C<require>. Return C<0> to exclude the plugin from
+results even though it has already been loaded.
+
+=head2 before_instantiate
+
+    before_instantiate => sub { my ($plugin) = @_; ... }
+
+Called with the plugin package name before the C<instantiate> method is
+invoked. Return C<0> to skip instantiation for this plugin.
+
+=head2 on_instantiate_error
+
+    on_instantiate_error => sub { my ($plugin, $err) = @_; ... }
+
+Called when the C<instantiate> method raises an exception. The default handler
+C<carp>s the error and returns C<0>.
+
+=head2 after_instantiate
+
+    after_instantiate => sub { my ($plugin, $obj) = @_; return $obj }
+
+Called after successful instantiation with the plugin name and the newly
+created object. The return value is used as the plugin entry in the results
+list; return a false value to discard the plugin entirely. Use this to wrap
+or decorate plugin objects transparently.
+
+=head1 BEHAVIOUR UNDER TEST ENVIRONMENTS
+
+When C<blib.pm> is present in C<%INC> and the calling file's path contains
+C<blib/>, only C<@INC> entries that themselves contain C<blib> are searched.
+This prevents installed (non-blib) versions of plugins from interfering with
+tests. See C<force_search_all_paths> to override this.
+
+=head1 @INC HOOKS AND App::FatPacker
+
+If an entry in C<@INC> is a blessed object with a C<files()> method, its
+C<files()> list is searched for plugin modules. This is how L<App::FatPacker>
+(from version 0.10.0 onwards) integrates with C<Module::Pluggable::Object>,
+and how L<PAR> can be made to do the same. See F<t/26inc_hook.t> for a
+working example.
 
 =head1 AUTHOR
 
@@ -430,17 +840,14 @@
 
 =head1 COPYING
 
-Copyright, 2006 Simon Wistow
+Copyright, 2006-2026 Simon Wistow
 
 Distributed under the same terms as Perl itself.
 
-=head1 BUGS
-
-None known.
-
 =head1 SEE ALSO
 
-L<Module::Pluggable>
+L<Module::Pluggable>, L<Devel::InnerPackage>,
+L<Module::Runtime>, L<App::FatPacker>, L<PAR>
 
 =cut
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/lib/Module/Pluggable.pm 
new/Module-Pluggable-6.4/lib/Module/Pluggable.pm
--- old/Module-Pluggable-6.3/lib/Module/Pluggable.pm    2025-01-10 
20:58:46.576197256 +0100
+++ new/Module-Pluggable-6.4/lib/Module/Pluggable.pm    2026-07-26 
00:07:28.000000000 +0200
@@ -10,7 +10,7 @@
 # Peter Gibbons: I wouldn't say I've been missing it, Bob!
 
 
-our $VERSION = '6.3';
+our $VERSION = '6.4';
 our $FORCE_SEARCH_ALL_PATHS = 0;
 
 sub import {
@@ -185,6 +185,8 @@
 
     use Module::Pluggable search_dirs => ['mylibs/Foo'];
 
+If you I<only> want to search those C<search_dirs>, provide a true value for
+C<search_dirs_strict>.
 
 Or if you want to instantiate each plugin rather than just return the name
 
@@ -279,6 +281,31 @@
 
 An array ref of namespaces to look in.
 
+    search_path => ['MyApp::Plugin', 'MyApp::Extension']
+
+Alternatively, you can pass a hash ref where each key is a namespace prefix and
+each value is a hash ref of option overrides for that path. The per-path
+options are merged on top of the global options, so you only need to
+specify what differs.
+
+    search_path => {
+        'MyApp::Plugin'    => { max_depth => 2 },
+        'MyApp::Extension' => { max_depth => 3, instantiate => 'new' },
+    }
+
+If you want plugins to be in a particular order, use an array ref of mixed
+plain strings and single-key hash refs instead. Order is preserved; only the
+hash ref entries carry per-path option overrides.
+
+    search_path => [
+        'MyApp::Plugin',
+        { 'MyApp::Extension' => { max_depth => 3, instantiate => 'new' } },
+    ]
+
+Results from all paths are combined and returned in a single list. Class
+names are deduplicated across paths; if any path uses C<instantiate> its
+objects are included as-is alongside any class names from other paths.
+
 =head2 search_dirs
 
 An array ref of directories to look in before @INC.
@@ -356,6 +383,24 @@
         package MyClass;
         use Module::Pluggable min_depth => 4;
 
+=head2 sort_results
+
+Controls the order plugin names (or objects) are returned in. One of:
+
+=over 4
+
+=item * C<'alpha'> (the default) - sorted alphabetically by package name.
+
+=item * C<'path'> - returned in search-path discovery order: all plugins
+under the first C<search_path> entry, then the second, and so on.
+
+=item * C<0> - no sort is applied at all. Fastest option, for callers that
+will sort or otherwise order the results themselves.
+
+=back
+
+        package MyClass;
+        use Module::Pluggable sort_results => 'path';
 
 =head1 TRIGGERS
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/10innerpack_super.t 
new/Module-Pluggable-6.4/t/10innerpack_super.t
--- old/Module-Pluggable-6.3/t/10innerpack_super.t      2012-03-22 
23:22:17.000000000 +0100
+++ new/Module-Pluggable-6.4/t/10innerpack_super.t      2026-07-26 
00:07:28.000000000 +0200
@@ -18,7 +18,7 @@
 sub whee {}
 
 package Bar;
-use base 'Foo';
+use parent -norequire => 'Foo';
 
 sub whee {
     shift->SUPER::whee;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/24local_inc_object.t 
new/Module-Pluggable-6.4/t/24local_inc_object.t
--- old/Module-Pluggable-6.3/t/24local_inc_object.t     2019-11-18 
22:48:46.000000000 +0100
+++ new/Module-Pluggable-6.4/t/24local_inc_object.t     2026-07-26 
00:07:28.000000000 +0200
@@ -4,15 +4,8 @@
 use FindBin;
 use Test::More tests => 5;
 
-eval { require 'Text::BibTex' };
-my $bibtex = !$@;
-
-SKIP: {
-
-skip "This test fails when Text::BibTex is installed", 2 if $bibtex;
-
 my $inc  = IncTest->new();
-my ($ta) = grep { ref($_) eq 'Text::Abbrev'} eval { local ($^W) = 0; 
$inc->plugins };
+my ($ta) = grep { ref($_) eq 'LocalIncTest::Plugin::Abbrev'} eval { local 
($^W) = 0; $inc->plugins };
 ok($ta);
 is($ta->MPCHECK, "HELLO");
 
@@ -25,13 +18,11 @@
 my $total = scalar values %after;
 ok($total == $norefs, 'after_instantiate has all refs');
 
-};
-
 package IncTest;
 our @BEFORE;
 our %AFTER;
 
-use Module::Pluggable search_path => "Text",
+use Module::Pluggable search_path => "LocalIncTest::Plugin",
                       search_dirs => "t/lib",
                       instantiate => 'module_pluggable',
                       before_instantiate => sub { push @BEFORE, $_[0]; return 
1 },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/24local_inc_package.t 
new/Module-Pluggable-6.4/t/24local_inc_package.t
--- old/Module-Pluggable-6.3/t/24local_inc_package.t    2013-05-28 
07:19:59.000000000 +0200
+++ new/Module-Pluggable-6.4/t/24local_inc_package.t    2026-07-26 
00:07:28.000000000 +0200
@@ -4,22 +4,14 @@
 use FindBin;
 use Test::More tests => 1;
 
-eval { require 'Text::BibTex' };
-my $bibtex = !$@;
-
-SKIP: {
-
-skip "This test fails when Text::BibTex is installed", 2 if $bibtex; 
-    
 IncTest->new()->plugins;
-is(Text::Abbrev->MPCHECK, "HELLO");
+is(LocalIncTest::Plugin::Abbrev->MPCHECK, "HELLO");
 
-}
 package IncTest;
-use Module::Pluggable search_path => "Text", search_dirs => "t/lib", require 
=> 1;
+use Module::Pluggable search_path => "LocalIncTest::Plugin", search_dirs => 
"t/lib", require => 1;
 
 sub new {
     my $class = shift;
     return bless {}, $class;
 }
-1;
\ No newline at end of file
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/30search_dirs_strict.t 
new/Module-Pluggable-6.4/t/30search_dirs_strict.t
--- old/Module-Pluggable-6.3/t/30search_dirs_strict.t   1970-01-01 
01:00:00.000000000 +0100
+++ new/Module-Pluggable-6.4/t/30search_dirs_strict.t   2026-07-26 
00:07:28.000000000 +0200
@@ -0,0 +1,48 @@
+#!perl -w
+
+use strict;
+use FindBin;
+use Test::More tests => 3;
+
+SKIP: {
+
+my $inc = IncTest->new();
+
+$inc->plugins;
+
+ok($inc->required() > 0,'before_instantiate fired');
+
+# In other words, we only required LocalIncTest::Plugin::Abbrev, not all the
+# other random stuff from a standard @INC.
+is_deeply(
+  [ $inc->required ],
+  [ 'LocalIncTest::Plugin::Abbrev' ],
+  'we only required the expected library',
+);
+
+my ($ta) = $inc->required;
+is($ta->MPCHECK, 'HELLO', 'we got our version of 
LocalIncTest::Plugin::Abbrev');
+
+};
+
+package IncTest;
+our %REQUIRED;
+
+use Module::Pluggable search_path => "LocalIncTest::Plugin",
+                      search_dirs => "t/lib",
+                      search_dirs_strict => 1,
+                      require => 1,
+                      after_require  => sub { $REQUIRED{$_[0]} = 1 },
+                      on_require_error     => sub { };
+
+sub new {
+    my $class = shift;
+    return bless {}, $class;
+}
+
+sub required {
+  my @req = sort keys %REQUIRED;
+  return @req;
+}
+
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/31per_path_opts.t 
new/Module-Pluggable-6.4/t/31per_path_opts.t
--- old/Module-Pluggable-6.3/t/31per_path_opts.t        1970-01-01 
01:00:00.000000000 +0100
+++ new/Module-Pluggable-6.4/t/31per_path_opts.t        2026-07-26 
00:07:28.000000000 +0200
@@ -0,0 +1,109 @@
+#!perl -w
+
+use strict;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use Test::More tests => 6;
+use Module::Pluggable::Object;
+
+# Basic per-path opts: two paths, no overrides
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => {
+            'MyTest::Plugin'        => {},
+            'MyTest::Extend::Plugin' => {},
+        },
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    is_deeply(
+        \@plugins,
+        [qw(MyTest::Extend::Plugin::Bar MyTest::Plugin::Bar 
MyTest::Plugin::Foo MyTest::Plugin::Quux::Foo)],
+        'both paths searched, results merged and sorted',
+    );
+}
+
+# Per-path max_depth: restrict one path, not the other
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => {
+            'MyTest::Plugin'        => { max_depth => 3 },
+            'MyTest::Extend::Plugin' => {},
+        },
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    is_deeply(
+        \@plugins,
+        [qw(MyTest::Extend::Plugin::Bar MyTest::Plugin::Bar 
MyTest::Plugin::Foo)],
+        'max_depth => 3 on MyTest::Plugin excludes Quux::Foo',
+    );
+}
+
+# Deduplication: same plugin reachable via two paths
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => {
+            'MyTest::Plugin' => {},
+            'MyTest::Plugin' => {},   # duplicate key, Perl keeps last — same 
path twice via hash trick won't work
+        },
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    my %seen;
+    my @dupes = grep { $seen{$_}++ } @plugins;
+    is_deeply(\@dupes, [], 'no duplicates when same plugin appears via 
multiple paths');
+}
+
+# Different options per path are isolated (max_depth on one does not bleed 
into the other)
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => {
+            'MyTest::Plugin'        => { max_depth => 3 },
+            'MyTest::Extend::Plugin' => { max_depth => 3 },
+        },
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    is_deeply(
+        \@plugins,
+        [qw(MyTest::Plugin::Bar MyTest::Plugin::Foo)],
+        'max_depth => 3 applied independently to each path',
+    );
+}
+
+# Mixed arrayref form: order is preserved (path-discovery order), not re-sorted
+# alphabetically across paths, and per-path opts still apply.
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => [
+            { 'MyTest::Plugin'        => { max_depth => 3 } },
+            { 'MyTest::Extend::Plugin' => {} },
+        ],
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    is_deeply(
+        \@plugins,
+        [qw(MyTest::Plugin::Bar MyTest::Plugin::Foo 
MyTest::Extend::Plugin::Bar)],
+        'mixed arrayref: MyTest::Plugin found (depth-limited) before 
MyTest::Extend::Plugin',
+    );
+}
+
+# Reversing the arrayref order reverses the result order, confirming it tracks
+# the spec rather than falling back to alphabetical sort.
+{
+    my $finder = Module::Pluggable::Object->new(
+        search_path => [
+            { 'MyTest::Extend::Plugin' => {} },
+            { 'MyTest::Plugin'        => { max_depth => 3 } },
+        ],
+        search_dirs => ["$FindBin::Bin/lib"],
+    );
+    my @plugins = $finder->plugins;
+    is_deeply(
+        \@plugins,
+        [qw(MyTest::Extend::Plugin::Bar MyTest::Plugin::Bar 
MyTest::Plugin::Foo)],
+        'mixed arrayref: reversing spec order reverses result order',
+    );
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Module-Pluggable-6.3/t/lib/LocalIncTest/Plugin/Abbrev.pm 
new/Module-Pluggable-6.4/t/lib/LocalIncTest/Plugin/Abbrev.pm
--- old/Module-Pluggable-6.3/t/lib/LocalIncTest/Plugin/Abbrev.pm        
1970-01-01 01:00:00.000000000 +0100
+++ new/Module-Pluggable-6.4/t/lib/LocalIncTest/Plugin/Abbrev.pm        
2026-07-26 00:07:28.000000000 +0200
@@ -0,0 +1,10 @@
+package LocalIncTest::Plugin::Abbrev;
+use strict;
+
+sub module_pluggable {
+    return bless {}, shift;
+}
+
+sub MPCHECK { "HELLO" }
+
+1;
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Module-Pluggable-6.3/t/lib/Text/Abbrev.pm 
new/Module-Pluggable-6.4/t/lib/Text/Abbrev.pm
--- old/Module-Pluggable-6.3/t/lib/Text/Abbrev.pm       2012-08-16 
00:17:22.000000000 +0200
+++ new/Module-Pluggable-6.4/t/lib/Text/Abbrev.pm       1970-01-01 
01:00:00.000000000 +0100
@@ -1,10 +0,0 @@
-package Text::Abbrev;
-use strict;
-
-sub module_pluggable {
-    return bless {}, shift;
-}
-
-sub MPCHECK { "HELLO" }
-
-1;
\ No newline at end of file

++++++ README.md ++++++

## Build Results

Current state of perl in openSUSE:Factory is

![Factory build 
results](https://br.opensuse.org/status/openSUSE:Factory/perl-Module-Pluggable/standard)

The current state of perl in the devel project build (devel:languages:perl)

![Devel project build 
results](https://br.opensuse.org/status/devel:languages:perl/perl-Module-Pluggable)



++++++ _scmsync.obsinfo ++++++
mtime: 1785052011
commit: 38bc54e29c49aca35ff4a1c9796aa6b8bb76704488ad8584515506a1c53bd776
url: https://src.opensuse.org/perl/perl-Module-Pluggable
revision: 38bc54e29c49aca35ff4a1c9796aa6b8bb76704488ad8584515506a1c53bd776
projectscmsync: https://src.opensuse.org/perl/_ObsPrj

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-07-26 09:46:51.000000000 +0200
@@ -0,0 +1 @@
+.osc

Reply via email to