Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Re: Why $FIle::Find::prune = 1 returns used only once error

by jcb (Parson)
on Apr 16, 2021 at 22:19 UTC ( [id://11131394] : note . print w/replies, xml ) Need Help??


in reply to Why $FIle::Find::prune = 1 returns used only once error

First, complain about the mispackaging of Perl by your distribution. Omitting core modules like that is a bug in the distribution.

Second, use Module is equivalent to BEGIN { require Module; Module->import; }; note the lack of arguments to the import method. If asked for default imports, File::Find apparently does some magic to make $File::Find::prune known that is not done if you explicitly import only the find procedure.

Replies are listed 'Best First'.
Re^2: Why $File::Find::prune = 1 returns used only once error
by haukex (Archbishop) on Apr 17, 2021 at 07:56 UTC
    If asked for default imports, File::Find apparently does some magic to make $File::Find::prune known that is not done if you explicitly import only the find procedure.

    No, it has nothing to do with import.

    Bar.pm:

    package Bar; use warnings; use strict; our $x; 1;

    foo.pl:

    use warnings; use strict; use FindBin; use lib $FindBin::Bin; #use Bar; # no warning #use Bar (); # no warning #BEGIN { require Bar; } # no warning require Bar; # warning when var used only once $Bar::x = 1; # warning with plain 'require' #$Bar::x = $Bar::x = 1; # no warning

    Edit: Added use Bar (); example.

Re^2: Why $FIle::Find::prune = 1 returns used only once error
by h2 (Beadle) on Apr 16, 2021 at 22:46 UTC

    Fedora isn't my distribution, but they distribute this code, so I have to make it run on Fedora without crashing on start due to missing module error, obviously I agree that this is a bug in Redhat/Fedora, but they believe it's a feature to remove core modules from Perl's core modules and then make you install them separately, so that's outside of the scope I can fix.

    While their packager can fix and I think does add as a dependency these missing core modules, many users do not use the packaged version since they want a current version, which leads to this issue. Again, the problem here which I'm looking for a solution is the Perl warning, not making Fedora do what I want them to do, which I can't do.

    I tried removing the explicit 'find' from File::Find->import, made no difference, though it was a good suggestion.

    This error appears without running this logic, it's a perl compile time warning.

    I'm guessing when "use File::Find;" is used to load the module, Perl then knows not to worry about specific instances of File::Find in the following code, but when require File::Find is used later one inside of a sub, it doesn't, but that seems weird because Perl knows not to complain about $File::Find::name for example.

    As noted, this is very confusing and is I think why I didn't use $File::Find::prune in the past for this feature, I could not figure out how to make Perl not complain on start about this without "use File::Find;" which can't be done.

      While their packager can fix and I think does add as a dependency these missing core modules, many users do not use the packaged version since they want a current version, which leads to this issue.

      There is nothing to prevent you from declaring File::Find as a dependency of your module. I always try to include core modules as dependencies when I release a dist as it avoids precisely this problem. It also means that I don't have to worry about p5p removing modules from core at a later date.

      BTW, the Name "File::Find::prune" used only once: possible typo at... message you receive is not an error, it is merely a warning.


      🦛

        It also means that I don't have to worry about p5p removing modules from core at a later date.

        I wholeheartedly agree with this (listing current core modules as dependencies explicitly).

        This program has a fairly unique set of core requirements, one of which would be to never be packaged as a module, since that would then defeat another of its core requirements, to be installable and upgradeable on almost anything out there, within reason, without any dependency on anything beyond Perl 5/7, and a tiny subset of Core Modules, the fewer the better. Perl has allowed it to meet all those goals, with flying colors.

        It would be fair to say I'm not a Perl programmer, I use Perl 5 because it was by far and away the best, and literally only, choice of language in existence to meet all the program's core requirements, so I might better be termed a Perl wrangler, in that, I wrangle Perl to get it to do what I want, while learning Perl more along the way so I can be a better wrangler and get even more stuff to work. This is turning out absolutely fantastically, by the way, but I'm still not a 'Perl programmer', but I am very very glad the Perl project has the internal discipline that lets Perl be literally the only thing in the world that could be used for this purpose. The fact Perl 5 is blazingly fast, particularly once I learned some key Perl native concepts which I found very difficult to get my head around, still do, certainly does not hurt matters at all.

        The other issue is that in fact, File::Find is NOT a dependency, it's in a subset of modules that are not required in any way unless the user runs a very specific action, which 99.99% (probably less than that) of users will never do. It's what we call in Debian a 'recommends', that is, something that if you want to do x or y, you can add so you can do x or y. I distinguish fairly strictly between 'dependencies' and 'recommends' because that really matters in this case, NOTHING that is not strictly required for the program to run should ever be a 'dependency' since that would block and hinder its utility. There are a range of such recommends, all of which get handled internally by error handlers, which then tell the user politely, hey, if you want to do this advanced feature, you are missing this Perl Module, so can't do that, sorry.

        Also, I've run execution optimizers on this fairly frequently, and the initial loading of modules is a non zero, non trivial performance hit, which is another reason not to load them until required, there's no reason to slow execution loading something that 99.99+% of users will never ever use, and don't even know exists as a feature unless they read the man pages, and even then, wouldn't in general do. It never ceases to amaze me how I can continuously squeeze out more optimizations by doing more advanced Perl techniques, still have not found the end of that, though I'm approaching it, but it's not there yet. Though maybe I am closer than I think, since some of the optimizations I've considered recently would really conflict with maintaining the code, and having it be clean and easy to use.

        Some distributions, I believe rpm/redhat/fedora are one, do not support the concept of 'recommends', their package managers only handle dependencies, which leads to real bloat in package installs since they tend to pull in all kinds of junk that is absolutely not required or a dependency, I know that is this case with this program, the Fedora packager includes as dependencies things that are not only not dependencies, but, which if installed, actually can force the os into installing stuff that it has no business installing, desktop stuff on a server, for example. FreeBSD is also guilty of doing this, which is unfortunate.

      because Perl knows not to complain about $File::Find::name for example.

      Based only on the code in the root node, I get the "used only once" warnings for both $File::Find::prune and $File::Find::name. Are you sure you're not using the latter twice?

      I tried removing the explicit 'find' from File::Find->import, made no difference

      find is exported by default, so yes, it doesn't make a difference. If you didn't call import at all (the equivalent with use is "use File::Find ();"), then you'd have to call it as File::Find::find(). And BTW, the other difference between importing find() at runtime (e.g. require + ->import) vs. compile time (e.g. use) is that in the latter case, you could omit the parens on the find function call.

        Lol, yes, I discovered this omitting the parens with use vs require, that's an issue that actually had made me give up initially on pulling the File::Find into a local require rather than a global use, that came about from using code I didn't understand copied from the interweb, but once I finally figured out why find was failing, I learned my lesson, and now do not omit parens even when I can with a few exceptions to keep things maintainable and readable.

      I found a way to trick the Perl compiler into not complaining, though I suspect this may be a bug but that's also not something I can fix or correct, the solution proved to be quite simple:

      $File::Find::prune = 1 if defined $File::Find::prune;

      I'd definitely put this in the category of a hackish fix, but it's easy to do, and worked, and didn't take any convolutions so I guess it's good enough. Hopefully anyone in the future who comes across this issue can resolve the problem with this simple trick. Since $File::Find::prune wont' be defined prior to require File::Find being run, perl doesn't care about it when it's checking the code on start, and since when this feature runs, File::Find::prune is defined, it works as expected. Not super intuitive, obviously, but there you have it, a fix, that works!

        $File::Find::prune = 1 if defined $File::Find::prune;

        Careful, I doubt this actually does what you think! None of the versions of File::Find that I looked at set prune to a defined value, meaning your code may silence the warning but wouldn't actually set $File::Find::prune to a true value! The code would only work if some piece of code somewhere was setting $File::Find::prune to some value other than undef, but you probably don't want to depend on that happening.

        Since $File::Find::prune wont' be defined prior to require File::Find being run, perl doesn't care about it when it's checking the code on start, and since when this feature runs, File::Find::prune is defined, it works as expected.

        Based on your wording here I think there may be a misunderstanding as to what defined does. File::Find says our $prune;, thus letting the variable be known to the compiler, but it still leaves the variable's value at undef, which is what defined tests - i.e. your test above will still be false.

        Re the comments, don't know how to reply to them:

        Obviously I was concerned that:

        $File::Find::prune = 1 if defined $File::Find::prune;

        would never execute, since that would not solve the issue I had had, but this is not correct, it executes fine, just tested it, printed out a test, say, you are running File::Find::find on /, and exclude /dev /etc/ /var /run etc, then print out inside the test block $File::Find::name and it works exactly as expected, first match, the directory, prune is set to 1, then never prints again for that directory. So apparently prune does get defined somewhere or other, though I'm not going to guess at the innards of Perl, lol, but I can confirm that my trick worked, at least in current Perl.

        Testing the two methods, they are identical in outcome, but given that the prune defined test may be uncertain across Perls, I'm going with the safer one, that will always work without depending on if prune had been defined or not when File::Find was imported. Empirically, it is defined, but I'm going to assume it may not always have been defined in the past (however, that past would have to be long ago, because I tested this on Perl 5.008 with no issues). This makes sense, but from my test, I'd then guess that prune is set to 0, not undefined, per directory, until told otherwise. It must be that otherwise, as you suggested, it would never have worked. I know I make this choice all the time, whether to use an explicit boolean 0/1, or to use undef/1, clearly prune uses 0/1.

        As noted, however, it was the simple use of $File::Find::prune twice that made the warning go away, so it's probably safer to just use:

        $File::Find::prune = 1; $File::Find::prune = 1;

        even though as code it looks really odd, but that also worked fine, and made the interpreter not issue its warning.

        Or, maybe looking a bit less odd:

        $File::Find::prune = 1 if !$File::Find::prune;

        This would I assume remove the possibility that the behavior I got with the defined test might not be general to all Perl versions, which would be a problem. All of these however work exactly the same empirically, but given there may be a Perl where in fact prune was not initially defined, that's risky, and given using prune = 1 2x looks silly in the code, I'm going with the if ! $File::Find::prune test

        However, in the end, I realize now that once I got the first warning out of the way, I added in a second use of prune in a different block, which then of course made the warning go away, so it turns out I never needed the first fix at all, I just didn't realize what was making the warning happen clearly enough.

        Thanks for the comments and observations, they were helpful.

        The Perl compiler is complaining that it only saw File::Find::prune once, so you solve the issue by having your program mention File::Find::prune twice. The warning is intended to catch "odd man out" typos in variable names.

      Are you calling import in a BEGIN block? Using use implicitly wraps the require and import into a BEGIN block. If you are waiting to load File::Find until runtime, then the compiler's warning is correct: perl has not seen File::Find yet and your code only mentions File::Find::prune once.

      The solution is to always call import in a BEGIN block, so the effects will be visible during the rest of the compilation.

        Using BEGIN would defeat the entire purpose of making File::Find NOT a dependency at runtime with use File::Find. This program loads many modules during its execution based on conditions and user selected options, and some of those are not in Core Modules, but they are all handled on failure by an error handler telling the user what to do, but in no case should execution halt unless the user has selected an action that does require a Module that may not be in core, or, as with the case of Fedora, has been removed from core. The primary function of this program is to always work for all users in as many cases as possible. It achieves this function with the occasional lapse or forgetting to test, so that's not an issue.