Home > Blockchain >  How to define multiple Perl packages in one file that are inter-dependent
How to define multiple Perl packages in one file that are inter-dependent

Time:10-05

I've tried following some other examples and have something like:

{
    package Foo::Bar;
}
{
    package Foo::Baz;

    use Foo::Bar;
}

use Foo::Baz;
# some more code 

# Fails with: Can't locate Foo/Bar.pm in @INC 

My real-world example is I'd like to bundle/concat https://github.com/TeX-Live/texlive-source/blob/trunk/texk/texlive/linked_scripts/texlive/fmtutil.pl along with its dependencies https://github.com/TeX-Live/installer/blob/master/tlpkg/TeXLive/TLConfig.pm and https://github.com/TeX-Live/installer/blob/master/tlpkg/TeXLive/TLUtils.pm into a single file.

Thank you!

CodePudding user response:

The problem is that Perl doesn't know the module is already loaded because you haven't faithfully replicated the loading process. Specifically, you didn't modify %INC.

You could also have problems from not loading the module at compile-time. This could be achieved using a BEGIN block.

To inline a module, add the following to the start of your script:

BEGIN {
   # Insert module here.

   $INC{ ( __PACKAGE__ =~ s{::}{/}rg ) . ".pm" } = 1;
}

So if you had

# script.pl
use strict;
use Foo::Baz;
# ...
# Foo/Bar.pm
package Foo::Bar;
use strict;
# ...
1;
# Foo/Baz.pm
package Foo::Baz;
use strict;
use Foo::Bar;
# ...
1;

You'd end up with

BEGIN {
   # Foo/Bar.pm
   package Foo::Bar;
   use strict;
   # ...
   $INC{ ( __PACKAGE__ =~ s{::}{/}rg ) . ".pm" } = 1;
}

BEGIN {
   # Foo/Baz.pm
   package Foo::Baz;
   use strict;
   use Foo::Bar;
   # ...
   $INC{ ( __PACKAGE__ =~ s{::}{/}rg ) . ".pm" } = 1;
}

# script.pl
use strict;
use Foo::Baz;
# ...

Note the above isn't 100% equivalent to inlining the modules. For example, the equivalent of

use 5.012;
use open ":std", ":encoding(UTF-8)";
use Some::Module;

would actually be

# Non-lexical effects
BEGIN {
   require 5.012;
   binmode(STDIN,  ":encoding(UTF-8)");
   binmode(STDOUT, ":encoding(UTF-8)");
   binmode(STDERR, ":encoding(UTF-8)");
}

BEGIN {
   package Some::Module;
   ...
   $INC{"Some/Module.pm"} = 1;
}

# Lexical effects
use 5.012;
use open ":encoding(UTF-8)";

To properly inline a module, it would be better to use an @INC hook. Using the files from the first approach, one would end up with

BEGIN {
   my %modules = (
      "Foo/Bar.pm" => <<'__EOI__',
# Foo/Bar.pm
package Foo::Bar;
use strict;
# ...
1;
__EOI__
      "Foo/Baz.pm" => <<'__EOI__',
# Foo/Baz.pm
package Foo::Baz;
use strict;
use Foo::Bar;
# ...
1;
__EOI__
   );

   unshift @INC, sub { $modules{$_[1]} };
}

# script.pl
use strict;
use Foo::Baz;
# ...

App::FatPacker can be used to inline modules this way.

The line numbers will be the line numbers of the original file. a #line directive added to the inlined module would adjust that.

CodePudding user response:

You do not use packages defined in the same file. They are already compiled and parsed by loading the file. Example:

{
    package Foo::Bar;

    sub bar { print "bar\n" }
}
{
    package Foo::Baz;

    sub baz {
        Foo::Bar::bar();
        print "baz\n"
    }
}

use strict;
use warnings;

Foo::Baz::baz();

Output:

bar
baz

The use statement is used to load other files (Perl modules) from @INC.

CodePudding user response:

In normal settings, the use Module call has two jobs:

  1. Find the source file Module.pm and parse it like a compile-phase require call, and
  2. Call the Module::import function

By inlining your modules, you don't need to do the first job. The functions and set up code for the module are already defined in the correct namespace.

Calling import for the second step may or may not be necessary, or may be necessary several times, depending on what functions can be exported and what namespaces you want to use them in.

package Foo::Bar;
use parent 'Exporter';
@Foo::Bar::EXPORT_OK = qw(bar1 bar2);
sub bar1 { 47 }
sub bar2 { 73 }

package Foo::Baz;
@Foo::Baz::EXPORT_OK = "baz1";
use parent 'Exporter';
Foo::Bar->import("bar1");
sub baz1 { return bar1()   42 }

package main;
Foo::Bar->import("bar2");
Foo::Baz->import("baz1");
sub main2 { return bar2()   baz1()   19 }
  • Related