The delicate process of making friends with symlinks

Alias on 2007-08-27T02:01:41

Symlinks are always tricky, and it's easy to ignore them a lot of the time.

I've typically tried to ignore them wherever I can... until now.

You see, my current $work project (a web application) has the interesting behaviour of configuring itself via the name of the directory ABOVE the project root. I'm still torn on whether this is bad (you can't just check it out and run the tests) or good (you can never accidentally deploy in the wrong configuration) on aggregate.

The setup of a typical dev environment looks like this

# The actual svn checkout (with typical contents)
/opt/productname
/opt/productname/trunk
/opt/productname/t
...etc etc...

# Per-site symlinks # (same code runs Australian site and New Zealand site) /opt/www.dev13au.local -> /opt/productname/trunk /opt/www.dev13nz.local -> /opt/productname/trunk

# Loading up the codebase in a "non-web" context # (this is used for places where back-end automation is done) /opt/backend -> /opt/productname/trunk

# And just to make things more interesting /home/adamk/opt -> /opt


Where this gets REALLY interesting is that the configuration classes do a directory search upwards to get the name of the directory ABOVE the project root, and then EVERYTHING is configured off this. And there's no way around it.

The first time this broke my code was with pler, which changes directory to get to the root path if you aren't in it (since tests need to be run from the root path with .t files). I was confused that prove would run my tests and they passed, but the tests behaved differently under pler.

Because pler uses File::Spec, which called Cwd::cwd, which ignores symlinks, it was changing to the absolute path instead of the symlinked one, which of course broke the config class, and the tests failed.

Module::Build also suffers from this problem. At some point in the code it must do a cwd, so we end up with the keys orig_dir and base_dir both set to the absolute path.

So unfortunately I can't use Module::Build for this project till that gets fixed (and since there's already some other tasks being done from a Makefile, it's essential to use Module::Build).

In any case, here's the fix I used for pler, in the hope it is useful for other people.

The only place that reliably retains the symlinks (that I'm aware of) is $ENV{PWD}. But this isn't portable, and so is not a safe replacement for Cwd::cwd.

What's interesting about Cwd and $ENV{PWD} is that Cwd provides an alternate version of chdir. And this chdir appears to be PWD-aware. If you use Cwd::chdir it will keep PWD up to date with the correct new path, using some code that looks utterly evil (because of so many platform-related special cases) but that works great.

So in pler, I've used the following to find the "symlink-aware current working directory" and it seems to work everywhere.
use Cwd        ();
use File::Spec ();

sub symlink_aware_cwd { unless ( defined $ENV{PWD} ) { Cwd::chdir(File::Spec->curdir); } return $ENV{PWD}; }


Of course, you need to call this somewhere near the beginning of your program, so you can be sure that nothing else has trounced over the path in the mean time.

But it does appear to work just fine.

UPDATE!

It would appear that I am completely wrong and Module::Build is blameless in this regard (although it DOES remember the wrong base_dir, so that change should probably be made anyways).

The ACTUAL problem seems to be that M:B sets -w by default when calling test scripts, which enables warnings globally and tickles some warnings that were otherwise NOT getting caught, which cascades outwards eventually resulting in a collection of spurious noise (which I'm fixing now) and some tests breaking which require no warnings to be thrown.

So atm I'm more interested in the fact that M:B enables -w by default, while prove and pler don't. This is both good (warnings enabled during testing are absolutely best practice, even if you don't enable them on production) and bad (doesn't match the behaviour of prove and pler and EU:MM).

I think I'm a fence sitter on whether correctness or pragmatism should triumph in this instance...