views:

298

answers:

6

How can someone distribute native (non-"compiled/perl2exe/...") Perl scripts without forcing users to be aware of the custom (non-CPAN) modules that the scripts needs in order to run?

The problem is users will inevitably copy the script somewhere else on the system and take the script out of its native environment and then it can no longer find the modules it needs to run.

I've sometimes settled with just copying the module into the actual script, but I'd prefer a cleaner solution.

Update: I better clarify. I distribute a bunch of scripts which happen to use similar modules in the backend. The users understand how to run Perl scripts, but rather than relying on telling them "no don't move the script" I'd prefer to simply allow them to move the files. The path of least resistence.

+3  A: 

If a script that your prepared for a client needs "custom" modules, simply pack your modules as if you were trying to upload them to cpan. Then give the package to the client and he can use the cpan utility to install the script and the modules.

innaM
Hmm... maybe. I read the problem as "end users move the *.pl after installation" (presumably under the false assumption that it's self-contained) rather than how to install in the first place.
Michael Carman
I read it as "how do I install modules that go with a script?". The fact that a package can also install the script itself is just an additional benefit, IMHO.
innaM
I suspect that the original "installation" consists of "untar this file," without the user realizing the dependencies between the files. If the installation is more involved — such as by invoking cpan or running an install script — then the temptation to simply move .pl files will go away. Whether it's required is immaterial; the mere *illusion* of a complicated installation will be enough to keep the files in their place.
Rob Kennedy
Of course the temptation is reduced if you do that. But if you install your modules properly, you will also be able to really move that stuff around without any problems.
innaM
+5  A: 

The right way is to tell them "Don't do that!" I would hope that they wouldn't expect to move an exe file and have the program continue to work. This is no different.

That said, there are a couple of alternatives. One is replacing the script with a wrapper (e.g. pl2bat) that knows the full path to the real script. Another is to use PAR, but that would require PAR and/or parl (from PAR::Packer) to be installed.

Michael Carman
+1 There is no need to downvote this. The OP's requirements are contradictory.
Sinan Ünür
I'd just appreciate a comment as to *why* someone found it unhelpful.
Michael Carman
I had trouble with both parts of your answer. Coming from a general-IT background, I haven't found it practical to expect known user behavior to change to accommodate an obscure technical requirement. Also I think an exe file without a formal installer /should/ work from anywhere on the system. If a program has external dependencies, those should be properly installed, so that location in the filesystem doesn't matter.
Ben Dunlap
Re: the alternatives, the first one (a wrapper) doesn't seem to solve the problem because the working-directory will still be different from the one where the modules live (but maybe I misunderstood your suggestion); meanwhile, I'm under the impression that PAR modules still need to be in @INC, which brings us back to the OP's question.
Ben Dunlap
@Sinan, where are you seeing a contradiction in the requirements? I read the question as "What's a robust, user-friendly way to distribute the Perl modules my scripts need, without contributing them to CPAN?"
Ben Dunlap
Michael is right. You *can* just use PAR to produce a binary that will be relocatable. While the OP asked for a different solution, he's not provided a reason why.
tsee
By "use PAR" I meant to package up the entire script (modules and all) into a single *.par file. That's based on the assumption that the modules are application-specific and he's just trying to prevent having them severed from the script. If they're supposed to be sharable it's a different story.
Michael Carman
If you install Perl scripts just like any other module, it isn't an obscure technical requirement. Make it easy to install the module using the tools that come with Perl and document it how to do it. Show people how to do it so they learn something.
brian d foy
+1  A: 

Distribute an installer along with the script. The installer will need to be run with root privileges and it will put the custom modules into the standard system location (/usr/local/lib/perl5/site_perl or whatever).

I've not tried this, but Module::Install looks helpful in this regard. It's described as a:

Standalone, extensible Perl module installer

Ben Dunlap
Installing application-specific modules into system-wide locations is a great way to risk file/namespace collisions.
Michael Carman
I suppose there are trade-offs to everything, but a bit of forethought can reduce the risk of namespace collisions to practically nil in the case of a Perl module. The author could simply create a top-level namespace that's based on his name or company name and is highly likely to be unique.
Ben Dunlap
Module::Installer doesn't really do anything that you can't get from Module::Build or ExtUtils::Makemaker.
brian d foy
A: 

It would be really nice if you could just use a NeXTSTEP style application bundle. Since you probably aren't developing for a platform that uses bundles, you'll have to make do.

Put all support files in a known location, and point your executable at those files for access to settings and libraries. The easiest way to do this is with a simple installer.

For example, with an app called foo, put all your required files in /opt/xlyd_apps/foo, libraries in /opt/xlyd_apps/foo/lib, configuration in/opt/xlyd_apps/foo/etc, and so on. Put the executable in /opt/xlyd_apps/foo/bin.

The important thing is to make sure the executable knows to look in /opt/xlyd_apps/foo for all its goodies, so if the customer/client move the foo script to a new location the install still works.

So, while you can't make the whole thing self contained and relocatable, you have made the actual calling script relocatable.

daotoad
This is simply hardcoding a library path in the application. That doesn't seem like a very good approach *unless* it's a feature of the platform. Maybe adding a step of indirection would make things saner. Put your custom modules into some /opt... folder and install just a single, super-simple module into the system libs which knows the path to the custom modules. That way, you don't hardcode paths in more than one place.
tsee
+2  A: 

As a variant of the "put your modules all in one place and make your applications aware of it" that will even work across multiple computers and networks, maybe you should check out PAR::Repository and respectively PAR::Repository::Client. You'd just provide a single binary client executable that connects to the repository (via file system or https) and executes any of the arbitrary number of programs (using an arbitrary set of modules) provided by the repository that the user asks for.

If there are many users, this also has a benefit for maintenance: Simply update the software provided by the repository and the users will start using the updated code for their system when they next start the programs.

tsee
Doesn't something like this require they have these modules installed in their perl installation? Or is it something that is completely standalone?
xyld
PAR::Packer produces completely standalone binary executables. It does, however, package all the dependencies with the application. Therefore, it may be undesirable if you wish to share code between multiple applications. (PAR provides multiple ways to fix this, but I'm running out of comment length.) In the repository scenario, you simply ship a single loader.exe which can fetch applications and dependencies from the repository as needed. See also: http://steffen-mueller.net/talks/appdeployment/
tsee
A: 

I've actually come up with my own solution, and I'm kind of curious what kind of reception it will have.

I've written a script that reads a perl script and looks for "use/require" statements. Upon finding them it checks if the module is part of the standard library (looks at module path for /perl5/\d+.\d+[\d.]+/) and then rewrites the use/require line in two different ways depending on usage.

If require is found:

{
    ... inline the entire module here...
}

If use is found:

BEGIN {
    ... inline the entire module here...
}

If use has imports, immediately follow above with:

BEGIN { import Module ...imports seen... }

I understand this doesn't work with modules that use XS, but I was fine with this. Mostly I need to support only pure perl modules.

xyld
Have a look at Module::ScanDeps for the dependency resolution and Module::CoreList for checking whether it comes with the default library.
tsee
Other than the fact that it doesn't use CPAN modules to do this... why is this process bad? The core of the answer is that you inline the module text into the script. You haven't given me a reason for why this is bad.
xyld
@xyld: If it works, it's fine. But there is many cases in which it doesn't work (including XS modules, of course). While I don't remember the exact module that made me trip when I went down this route, trust me it's not a robust solution. Back then, I actually wrote some moderately sophisticated code to work around various problems automatically. Never finished it and went with what I know to work well. I'll let you know when I come across my attempts.
tsee